一、介绍
1、简介
Java的Lambda表达式是Java 8引入的一个特性,它支持函数式编程,允许将函数作为方法的参数或返回值,从而简化了匿名内部类的使用,并提供了对并行编程的更好支持。
可以将Lambda表达式理解为一个匿名函数; Lambda表达式允许将一个函数作为另外一个函数的参数; 我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码作为实参),也可以理解为函数式编程,将一个函数作为参数进行传递。
2、优点
Lambda表达式的主要优势包括:
- 简化匿名内部类的使用,使代码更加简洁明了。
- 支持函数式编程,允许函数作为第一类对象进行传递和操作。
- 促进并行编程,因为函数式编程中的纯函数天然具备无副作用的特性,使得在并行编程中更容易实现可靠的多线程和并行处理。
3、用途
Lambda表达式主要用于函数式接口,即只包含一个抽象方法的接口,可以使用@FunctionalInterface
注解进行标识。常用场景有:
- 创建Runnable实例:
Runnable runnable = () -> {System.out.println("Hello, Lambda!");};
- 实现Consumer接口:
numbers.forEach(n -> System.out.println(n));
- 实现Predicate接口:
filteredNames = names.stream().filter(name -> name.length() > 5).collect(Collectors.toList());
- 创建线程:
new Thread(() -> System.out.println("haha")).start();
二、语法与使用
1、基本语法
([Lambda参数列表,即形参列表]) -> {Lambda体,即方法体}
如果主体只有一行代码可以更简化,去掉{}:
(parameters) -> expression
(1)Lambda 表达式关注的是接口中方法的返回值和参数,方法名不重要
(2)使用 "->"将参数和实现逻辑分离;
( ) 中的部分是需要传入Lambda体中的参数,参数可以是任意合法的Java参数列表,可以为空或包含一个或多个参数;
{ } 中部分,接收来自 ( ) 中的参数,完成一定的功能。Lambda主体可以是一个表达式,也可以是一个代码块。如果主体是一个表达式,它将直接返回该表达式的结果。如果主体是一个代码块,它将按照常规的Java语法执行,并且您可能需要使用return
语句来返回值。
(3)只有函数式接口的变量或者是函数式接口,才能够赋值为Lambda表达式。这个接口中,可以有默认方法,或者是静态方法。
2、使用条件
lambda表达式和方法引用使用前提:函数式接口
(1)@FunctionalInterface 语法格式严格要求当前接口有且只能有一个尚未完成的缺省属性为 public abstract 修饰方法。Lambda表达式的使用前提是存在一个接口,该接口中有且只有一个抽象方法。在主方法中可以通过创建接口的匿名内部类或使用Lambda表达式来调用该接口的方法。
(2)函数式接口一般用于方法的增强,直接作为方法的参数,实现函数式编程。只有函数式接口的变量或者是函数式接口,才能够赋值为Lambda表达式。这个接口中,可以有默认方法,或者是静态方法。
3、四种lambda表达式
(1)无参数无返回值
() -> System.out.println("Hello, Lambda!");
如:
//接口设计
@FunctionalInterface
interface A {void 方法名真的没有用();
}
//方法设计
public static void testLambda(A a) {a.方法名真的没有用();
}
//代码实现
public static void main(String[] args) {//1.匿名内部类方法//接口无法实例化,这里实例化的是 A 接口的实现类对象(该方法在jdk1.8以后被lambda表达式完虐)testLambda(new A() {//缺省属性为abstract,需要重写@Overridepublic void 方法名真的没有用() {System.out.println("无参数返回值 匿名内部类对象方法实现");}});/*2. Lambda 表达式实现【分析】void 方法名真的没有用();接口方法【返回值类型】 void,无返回值接口方法【参数】 无参数*/testLambda(() -> {System.out.println("Lambda 表达式初体验");});//Lambda 表达式有且只有一行代码,可以省略大括号testLambda(() -> System.out.println("Lambda 表达式初体验"));
(2) 无参数有返回值
(x, y) -> System.out.println(x + y);(x, y) -> {int sum = x + y;System.out.println("Sum: " + sum);return sum;
}
//接口设计
@FunctionalInterface
interface Supplier<T> {/*** 无参数有返回值方法,泛型约束的是接口对应的返回值数据类型,要求按照泛型约束返回对应的数据内容** @return 返回一个数据,符合泛型约束*/T get();
}
/** 当前方法要求返回一个字符串数据内容*/
public static String testLambda(Supplier<String> s) {return s.get();
}
public static void main(String[] args) {/*【分析】T get(); ==> 泛型约束为 String ==> String get();接口方法【返回值类型】 String接口方法【参数】 无参数Lambda 格式:() -> {必须返回一个 String 类型}*/String s1 = testLambda(() -> {return "这里也是一个字符串";});System.out.println(s1);/*Lambda 优化,只要 -> 之后是一个 字符串数据内容就可以满足当前 Lambda 所需可以省略 return ,前提是当前 Lambda 有且只有一行代码*/String s2 = testLambda(() -> "这里也是一个字符串");System.out.println(s2);/*Lambda 内部使用使用方法局部变量*/String str = "name=王小明&age=23&country=中国";String s3 = testLambda(() -> {// str 是当前 main 方法局部变量,Lambda 内部可以直接使用String[] split = str.split("&");return split[0];});System.out.println(s3);//返回王小明
}
(3) 有参数无返回值
@FunctionalInterface
interface Consumer<T> {/*** 消费者接口,数据最终处理接口,数据处理终止方法接口,对应的方法要求方法有参数无返回值** @param t 泛型数据数据类型 T ,支持任意类型,在接口约束之后,要求符合数据类型一致化要求*/void accept(T t);
}
/*** 有参数无返回 Lambda 测试方法,方法参数是 String 类型和针对于 String 类型* 进行数据处理的 Consumer 接口,Consumer 接口可以传入实现类对象和 Lambda 表达式** @param str 目标处理的 String 字符串数据* @param handle 已经约束为处理 String 类型数据的 Consumer 接口处理器*/
public static void testLambda(String str, Consumer<String> handle) {handle.accept(str);
}
public static void main(String[] args) {/*1、匿名内部类 Low*/testLambda("孟州市炒面第一名", new Consumer<String>() {@Overridepublic void accept(String t) {System.out.println(t);}});/*2. Lambda 表达式【分析】void accept(T t); ==> 泛型约束为 String ==> void accept(String t);接口方法【返回值】 void接口方法【参数】 1 个参数,String 类型Lambda 格式Lambda 小括号中的临时变量名称,没有数据类型体现,需要【联想】目标方法数据类型只按照参数的个数定义临时小变量(s) -> {大括号中无需返回值类型}Lambda 表达式临时变量 s 对应的数据类型为 String 类型 【联想可得】*/testLambda("lambda表达式需要联想!!!", (s) -> {System.out.println(Arrays.toString(s.toCharArray()));});/*Lambda 优化1. 代码块有且只有一行,可以省略大括号2. 小括号中有且只有一个 参数,可以省略小括号【注意】Lambda 承担的角色是一个针对于 String 字符串的处理器*/testLambda("lambda表达式需要联想!!!", s -> System.out.println(Arrays.toString(s.toCharArray())));
}
(4)有参数有返回值
4、方法引用(拓展):
当Lambda表达式满足某种条件的时候,使用方法引用,可以再次简化代码。
(1)构造引用
当Lambda表达式是通过new一个对象来完成的,那么可以使用构造引用。
import java.util.function.Supplier;
public class TestLambda {public static void main(String[] args) {
// Supplier<Student> s = () -> new Student();Supplier<Student> s = Student::new;}//实际过程:将new Student()赋值给了Supplier这个函数式接口中的那个抽象方法
}
(2) 类名::实例方法
Lambda表达式的的Lambda体也是通过一个对象的方法完成,但是调用方法的对象是Lambda表达式的参数列表中的一个,剩下的参数正好是给这个方法的实参。
import java.util.TreeSet;
public class TestLambda {public static void main(String[] args) {TreeSet<String> set = new TreeSet<>((s1,s2) -> s1.compareTo(s2));
}
(3) 对象::实例方法
*/ //类名::实例方法TreeSet<String> set = new TreeSet<>(String::compareTo);set.add("Hello");set.add("isea_you");// set.forEach(t -> System.out.println(t));//Hello \n isea_youset.forEach(System.out::println);//(1)对象::实例方法,Lambda表达式的(形参列表)与实例方法的(实参列表)类型,个数是对应}
}
(4)类名::静态方法
package com.isea.java;
import java.util.stream.Stream;
public class TestLambda {public static void main(String[] args) {
// Stream<Double> stream = Stream.generate(() -> Math.random());
// 类名::静态方法, Lambda表达式的(形参列表)与实例方法的(实参列表)类型,个数是对应Stream<Double> stream = Stream.generate(Math::random);stream.forEach(System.out::println);}
}
5、变量捕获
5.1、匿名内部类的变量捕获
在Java中,匿名内部类可以捕获外部变量,即在匿名内部类中引用并访问外部作用域的变量。这种行为称为变量捕获(Variable Capturing)。在匿名内部类中,可以捕获以下类型的变量:
(1)实例变量(Instance Variables):如果匿名内部类位于一个实例方法中,它可以捕获并访问该实例的实例变量。
(2)静态变量(Static Variables):匿名内部类可以捕获并访问包含它的类的静态变量。
(3)方法参数(Method Parameters):匿名内部类可以捕获并访问包含它的方法的参数。
(4)本地变量(Local Variables):匿名内部类可以捕获并访问声明为final的本地变量。从Java 8开始,final关键字可以省略,但该变量实际上必须是最终的(即不可修改)。
当匿名内部类捕获变量时,它们实际上是在生成的字节码中创建了一个对该变量的副本。这意味着即使在外部作用域中的变量发生改变,匿名内部类中捕获的变量仍然保持其最初的值。demo:
public class OuterClass {private int instanceVariable = 10;private static int staticVariable = 20;public void method() {final int localVar = 30; // 或者直接使用 Java 8+ 的隐式 finalRunnable runnable = new Runnable() {@Overridepublic void run() {System.out.println("Instance variable: " + instanceVariable);System.out.println("Static variable: " + staticVariable);System.out.println("Local variable: " + localVar);}};runnable.run();}
}
5.2、Lambda表达式的变量捕获
在Lambda表达式中,同样可以捕获外部作用域的变量。Lambda表达式可以捕获以下类型的变量:
(1)实例变量(Instance Variables):Lambda表达式可以捕获并访问包含它的实例的实例变量。
(2)静态变量(Static Variables):Lambda表达式可以捕获并访问包含它的类的静态变量。
(3)方法参数(Method Parameters):Lambda表达式可以捕获并访问包含它的方法的参数。
(4)本地变量(Local Variables):Lambda表达式可以捕获并访问声明为final的本地变量。从Java 8开始,final关键字可以省略,但该变量实际上必须是最终的(即不可修改)。
与匿名内部类不同,Lambda表达式不会创建对变量的副本,而是直接访问变量本身。这意味着在Lambda表达式中捕获的变量在外部作用域中发生的改变也会在Lambda表达式中反映出来。demo:
public class LambdaVariableCapture {private int instanceVariable = 10;private static int staticVariable = 20;public void method() {int localVar = 30;// Lambda表达式捕获外部变量Runnable runnable = () -> {System.out.println("Instance variable: " + instanceVariable);System.out.println("Static variable: " + staticVariable);System.out.println("Local variable: " + localVar);};runnable.run();}
}
三、使用场景
1、在集合中的使用
Lambda表达式在Collection
接口中的使用主要涉及对集合进行迭代、筛选和转换等操作。在Java 8及以上的版本中,Collection
接口增加了一些默认方法,例如forEach()
、removeIf()
和stream()
等,使得使用Lambda表达式更加方便。
1.1、Collection接口
(1)迭代
List<String> stringList = Arrays.asList("apppe","organge","banana");// 原来的方式for (String s : stringList) {System.out.println(s);}// lambda 表达式stringList.forEach(s->{System.out.println(s);});
(2)筛选
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);numbers.removeIf(n -> n % 2 == 0);
(3)转换
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");List<Integer> nameLengths = names.stream().map(name -> name.length()).collect(Collectors.toList());
(4)获取集合流:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);Stream<Integer> stream = numbers.stream();
(5)使用filter()
筛选集合元素:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");List<String> filteredNames = names.stream().filter(name -> name.startsWith("A")).collect(Collectors.toList());
1.2、 List接口
(1)遍历
List<String> fruits = Arrays.asList("Apple", "Banana", "Orange");fruits.forEach(fruit -> System.out.println(fruit));
(2)过滤
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);List<Integer> evenNumbers = numbers.stream().filter(number -> number % 2 == 0).collect(Collectors.toList());
(3)映射
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");List<Integer> nameLengths = names.stream().map(name -> name.length()).collect(Collectors.toList());
(4)查找
List<String> fruits = Arrays.asList("Apple", "Banana", "Orange");Optional<String> foundFruit = fruits.stream().filter(fruit -> fruit.startsWith("B")).findFirst();
(5)排序
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 6, 3, 9, 4, 7, 10);List<Integer> sortedNumbers = numbers.stream().sorted().collect(Collectors.toList());
1.3、Map接口
(1)迭代Map的键值对:
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 35);map.forEach((key, value) -> System.out.println(key + ": " + value));
(2)遍历Map的键或值:
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 35);map.keySet().forEach(key -> System.out.println(key));
map.values().forEach(value -> System.out.println(value));
(3)使用Stream过滤Map的键值对:
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 35);Map<String, Integer> filteredMap = map.entrySet().stream().filter(entry -> entry.getValue() > 30).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
(4)对Map的值进行映射:
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 35);Map<String, String> mappedMap = map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> "Age: " + entry.getValue()));
(5)对Map的键或值进行归约操作:
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 35);int sumOfValues = map.values().stream().reduce(0, (a, b) -> a + b);
String concatenatedKeys = map.keySet().stream().reduce("", (a, b) -> a + b);