Lambda表达式
JDK8新特性:
- 支持了Lambda表达式和Stream流;
- Hash链表、数组和红黑树;
- JVM元空间代替了永久代;
前言:
- Lambda表达式是基于函数式接口的。
- Lambda表达式语法格式了解。
Lambda表达式的简化规则。 - 变量作用域。
- 四大内置核心的函数接口。(重要)
- 方法引用:构造方法引用;静态方法引用;实例方法引用;对象方法引用。(难点)
1. 函数式接口
- 函数式接口是指:有且只有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java内的函数式编程体现就是Lambda,故函数式接口就是可适用于Lambda使用的接口。
只有确保接口内有且只有一个抽象方法,Java中的Lambda才能很顺利地进行推导。
- @FunctionalInterface注解:是放在接口上的,仅显示此接口是函数式接口。在定义函数式接口时就会检查是否符合函数式接口规范;Java自己实现的函数式接口都有此注解。
如:在一个接口内有多个抽象方法,此注解便会提示。
2. Lambda表达式语法格式
-> 是Lambda操作符或箭头操作符;
-> 左侧是Lambda形参列表(接口内抽象方法的形参列表);
-> 右侧是Lambda体(Lambda表达式要执行的功能/重写的接口的方法);
语法格式特征:
接口:
// 1. 无参无返 interface NoReturnNoParam{ void test01(); } // 2. 一参无返 interface OneParamNoReturn{ void test02(int a); } // 3. 多参无返 interface MultiParamNoReturn{ void test03(double a, int b); } // 4. 无参有返 interface NoParamWithReturn{ String test04(); } // 5. 一参有返 interface OneParamWithReturn{ int test05(int e); } // 6. 多参有返 interface MultiParamWithReturn{ String test06(String f,String g, int h); }
实现:
public class lambdaImpl { public static void main(String[] args) { // 1. 无参无返 NoReturnNoParam noA = () -> System.out.println("无参无返."); // 当lambda体只有单条语句,{}可不写; noA.test01(); // 2. 一参无返 OneParamNoReturn onB = (int a) -> { // ()内的类型可省略不写 System.out.println(a); }; onB.test02(2024); // 3. 多参无返 MultiParamNoReturn muC = (a , b) -> { System.out.println(a + b); }; muC.test03(0.0221,2024); // 4. 无参有返 NoParamWithReturn noD = () -> {System.out.println("Hello World"); return "ok"; }; System.out.println(noD.test04()); // 5. 一参有返 OneParamWithReturn onE = (int e) -> { return e; }; System.out.println(onE.test05(2024)); // 6. 多参有返 MultiParamWithReturn muF = (f,g,h) -> { return f + " " + g + " " + h; }; System.out.println(muF.test06("Hello", "World", 2024)); } }
语法重点特征:(简化规则)
参数列表: Lambda表达式的参数列表可以为空,也可以有一个或多个参数。如果只有一个参数,可以省略括号;如果没有参数,则使用空括号表示。
Lambda体: Lambda表达式的执行体可以是一个表达式,也可以是一个代码块(多条语句)。
如果是一个表达式,它会被隐式地返回,return与{}可不写;如果是一个代码块(多条语句),必须使用大括号包围,并且需要显式地使用return
语句来返回值(有返回值)。
类型声明: Java编译器可以根据上下文推断Lambda表达式中参数的类型。非特殊情况,不需要显式地声明参数的类型;参数类型可不写。
上面代码可针对简化。
3. 变量作用域
Lambda表达式可以捕获其所在作用域内的变量,这种捕获称为闭包;Lambda表达式的变量捕获行为会影响变量的作用域和生命周期。
- 可直接在lambda表达式中访问外层的局部变量或成员变量;
- Lambda表达式中访问的局部变量必须是隐式最终或明确声明为final的;
在lambda表达式中被引用的变量的值不可被更改;(本质上因为Lambda用的是内部类)
在lambda表达式当中不允许声明一个与局部变量同名的参数(包括参数的名称),否则可能会引起混淆与歧义;
public static void main(String[] args) { // 外层-局部变量 double date = 2024.0221; // lambda表达式方法体 IInterface ii = () -> { System.out.println(date); // 在lambda表达式内引用了外部变量date。 // double date = 2026.0221; // 内存内开辟的空间,同一块空间两者同名是不可以的。 // date = 2025.0221; // lambda表达式内的常量是不可以改变的。 }; ii.test01(); }
补充内部类应用的变量为什么是常量(final)? 1. 因为局部内部类是相当于拷贝一份局部变量的值,如果局部变量的值可以随便的改变,那么可能会造成结果的不一致性,所以局部变量必须定义成常量。 2. 从生命周期上来看,内部类运行期间可能会使用局部变量的值的时间超过内部类的存在时间,若局部变量的值不是final类型,则会出现引用非法。(线程使用)
4. 四大内置核心函数式接口
函数式接口:Supplier<T>、Cousumer<T>、Function<T R>、Predicate<T>
1) Supplier<T>
供给型接口,不接受任何参数(无参),返回 T 类型;通常用于生成或提供数据。
public static void main(String[] args) { // 1. Supplier<T> 接口 // 创建一个供应商,用于提供 Supplier<String> supplier = () -> "Hello,World"; // 使用供应商提供的值 System.out.println(supplier.get()); }
2) Consumer<T>
消费性接口,接受一个 T 类型的参数,无返回值。通常用于消费传入的数据,执行某些操作。
// 2. Consumer<T> // 创建一个消费者,用于打印 Consumer<String> consumer = (str) -> System.out.println("Printing:" + str); // (str)-t:名字可随意 // 使用消费者消费数据 consumer.accept("Hello,World!");
// 创建一个list List<String> listName = Arrays.asList("张三", "李四", "王五", "赵六"); // 创建一个消费者,用于打印 Consumer<String> consumer = (name) -> System.out.println("姓名:" + name); // 遍历List,并使用消费者消费数据 for (String name : listName) { consumer.accept(name); }
3) Function<T,R>
功能型/函数型接口,接收一个 T 类型参数,返回一个 R 类型。用于将传入的数据转换为其他形式。(T转换为R)
// 3. Function<T,R> // 创建一个函数,将字符转转为大写 Function<String,String> function = (str) -> str.toUpperCase(); // 使用函数进行转换 String result = function.apply("hello,world!"); System.out.println(result);
// List 转为HashMap // 创建List List<String> names = Arrays.asList("刘备", "曹阿瞒", "孙仲谋", "诸葛孔明"); // names.stream转换为流;collect()方法传入收集器-toMap传入map,转化为键值对; Map<String,Integer> transformation = names.stream().collect(Collectors.toMap(Function.identity(),String::length)); System.out.println(transformation);
4) Predicate<T>
断言型接口,接受一个 T 类型,返回 boolean 值。通常用于检查传入的值是否满足某种条件。
// 4. Predicate<T> // 创建一个Predicate,判断一个整数是否为偶数 Predicate<Integer> judgement = (num) -> num % 2 ==0; // 测试数字是否满足条件 System.out.println(judgement.test(0221)); // false System.out.println(judgement.test(2024)); // true
5. 方法引用
构造方式是Java中一种简化Lambda表达式的语法结构,使得直接引用现有的方法或构造函数;
常用于函数式接口的实例化;减少代码冗余,更为清洁易懂。
语法:类型 :: 方法名
类名:可为类名,接口名,对象名;
方法名:需被引用的方法名称;
双冒号 ::为引用运算符,所在的表达式为方法引用。
当Lambda表达式要表示的函数方案已经存在于某个方法的实现中时,就可以通过双冒号来引用该方法作为Lambda。
1) 构造方法引用
语法格式:对象 :: new
注意:需要调用构造器的参数列表 要与 函数式接口中的抽象方法的参数列表一致,可为一个参或多个参。
语法形式符合 (方法参数) -> new 对象 (构造参数),此类型,就可使用 对象 :: new,若为有参的构造方法,参数由方法参数决定。
无参构造方法引用:
public class Person { // person类,无参构造方法 public Person() { } }
public static void main(String[] args) { // 匿名内部类实现 Supplier 接口的get方法 Supplier<Person> s = new Supplier<Person>() @0verride public Person get(){ return new Person(); } // 无参构造方法引用Person对象
Supplier<Person> s2 =()->new Person(); // 使用 lambda表达式创建Supplier接口实例
Supplier<Person> personSupplier = Person::new; // 使用构造方法的引用 Supplier<Person> 函数式接口 Person person = personSupplier.get(); // 返回 Person 对象 }
有参构造方法引用:
public class Book { // Book 类,有参构造函数 private String bookName; public Book(String bookName) { this.bookName = bookName; } @Override public String toString() { return "Book{" + "bookName='" + bookName + '\'' + '}'; } }
public static void main(String[] args) { // 使用构造方法引用,创建Book对象 Function<String,Book> function = Book::new; // Function<String,Book> 函数式接口 Book book = function.apply("Java8--Lambda表达式."); // 接受返回Book对象 System.out.println(book); }
2) 静态方法引用
静态方法引用允许通过方法的名称来引用一个静态方法。
语法格式:类名 :: 静态方法名
本质上要求:接口的方法,调用另一个类的静态方法。两个方法的参数要一致。
// 接口内包含静态方法 public interface StringUtils { // 静态方法 public static String upperCase(String str){ return str.toUpperCase(); } }
public static void main(String[] args) { // 静态方法引用 Function<String,String> stringFunction = String::toUpperCase; // 创建函数式接口实例stringFunction,接受字符串并转换字符换结果 // 使用静态方法引用将字符串转换为大写 String result = stringFunction.apply("Hello,World!"); // 调用后将字符串传递给函数式接口的apply方法 System.out.println(result); // 结果:HELLO,WORLD! }
3) 实例方法引用
语法格式:对象名 :: 方法名
本质要求:实例方法允许接口的方法调用特定对象的实例方法。两个方法参数要求一致。
// 例子:反转字符串 interface StringProcessor { // 定义接口内有方法 String process(String str); // 方法接受字符串作为参数并返回处理后后的字符串 } class StringUtils{ // 类内包含一个实例方法,用于反转字符串 String reverse(String str) { return new StringBuilder(str).reverse().toString(); } }
public static void main(String[] args) { // 实例方法引用将接口方法process绑定到StringUtils类的实例方法reverse内 // 创建对象 StringUtils stringUtils = new StringUtils(); // 实力方法引用 StringProcessor 调用 stringUtils StringProcessor stringProcessor = stringUtils::reverse; // 调用实例方法引用处理字符串 String result = stringProcessor.process("Hello,World!"); System.out.println(result); // 打印输出:!dlroW,olleH }
4) 对象方法引用
语法格式:对象名 :: 方法名
条件:在抽象方法中,参数作为实例方法调用者,就可简化。
// 有接口 Quote,有抽象方法 say,用于向某人打招呼。将使用对象方法引用来实现该接口 // 有 Person 类,内有实例方法 greet,用于向指定的人打招呼。使用 Quote 接口来实现这个功能,并使用对象方法引用来简化代码。 public interface Quote { // 定义接口 void say(String name); } class Person{ // 实例方法 public void greet(String name) { System.out.println("hello," + name + "!"); } }
public static void main(String[] args) { // 创建Person对象 Person person = new Person(); // 使用对象方法引用实现Quote接口 Quote quote = person::greet; // 使用对象方法引用简化,say()方法第一个参数调用者为person对象 // 调用 Quote 接口的方法,指定人打招呼 quote.say("张三"); quote.say("王五"); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南