JDK新特性
Lambda表达式
什么是Lambda表达式
Lambda表达式是一个匿名代码块,用于简单的传递一段代码片段。
Lambda表达式标准格式
格式:(形式参数) -> {代码块}
- 形式参数
如果有多个参数,参数只见用逗号隔开;如果没有,留空即可- ->
由英文中画线和大于符号组成,固定写法。代表指向动作。- 代码块
是我们具体要做的事情,也就是我们以前写的方法体内容
// 组成Lambda表达式的三要素:形式参数,箭头,代码块
// 示例
new Thread(() -> System.out.println("这是一个示例")).start();
Lambda表达式的使用前提
- 有一个接口
- 接口中有且仅有一个抽象方法
理解Lambda表达式
定义一只接口(Eatable),里面定义一个抽象方法:void eat();表示吃这一个动作。
然后以实现类,匿名内部类,Lambda表达式分别实现一下小明吃饭了这一动作。
通用接口 Eatable.java
package demo01;
/**
* 定义吃接口
*
* @author Anna.
* @date 2024/3/31 22:35
*/
public interface Eatable {
/**
* 吃
*
* @param
* @return void
* @author Anna.
* @date 2024/3/31 22:35
*/
void eat();
}
实现类方式实现
创建一个实现类(EatImpl)实现(Eatable)接口
package demo01;
/**
* 吃实现类
*
* @author Anna.
* @date 2024/3/31 22:37
*/
public class EatImpl implements Eatable{
@Override
public void eat() {
System.out.println("通过实现类-小明吃东西了");
}
}
测试代码
package demo01;
import java.util.Arrays;
/**
* Demo01:
* 定义一只接口(Eatable),里面定义一个抽象方法:void eat();表示吃这一个动作。
* 然后以实现类,匿名内部类,Lambda表达式分别实现一下小明吃饭了这一动作。
*
* @author Anna.
* @date 2024/3/31 22:36
*/
public class LambdaDemo01 {
public static void main(String[] args) {
// 通过实现类实现
Eatable eat = new EatImpl();
eat.eat();
}
}
执行结果
匿名内部类方式实现
测试代码
package demo01;
import java.util.Arrays;
/**
* Demo01:
* 定义一只接口(Eatable),里面定义一个抽象方法:void eat();表示吃这一个动作。
* 然后以实现类,匿名内部类,Lambda表达式分别实现一下小明吃饭了这一动作。
*
* @author Anna.
* @date 2024/3/31 22:36
*/
public class LambdaDemo01 {
public static void main(String[] args) {
// 匿名内部类方式实现
eat(new Eatable() {
@Override
public void eat() {
System.out.println("通过匿名内部类方式实现-小明吃东西了");
}
});
}
/**
* 定义一个调用Eatable接口eat()的方法
*
* @param eatable
* @return void
* @author Anna.
* @date 2024/3/31 22:50
*/
private static void eat(Eatable eatable){
eatable.eat();
}
}
执行结果
Lambda表达式实现
测试代码
package demo01;
import java.util.Arrays;
/**
* Demo01:
* 定义一只接口(Eatable),里面定义一个抽象方法:void eat();表示吃这一个动作。
* 然后以实现类,匿名内部类,Lambda表达式分别实现一下小明吃饭了这一动作。
*
* @author Anna.
* @date 2024/3/31 22:36
*/
public class LambdaDemo01 {
public static void main(String[] args) {
// Lambda表达式实现
eat(() -> {
System.out.println("通过Lambda表达式实现-小明吃东西了");
});
}
/**
* 定义一个调用Eatable接口eat()的方法
*
* @param eatable
* @return void
* @author Anna.
* @date 2024/3/31 22:50
*/
private static void eat(Eatable eatable) {
eatable.eat();
}
}
执行结果
对比实现类,匿名内部类,Lambda表达式编译文件
通过实现类方式实现,会生成EatImpl.class文件
通过匿名内部类方式实现,会生成LambdaDemo01$1.class匿名内部类文件
通过Lambda方式实现,不会额外生成文件,实现字节码会动态生成
Lambda表达式的省略模式
省略规则:
- 参数类型可以省略,但是多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,大括号和分号可以省略,甚至是return
测试代码
package demo02;
import demo01.Eatable;
/**
* Demo02:
* Lambda表达式省略模式
*
* @author Anna.
* @date 2024/3/31 22:36
*/
public class LambdaDemo02 {
public static void main(String[] args) {
// 正常调用
System.out.println("=========正常调用=========");
eat(() -> {
System.out.println("小明吃东西了");
});
sum((int x, int y) -> {
return x + y;
});
sleep((String name) -> {
System.out.println(name + "睡觉了");
});
// 省略模式
System.out.println("=========省略模式=========");
System.out.println("=========参数类型可以省略=========");
sleep((name) -> {
System.out.println(name + "睡觉了");
});
// 参数类型可以省略,但是多个参数的情况下,不能只省略一个
System.out.println("=========参数类型可以省略,但是多个参数的情况下,不能只省略一个=========");
sum((x, y) -> {
return x + y;
});
// 如果参数有且仅有一个,那么小括号可以省略
System.out.println("=========如果参数有且仅有一个,那么小括号可以省略=========");
sleep(name -> {
System.out.println(name + "睡觉了");
});
// 如果代码块的语句只有一条,大括号和分号可以省略
System.out.println("=========如果代码块的语句只有一条,大括号和分号可以省略=========");
sleep(name -> System.out.println(name + "睡觉了"));
// 如果代码块的语句只有一条,且有返回值 return 也可以省略
System.out.println("=========如果代码块的语句只有一条,且有返回值 return 也可以省略=========");
sum((x, y) -> x + y);
}
/**
* 定义一个调用Eatable接口eat()的方法
*
* @param eatable
* @return void
* @author Anna.
* @date 2024/3/31 23:11
*/
private static void eat(Eatable eatable) {
eatable.eat();
}
/**
* 定义一个调用Sumable接口sum()的方法
*
* @param sumable
* @return void
* @author Anna.
* @date 2024/3/31 23:11
*/
private static void sum(Sumable sumable) {
System.out.println(sumable.sum(20, 30));
}
/**
* 定义一个调用Sleepable接口sleep()的方法
*
* @param sleepable
* @return void
* @author Anna.
* @date 2024/3/31 23:11
*/
private static void sleep(Sleepable sleepable) {
sleepable.sleep("小明");
}
}
执行结果
Lambda作用域
在Lambda表达式中访问外层作用域和旧版本的匿名对象中的方式类似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。
Lambda表达式不会从超类(supertype)中继承任何变量名,也不会引入一个新的作用域。
Lambda表达式基于词法作用域,也就是说lambda表达式函数体里面的变量和它外部环境的变量具有相同的语义(也包括lambda表达式的形式参数)。
此外,this关键字及其引用,在Lambda表达式内部和外部也拥有相同的语义。
访问局部变量
可以直接在lambda表达式中访问外层的局部变量<br/>
在Lambda 表达式当中被引用的变量的值不可以被更改。<br/>
在 Lambda 表达式当中被引用的变量的值不可以被更改。
案例
public class LambdaDemo5 {
public static void main(String[] args) {
final String count = "123";
int num = 0;
new Thread(() -> {
num = 100; // 编译报错
count = "232"; // 编译报错
}, "Lambda表达式").start();
}
}
编译结果
访问外层的局部变量
可以直接在lambda表达式中访问外层的局部变量。
lambda表达式的局部变量(eg:num)可以不用声明为final,不过这里的局部变量(eg:num)必须不可被后面的代码修改(即隐性的具有final的语义)。
案例
public class LambdaDemo501 {
public static void main(String[] args) {
final String count = "123";
int num = 0;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "num:" + num);
System.out.println(Thread.currentThread().getName() + "count:" + count);
}, "Lambda表达式").start();
}
}
执行结果
Lambda表达式中的this
Lambda 表达式中使用 this 会引用创建该 Lambda 表达式的方法的 this 参数。
案例
package demo05;
import java.util.function.Supplier;
/**
* Lambda作用域
*
* @author Anna.
* @date 2024/4/1 11:07
*/
public class LambdaDemo05 {
public static void main(String[] args) {
LambdaDemo05 lambdaDemo05 = new LambdaDemo05();
lambdaDemo05.scopeTest01();
}
public void scopeTest01() {
System.out.println("方法体中使用this:" + this);
pase(() -> {
System.out.println("Lambda中使用this:" + this);
return null;
});
}
public static <T> T pase(Supplier<T> supplier) {
return supplier.get();
}
}
执行结果
方法引用
什么是方法引用
::该符号为引用运算符,而它所在的表达式被称为方法引用
方法引用的使用理解
通过实现将字符串数字转换为int类型,来体验方法引用
Conversion.java
package demo04;
/**
* 定义一个接口将字符串数字转换成int类型
*
* @author Anna.
* @date 2024/4/1 11:06
*/
public interface Conversion {
int pase(String s);
}
LambdaDemo04.java
package demo04;
/**
* 方法引用
*
* @author Anna.
* @date 2024/4/1 11:07
*/
public class LambdaDemo04 {
public static void main(String[] args) {
// 使用Lambda表达式实现
System.out.println("使用Lambda表达式实现");
pase(s -> Integer.parseInt(s));
// 使用方法引用实现
System.out.println("使用方法引用实现");
pase(Integer::parseInt);
}
public static void pase(Conversion c) {
System.out.println(c.pase("123"));
}
}
执行结果
回顾分析:
Lambda表达式: pase(s -> Integer.parseInt(s));
拿到参数s之后,传递给Integer.parseInt方法处理,返回结果
方法引用:pase(Integer::parseInt);
直接使用Integer::parseInt方法来取代Lambda表达式,代码更加简洁
结论:
如果使用Lambda表达式,那么根据“可推导即可省略”的原则,无需指定参数类,也无需指定的重载形式,他们都将被自动推导
如果使用方法引用,也同样可以根据上下文进行推导
方法引用类型
方法引用可以被分为一下几类:
- 静态方法引用:引用静态的方法
格式: 类名::静态方法
- 实例方法引用:
格式: 对象::成员方法
示例: pase(“HelloWorld”::toUpperCase); 等价于 pase(s -> s.toUpperCase());
- 引用类中的非静态方法:
格式: 类名::成员方法
- 引用类的构造方法:
格式: 类名::new
Sumable.java
package demo3;
public interface Sumable {
int sum(int x, int y);
}
SumImpl.java
package demo3;
public class SumImpl implements Sumable {
@Override
public int sum(int x, int y) {
return x + y;
}
}
SumUtils.java
package demo3;
public class SumUtils {
public int sum(int x, int y) {
return x + y;
}
}
IEntityDo.java
package demo3;
public interface IEntityDo {
SumEntity init(int x, int y);
}
SumEntity.java
package demo3;
public class SumEntity {
private int x;
private int y;
public SumEntity(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "SumEntity{" +
"x=" + x +
", y=" + y +
'}';
}
}
LambdaDemo03.java
package demo3;
/**
* Demo03:
* Lambda表达式方法引用
*
* @author Anna.
* @date 2024/3/31 22:36
*/
public class LambdaDemo03 {
public static void main(String[] args) {
System.out.println("Lambda方法");
sum(((x, y) -> x + y));
System.out.println("静态方法引用");
sum(Integer::sum);
System.out.println("实例方法引用");
Sumable sumable = new SumImpl();
sum(sumable::sum);
System.out.println("如果该接口有且仅有一个抽象方法,还可以进一步省略::内容");
sum(sumable);
System.out.println("引用类中的非静态方法");
SumUtils sumUtils = new SumUtils();
sum(sumUtils::sum);
System.out.println("引用类的构造方法");
init(SumEntity::new);
}
/**
* 定义一个调用Sumable接口sum()的方法
*
* @param sumable
* @return void
* @author Anna.
* @date 2024/3/31 23:11
*/
private static void sum(Sumable sumable) {
System.out.println(sumable.sum(20, 30));
}
/**
* 定义一个调用EntityDo接口init()的方法
*
* @param entityDo
* @return void
* @author Anna.
* @date 2024/3/31 23:11
*/
private static void init(IEntityDo entityDo) {
System.out.println(entityDo.init(20, 30));
}
}
执行结果
Lambda表达式优缺点
Java 8中引入了lambda表达式,使得代码编写更加简洁、优雅。lambda表达式是一个匿名函数,可以作为参数传递给方法或者存储在变量中。在Java中使用lambda表达式可以有以下优点和缺点:
优点
- 代码简洁:使用lambda表达式可以减少冗余的代码量,使得代码更加简洁明了。特别是在编写只有一个抽象方法的接口时,可以将其替换为lambda表达式。
- 促进函数式编程:lambda表达式使得函数式编程成为可能。函数式编程强调函数的纯粹性和不可变性,有助于减少副作用和提高代码的可维护性。通过lambda表达式,我们可以将函数作为一等公民,并在不改变状态的情况下进行操作。
- 更好的代码复用:使用lambda表达式可以更方便地实现代码的复用。可以将一段逻辑封装在一个lambda表达式中,然后在多个地方使用。
- 并行处理能力:在Java 8之前,要实现多线程或并行处理需要编写大量的代码。使用lambda表达式可以方便地编写并行处理的代码,充分利用多核处理器的性能。
- 提升代码可读性:lambda表达式将逻辑封装在一个更加紧凑的形式中,使得代码更易读。通过命名良好的lambda表达式,可以使代码的意图更加清晰。
缺点
- 学习成本:lambda表达式是一个新的概念,在Java中引入了新的语法。对于初学者来说,学习和理解lambda表达式需要一些时间和努力。
- 可读性差:虽然lambda表达式可以提升代码的可读性,但是过于复杂的lambda表达式可能反而降低代码的可读性。尤其是当lambda表达式包含多个参数、多行代码或复杂的逻辑时,可读性会受到影响。
- 调试困难:由于lambda表达式是匿名函数,调试时可能会更加困难。在调试过程中,无法直接跟踪lambda表达式的执行路径,可能会增加调试的难度。
- 性能问题:lambda表达式的性能可能比传统的方法调用略差。虽然这个差距很小,但是在某些高性能场景下可能会有所影响。
总结:
lambda表达式是Java 8引入的一项强大的功能,对于简化代码、促进函数式编程、提升代码可读性和代码复用等方面都有很大的帮助。
然而,它也存在着一些学习成本高、可读性差、调试困难和性能问题等缺点。
在使用lambda表达式时,需要根据具体情况权衡利弊,选择合适的使用方式。
gitee源码
git clone https://gitee.com/dchh/JavaStudyWorkSpaces.git