本系列博客包含如下内容,此篇帖子介绍前3篇
一,Lambda表达式
二,函数式接口
三,方法引用与构造器引用
四,Stream API
链接 https://blog.youkuaiyun.com/weixin_43833851/article/details/129571127
五,接口中的默认方法与静态方法
六,新的日期、时间API
七,其他新特性
本文介绍前三节
Lambda表达式
lambda是什么?
官方的概念就不说了,不太好理解,以我自己的理解而言,它就是一种简写的匿名内部类。
先简单对比一下:
1.1 使用匿名内部类实现Comparator
Comparator<Integer> comparator1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
1.2 使用Lambda 实现Comparator
Comparator<Integer> comparator2 = (o1,o2) -> o1.compareTo(o2);
实际上就是对匿名内部类进一步进行了代码量的削减,省略了类名、方法名、参数类型,只保留形参和方法体,如下图:

1.3 为什么类名、方法名、参数类型可以省略?
因为进行了自动推导,具体展开来说
1.3.1 为什么类名可以省略
因为 等号 左边已经写了类型是Comparator,所以可以推导出 右边的匿名子对象一定是Comparator的实现类,所以可以省略类名;
1.3.2 为什么方法名可以省略
因为 Comparator 接口中,仅有一个需要实现的方法,所以方法名也是可以推导出来的,
这也是Lambda使用的一个限制,那就是对于接口而言,仅只有一个需要实现的方法时才能用Lambda表达式,
只有一个抽象方法的interface称之为 "函数式接口",给interface加上注解 @FunctionalInterface可强制申明为 函数式接口,若定义多个抽象方法,就会报编译错误,如下:
1.3.2.1只定义一个方法时不报错:

1.3.2.2定义多个方法时就报错了:

1.3.3 为什么参数类型可以省略
既然类名、方法名都可以推导出来,那么方法的参数类型也是可以推导出来的,所以参数类型也可以省略;
那为什么参数名(比如o1和o2)不能省略呢?
因为你需要在方法体中使用参数,没有参数名,你咋使用这个参数了,比如compare方法的o1.compareTo(o2), 如果不保留参数名o1和o2,方法体就没法写了;
当然参数类型也是可以保留的,提升阅读性,比如这样写也是可以的:
Comparator<Integer> comparator2 = (Integer o1,Integer o2) -> o1.compareTo(o2);
1.3.4 小结
所以,我对于Lambda的理解,就认为是对匿名内部类的一种能省则省的简写(语法糖),它包含一个右箭头,箭头左边是参数,箭头右边是方法体,在不同场景,它有一些不同的写法,下面具体介绍一下它的不同写法
2,Lambda表达式的几种写法
2.1 场景一:需要覆盖的方法 无入参 、 无返回值、方法体仅有一句代码
例如Runnable的run方法,可以这样实现:
//使用匿名内部类实现Runnable
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类创建线程");
}
};
new Thread(runnable1).start();
//使用Lambda实现Runnable
Runnable runnable2 = ()-> System.out.println("Lambda创建线程");
new Thread(runnable2).start();
//也可直接写成一行,看起来就像把一段代码在作为参数传递
new Thread(()-> System.out.println("Lambda创建线程")).start();
因为没有入参,但是箭头左边必须有参数,所以左边仅写一对小括号即可,箭头右边是方法体,因为只有一句代码,所以方法体的 大括号 也省略了,这里给方法体加上大括号也是可以的。
2.2 场景二:需要覆盖的方法 只有1个入参、无返回值、方法体仅有一句代码
例如实现 java.util.function.Consumer 类,如下:
//使用匿名内部类实现 java.util.function.Consumer 类
Consumer<Integer> consumer1= new Consumer<Integer>() {
@Override
public void accept(Integer x) {
System.out.println(x);
}
};
//使用Lambda实现 java.util.function.Consumer 类
Consumer<Integer> consumer2 = x-> System.out.println(x);
//使用Lambda实现 java.util.function.Consumer 类,也可以保留参数类型
Consumer<Integer> consumer2 = (Integer x)-> System.out.println(x);
因为只有一个参数,所以连方法的小括号也省略了;
2.3 场景三:需要覆盖的方法 有多个入参、有返回值、方法体只有一句代码
例如上面的 Comparator 接口,如下:
//匿名内部类实现
Comparator<Integer> comparator1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
//Lambda实现, 省略return
Comparator<Integer> comparator2 = (o1,o2) -> o1.compareTo(o2);
//Lambda实现, 不省略return
Comparator<Integer> comparator3 = (o1,o2) -> {return o1.compareTo(o2);};
因为有多个入参,所以多个入参都需要写出来,此时小括号不能省略;
由于方法体只有一句代码,所以方法体的大括号可以省略;
注意:
当方法体只有一句代码时,return 也可以省略,因为就一句代码,如果需要return,那么return的一定是这句代码的结果,所以return可以省略,但是这样写是错误的:
//这样写是错误的
Comparator<Integer> comparator3 = (o1,o2) -> return o1.compareTo(o2);
但如果是多句代码,return 和 大括号都不可省略。
2.4 场景四:需要覆盖的方法 有多个入参、有返回值、方法体有多句代码
例如上面的 Comparator 接口,如下:
Comparator<Integer> comparator3 = (o1,o2) -> {
System.out.println("这是多行代码的示例");
return o1.compareTo(o2);
};
此时方法体的大括号、return 都不能省略。
2.5 小结
对于上述几种场景,不用死记硬背,只是在说参数的括号、方法体的括号、return 什么时候可以省略,如果记不住不省略也是可以的,写的时候开发工具也是有提示的,不用纠结。
3,Lambda表达式的四大核心函数式接口
3.1 为什么要有这4个接口
先看下面的铺垫便于理解,如果不想看,直接跳到3.2
示例:对一个数字进行多种处理,比如取平方、取立方、取负数等,分别看下几种实现方式
第一种做法:每种处理写一个方法,
public static void main(String[] args) {
pingFang(5);
liFang(5);
fuShu(5);
}
//取平方
public static int pingFang(int x){
return x*x;
}
//取立方
public static int liFang(int x){
return x*x*x;
}
//取负数
public static int fuShu(int x){
return -x;
}
第二种做法:定义一个接口,处理逻辑写在接口的实现类中(策略模式)
//定义一个接口
public interface NumberProcess {
int process(int x);
}
//测试类
public class Test{
public static void main(String[] args) {
//取平方
int res= calc(5, new NumberProcess() {
@Override
public int process(int x) {
return x*x;
}
});
//取立方
res= calc(5, new NumberProcess() {
@Override
public int process(int x) {
return x*x*x;
}
});
}
//定义一个公共的计算方法
public static int calc(int x,NumberProcess p){
return p.process(x);
}
}
第二种比第一种稍好一些,不需要有N个处理就写N个方法,但是需要额外定义一个接口,如果能把这个接口省略就更好了,所以java就定义了一些通用功能的接口,比如接口 java.util.function.Function;
其源码如下:

这是一个函数式接口(接口内只有一个抽象方法),接口内定义了一个apply方法,有两个泛型,第一个泛型T是入参类型,第二个泛型R是返回值类型,所以我们就可以不用自己定义上面的NumberProcess接口了,使用Function接口代替,因此有了第三种做法。
第三种做法:使用Function接口:
入参和返回值都是Integer类型
//测试类
public class Test{
public static void main(String[] args) {
//取平方
int res = calc(5, new Function<Integer, Integer>() {
@Override
public Integer apply(Integer x) {
return x*x;
}
});
//取立方
res = calc(5, new Function<Integer, Integer>() {
@Override
public Integer apply(Integer x) {
return x*x*x;
}
});
}
//定义一个公共的计算方法
public static int calc(int x,Function<Integer,Integer> f){
return f.apply(x);
}
}
这个写法,就可以使用Lambda进行简写,由此产生第四种做法。
第四种做法:使用Lambda对第三种进行简写
//测试类
public class Test{
public static void main(String[] args) {
//取平方
int res = calc(5, x -> x*x);
//取立方
res = calc(5, x -> x*x*x);
}
//定义一个公共的计算方法
public static int calc(int x,Function<Integer,Integer> f){
return f.apply(x);
}
}
小结:
为了简化一些策略模式的功能开发,java为我们提供了4种接口,上面用到的Function接口就是其中之一,让我们不需要再定义自己的接口就可以实现一些策略模式的功能。
3.2 有哪4个核心函数式接口?各有什么用途?
这4个接口的 入参个数 和 返回值类型 不一样
① java.util.function.Consumer;
有一个入参,无返回值时可以使用这个接口, 之所以叫消费者,就是因为它只进不出,比如上面3.1的例子就不适合使用它,因为3.1的例子有入参也有返回值(传入一个数字x,返回x的平方或立方)

示例:
Consumer<Integer> consumer = id->{
System.out.println("在这里做无返回值的操作");
System.out.println("比如执行delete语句,删除id="+id+"的数据");
};
consumer.accept(123);
② java.util.function.Supplier;
无入参,但是有返回值时可以使用这个接口,之所以叫提供者,就是因为它能无中生有,比如产生随机数的方法就可以用它来做

示例:
//产生随机数的例子,当然这里是为了使用而使用,旨在演示
Supplier<Integer> supplier = ()-> new Random().nextInt();
int randNum = supplier.get();
③ java.util.function.Function;
有一个入参和一个返回值,示例可参考3.1的第四种做法

还有个类似接口java.util.function.BiFunction; 它有两个入参,前两个泛型T 、U都是入参

如果入参个数超过2个,可以考虑使用集合类型作为入参、或者自己定义一个Bean
④ java.util.function.Predicate;
一个入参,返回值为boolean, 用来实现一些判断逻辑

示例:
这里使用到了stream方法,看不懂不要紧,主要关注Predicate接口:它只有一个入参、返回值为boolean
public class Test1 {
static List<User> userList = Arrays.asList(new User("A",20),
new User("B",25),new User("C",30));
public static void main(String[] args) {
//需求:获取age>20的user,存入新的list集合中
//第一种方式:通过Predicate的内部类实现
List<User> list = userList.stream().filter(new Predicate<User>() {
@Override
public boolean test(User user) {
return user.getAge() > 20;
}
}).collect(Collectors.toList());
//第二种方式:通过lambda简化内部类写法
list = userList.stream().filter(user -> user.getAge() > 20).collect(Collectors.toList());
}
}
小结:
这四大核心接口先了解即可,它们在Stream API中会大量使用。
4,方法引用、构造器引用、数组引用
4.1 方法引用
就是双冒号的用法( :: 的用法), 怎么理解它呢?看个例子:
//User类
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//测试方法,用到了java.util.function.Supplier类
public static void main(String[] args) {
User user = new User();
user.setName("tom");
//使用匿名内部类写
Supplier<String> supplier2 = new Supplier<String>() {
@Override
public String get() {
return user.getName();
}
};
String name = supplier2.get();
//使用方法引用进行改写,引用user类的getName方法作为Supplier的实现
Supplier<String> supplier = user::getName;
name = supplier.get();
}
实例中使用了两种写法,是等效的, 我对于方法引用的理解:
它也是一种Lambda表达式,前面说Lambda是一种匿名内部类的简写,那么 “方法引用”既然也是一种Lambda表达式,它必然也是一种匿名内部类的简写,即对接口实现的简写,只不过以往的 接口实现代码 都是我们自己在写,而这个“方法引用” 就是我们不用自己写接口实现的代码了,直接告诉它接口的实现代码 是哪个类的哪个方法(告诉它Supplier接口的实现代码就是user对象的getName方法),对象和方法名之间用双冒号隔开。
注意:引用某个对象的某个方法作为 接口的实现代码,那么一定要保证: 接口的方法的入参类型和返回值类型 一定要与你引用的方法 的入参类型和返回值类型 一样,否则不可以引用,如下:
//User类
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//MyInterface 接口
@FunctionalInterface
public interface MyInterface {
String hello();
}
测试方法如下:

可以看到user::getAge报错了,该方法不能作为MyInterface接口的实现,因为MyInterface的返回值类型是String,而getAge返回int,所以getAge不能被引用做MyInterface的实现方法;
方法引用有三种使用方式:
① 对象::实例方法名
示例:
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
User user = new User();
user.setName("tom");
//引用user对象的getName方法作为Supplier的实现
Supplier<String> supplier = user::getName;
String name = supplier.get();
}
方法引用时,方法的入参是可以省略的,把上面的user.setName用Consumer接口改写,加深理解,如下:
public static void main(String[] args) {
User user = new User();
//引用user对象的setName方法作为Consumer的实现
Consumer<String> consumer = user::setName;
consumer.accept("Tom");//类似调用了user。setName("Tom")
//引用user对象的getName方法作为Supplier的实现
Supplier<String> supplier = user::getName;
String name = supplier.get();
System.out.println("name="+name);
}
方法引用,说简单点就是不用写接口的实现代码了,直接引用某个类的某个方法作为实现代码。
使用条件:
接口的方法的入参类型和返回值类型 一定要与你引用的方法 的入参类型和返回值类型 一样。
② 类名::静态方法名
public static void main(String[] args) {
//这两种写法等价
Comparator<Integer> comparator1 = (x,y) -> Integer.compare(x,y);
Comparator<Integer> comparator2 = Integer::compare;
}
使用条件:
接口的方法的入参类型和返回值类型 一定要与你引用的方法 的入参类型和返回值类型 一样。
③ 类名::实例方法名
public static void main(String[] args) {
//这三种写法等价
Comparator<String> comparator1 = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
};
Comparator<String> comparator2 = (x,y) -> x.compareTo(y);
Comparator<String> comparator3 = String::compareTo;
}
使用条件:
1) 接口的方法的入参类型和返回值类型 一定要与你引用的方法 的入参类型和返回值类型 一样。比如上例中入参都是2个String类型,返回值都是int类型。
2) 都有2个入参,且第一个参数用来调用 引用的方法,将第二个参数作为被引用方法的入参传入
可以看出这种使用方式的条件更为特殊.
4.2 ,构造器引用
一种特殊的方法引用,用法:类名::new
示例:
//User对象
public class User {
private String name;
private int age;
public User() {
}
public User(String name) {
this.name = name;
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//测试方法
public static void main(String[] args) {
//因为Supplier接口的方法是无参的,因此匹配User的无参构造
Supplier<User> supplier1 = User::new;
User user1 = supplier1.get();
//因为Function接口的方法有一个入参,且泛型为String,因此匹配User(String) 的构造
Function<String,User> function1 = User::new;
User user2 = supplier1.get();
//因为BiFunction接口的方法有2个入参,且入参泛型为String和Integer,因此匹配User(String,Integer) 的构造
BiFunction<String,Integer,User> function2 = User::new;
User user3 = supplier1.get();
}
4.3,数组引用
一种特殊的方法引用,用于创建数组,用法: 数组类型[]::new
public static void main(String[] args) {
//创建一个长度为x的User[]几种写法
//第一种
User[] xx = new User[3];
//第二种
Function<Integer,User[]> function1 = x->new User[x];
xx = function1.apply(3);
//第三种,数组引用
Function<Integer,User[]> function2 = User[]::new;
xx = function2.apply(3);
}