当前位置: 首页 > news >正文

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 表达式太复杂看不懂,可以实现表达式所对应的函数式接口,看一下里面的方法。

http://www.xdnf.cn/news/285355.html

相关文章:

  • 【Linux】网络基础
  • Python内置函数
  • python打卡day16
  • PyCharm 安装教程
  • 【神经网络与深度学习】深度学习中的生成模型简介
  • OpenCV 第6课 图像处理之几何变换(透视)
  • word导出pdf带有目录导航栏-error记
  • 硬件工程师面试常见问题(15)
  • Docker(三):DockerFile
  • linux-文件操作
  • 【向量数据库】用披萨点餐解释向量数据库:一个美味的技术类比
  • android-ndk开发(3): 连接设备到开发机
  • RViz(机器人可视化工具)的配置文件(moveitcpp)
  • 【C++指南】STL list容器完全解读(一):从入门到掌握基础操作
  • 华为昇腾CANN架构
  • GM DC Monitor v2.0 - 平台自定义-使用说明
  • day16 numpy和shap深入理解
  • flink监控指标
  • C++负载均衡远程调用学习之负载均衡算法与实现
  • 数据库的范围查询
  • Java---Object和内部类
  • 数据链路层(MAC 地址)
  • AI Agent 要用到的技术
  • 《 C++ 点滴漫谈: 三十六 》lambda表达式
  • DEX平台引领风尚 XBIT让数字资产回归简单与透明
  • 华为云Astro大屏中桥接器、连接器、转化器以及数据源、数据集、资产管理等概念梳理
  • 【纪念我的365天】我的创作纪念日
  • 大模型学习专栏-导航页
  • STM32标准库连接阿里云物联网平台(新)
  • Redis能保证数据不丢失吗之AOF