文章目录
前言
做项目的时候发现自己看不懂Lambda表达式了,明明之前学过,现在忘得一干二净了😵
所以决定写一篇文章复习一下。如果觉得文章中有存在问题的地方,请大佬于评论区指出,莫喷哈 /(ㄒoㄒ)/~~
参考文章:
⭐Lambda表达式,函数式接口,方法引用详解(推荐)
Java lambda表达式10个示例
Java基础-Lambda表达式
一、什么是Lambda表达式?
Lambda 表达式的主要作用就是可以用于简化匿名函数实现,可以理解为我们按格式书写,它最后会自动识别。看下面代码,可以认为只是格式变了,当经过它识别后又会变成一样了。
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("匿名函数实现写法");
}
};
Runnable r2 = ()-> System.out.println("Lambda表达式写法");
对比来看其实就是把new Thread后的括号内容简化了,代码看起来优雅了不少。
二、Lambda表达式的结构
Lambda 表达式由三部分组成:
- 形参列表:形参列表允许省略类型,如果形参列表中只有一个参数,形参列表的圆括号也可以省略;
- 箭头(->):通过英文画线和大于符号组成;
- 代码块:如果代码块只有一条语句,花括号可以省略。Lambda 代码块只有一条 return 语句,可以省略 return 关键字,Lambda 表达式会自动返回这条语句的值作为返回值。
所以我们通常会看到如下三种结构的:
- (params) -> expression
- (params) -> statement
- (params) -> { statements }
三、函数式接口
3.1 什么是函数式接口?
通过前面的内容,我们大致对Lambda表达式有了大致的了解了。在举例Lambda的使用前我先来说明一下函数式接口,这个可以认为式Lambda的使用前提条件。现在举个例子来说明一下函数式接口:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
从上面Runnable的源码可以发现这个接口只包含一个抽象方法的接口,所以它就是函数式接口。java不仅支持面向对象编程(OOP)也可以面向函数编程(OOF),Lambda表达式就是一个函数式接口的对象。注解@FunctionalInterface可以来检查是否为函数式接口。
注意:函数式接口可以包含多个默认方法、类方法,但仅能声明一个抽象方法。
3.2 举几个例子来加深一下印象
例子1:来看一下这一段代码,你觉得如下代码是否有问题?答案是可以通过编译的哈,如果错了的话,应该是没注意到,接口里的方法都是默认抽象方法,不管你写没写abstract。
@FunctionalInterface
interface MyInterface {
public void menthod();
}
class MyClass {
public static void main(String[] args) {
MyInterface myInterface = ()-> {
System.out.println("代码有问题吗?");
};
}
}
例子2:在例子1的接口再加一方法,现在是否有问题?答案应该显而易见,现在就有两个抽象方法了,所以肯定是不能通过编译的!
@FunctionalInterface
interface MyInterface {
public void menthod();
public void menthod2();
}
class MyClass {
public static void main(String[] args) {
MyInterface myInterface = () -> {
System.out.println("");
};
}
}
为了保证 Lambda 表达式的目标类型是明确的函数式接口,有如下三种常见方式:
- 将 Lambda 表达式赋值给函数式接口类型的变量;
- 将 Lambda 表达式作为函数式接口类型的参数传给某个方法;
- 使用函数式接口对 Lambda 表达式进行强制类型转换;
可以把对象强转再接收给非函数式接口:
Object obj = (Runnable)() -> {
System.out.println("强转");
};
3.3 补充-四大函数式接口
四大函数式接口指的是Consumer、Function、Predicate、Supplier,位于java.util.function包下
-
Consumer:消费型接口 void accept(T t);
消费了参数t,没有返回任何东西 -
Supplier:供给型接口 T get();
没有消费,且有返回T -
Function<T,R>:函数型接口 ,T为自变量,R为因变量 R apply(T t);
消费t,返回R,类似我们初中学到函数,传入x,得到y -
Predicate:断言型接口 boolean test(T t)
给t它返回布尔值
四、Lambda的使用
4.1 啥时候可以省略?
-
无参,无返回值
比如Runnable接口里的run方法按下面这段代码实现。无参,所以左边只写 () 即可;无返回值,所以没有return语句;且只有一条语句,所以不需要 {}Runnable r2 = ()-> System.out.println("Lambda表达式写法");
-
一个参数,无返回值
这里有两种写法,有参数可以省去参数类型(编译器会推断,“类型判断”),只有一个参数可以省去() ,只有一条语句可以省去{}。interface Function{ public void method(int a); } Function f1 = (int a) -> {System.out.println("只有一个参数无返回值");}; Function f2 = a -> System.out.println("只有一个参数无返回值");
-
Lambda有多个参数,有返回值,多条语句
多个参数不能省去(),这里直接用了Integer的compare方法来实现,如果是自己写并且有多条语句就要保留{},其中最后一定要return。(这里如果看不太懂没关系,等会说明方法引用时会再说明一下)Comparator<Integer> comparator = (o1, o2) -> Integer.compare(o1,o2); System.out.println(comparator.compare(12,3)); //下面是Integer实现的compare方法源码 public static int compare(int x, int y) { return (x < y) ? -1 : ((x == y) ? 0 : 1); }
-
Lambda有多个参数,有返回值,单条语句
看下面这段代码,因为只有一条语句且为return,所以可以直接写结果a-b(f2那种写法)interface Function { public int method(int a,int b); } Function f1 = (a,b) -> {return a-b;}; Function f2 = (a,b) -> a-b;
4.2 方法引用
方法引用有三种,对象::实例方法(即非静态方法)、类::静态方法、类::非静态方法;先说明一下什么是方法引用,其实上面4.1的第三点已经使用到了,其实就是我们把一个类或对象的方法复制成函数式接口抽象方法的具体实现(像上面把Integer的compare方法“复制”到了Comparator的compare,可以理解为对抽象方法的“继承”和“实现”)
Comparator<Integer> comparator = (o1, o2) -> Integer.compare(o1,o2);
//⭐comparator对象中的compare方法其实就是Integer类的compare方法
- 对象::非静态方法
我稍微说明一下下面的代码,con2才是使用方法引用实现的。因为ps对象的非静态方法println()的参数和返回值与con2的accept()方法是一样的(都是接收字符串String无返回值),所以可以使用方法引用,把ps对象的println()方法“复制”给con2的accept。
- 类 : : 静态方法
这段代码和对象::非静态方法是很类似的,就不过多阐述了。
- 🔺类 : : 非静态方法
compare的两个参数,第一个参数可以当做调用者调用String类的compareTo方法,第二个参数仍然作为参数。这时就可以使用类::非静态方法的形式。注意我们仍然不写compareTo的参数列表。
上面两种方式都等价于下面这种写法,个人认为不必被这个叫法(类::静态方法)误导,其实就是利用一个参数去调用某个类的方法,最终返回结果和函数式接口的抽象方法是一致的!Comparator<String> com = new Comparator<String>() { @Override public int compare(String s1, String s2) { return s1.compareTo(s2); } };
再看一个例子,看代码应该可以发现规律了:
按照我前面加粗的结论,Employee : : getName,左边类就是传入参数对象,右边就是要调用的方法。基于此,我大致推断一下使用匿名函数的写法(不一定对哈):
interface Function<T,R> {
public R apply(T t);
}
//R = String, T = Employee
Function<Employee,String> func3 = new Function<Employee,String>() {
@Override
public String apply(Employee t) {
return t.getName(t);
}
};
4.3 构造器引用
-
无参构造器
其实构造器也是方法,所以和前面方法引用很类似,无非使用的是new。也就是把Employee类的构造器方法作为sup、sup1或sup2的get方法的实际方法(get方法和和构造器方法都是无参且返回值都是Employee)。
- 有参构造器
和无参构造器的类似,引用直接Employee::new即可,区别在于调用apply方法时才要传入参数。
4.4 数组引用
返回值为一个数组对象,其他的和构造器引用类似。把数组看做一个特殊的类,则写法与构造器引用一致。
总结
本文由浅入深,先介绍了Lambda表达式,再说明Lambda表达式的结构,也就是我们的书写格式。继而说明了Lambda表达式的使用前提-函数式接口。最后一节说明了一下格式的省略,以及方法引用、构造器引用和数组引用。