lambda表达式和方法引用
lambda表达式
简介
lambda表达式:java 1.8引入的新特性,主要是为了在java中支持函数式编程,lambda表达式实际上是匿名内部类的简写,并且要求这个匿名内部类是一个实现了函数式接口的匿名内部类,如果不是,则无法把匿名内部类的实现简化为lambda表达式。
函数式接口:一个接口中有且只有一个抽象方法,不包括Object类中的方法、默认方法、静态方法,这样的一个接口就叫做函数式接口。函数式接口可以被@FunctionalInterface注解修饰,这个注解用于告诉程序员这个一个函数式接口,函数式接口是实现lambda表达式的基础。
lambda表达式的格式:[(目标接口)] (形参列表) -> {函数体}
,可以选择指定lambda表达式使用的哪个接口
入门案例
案例1:lambda表达式
第一步:自定义一个函数式接口
@FunctionalInterface // 这个注解表示当前接口是一个函数式接口,接口中只有一个方法,如果有多个,编译时会报错
public interface Func<T> {T eva(T t);
}
第二步:使用匿名内部类来实现函数式接口
public static void main(String[] args) {Func<Integer> func = new Func<Integer>() {@Overridepublic Integer eva(Integer i) {return i * 10;}};Integer eva = func.eva(10);System.out.println("eva = " + eva); // 100
}
第三步:把匿名内部类简写为函数式接口
public static void main(String[] args) {Func<Integer> func = i -> i * 10;Integer eva = func.eva(10);System.out.println("eva = " + eva); // 100
}
基本使用
手动指定lambda表达式的类型
lambda表达式中,变量的数据类型是根据上下文推断出来的,但是,如果上下文之间存在歧义,就需要手动指定lambda表达式的类型,也就是lambda表达式具体是实现了哪个函数式接口。
案例1:
第一步:定义两个函数式接口
@FunctionalInterface
public interface NumberProcessor {Number process(Number n);
}@FunctionalInterface
public interface StringProcessor {String process(String s);
}
第二步:使用函数式接口
public class Demo4LambdaTest {public static void main(String[] args) {// 在这里使用lambda表达式的时候,由于有两个重载的方法,方法都以// 函数式接口作为参数,如果不指定lambda表达式的目标接口,根据// 上下文编译器无法推断出lambda表达式实现的是哪个接口。execute((StringProcessor) (s) -> s + "aaa");}// 函数1public static void execute(NumberProcessor np) {Number process = np.process(100);System.out.println("s = " + process); // helloaaa}// 函数2public static void execute(StringProcessor sp) {String process = sp.process("hello");System.out.println("n = " + process); // 200}
}
案例2:spring中的源代码
spring中的一段源码,在ScheduledAnnotationBeanPostProcessor类中
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethod(targetClass,(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);return (!scheduledMethods.isEmpty() ? scheduledMethods : null);});
重点看这里lambda表达式的类型转换,它表示把lambda表达式转换为MetadataLookup接口的实现,MetadataLookup是一个函数式接口。因为selectMethod方法有一个重载的方法,该方法接受另一个函数式接口的实现类,所以编译器无法根据上下文推断出lambda表达式的目标接口,需要用户手动转换。
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {public static Set<Method> selectMethods(Class<?> targetType, final ReflectionUtils.MethodFilter methodFilter) {
@FunctionalInterface
public interface MetadataLookup<T> {@NullableT inspect(Method method);
}@FunctionalInterface
public interface MethodFilter {boolean matches(Method method);
}
变量捕获
lambda表达式实际上是一个内部类,如果它引用了外部的局部变量,编译器会将局部变量转换为final
案例1:局部变量捕获
public static void main(String[] args) {int factor = 10;Func<Integer> func = x -> x * factor;// int factor = 10; // 此时这个局部变量已经被隐式地转换为final,不可以再使用修改它了Integer eva = func.eva(10);System.out.println("eva = " + eva);
}
案例2:for循环中的变量不可以用在lambda表达式中,因为lambda表达式实际上是一个内部类,不可以引用非final的局部变量。
public static void main(String[] args) {List<Person> personList = new ArrayList<>();personList.add(new Person(1L, "aaa", 20));personList.add(new Person(2L, "bbb", 21));personList.add(new Person(3L, "ccc", 22));for (int i = 0; i < personList.size(); i++) {int finalI = i; // 这里必须要手动转换new Thread(() -> {System.out.println("personList.get(i) = " + personList.get(finalI));}).start();}
}
内部机制
lambda在编译时不会生成独立的类文件,而是在运行时生成一个类文件,并且这个文件不会被持久化到磁盘上,这种设计旨在减少类数量、提升性能、保持字节码的简洁性,尽管lambda表达式是匿名内部类的简写,但是lambda表达式在这点和和匿名内部类是不一样的,匿名内部类在编译时会生成独立的类文件,文件名称是 外部类名$数字.class
。
方法引用
简介
方法引用:method reference,lambda表达式的简写,当lambda表达式仅仅是调用一个函数时,可以使用方法引用来替代。
方法引用的格式:
- 类名::方法名:方法必须是静态方法
- 对象名::方法名:方法必须是实例方法
入门案例
案例1:
public static void main(String[] args) {List<Person> personList = new ArrayList<>();personList.add(new Person(1L, "aaa", 20));personList.add(new Person(2L, "bbb", 21));personList.add(new Person(3L, "ccc", 22));// 第一种写法:不使用方法引用的写法personList.forEach(p -> System.out.println(p));System.out.println("---------");// 第二种写法:方法引用,因为这里仅仅是使用println函数输出变量,所以可以使用方法引用来简化lambda表达式personList.forEach(System.out::println);// 第三种写法:方法引用是lambda的简写,所以它本身也可以被赋值给一个函数式接口System.out.println("---------");Consumer<Person> consumer = System.out::println;personList.forEach(consumer);
}
使用经验
通常把lambda表达式当做实参传递给一个方法,该方法的形参要求是一个函数式接口类型的对象,lambda 表达式的本质正是一个实现了函数式接口的匿名内部类。如果 lambda 表达式太复杂看不懂,可以实现表达式所对应的函数式接口,看一下里面的方法。