Java8函数式编程和lambda表达式

缺乏、安全感 2022-01-29 09:55 375阅读 0赞

文章目录

      • 函数式编程
      • JDK8接口新特性
      • 函数接口
      • 方法引用

函数式编程

函数式编程更多时候是一种编程的思维方式,是一种方法论。函数式与命令式编程区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做。简单说,函数式编程是基于某种语法或调用API去进行编程。

例如,从整型数组中找出最小的那个数字,采用命令式编程实现如下:

  1. public static void main(String[] args){
  2. int[] array={ 1,2,3,4,5,6,7,8,9,10};
  3. int minx=Integer.MAX_VALUE;
  4. for(int a:array){
  5. if(a<minx)
  6. minx=a;
  7. }
  8. System.out.println(minx);
  9. }

而采用函数式编程来实现,则简化为如下代码:

  1. int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  2. int minx = IntStream.of(array).min().getAsInt();
  3. System.out.println(minx);

我们可以看出,命令式编程需要自己实现具体逻辑,而函数式编程则是调用API完成需要,将命令式的代码写成一系列函数调用,在函数式编程下代码更简洁、易懂,这也就是为什么要使用函数式编程的原因之一。所以才说函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做,这是一种思维的转变。

lambda表达式作为函数式编程的基础,也就显得十分重要:

Java不支持lambda表达式的时候,我们会去这样创建一个线程:

  1. new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. // TODO Auto-generated method stub
  5. System.out.println("run()方法");
  6. }
  7. }).start();

而采用lambda表达式:

  1. new Thread(() -> System.out.println("run()")).start();

lambda表达式一句话即完成线程的创建,它强调了函数的输入输出,而隐藏了内部实现细节,并且可以接收函数作为输入(参数)和输出(返回值).

->的左边是输入,右边是输出.上述代码参数为空。

lambda表达式的作用就是返回了Runnable接口的实现对象,这调用某个方法获取实例对象类似,只不过是将实现代码直接写进了lambda表达式中.

JDK8接口新特性

  1. 函数接口,接口只能有一个需要实现的方法,可以使用FunctionalInterface注解进行声明

    1. @FunctionalInterface
    2. public interface InterfaceOne {
    3. int doubleNum(int i);
    4. }

    使用lambda表达式获取该接口的实现实例的几种写法:

    1. InterfaceOne num1 = (i) -> i * 2;
    2. System.out.println(num1.doubleNum(10));
    3. InterfaceOne num2 = i -> i * 2;
    4. // 指定参数类型
    5. InterfaceOne num3 = (int i) -> i * 2;
    6. InterfaceOne num4 = (int i) -> {
    7. System.out.println(i);
    8. return i * 2;
    9. };
  2. 接口的默认方法,用于提供默认实现。默认方法和普通实现类的方法一样,可以使用this关键字。

    1. @FunctionalInterface
    2. public interface InterfaceOne {
    3. int doubleNum(int i);
    4. }
    5. default int add(int x,int y){
    6. return x+y;
    7. }

    之所以说默认方法这个特性比较重要,是因为我们借助这个特性可以在以前所编写的一些接口上提供默认实现,并且不会影响任何的实现类以及既有的代码。例如我们最熟悉的List接口,在JDK1.2以来List接口就没有改动过任何代码,到了1.8之后才使用这个新特性增加了一些默认实现。这是因为如果没有默认方法的特性的话,修改接口代码带来的影响是巨大的,而有了默认方法后,增加默认实现可以不影响任何的代码。

  3. 当接口多重继承时,可能会发生默认方法覆盖的问题,这时要指定使用哪一个接口的默认方法实现。

    1. @FunctionalInterface
    2. public interface InterfaceOne {
    3. int doubleNum(int i);
    4. default int add(int x, int y) {
    5. return x + y;
    6. }
    7. }
    8. @FunctionalInterface
    9. interface InterfaceTwo {
    10. int doubleNum(int i);
    11. default int add(int x, int y) {
    12. return x + y;
    13. }
    14. }
    15. @FunctionalInterface
    16. interface InterfaceThree extends InterfaceOne, InterfaceTwo {
    17. // 指定使用哪一个接口的默认方法实现
    18. @Override
    19. default int add(int x, int y) {
    20. return InterfaceOne.super.add(x, y);
    21. }
    22. }

    函数接口






















































    接口 输入参数 返回类型 说明
    Predicate T boolean 断言
    Consumer T / 消费一个数据
    Function<T,R> T R 输入T,输出R
    Supplier / T 提供一个数据
    UnaryOperator T T 一元函数(输入输出类型相同)
    BiFunction<T,U,R> (T,U) R 两个输入参数的函数
    BinaryOperator (T,T) T 二元函数(输入输出类型相同)

    其中最常用的是Function接口,该接口为我们省去定义一些不必要的函数接口。使用如下例子说明Function接口:

    1. public class TestFunction {
    2. public static void main(String[] args) {
    3. Account account = new Account("FEEL", 9999999);
    4. Function<Long, String> func = i -> new DecimalFormat("#,###").format(i);
    5. account.show(func);
    6. }
    7. }
    8. class Account {
    9. private String name;
    10. private long balance;
    11. public Account(String name, long balance) {
    12. this.name = name;
    13. this.balance = balance;
    14. }
    15. public void show(Function<Long, String> func) {
    16. System.out.println(name + "的账户余额: " + func.apply(this.balance));
    17. }
    18. }

    输出为:

    FEEL的账户余额: 9,999,999

如果不使用Function接口,则需要自己定义一个接口,并且不支持链式操作。

  1. @FunctionalInterface
  2. interface Format {
  3. String format(long i);
  4. }
  5. class Account {
  6. private String name;
  7. private long balance;
  8. public Account(String name, long balance) {
  9. this.name = name;
  10. this.balance = balance;
  11. }
  12. public void show(Format fm) {
  13. System.out.println(name + "的账户余额: " + fm.format(balance));
  14. }
  15. }
  16. public class Test_No_Function {
  17. public static void main(String[] args) {
  18. Account account = new Account("FEEL", 999999999);
  19. Format fm = i -> new DecimalFormat("#,###").format(i);
  20. account.show(fm);
  21. }
  22. }

下面是PredicateConsumer接口的使用:

  1. public static void main(String[] args) {
  2. // 断言函数接口
  3. Predicate<Integer> p = i -> i > 0;
  4. System.out.println("1>0? " + p.test(1));
  5. // 消费函数接口
  6. Consumer<String> consumer = System.out::println;
  7. System.out.println("Hello Consumer Interface!");
  8. }

输出结果:

1>0? true
Hello Consumer Interface!

这些接口一般对基本类型做了封装,所以使用基本类型时无需指定泛型:

  1. public static void main(String[] args){
  2. //断言函数接口
  3. IntPredicate IP=i->i>0;
  4. System.out.println(IP.test(1));
  5. //消费函数接口
  6. IntConsumer ic=(v)->System.out.println("输入整数:"+v);
  7. ic.accept(1999);
  8. }

输出结果:

  1. true
  2. 输入整数:1999

下面是其他一些接口的使用方式:

  1. public static void main(String[] args) {
  2. // 提供数据接口
  3. Supplier<Integer> supplier = () -> 10;
  4. System.out.println("提供的数据是: " + supplier.get());
  5. // 一元函数接口
  6. UnaryOperator<Integer> unaryOperator = i -> i >> 1;
  7. System.out.println("计算结果: " + unaryOperator.apply(10));
  8. //二元函数接口
  9. BinaryOperator<Integer> binaryOperator = (a, b) -> a * b;
  10. System.out.println("计算结果: " + binaryOperator.apply(10, 10));
  11. }

输出结果为:

  1. 提供的数据是: 10
  2. 计算结果: 5
  3. 计算结果: 100

方法引用

我们通常会使用lambda表达式来创建匿名方法但有时仅仅需要调用一个已存在的方法.如下:

  1. Arrays.sort(array,(s1,s2)->s1.compareToIgnoreCase(s2));

而在Jdk8中,可以通过一个新特性来简化上述lambda表达式:

  1. Arrays.sort(array,String::compareToIgnoreCase);

上述这行称为Method Reference(方法引用),其标准形式:

类名::方法名::(只需写方法名,不需要写括号)

方法引用的四种形式:




































类型 示例 代码示例 对应lambda表达式
引用静态方法 ContainingClass::staticMethodName String::valueOf (s)->String.valueOf(s)
引用某个对象的实例方法 containingObject::instanceMethodName x::toString() ()->this.toString()
引用某个类型的任意对象的实例方法 ContainingType::methodName String::toString (s)->s.toString
引用构造方法 ClassName::new String::new ()->new String()

定义实体类如下:

  1. package com.demo.after;
  2. /** * @author bushizhe * @Time 2019年5月21日 下午4:38:56 * @Description */
  3. public class FEEL {
  4. private String name = "FEEL";
  5. private int age = 21;
  6. public FEEL() {
  7. }
  8. public FEEL(String name) {
  9. this.name = name;
  10. }
  11. public FEEL(String name, int age) {
  12. this.name = name;
  13. this.age = age;
  14. }
  15. public static void show(FEEL feel) {
  16. System.err.println(feel.name + "今年" + feel.age + "岁了!!");
  17. }
  18. public int getAge(int age) {
  19. System.out.println(name + "年龄:" + age);
  20. return age + 10;
  21. }
  22. @Override
  23. public String toString() {
  24. return "FEEL [name=" + name + ", age=" + age + "]";
  25. }
  26. }

通过方法引用调用实体类中方法:

  1. package com.demo.after;
  2. import java.util.function.BiFunction;
  3. import java.util.function.Consumer;
  4. import java.util.function.IntUnaryOperator;
  5. import java.util.function.Supplier;
  6. /** * @author bushizhe * @Time 2019年5月21日 下午4:44:20 * @Description */
  7. public class TestMethod {
  8. public static void main(String[] args) {
  9. // 方法引用,调用打印方法
  10. Consumer<String> consumer = System.out::println;
  11. consumer.accept("接收的数据:");
  12. // 静态方法引用
  13. Consumer<FEEL> consumer2 = FEEL::show;
  14. consumer2.accept(new FEEL("FEEL", 20));
  15. // 实例方法引用,通过对象实例进行引用
  16. FEEL feel = new FEEL();
  17. IntUnaryOperator func = feel::getAge;
  18. System.out.println("十年之后:" + func.applyAsInt(20) + "岁");
  19. // 无参构造方法的引用,
  20. Supplier<FEEL> supplier = FEEL::new;
  21. System.out.println("创建了新对象" + supplier.get());
  22. // 有参构造函数的方法引用
  23. BiFunction<String, Integer, FEEL> func2 = FEEL::new;
  24. System.out.println("创建了新对象" + func2.apply("FLING", 20));
  25. }
  26. }

输出结果:

接收的数据:
FEEL今年20岁了!!
FEEL年龄:20
十年之后:30岁
创建了新对象FEEL [name=FEEL, age=21]
创建了新对象FEEL [name=FLING, age=20]

发表评论

表情:
评论列表 (有 0 条评论,375人围观)

还没有评论,来说两句吧...

相关阅读