JAVA8之函数式接口 1.FunctionalInterface注解 注解在Inteface上,且interface里只能有一个抽象方法,可以有default方法。从语义上来讲,一个函数式接口需要通过一个***逻辑上的***方法表达一个单一函数。
1 2 @FunctionalInterface interface F { }
声明一个接口,没有定义方法,编译器会报错No target method found。如果添加一个方法
1 2 3 4 @FunctionalInterface interface F { void f () ; }
此时,编译正常,一个函数式接口声明完成,如果再定义一个方法呢?
1 2 3 4 5 6 @FunctionalInterface interface F { void f () ; void f1 () ; }
编译报错,说明这个注解只允许接口定义一个抽象方法。如果换一种函数签名呢?
1 2 3 4 5 6 @FunctionalInterface interface F { boolean equals (Object obj) ; }
错误依旧,因为这个方法签名是Object类的public方法。而再改一下:
1 2 3 4 5 @FunctionalInterface interface F { boolean equals (Object obj) ; void f () ; }
这就OK了。一个抽象方法,一个Object的public方法,相安无事。Object还有其他方法,clone方法试试会怎么样?
1 2 3 4 5 6 @FunctionalInterface interface F { Object clone () ; void f () ; }
这又不行了,因为前面明确说了,要是Object的public方法,而clone是protected的。
所以总结一句话就是:
函数式接口,有且仅有一个抽象方法,Object的public方法除外。
因为Java本身支持多接口实现,你定义一个Class可以implements多个interface。所以这个限制也没什么影响,如果想约定一个函数式接口来统一,也可以做一些默认的实现来达到一个接口多个抽象方法的目的,比如下面这种做法: 一个普通接口NonFunc:
1 2 3 4 public interface NonFunc { void foo () ; void voo () ; }
函数式接口Func:
1 2 3 4 5 6 7 8 9 10 @FunctionalInterface public interface Func extends NonFunc { void run () ; default void foo () { } default void voo () { } }
实现的测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class TestJ8FunctionalInterface implements Func { public static void main (String[] args) { Func func = new TestJ8FunctionalInterface (); func.run(); func.foo(); func.voo(); } @Override public void run () { System.out.println("run" ); } @Override public void foo () { System.out.println("foo" ); } @Override public void voo () { System.out.println("voo" ); } }
函数式接口的一大特性就是可以被lambda表达式和函数引用表达式代替。也就是说声明这样的接口,是可以灵活的以方法来传参。看个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class TestJ8FunctionalInterface2 { public static void main (String[] args) { TestJ8FunctionalInterface2 testJ8FunctionalInterface2 = new TestJ8FunctionalInterface2 (); testJ8FunctionalInterface2.test(10 , () -> System.out.println("A customed Func." )); testJ8FunctionalInterface2.test(100 , testJ8FunctionalInterface2::customedFunc); } public void customedFunc () { System.out.println("A customed method reference." ); } public void test (int x, Func func) { System.out.println(x); func.run(); } }
上面例子列举了一个lambda模式和一个方法引用模式,这样就可以利用函数式编程强大的能力,将方法作为参数了
另一个大的话题是针对上文的***逻辑上的方法***。所谓逻辑上,就是说当你出现函数式接口多重继承其他接口时,如果继承的多个接口有相同的方法签名,那么也是OK的。而这种相同签名的方法,也包括了泛型的情况,以下的声明中的Z接口,都是函数式接口。
1 2 3 4 5 6 7 8 9 10 interface X { int m (Iterable<String> arg) ; } interface Y { int m (Iterable<String> arg) ; } interface Z extends X , Y {}
但是要注意的是,这种泛型的支持,是因为函数式接口的官方声明规范里要求类型可替换和子签名,不是因为泛型擦除。 比如下面的例子就不是函数式接口:
1 2 3 4 5 6 7 8 9 10 interface X { int m (Iterable<String> arg) ; } interface Y { int m (Iterable<Integer> arg) ; } interface Z extends X , Y {}
1 2 3 4 5 6 7 8 9 10 interface X { int m (Iterable<String> arg, Class c) ; } interface Y { int m (Iterable arg, Class<?> c) ; } interface Z extends X , Y {}
1 2 3 4 5 6 7 8 9 10 interface X <T> { void m (T arg) ; } interface Y <T> { void m (T arg) ; } interface Z <A, B> extends X <A>, Y<B> {}
2.Function 关于Function接口,其接口声明是一个函数式接口,其抽象表达函数为
1 @FunctionalInterfacepublic interface Function <T, R> {R apply (T t) ;...}
函数意为将参数T传递给一个函数,返回R。即R=Function(T);(y=f(x))
其默认实现了3个default方法,分别是compose、andThen和identity,对应的函数表达为:compose对应,体现嵌套关系;andThen对应,转换了嵌套的顺序;还有identity对应了一个传递自身的函数调用对应。从这里看出来,compose和andThen对于两个函数f和g来说,f.compose(g)
等价于g.andThen(f)
。看个例子:
1 2 3 4 5 6 7 8 9 10 11 12 class Fun { public static void main (String[] args) { Function<Integer, Integer> incr1 = x -> x + 1 ; Function<Integer, Integer> multiply = x -> x * 2 ; int x = 2 ; System.out.println("f(x)=x+1,when x=" + x + ", f(x)=" + incr1.apply(x)); System.out.println("f(x)=x+1,g(x)=2x, when x=" + x + ", f(g(x))=" + incr1.compose(multiply).apply(x)); System.out.println("f(x)=x+1,g(x)=2x, when x=" + x + ", g(f(x))=" + incr1.andThen(multiply).apply(x)); System.out.println("compose vs andThen:f(g(x))=" + incr1.compose(multiply).apply(x) + "," + multiply.andThen(incr1).apply(x)); } }
3.高阶函数 只是普通的lambda表达式,其能力有限。我们会希望引入更强大的函数能力——高阶函数,可以定义任意同类计算的函数。
Function<Integer, Function<Integer, Integer>> makeAdder = z -> y -> z + y;
比如这个函数定义,参数是z,返回值是一个Function,这个Function本身又接受另一个参数y,返回z+y。于是我们可以根据这个函数,定义任意加法函数:
1 2 3 4 5 6 7 8 9 Function<Integer, Function<Integer, Integer>> makeAdder = z -> y -> z + y; x = 2 ; Function<Integer, Integer> add1 = makeAdder.apply(1 ); System.out.println("f(x)=x+1,when x=" + x + ", f(x)=" + add1.apply(x)); Function<Integer, Integer> add5 = makeAdder.apply(5 ); System.out.println("f(x)=x+5,when x=" + x + ", f(x)=" + add5.apply(x));
由于高阶函数接受一个函数作为参数,结果返回另一个函数,所以是典型的函数到函数的映射。
BiFunction提供了二元函数的一个接口声明,举例来说:
1 2 3 4 BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b; System.out.println("f(z)=x*y, when x=3,y=5, then f(z)=" + multiply.apply(3 , 5 ));
二元函数没有compose能力,只是默认实现了andThen。
有了一元和二元函数,那么可以通过组合扩展出更多的函数可能。
Function接口相关的接口包括: - BiFunction :R apply(T t, U u);
接受两个参数,返回一个值,代表一个二元函数; - DoubleFunction :R apply(double value);
只处理double类型的一元函数; - IntFunction :R apply(int value);
只处理int参数的一元函数; - LongFunction :R apply(long value);
只处理long参数的一元函数; - ToDoubleFunction:double applyAsDouble(T value);
返回double的一元函数; - ToDoubleBiFunction:double applyAsDouble(T t, U u);
返回double的二元函数; - ToIntFunction:int applyAsInt(T value);
返回int的一元函数; - ToIntBiFunction:int applyAsInt(T t, U u);
返回int的二元函数; - ToLongFunction:long applyAsLong(T value);
返回long的一元函数; - ToLongBiFunction:long applyAsLong(T t, U u);
返回long的二元函数; - DoubleToIntFunction:int applyAsInt(double value);
接受double返回int的一元函数; - DoubleToLongFunction:long applyAsLong(double value);
接受double返回long的一元函数; - IntToDoubleFunction:double applyAsDouble(int value);
接受int返回double的一元函数; - IntToLongFunction:long applyAsLong(int value);
接受int返回long的一元函数; - LongToDoubleFunction:double applyAsDouble(long value);
接受long返回double的一元函数; - LongToIntFunction:int applyAsInt(long value);
接受long返回int的一元函数;
4.Operator Operator其实就是Function,函数有时候也叫作算子。算子在Java8中接口描述更像是函数的补充,和上面的很多类型映射型函数类似。
算子Operator包括:UnaryOperator和BinaryOperator。分别对应单元算子和二元算子。 算子的接口声明如下:
1 2 3 4 5 @FunctionalInterface public interface UnaryOperator <T> extends Function <T, T> { static <T> UnaryOperator<T> identity () { return t -> t; }
二元算子的声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @FunctionalInterface public interface BinaryOperator <T> extends BiFunction <T,T,T> { public static <T> BinaryOperator<T> minBy (Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) <= 0 ? a : b; } public static <T> BinaryOperator<T> maxBy (Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) >= 0 ? a : b; } }
很明显,算子就是一个针对同类型输入输出的一个映射。在此接口下,只需声明一个泛型参数T即可。对应上面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 class TestOperator { public static void main (String[] args) { UnaryOperator<Integer> add = x -> x + 1 ; System.out.println(add.apply(1 )); BinaryOperator<Integer> addxy = (x, y) -> x + y; System.out.println(addxy.apply(3 , 5 )); BinaryOperator<Integer> min = BinaryOperator.minBy((o1, o2) -> o1 - o2); System.out.println(min.apply(100 , 200 )); BinaryOperator<Integer> max = BinaryOperator.maxBy((o1, o2) -> o1 - o2); System.out.println(max.apply(100 , 200 )); } }
例子里补充一点的是,BinaryOperator提供了两个默认的static快捷实现,帮助实现二元函数min(x,y)和max(x,y),使用时注意的是排序器可别传反了:)
其他的Operator接口:(不解释了) - LongUnaryOperator:long applyAsLong(long operand);
- IntUnaryOperator:int applyAsInt(int operand);
- DoubleUnaryOperator:double applyAsDouble(double operand);
- DoubleBinaryOperator:double applyAsDouble(double left, double right);
- IntBinaryOperator:int applyAsInt(int left, int right);
- LongBinaryOperator:long applyAsLong(long left, long right);
5.Predicate predicate是一个谓词函数,主要作为一个谓词演算推导真假值存在,其意义在于帮助开发一些返回bool值的Function。本质上也是一个单元函数接口,其抽象方法test接受一个泛型参数T,返回一个boolean值。等价于一个Function的boolean型返回值的子集。
1 2 3 4 5 @FunctionalInterface public interface Predicate <T> { boolean test (T t) ; ... }
其默认方法也封装了and、or和negate逻辑。写个小例子看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class TestJ8Predicate { public static void main (String[] args) { TestJ8Predicate testJ8Predicate = new TestJ8Predicate (); testJ8Predicate.printBigValue(10 , val -> val > 5 ); testJ8Predicate.printBigValueAnd(10 , val -> val > 5 ); testJ8Predicate.printBigValueAnd(6 , val -> val > 5 ); BiPredicate<Integer, Long> biPredicate = (x, y) -> x > 9 && y < 100 ; System.out.println(biPredicate.test(100 , 50L )); } public void printBigValue (int value, Predicate<Integer> predicate) { if (predicate.test(value)) { System.out.println(value); } } public void printBigValueAnd (int value, Predicate<Integer> predicate) { if (predicate.and(v -> v < 8 ).test(value)) { System.out.println("value < 8 : " + value); } else { System.out.println("value should < 8 at least." ); } } }
Predicate在Stream中有应用,Stream的filter方法就是接受Predicate作为入参的。这个具体在后面使用Stream的时候再分析深入。
其他Predicate接口: - BiPredicate:boolean test(T t, U u);
接受两个参数的二元谓词 - DoublePredicate:boolean test(double value);
入参为double的谓词函数 - IntPredicate:boolean test(int value);
入参为int的谓词函数 - LongPredicate:boolean test(long value);
入参为long的谓词函数
6.Consumer 看名字就可以想到,这像谓词函数接口一样,也是一个Function接口的特殊表达——接受一个泛型参数,不需要返回值的函数接口。
1 2 3 4 5 @FunctionalInterface public interface Consumer <T> { void accept (T t) ; ... }
这个接口声明太重要了,对于一些纯粹consume型的函数,没有Consumer的定义真无法被Function家族的函数接口表达。因为Function一定需要一个泛型参数作为返回值类型(当然不排除你使用Function来定义,但是一直返回一个无用的值)。比如下面的例子,如果没有Consumer,类似的行为使用Function表达就一定需要一个返回值。
1 2 3 4 5 6 7 8 9 10 11 12 class TestJ8Consumer { public static void main (String[] args) { Consumer<Integer> consumer = System.out::println; consumer.accept(100 ); Function<Integer, Integer> function = x -> { System.out.println(x); return x; }; function.apply(100 ); } }
其他Consumer接口: - BiConsumer:void accept(T t, U u);
接受两个参数 - DoubleConsumer:void accept(double value);
接受一个double参数 - IntConsumer:void accept(int value);
接受一个int参数 - LongConsumer:void accept(long value);
接受一个long参数 - ObjDoubleConsumer:void accept(T t, double value);
接受一个泛型参数一个double参数 - ObjIntConsumer:void accept(T t, int value);
接受一个泛型参数一个int参数 - ObjLongConsumer:void accept(T t, long value);
接受一个泛型参数一个long参数
7.Supplier 最后说的是一个叫做Supplier的函数接口,其声明如下:
1 2 3 4 @FunctionalInterface public interface Supplier <T> { T get () ; }
其简洁的声明,会让人以为不是函数。这个抽象方法的声明,同Consumer相反,是一个只声明了返回值,不需要参数的函数(这还叫函数?)。也就是说Supplier其实表达的不是从一个参数空间到结果空间的映射能力,而是表达一种生成能力,因为我们常见的场景中不止是要consume(Consumer)或者是简单的map(Function),还包括了new这个动作。而Supplier就表达了这种能力。
比如你要是返回一个常量,那可以使用类似的做法:
1 2 Supplier<Integer> supplier = () -> 1 ; System.out.println(supplier.get());
这保证supplier对象输出的一直是1。 如果是要利用构造函数的能力呢?就可以这样:
1 2 3 4 5 6 7 8 9 class TestJ8Supplier { public static void main (String[] args) { Supplier<TestJ8Supplier> anotherSupplier; for (int i = 0 ; i < 10 ; i++) { anotherSupplier = TestJ8Supplier::new ; System.out.println(anotherSupplier.get()); } } }
这样的输出可以看到,全部的对象都是new出来的。
这样的场景在Stream计算中会经常用到,具体在分析Java 8中Stream的时候再深入。
其他Supplier接口: - BooleanSupplier:boolean getAsBoolean();
返回boolean - DoubleSupplier:double getAsDouble();
返回double - IntSupplier:int getAsInt();
返回int - LongSupplier:long getAsLong();
返回long
总结 整个函数式接口的大概总结如下:
名称
一元接口
说明
二元接口
说明
一般函数
Function
一元函数,抽象apply方法
BiFunction
二元函数,抽象apply方法
算子函数(输入输出同类型)
UnaryOperator
一元算子,抽象apply方法
BinaryOperator
二元算子,抽象apply方法
谓词函数(输出boolean)
Predicate
一元谓词,抽象test方法
BiPredicate
二元谓词,抽象test方法
消费者(无返回值)
Consumer
一元消费者函数,抽象accept方法
BiConsumer
二元消费者函数,抽象accept方法
供应者(无参数,只有返回值)
Supplier
供应者函数,抽象get方法
-
-
JAVA8之Lambda表达式和方法引用 定义 Java 表达式有很多种,声明一个class是一个表达式,定义一个变量是一个表达式,写一个=赋值逻辑是一个表达式……
Lambda表达式是这样一个表达式:
lambdaParameters -> lambdaBody
在lambdaParameters传递参数,在lambdaBody中编写逻辑。lambda表达式生成的结果就是一个函数式接口(上文提到过的)。lambdaBody中的逻辑内容(各种表达式)不会在定义时执行,在实际函数式接口调用时才会执行。
举几个官方的例子看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 () -> {} () -> 42 () -> null () -> { return 42 ; } () -> { System.gc(); } () -> { if (true ) return 12 ;else {int result = 15 ;for (int i = 1 ; i < 10 ; i++)result *= i; return result;} } (int x) -> x+1 (int x) -> { return x+1 ; } (x) -> x+1 x -> x+1 (String s) -> s.length() (Thread t) -> { t.start(); } s -> s.length() t -> { t.start(); } (int x, int y) -> x+y (x, y) -> x+y (x, int y) -> x+y (x, final y) -> x+y
lambda表达式的参数部分 可以通过上面的例子看到,lambda的参数声明主要包含两大类,一类是声明类型的,一类是不声明类型的(依赖推断的)。其中声明类型的参数,与定义一个方法时声明参数是一样的。
几个注意的点: \1. _不能作为lambda参数。 \2. int…与int[]是一致的。 \3. 当参数是推断类型时,注意推断类型的类型转换错误,类型是依据上下文变化的。
来个推断的例子:
1 2 3 4 5 6 Function inferedFunc = x -> { System.out.println(x.getClass().getTypeName()); return x.toString(); }; Object a = inferedFunc.apply(10 );Object b = inferedFunc.apply(100D );
方法引用 方法引用表达式是另一类执行函数式接口的模式,在Java 8之前是没有能力表达一个函数方法的,在Java 8引入函数式接口后,每个lambda表达式都代表了一个函数,可以指向性的将lambda表达式赋值给一个Function类的接口。另一个重要的方法就是直接使用函数方法引用。
方法引用是通过[对象名]::[方法名]这种模式来引用的,其中::两个冒号的操作符非常重要。具体的场景针对类、对象实例、数组、泛型等均有不同的支持,下面的例子看看各种方法引用的表达方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class TestJ8MethodReference { public static void main (String[] args) { Function<Integer, Integer> f1 = TestJ8MethodReference::add; System.out.println(f1.apply(1 )); Function<String, String> f2 = String::trim; System.out.println(f2.apply(" abd b" )); TestJ8MethodReference testJ8MethodReference = new TestJ8MethodReference (); Function<Integer, String> f3 = testJ8MethodReference::getStr; System.out.println(f3.apply(3 )); testJ8MethodReference.testSuper(); testJ8MethodReference.testExplicitType(); testJ8MethodReference.testImplicitType(); Supplier s1 = TestJ8MethodReference::new ; System.out.println(s1.get()); Consumer<int []> c1 = Arrays::sort; int [] array = new int []{4 , 3 , 2 , 1 }; c1.accept(array); Consumer<int []> c2 = Arrays::<int []>sort; c2.accept(array); Function<Integer, int []> f4 = (int []::new ); int [] a = f4.apply(10 ); System.out.println(a.length); } public static int add (int x) { return x + 1 ; } public String getStr (int x) { return "" + x; } public void testSuper () { Supplier<String> f = super ::toString; System.out.println(f.get()); } public void testExplicitType () { List<String> list = new ArrayList <>(); Function<String, Boolean> func = list::add; System.out.println(func.apply("a" )); } public void testImplicitType () { List list = new ArrayList (); Function<String, Boolean> func = list::add; System.out.println(func.apply("a" )); } }
其中需要注意的是,数组的new方法引用等价于一个有入参的Function,因为new一个数组是需要指定size的。
总结 无论lambda表达式还是方法引用表达式,所指向的都是一个方法或者是函数。而它们指向的内容能赋值的也一定是函数式接口。这两种指向也是实用场景各异,方法引用需要使用在已有方法上(显而易见),而lambda表达式是一种快速行内声明一个方法且指向一个函数式接口的方法。两者交互配合,基本可以覆盖各种函数式接口使用的场景。