java8-lambda表达式和函数式接口

java8-lambda表达式

lambda是希腊字母λ的读音,意指抽象函数。在java中lambda表达式的语法结构为:

(params)->{statement}

params是参数,可以是一个或者多个,statement是执行语句,也可以是一个或者多个,->是表达式符号,表示goesto

lambda表达式的使用,lambda表达式本质是对接口实现的一种简化,如下,假如我们定义一个接口:

public interface TestFunctionInterface {
          R transform(T t);
}

这个接口只有一个方法,该方法接受一个参数类型为T的参数,通过转换返回一个类型为R的结果。如果我们项目中需要使用到Integer转换为String,和String转换为Integer的场景,我们常规的做法是写两个实现类:

public class StringToIntFunction implements TestFunctionInterface {
          @Override
          public Integer transform(String t) {
                   return Integer.valueOf(t);
          }
}
public class IntToStringFunction implements TestFunctionInterface {
          @Override
          public String transform(Integer t) {
                   return Integer.toString(t);
          }
}

然后在分别实例化这两个实现类,调用其tranform方法:

           public void printTransform() {
                   int num = 1;
                   String str = "9";
                   System.out.println(new IntToStringFunction().transform(num));
                   System.out.println(new StringToIntFunction().transform(str));
          }

或者我们使用稍微高大上点的匿名内部类的方式去实现:

        public void printTransform() {
                   int num = 1;
                   String str = "9";
                   System.out.println(new TestFunctionInterface() {
                             @Override
                             public String transform(Integer t) {
                                      return Integer.toString(t);
                             }
                   }.transform(num));
                   
                   System.out.println(new TestFunctionInterface() {
                             @Override
                             public Integer transform(String t) {
                                      return Integer.valueOf(t);
                             }
                   }.transform(str));
          }

但是现在java8支持lambda表达式,就不需要那么费劲,我们只需要用lambda表达式的方式实现接口就行。

        public void printTransform() {
                   int num = 1;
                   String str = "9";
                   System.out.println(((TestFunctionInterface)i -> Integer.toString(i)).transform(num));

                   System.out.println(((TestFunctionInterface) s -> Integer.valueOf(s)).transform(str));

          }

上述中写法i->Integer.toString(i)这部分lambda表达式就是对接口的实现,但是前面说到脱离接口的lambda表达式不仅是语法不允许的,更是没有意义的,因为编译器不清楚你这个lambda表达式是为了实现哪个接口,因此我们需要在lambda表达式之前加上接口类型TestFunctionInterface。之后调用transform方法即可。

不过大部分情况下我们不会这么去使用,会显得很累赘逻辑不清晰,大部分情况我们应该去定一个一个公共的接收lambda表达式所实现的函数式接口作为参数的方法。

          public  R transform(T t, TestFunctionInterface function) {
                   return function.transform(t);
          }

然后调用的时候直接传入lambda表达式即可。

          public void printTransform() {
                   int num = 1;
                   String str = "9";
                   System.out.println(transform(num, i -> Integer.toString(i)));
                   System.out.println(transform(str, s -> Integer.valueOf(s)));
          }

能使用lambda表达式的接口必须是函数式接口,函数式接口的要求是必须只有一个抽象方法,因为java8以后接口支持了default方法,因此并不要接口中只能有一个方法,但必须是只有一个抽象方法。函数式的接口声明需要加上@FunctionalInterface,表明这是一个函数式接口,也可以不加该注解,接口如果只有一个抽象方法编译器会自动判断是否是函数式接口。
除了自定义的函数式接口,Java8本身还提供了一个函数式接口Function。

java8-Function接口

Function的源码的定义如下:

@FunctionalInterface
public interface Function {

    R apply(T t);

    default  Function compose(Function before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }


    default  Function andThen(Function after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static  Function identity() {
        return t -> t;
    }
}

根据Function接口的源码也印证了上面说到的关于lambda表达式对接口的要求,只能有一个抽象方法,Function接口本身只有一个抽象方法apply,这个方法和上面我们自定义用于测试的接口的方法意义很像,都是传入一个类型为T的参数,得到一个类型为R的结果,其中具体的操作由节课实现类实现。
Function接口的另外两个方法是在多个函数接口组合的时候使用,compose接口的源码很容易看得出是将before函数的运行结果传入给当前函数执行。andThen方法是将当前函数的运行结果传入给after函数执行。
看下例子,同样是我们上面提到的关于类型转换的方法,我们可以在不自定义函数接口的情况下直接使用Function接口实现。

          public  R transform1(T t, Function function) {
                   return function.apply(t);
          }

          public void printTransform() {
                   int num = 1;
                   String str = "9";
                   System.out.println(transform1(num, i -> Integer.toString(i)));
                   System.out.println(transform1(str, s -> Integer.valueOf(s)));
          }

下面的代码是对Function接口的测试:

public static int testApply(int num, Function function) {
                   return function.apply(num);
          }

          public static int testCompose(int num, Function before, Function after) {
          //这部分根据源码等同于 after.apply(before.apply(num))
                   return after.compose(before).apply(num);
          }

          public static int testAndThen(int num, Function before, Function after) {
          //这部分根据源码等同于before.appy(after.apply(num))
                   return after.andThen(before).apply(num);
          }

          public static void main(String[] args) {
                    //输出结果为2
                   System.out.println(testApply(1, e -> e + 1));
                   
                   //输出结果4
                   System.out.println(testCompose(1, e -> e + 1, e -> e * 2));
                   
                   //输出结果为3
                   System.out.println(testCompose(1, e -> e * 2, e -> e + 1));
                   
                   //输出结果为3
                   System.out.println(testAndThen(1, e -> e + 1, e -> e * 2));
          }
}

此外JDK同时提供了BiFunction接口,BiFunction接口和Function接口类似,不同的是BiFunction接口的apply方法接受的是两个参数。
R apply(T t,U u);

除了Function接口之外,java8常用的函数式接口还有如下几个:

其他函数式接口

Supplier

该接口的抽象方法为 T get();方法无参数,返回一个T类型的对象。我们可以使用Supplier接口实现获取随机数或者获取集合的Stream对象等。

            int random = getRandom(() -> new Random().nextInt(10));

           System.out.println(random);
                   
          public static Integer getRandom(Supplier supplier) {
                   return supplier.get();
          }
Consumer

该接口的抽象方法为 void accept(T t);方法接受一个T类型的参数,没有返回值,我们可以使用该接口对对象进行处理,比如对List的元素迭代处理输出等。

            testConsumer(stuList, list -> list.forEach(stu -> 
            System.out.println(JSON.toJSONString(stu))));

          public static  void testConsumer(T t, Consumer consumer) {
                   consumer.accept(t);
          }
Predicate

该接口的抽象方法为 boolean test(T t);表示接收T类型的参数,返回boolean类型的值。该接口会在java8集合新特性中详细讲解。

上述几个接口如果单独使用意义不明显,一般都是多个函数式接口结合使用。

BiFunction

该接口抽象方法为同样为 R apply(T t,U u),不同于Function是BiFunction接收两个参数,返回一个结果。

BinaryOperator

BinaryOperator继承自BiFunction,不同的是BinaryOperator限制输入的两个参数以及输出的结果都是T类型

总结

  • lambda表达式必须是函数式接口,自定义函数式接口需要加上@FunctionalInterface注解,且接口内只能有一个抽象方法。
  • jdk提供了一个通用的函数式接口Function和BiFunction,能够满足大多数情况下的需求,实际使用时可以使用该接口,避免自定义接口。
  • lambda表达式支持一到多个参数,支持一个或者零个返回值
  • Function接口中常用的方法为apply,apply的参数是对应函数的输入值,apply的调用者是对应的function
  • lambda表达式不需要声明参数和返回值的类型,编译器会自动判断。

你可能感兴趣的:(java)