1. lambda表达式
Lambda表达式:特殊的匿名内部类,语法更简洁。
Lambda表达式允许把函数作为一个方法的参数(函数作为方法参数传递),将代码像数据一样传递
基本语法:
<函数式接口><变量名>=(参数1,参数2...) ->{
// 方法体
}
注意:
函数式接口:接口中只有一个抽象方法
(参数1,参数2):抽象方法的参数
->:分隔符
{}:表示抽象方法的实现
首先我们先从三个例子来大致了解一下什么是lombda表达式
1.1 普通lambda表达式
**例子:**用线程打印一句话,以下用普通的两种方法完成
public class Test01 {
public static void main(String[] args) {
// 该构造方法需要传递一个线程任务对象。Runnable类型
MyThread task01 = new MyThread();
Thread thread01 = new Thread(task01);
thread01.start();
// 匿名内部类
Runnable task02 = new Runnable() {
@Override
public void run() {
System.out.println("这是匿名内部类方式的任务对象");
}
};
Thread thread02 = new Thread(task02);
thread02.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println("自定义任务接口类");
}
}
运行结果:
分析代码:
- Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核32。
- 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类。
- 为了省去定义一个 Runnable 实现类的麻烦,不得不使用匿名内部类。
- 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错。
- 而实际上,似乎只有方法体才是关键所在。
这时可以使用Lambda表达式:
// 使用lambda表达式
Runnable task03 = () -> {
System.out.println("这是使用Lambda表达式完成的");
};
Thread thread03 = new Thread(task03);
thread03.start();
运行结果:
是不是比之前的两种方法简介很多
注意:
使用lambda表达式的前提:必须是函数式接口
1.2 无参无返回值的lambda表达式
举例:
自定义一个接口,包含一个方法,实现该接口并重写其中的方法,打印一句话
首先我们先自定义一个接口:
// 自定义一个接口
interface Swim{
public void swimming();
}
写一个调用接口中方法的方法:
// 调用接口中的方法的方法
private static void fun(Swim s){
s.swimming();
}
匿名内部类方式:
public static void main(String[] args) {
// 匿名内部类
Swim swim = new Swim() {
@Override
public void swimming() {
System.out.println("这是使用匿名内部类的方式");
}
};
fun(swim);
}
使用lambda表达式:
// lambda表达式
Swim swim02 = () -> {
System.out.println("使用lambda表达式的方式1");
};
fun(swim02);
是不是看起来简短了一些,我们还可以再次缩减代码
接下来我们利用lamebda表达式允许把函数作为一个方法的参数(函数作为方法参数传递)的特性再次缩减代码
// lambda表达式,再次缩减
fun(() -> {System.out.println("使用lambda表达式的方式2");});
// 由于{}内只有一条语句,{}可以省略
fun(() -> System.out.println("使用lambda表达式的方式3"));
运行结果:
我们发现,使用lambda表达式直接把原来的有段代码缩减成了一句代码,使用起来更加方便
1.3 有参有返回值的lambda表达式
举例:
创建一个对象类,添加几条数据,对类的进行排序
传统的方法:
public class Test03 {
public static void main(String[] args) {
// 创建人类集合
List<Person> personList = new ArrayList<>();
// 向集合中添加内容
personList.add(new Person("萧炎",18));
personList.add(new Person("美杜莎",20));
personList.add(new Person("古熏儿",16));
// 根据年龄进行排序
// 传统做法:使用Comparator排序规则接口
Comparator<Person> comparator = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
// 按照返回的数的正负进行换位,正的在前
return o2.getAge() - o1.getAge();
}
};
Collections.sort(personList,comparator);
// 遍历打印
for (Person person : personList) {
System.out.println(person);
}
}
}
// 创建一个人类对象,使用lombok的注解自动生成get、set和toString方法
@Data
@NoArgsConstructor
@AllArgsConstructor
class Person{
private String name;
private Integer age;
}
使用lambda表达式:
只需要把上面的传统做法后的内容替换为下面的代码即可
// 使用lambda表达式
Comparator<Person> comparator1 = (o1,o2) -> o2.getAge() - o1.getAge();
Collections.sort(personList,comparator1);
// 遍历打印
personList.forEach(item -> System.out.println(item));
运行结果:
通过以上三个例子,是不是对lambda表达式有一定的了解了,我们可以总结一下它的特点
1.4 lombda表达式的特点
Lambda引入了新的操作符:->(箭头操作符),->将表达式分成两部分
左侧:(参数1,参数2…)表示参数列表
右侧:{}内部是方法体
注意事项:
- 形参列表的数据类型会自动推断。
- 如果形参列表为空,只需保留()。
- 如果形参只有1个,()可以省略,只需要参数的名称即可。
- 如果执行语句只有一句,且无返回值,{}可以省略,若有返回值,则若想省去,则必须同时省略return,且执行语句也保证只有一句。
- Lambda不会生成一个单独的内部类文件。
2. 函数式接口
如果一个接口只有一个抽象方法,则该接口称之为函数式接口。
函数式接口可以使用Lambda表达式,Lambda表达式会被匹配到这个抽象方法上。
@Functionallnterface 注解检测接口是否符合函数式接口。
内置函数接口的由来:
比如以下求数组和的代码:
public class Test04 {
public static void main(String[] args) {
Operator o = arr -> {
int sum = 0;
for (int i : arr) {
sum += i;
}
System.out.println("数组的和为:"+sum);
};
fun(o);
}
public static void fun(Operator operator){
int[] arr = {1,2,3,4,5};
operator.getSum(arr);
}
}
@FunctionalInterface
interface Operator {
// 求数组的和
void getSum(int[] arr);
}
分析:
我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽 象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,IDK提供了大量常用的函数式接口。
3. 常见的函数式接口
常见的函数式接口有四种,分别是Consumer
接口、Supplier
接口、Function
接口、Predicate
接口。这四个接口都在java.util.function包下
函数式接口 | 参数类型 | 返回类型 | 说明 |
---|---|---|---|
Consumer 消费型接口 | T | void | void accept(T); 对类型为T的对象应用操作 |
Supplier 供给行接口 | 无 | T | T get(); 返回类型为T的对象 |
Function<T,R> 函数型接口 | T | R | R apply(T t); 对类型为T的对象应用操作,并返回类型为R类型的对象。 |
Predicate 断言型接口 | T | boolean | boolean test(T t); 确定类型为T的对象是否满足条件,并返回boolean类型。 |
3.1 Consumer接口
Consumer接口是Java中的一个函数式接口,用于表示接受单个输入参数并且不返回任何结果的操作。它定义了一个名为
accept
的抽象方法,该方法接受一个泛型类型的参数,通常用于执行一些操作或者消费给定的输入。Consumer接口通常与Lambda表达式结合使用,可以在不创建额外命名方法的情况下,直接传递操作或行为。
public class Test05 {
public static void main(String[] args) {
Consumer<Double> consumer = t -> System.out.println("吃饭消费了:"+t+"元");
fun(consumer,88);
}
// 调用某个方法时,该方法需要的参数为接口类型,这时就应该能想到使用lambda
public static void fun(Consumer<Double> consumer,double money){
consumer.accept(money);
}
}
运行结果:
3.2 Supplier接口
Supplier接口是Java中的一个函数式接口,用于表示一个供给型的操作,它不接受任何参数,但返回一个泛型类型的结果。在函数式编程中,Supplier接口通常用于延迟计算或者提供数据的场景,例如当需要动态获取数据、生成随机数或者创建对象时非常有用。
public class Test06 {
public static void main(String[] args) {
// 生成一个[0,10)的一个随机数
fun(() -> new Random().nextInt(10));
}
public static void fun(Supplier<Integer> supplier){
System.out.println("内容为"+supplier.get());
}
}
运行结果:
3.3 Function接口
Function接口是Java中的一个函数式接口,用于表示一个接受一个参数并产生结果的函数。它定义了一个名为
apply
的抽象方法,该方法接受一个泛型类型的参数,并返回另一个泛型类型的结果。Function接口在函数式编程中非常有用,特别是在需要对输入进行转换、映射或处理的场景中经常被使用。
public class Test07 {
public static void main(String[] args) {
// 将字符串转换成大写
fun(t->t.toUpperCase(),"hello world");
}
public static void fun(Function<String,String> function,String result){
System.out.println("结果为:"+function.apply(result));
}
}
运行结果:
3.4 Predicate接口
Predicate接口是Java中的一个函数式接口,用于表示一个断言(即判断条件),它接受一个参数并返回一个boolean值。Predicate接口通常用于过滤、筛选或者判断输入数据是否满足特定的条件。
public class Test08 {
public static void main(String[] args) {
// 判断名称的长度是否大于3
fun(n->n.length()>3?true:false,"萧炎");
}
public static void fun(Predicate<String> predicate,String name){
System.out.println("该名称的长度是否大于3:"+predicate.test(name));
}
}
运行结果:
4. 方法的引用
4.1 lombda表达式的冗余
我们来看下面这段代码:
public class Test09 {
public static void main(String[] args) {
Consumer<Integer[]> c = arr ->{
int sum = 0;
for (Integer b : arr) {
sum += b;
}
System.out.println("数组的和为:"+sum);
};
fun(c);
}
public static void fun(Consumer<Integer[]> consumer){
Integer[] arr = {1,2,3,4,5};
consumer.accept(arr);
}
public static void sum(Integer[] arr){
int sum = 0;
for (Integer a : arr) {
sum += a;
}
System.out.println("数组的和为:"+sum);
}
}
分析:
我们用lombda表达式的求和的方法,已经在本类或其它类中被写过,如其中的sum方法,我们再次书写就造成了代码的冗余
如果我们在Lambda中所指定的功能,已经有其他方法存在相同方案,那是否还有必要再写重复逻辑?可以直接”引用”过去就好了
4.2 什么是方法引用
方法引用是Lambda表达式的一种简写形式。 如果Lambda表达式方法体中只是调用一个特定的已经存在的方法,则可以使用方法引用。
我们可以对上面的代码进行修改,修改如下:
public class Test09 {
public static void main(String[] args) {
// 方法的引用
Consumer<Integer[]> c = Test09::sum;
fun(c);
}
public static void fun(Consumer<Integer[]> consumer){
Integer[] arr = {1,2,3,4,5};
consumer.accept(arr);
}
public static void sum(Integer[] arr){
int sum = 0;
for (Integer a : arr) {
sum += a;
}
System.out.println("数组的和为:"+sum);
}
}
运行结果:
我们发现用这次我们并没有重复写求和的代码,而是直接引用了sum求和的代码。
其中的::写法就算方法引用。
4.3 方法引用的分类
方法引用分为四种类型,分别是:静态方法引用、实例方法引用、对象方法引用、构建方法引用。
类型 | 语法 | 对应的Lambda表达式 |
---|---|---|
静态方法引用 | 类名::staticMethod | (args)->类名.staticMethod(args) |
实例方法引用 | inst:instMethod | (args)->inst:instMethod(args) |
对象方法引用 | 类名::instMethod | (inst,args)->inst.instMethod(args) |
构建方法引用 | 类名::new | (args)->new 类名(args) |
4.3.1 静态方法引用
在1.3有参有返回值的lambda表达式的例子中,我们通过年龄进行了排序
public class Test03 {
public static void main(String[] args) {
// 创建人类集合
List<Person> personList = new ArrayList<>();
// 向集合中添加内容
personList.add(new Person("萧炎",18));
personList.add(new Person("美杜莎",20));
personList.add(new Person("古熏儿",16));
// 根据年龄进行排序
// 使用lambda表达式
Comparator<Person> comparator1 = (o1,o2) -> o2.getAge() - o1.getAge();
Collections.sort(personList,comparator1);
// 遍历打印
personList.forEach(item -> System.out.println(item));
}
}
// 创建一个人类对象,使用lombok的注解自动生成get、set和toString方法
@Data
@NoArgsConstructor
@AllArgsConstructor
class Person{
private String name;
private Integer age;
}
假如其中的Person类中有一个排序的方法,我们就可以直接引用这个方法,如下
public class Test03 {
public static void main(String[] args) {
// 创建人类集合
Person[] personList = {new Person("萧炎",18),new Person("美杜莎",20),new Person("古熏儿",16)};
// 方法引用
// 我们之前是这样进行排序的
// Comparator<Person> comparator1 = (o1,o2) -> o2.getAge() - o1.getAge();
// 使用方法引用可以修改为
// Comparator<Person> c = (o1,o2) -> Person.compareTo(o1,o2);
// 我们可以再次简写为
Comparator<Person> c = Person::compareTo;
Arrays.sort(personList,c);
System.out.println(Arrays.toString(personList));
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Person{
private String name;
private Integer age;
public static int compareTo(Person p1,Person p2){
return p1.getAge()-p2.getAge();
}
}
运行结果:
这样一来,我们的代码又缩减了许多。
4.3.2 实例方法引用
实例方法引用,顾名思义就是调用已经存在的实例的方法,与静态方法引用不同的是类要先实例化,静态方法引用类无需实例化,直接用类名去调用。
举例:
还在上面的内容上举例,写一个打印名称的方法,用引用调用它
public class Test03 {
public static void main(String[] args) {
Person p = new Person("韩立",25);
// 打印名称
// Supplier<String> supplier = () -> p.getName();
// 可以修改为
Supplier<String> supplier = p::getName;
fun(supplier);
}
public static void fun(Supplier<String> supplier){
System.out.println(supplier.get());
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Person{
private String name;
private Integer age;
public static int compareTo(Person p1,Person p2){
return p2.getAge()-p1.getAge();
}
}
运行结果:
4.3.3 对象方法引用
public class Test10 {
public static void main(String[] args) {
// 计算字符串的长度
// Function<String,Integer> function = (str) ->str.length();
// 可以改写为
Function<String,Integer> function = String::length;
System.out.println(function.apply("hello"));
// 比较两个字符串的内容是否一致,T第一个参数类型,U第二个参数类型,R返回结果类型
// BiFunction<String,String,Boolean> bi = (t,u) -> t.equals(u);
// 可以改写为
BiFunction<String,String,Boolean> bi = String::equals;
// R apply(T t,U u);
System.out.println(bi.apply("hello", "world"));
}
}
运行结果:
4.3.4 构建方法引用
public class Test11 {
public static void main(String[] args) {
// 创建一个对象
// 无参构造
// Supplier<Man> supplier = ()->new Man();
// 可以改写为
Supplier<Man> supplier = Man::new;
System.out.println(supplier.get());
// 有参构造
// Function<String,Man> function = (n) -> new Man(n);
// 该可以改写为
Function<String,Man> function = Man::new;
System.out.println(function.apply("萧炎"));
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Man{
private String name;
}
运行结果: