简介
Lambda 表达式是推动 Java 8 发布的最重要新特性,也是Java进入函数式编程的第一步。学会使用Lambda表达式可以让你的代码变得精简而优雅。
Lambda 其实也可以称为一种匿名函数,但它可以帮我们简化匿名内部类的使用。
-
什么是匿名内部类?
匿名内部类,就是没有名字的一种嵌套类。
-
为什么使用?
一个接口/类的方法的某个实现方式在程序中
只会执行一次
,但为了使用它,我们需要创建它的实现类/子类去实现/重写
。此时可以使用匿名内部类的方式,可以无需创建新的类,减少代码冗余。 -
案例
public interface InterfaceDome { void show(); } public static void main(String[] args) { InterfaceDome interfaceDome = new InterfaceDome() { @Override public void show() { System.out.println("这里使用了匿名内部类"); } }; //调用接口方法 interface01.show(); }
语法
(parameters) -> expression
或
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
-
可选类型声明:参数类型声明可省略,编译器可以统一识别参数值(即类型推断)。
例如:
// 接收2个int型整数,输出他们的和 (int x, int y) -> { System.out.println(x + y); }; // 省略写法 (x, y) -> { System.out.println(x + y); };
-
可选的参数圆括号:一个参数可省略圆括号,但多个参数需要定义圆括号。
例如
// 接收一个数值x,返回2*x值 (x) -> { return x * 2; }; // 省略写法 x -> { return x * 2; };
-
可选的大括号:方法主体只有一个语句,则可省略大括号。
例如
(x, y) -> { return x + y; }; // 省略写法 (x, y) -> x + y;
-
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
例如
(x, y) -> { x++; y++; return x + y; }
实践
lambda 引入了两个核心思想
- 行为参数化 (方法函数可作为参数传递进其他方法中)
- 方法引用
行为参数化
注意:Java中的lambda它需要一个函数式接口(只有一个方法的接口)来实现,lambda表达式方法体其实就是函数接口方法的实现。
@FunctionalInterface
public interface TestInterface {
int test(int x, int y);
}
// 使用案例: x+y 即为test方法的实现代码块
TestInterface testInterface = (x, y) -> x + y;
@FunctionalInterface作为注解用来声明该接口为函数式接口。这个注解是非必须的,只要接口符合函数式接口的标准,虚拟机会自动判断。但使用该注解可以避免其他合作开发人员往接口中添加新的方法。
四大核心函数式接口:
函数式接口 | 参数类型 | 返回值类型 | 描述 |
---|---|---|---|
Consumer(消费型) | T | 无 | 处理一个接受T类型的值 |
Function(功能性) | T | R | 处理T类型的值,并返回R类型的值 |
Supplier(供给型) | 无 | T | 接受一个T类型的值 |
Predicate(断言型) | T | boolean | 处理T类型的值,返回true或者false |
示例
按照年龄将User排序
public class test {
public static void main(String[] args) {
List<User> list = Arrays.asList(
new User(1, "张三", 9500),
new User(2, "李四", 10000),
new User(3, "王五", 8000)
);
System.out.println("排序前:");
System.out.println(list);
list.sort((a, b) -> Integer.compare(a.getAge(), b.getAge()));
System.out.println("排序后:");
System.out.println(list);
}
}
class User {
private int id;
private String name;
private int age;
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
方法引用
方法引用也是函数式接口的一个实例,通过方法的名字来指向一个方法,也是Lambda表达式的一种语法。
什么时候使用?
- 创建了函数式接口的匿名内部类对象
- 重写了函数式接口的抽象方法并在重写的方法中调用被引用的方法
简而言之:就是用lambda创建了函数式接口的实现类对象,是其他方法的方法体
静态方法引用
- 格式
类名::静态方法
- 范例
Integer::compare
- Integer类的方法:public static int compare(int x, int y) 比较x,y的大小
public static void main(String[] args) {
// 匿名内部类创建,<Integer>这个泛型一定要带,不然下面就只能是object
Comparator comparator = new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return Integer.compare(a, b);
}
};
// lambda方式创建,<Integer>这个泛型一定要带,不然报错
Comparator<Integer> comparator1 = (a, b) -> Integer.compare(a, b);
// 方法引用,<Integer>这个泛型一定要带,不然报错
Comparator<Integer> comparator2 = Integer::compare;
}
Lambda表达式被类方法替代的时候,它的形式参数全部传递给静态方法作为参数
对象方法引用
- 格式
对象名::非静态方法名
- 范例
String::equals
- String类的方法:public boolean equals(Object anObject) 比较参入参数的anObject 与调用对象是否相同
public static void main(String[] args) {
// 这里的<String, String>类型不能丢,因为下面用的是String,如果不声明类型就是Object
BiPredicate biPredicate = new BiPredicate<String, String>() {
@Override
public boolean test(String o, String o2) {
return o.equals(o2);
}
};
// 这里变量没有声明类型,那么就是Object,equals也是调用的Object的
BiPredicate biPredicate1 = (a, b) -> a.equals(b);
// 这里类型必须要写,因为后面写明了调用String的equals
BiPredicate<String, String> biPredicate2 = String::equals;
}
构造方法引用
- 格式
类名::new
- 范例
User::new
- User类构造方法:public User(String name),通过name参数构造User类
public interface Converter {
User creat(String name);
}
class User {
private int id;
private String name;
private int age;
public User(String name) {
this.name = name;
}
}
public static void main(String[] args) {
// 匿名内部类
Function function = new Function<String, User>() {
@Override
public User apply(String name) {
return new User(name);
}
};
// lambda
Function<String, User> function1 = name -> new User(name);
// 构造引用
Function<String, User> function2 = User::new;
}
扩展
变量作用域
Lambda 表达式,也可称为闭包(能够读取其他函数内部变量的函数),但在访问局部变量要注意如下 3 点:
- 可以直接在 Lambda 表达式中访问外层的局部变量;
- 在 Lambda 表达式当中被引用的变量的值不可以被更改;
- 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
关于第二点,不可以被更改:lambda 表达式中不能更改、也不能被后面的代码修改值,否则会编译错误。若想在lambda 表达中修改引用标记需要在外层局部变量加上 final 修饰词。
为什么有此限制?
- 实例变量和局部变量的实现不同:实例变量都存储在堆中,而局部变量则保存在栈上。如果在线程中要直接访问一个非
final
局部变量,可能线程执行时这个局部变量已经被销毁了。因此,Java 在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没什么区别(即隐性的具有final
的语义) - 这个局部变量的访问限制也是 Java 为了促使你从命令式编程模式转换到函数式编程模式,这样会很容易使用 Java 做到并行处理。
Lambda表达式和匿名内部类的区别
-
所需类型不同
匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
Lambda表达式:只能是接口 -
使用限制不同
如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式 -
实现原理不同
匿名内部类:编译之后,产生一个单独的.class字节码文件
Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成。
关注微信公众号菜鸟乐编程
,更多精彩等你发现…