java8中专门为函数式接口提供了一个@FunctionalInterface注解,该注解通常方法接口定义前面,这个注解没有任何作用,只是告诉编译器执行更加严格,检查该接口必须是函数式接口,否则编译器就会报错。
使用lambda表达式一定要记住一点:lambda表达式的目标类型必须是明确的函数式接口。我们在使用过程中最好将一个lambda表达式理解成一个函数,而不是一个对象,并记住它可以被转换成一个函数式接口。为了保证lambda表达式的目标类型是一个明确的函数式接口,一般情况下lambda有下面3种常用的使用方式:
1),将lambda表达式赋值给函数式接口类型的变量。
public class Test { public static void main(String[] args) { A a = () -> System.out.println("这里使用A的函数式接口来赋值给类型是A的变量a"); } } @FunctionalInterface interface A { void test(); }
2),将lambda表达式作为函数式接口类型的参数传递给某个方法。
public class Test { public static void main(String[] args) { new Thread(() -> System.out.println("使用Runnable函数式接口来传递给Thread构造器一个参数")).start(); } }
3),使用函数式接口对lambda表达式进行强制类型转换。
public class Test { public static void main(String[] args) { Object a = (A) () -> System.out.println("这里使用A的函数式接口来赋值给类型是A的变量a"); } } @FunctionalInterface interface A { void test(); }
需要说明的一点:同样的lambda表达式的目标类型完全有可能是变化的,唯一的要求就是,lambda表达式实现的匿名方法与目标类型中唯一的抽象方法有着相同的形参列表。看下面的代码:
public class Test { public static void main(String[] args) { Object a = (A) () -> System.out.println("这里使用A的函数式接口来赋值给类型是A的变量a"); Object b = (B) () -> System.out.println("lambda表达式一样,但是目标类型不同"); } } @FunctionalInterface interface A { void test(); } @FunctionalInterface interface B { void test(); }
当一个lambda表达式被转换成一个函数式接口的实例时,请注意处理检查期异常。
比如下面代码:
public class Test { public static void main(String[] args) { new Thread(() -> { System.out.println("下面的编译时异常必须要捕获,不然就要报错"); Thread.sleep(100); }).start(); } }
我们现在自己处理一下,当然上面的编译时异常我可以try,catch,或者我们直接在声明函数式接口的时候抛出异常就OK。
public class Test { public static void main(String[] args) { A a = () -> { Thread.sleep(1000); }; B b = () -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }; } } @FunctionalInterface interface A { void test() throws Exception; } @FunctionalInterface interface B { void test(); }
在java8中的java.util.function包下预定义了大量函数式接口,典型的包含如下4类接口:
1),XxxFuncion:这类接口通常包含一个apply()抽象方法,该方法对参数进行处理,转换,然后返回一个新的值。具体的apply()方法的处理逻辑由lambda表达式来实现。该函数式接口通常用于对指定数据进行转换处理
2),XxxConsumer:这类接口通常包含一个accept()抽象方法,这个方法与上面的XxxFunction接口中的apply()方法基本相似,也是负责对参数进行处理,只是该方法不会返回处理结果。
3),XxxPredicate:这类接口通常包含一个test()抽象方法,该方法通常用来对参数进行某种判断,然后返回一个boolean值。具体的test()方法的判断逻辑由lambda表达式来实现,这个接口用于判断参数是否满足特定条件,经常用于过滤数据。
4),XxxSupplier:这类接口通常包含一个getAsXxx()抽象方法,该方法不需要输入参数,该方法会按某种逻辑算法来返回一个数据。
- 选择一个函数式接口
Runnable:public abstract void run():执行一个没有参数和返回值的操作
Supplier<T>:T get():提供一个T类型的值
Comsumer<T>:void accept(T t):处理一个T类型的值
BiComsumer<T,U>:void accept(T t, U u):处理T类型和U类型的值
Function<T,R>:R apply(T t):处理一个参数类型是T的函数,返回R
BiFunction<T,U,R>:R apply(T t, U u):处理参数类型是T,U的函数,返回R
UnaryOperator<T>:T apply(T t):对类型T进行一元操作,这个接口extends Function<T, T>
BinaryOperator<T>:T apply(T t,T t):对类型T进行二元操作,这个接口extends Function<T, T, T>
Predicate<T>:boolean test(T t):对类型T进行计算,返回boolean值
BiPredicate<T, U>:boolean test(T t, U u):对类型T和U进行计算boolean值
下面我们举一个例子,研究下怎么使用上面这些定义的函数式编程。
public class Test { public void test(BiPredicate<String, String> huhu) { System.out.println("定义了一个方法,需要传入A函数式接口。。。"); } public static void main(String[] args) { Test test = new Test(); test.test((str, str1) -> str.equals(str1)); } }