一、Lambda表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java的语言表达能力得到了提升。
在以往的开发中,我们使用方法传递主要是通过接口使用匿名内部类的方式:
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}
});
当使用 Lambda 表达式时,则可以使用以下方式:
TreeSet<String> t2 = new TreeSet<>((o1, o2) -> Integer.compare(o1.length(), o2.length()));
Lambda 表达式的本质:作为接口的实例。
语法
(parameters) -> expression
或
(parameters) -> { statements; }
Lambda 表达式在 Java 语言中引入了一个新的语法元素和操作符。这个操作符为 ->
, 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为 两个部分:
- 左侧:指定了 Lambda 形参列表(其实就是接口中的抽象方法的形参列表)
- 右侧:指定了 Lambda 体(其实就是重写的抽象方法的方法体)
Lambda 表达式语法分为以下六类:
//语法格式一:无参,无返回值,Lambda体只需一条语句
Runnable l1 = () -> System.out.println("Hello Lambda");
//语法格式二:Lambda需要一个参数
Consumer<String> l2 = (x) -> System.out.println(x);
//语法格式三:Lambda只需要一个参数时,参数的小括号可以省略
Consumer<String> l3 = x -> System.out.println(x);
//语法格式四:Lambda需要两个参数,并且有返回值
BinaryOperator<Long> l4 = (x, y) ->{
System.out.println("Lambda体有两条语句");
return x + y;
};
//语法格式五:当Lambda体只有一条语句时,return与大括号可以省略
BinaryOperator<Long> l5 = (x, y) -> x + y;
//语法格式六:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
BinaryOperator<Long> l6 = (Long x, Long y) ->{
System.out.println("类型推断");
return x + y;
};
优势
- 简洁性: Lambda 表达式大大简化了代码的编写方式,尤其是在处理匿名内部类和函数式接口时。它避免了大量的样板代码,使得代码更加简洁易读。
- 灵活性: 它提供了一种更加灵活的方式来传递代码逻辑。我们可以根据需要随时创建和使用 Lambda 表达式,而不需要像传统方式那样定义一个专门的类来实现接口。
- 与函数式编程的融合: Lambda 表达式是 Java 向函数式编程迈进的重要一步。它使得 Java 开发者能够更好地利用函数式编程的思想和技术,提高代码的质量和可维护性。
二、函数式接口
想要对Lambda表达式有一个深入的理解,我们需要去认识另外一个知识点,那就是函数式接口。在上面我们的举得例子中比如Consumer或者是BinaryOperator为什么能够使用Lambda呢?就是因为实函数式接口,那么什么是函数式接口?
-
只包含一个抽象方法的接口,称为函数式接口。
-
我们可以在任意函数式接口上使用
@FunctionalInterface
注解, 这样做可以检查它是否是一个函数式接口。
@FunctionalInterface
public interface MyInterface<T> {
public T getValue(T t);
}
如果不是抽象接口,@FunctionalInterface
则会保错
四种内置核心函数
简而言之,接口要想使用Lambda表达式,就必须是函数式接口,为了方便日常开发需求,java也内置了许多函数式接口,下面介绍四种核心的函数式接口:
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer<T> 消费型接口 | T | void | 对类型为T的对象应用作,包含方法: void accept(T t) |
Supplier<T> 供给型接口 | 无 | T | 返回类型为T的对象,包含方法:T get( ); |
Function<T, R> 函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t); |
Predicate<T> 断定型接口 | T | boolean | 确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法 boolean test(T t); |
1、Consumer<T>
消费型接口
// Consumer<T> 消费型接口:
@Test
public void test1(){
happy(100, (m) -> System.out.printf("今天消费:" + m + " 元"));
}
private void happy(double money, Consumer<Double> consumer){
consumer.accept(money);
}
2、Supplier<T>
供给型接口
// Supplier<T> 供给型接口
@Test
public void test2(){
List<Integer> n1 = getNumList1(10, () -> (int) (Math.random() * 100));
System.out.println(n1);
List<Integer> n2 = getNumList2(10, () -> (int) (Math.random() * 100));
System.out.println(n2);
}
//差生指定个数的整数, 并放入集合中
public List<Integer> getNumList1(int num, Supplier<Integer> supplier){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
list.add(supplier.get());
}
return list;
}
//在后续我们会学习Stream类,先来混个脸熟
public List<Integer> getNumList2(int num, Supplier<Integer> supplier){
return IntStream.range(0, num)
.mapToObj(i -> supplier.get())
.collect(Collectors.toList());
}
3、Function<T, R>
函数型接口
// Function<T, R> 函数型接口
@Test
public void test3(){
String t1 = transform("hello function", (str) -> str.toUpperCase());
System.out.println(t1);
String t2 = transform("hello function", (str) -> str.toLowerCase());
System.out.println(t2);
}
// 用于处理字符串
public String transform(String str, Function<String, String> function){
return function.apply(str);
}
4、Predicate<T>
断言型接口
// Predicate<T> 断言型接口
@Test
public void test4(){
List<String> list = Arrays.asList("Hello", "Java", "Lambda", "Function", "Stream");
List<String> stringList1 = filterStr1(list, s -> s.length() > 4);
System.out.println(stringList1);
List<String> stringList2 = filterStr2(list, s -> s.length() > 4);
System.out.println(stringList2);
}
//将满足条件的字符串放入集合
public List<String> filterStr1(List<String> list, Predicate<String> predicate){
List<String> strList = new ArrayList<>();
for (String s : list) {
if (predicate.test(s)){
strList.add(s);
}
}
return strList;
}
//在后续我们会学习Stream类,先来混个脸熟
public List<String> filterStr2(List<String> list, Predicate<String> predicate){
return list.stream()
.filter(x -> predicate.test(x))
.collect(Collectors.toList());
}
其他接口
除了以上四种核心接口,java还内置了其他的一些函数接口 :
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction<T, U, R> | T, U | R | 对类型为T, U参数应用操作,返回R类型的结果。包含方法为 Rapply(T t,U u); |
UnaryOperator<T> (Function子 接口) | T | T | 对类型为T的对象进行一元运算,并返回T类型的结果。包含方法为 Tapply(T t); |
BinaryOperator<T, U> (BiFunction子接口) | T, T | T | 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为 Tapply(T t1,T t2); |
BiConsumer<T, U> | T, U | void | 对类型为T, U参数应用操作。包含方法为 void accept(T t, U u) |
ToIntFunction<T> ToLongFunction<T> ToDoubleFunction<T> | T | int long double | 对类型为T, U参数应用操作。包含方法为 void accept(T t, U u) |
IntFunction<R> LongFunction<R> DoubleFunction<R> | int long double | R | 参数分别为int、long、 double类型的函数 |
三、方法引用和构造器引用
方法引用
若 Lambda 体中的内容有方法已经实现了, 我们可以使用“方法引用”,(可以理解为方法引用是 Lambda 表达式的另一种表现形式)
方法引用的语法有三种:
- 对象 : : 实例方法
- 类 : : 静态方法
- 类 : : 实例方法
1、对象 : : 实例方法
// 对象::实例方法 @Test public void test1(){ Employee employee = new Employee("Lambda表达式...", "方法引用..."); // 原写法 Supplier<String> s1 = () -> employee.getS1(); System.out.println(s1.get()); // 对象::实例方法 Supplier<String> s2 = employee::getS2; System.out.println(s2.get()); } class Employee{ private String s1; private String s2; public Employee(String s1, String s2) { this.s1 = s1; this.s2 = s2; } public String getS1() { return s1; } public String getS2() { return s2; } }
2、类 : : 静态方法
// 类::静态方法 @Test public void test2(){ // 原写法 Comparator<Integer> c1 = (x, y) -> Integer.compare(x, y); // 类::静态方法 Comparator<Integer> c2 = Integer::compare; }
Integer.compare( ) 原码:
public static int compare(int x, int y) { return (x < y) ? -1 : ((x == y) ? 0 : 1); }
3、类 : : 实例方法
// 类::实例方法 @Test public void test3(){ // 原写法 BiPredicate<String, String> p1 = (x, y) -> x.equals(y); // 类::实例方法 BiPredicate<String, String> p2 = String::equals; }
String 的equals( ) 原码:
public boolean equals(Object anObject) { if (this == anObject) { return true; } return (anObject instanceof String aString) && (!COMPACT_STRINGS || this.coder == aString.coder) && StringLatin1.equals(value, aString.value); }
注意:
-
Lambda 体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值一致;
-
Lambda 参数列表的第一个参数是实例方法的调用者, 第二个方法是实例方法的参数时可以使用 “类 : : 实例方法”
构造器引用
与函数式接口相结合,自动与函数式接口中方法兼容。 可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!
格式:ClassName : : new
// 构造器引用 ClassName::new
@Test
public void test5(){
// 构造器的使用取决于 方法的参数与构造方法的参数相匹配
Supplier<Student> s1 = () -> new Student();
Supplier<Student> s2 = Student::new;
System.out.println(s2.get().name);
Function<String, Student> f1 = (x) -> new Student(x);
Function<String, Student> f2 = Student::new;
System.out.println(f2.apply("张三").name);
}
class Student{
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
}
四、总结
Lambda 表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。
优点:
- 代码简洁,开发迅速
- 方便函数式编程
- 非常容易进行并行计算
- Java 引入 Lambda,改善了集合操作
缺点:
- 代码可读性变差
- 在非并行计算中,很多计算未必有传统的 for 性能要高
- 不容易进行调试