文章目录
- 1 Lambda表达式
- 1.1 Lambda特性
- 1.2 Lambda基本语法
- 2 使用场景 -- 函数式接口
- 2.1 函数式接口
- 2.2 Java8引入新的函数式接口
- 3 类型推断与类型检查
- 3.1 类型推断
- 3.2 Lambda签名类型推断
- 4 Lamda使用的限制
- 4.1 变量限制
- 4.2 this用法
- 5 方法引用
- 6 实践案例
通过行为参数化传递代码有助于应对不断变化的需求,而lambda表达式和方法引用便是Java8对行为参数化的新实践。Lambda表达式和方法引用不仅实践了行为参数化,也极大地简化了代码。
1 Lambda表达式
1.1 Lambda特性
Lambda表达式,是表示可传递的匿名函数的一种方式,具有这样几个特点:
- 没有名称。
- Lambda函数,不属于某个特定类(方法归属于特定类),有着参数列表、函数主体、返回类型以及异常列表。
- Lambda表达式可以作为参数传递给方法或者赋值给某个变量。
- 整个表达式就是返回值。
1.2 Lambda基本语法
Lambda表达式包含三部分:参数列表、箭头、Lambda主体。参数列表,即函数式接口方法的参数,Lambda主体是Lambda的返回值,箭头仅用于隔断两部分。
参数列表 --> Lambda主体
(parameters) -> expression
例
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
// 没有参数
() -> 42;
// Lambda主体有多条,并且有返回值情况
(Apple a1, Apple a2) -> {
return a1.getWeight().compareTo(a2.getWeight());
};
2 使用场景 – 函数式接口
2.1 函数式接口
只有在需要函数式接口的时候才可以传递Lambda。函数式接口是只定义了一个抽象方法的接口。例如:
public interface Runnable
public interface Comparator<T>
public interface Callable<V>
public interface FileFilter
需要注意的点:
- 接口中可以有默认方法,默认方法的存在不会影响函数式接口。
- 如果接口声明了一个抽象方法,用于覆盖Object类的public方法,该方法也不计入到函数式接口的抽象方法数中。Comparator接口中有两个抽象方法,但该接口仍然是函数式接口。
@FunctionalInterface // 用于标注当前接口为函数式接口
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj); // 覆盖Object的equals方法
}
Lambda是函数式接口的一个具体实现,它以内联的形式为接口的抽象方法提供的实现,并把整个Lambda表达式作为该接口的一个实例。因此Lambda表达式可以赋值给变量,也可以作为参数传递给方法。
2.2 Java8引入新的函数式接口
抽象方法的签名可以描述Lambda表达式的签名。函数式接口的抽象方法的签名称为函数描述符。
Java 8在java.util.function包引入了几类函数式接口。例如
函数式接口 | 函数描述符 | 注释 | 可以使用的场景 |
Predicate | T -> boolean | 定义test方法,接受泛型T对象,并返回一个boolean。 | 布尔表达式 |
BiPredicate<L,R> | (L,R) -> boolean | 定义test方法,接受泛型T和U类型对象,返回boolean | |
Consumer | T -> void | 定义accept方法,接受泛型T的对象,没有返回(void)。 | 消费对象 |
BiConsumer<T,U | (T,U) -> void | 定义accept方法,接受泛型T和U类型对象,没有返回(void) | |
Function<T,R> | T -> R | 定义apply方法,它接受一个泛型T的对象,并返回一个泛型R的对象。 | 从对象中选择或提取 |
BiFunction<T,U,R> | (T,U) -> R | 定义apply方法,它接受一个泛型T和U类型对象,并返回一个泛型R的对象。 | 比较对象 |
Supplier | () -> T | 定义get方法,没有输入,返回泛型T类型对象 | 创建新对象 |
UnaryOperator | T -> T | 继承于Function接口 | 合并值 |
BinaryOperator | (T,T) -> T | 继承于BiFunction接口 |
函数式接口通常会采用泛型,然而泛型又只能绑定到引用类型。对于基本数据类型,Java通过自动装箱机制转换成引用类型。装箱过程的本质是将原始类型包裹起来,保存着堆内存中,因此装箱操作消耗部分内存。为了避免输入和输出为原始类型时的自动装箱操作,java.util.function包提供了很多函数式接口的特殊版本,例如IntPredicate接口和ToIntFunction接口。
// 入参是int类型,出参是泛型
public interface IntFunction<R> {
R apply(int value);
}
// 入参是泛型,出参是int类型
public interface ToIntFunction<T> {
int applyAsInt(T value);
}
3 类型推断与类型检查
3.1 类型推断
Lambda表达式可以作为传递给方法的参数,也可赋值给某个变量。类型推断,是指通过这些上下文推断出Lambda表达式的类型。以上面的代码为例,分析类型判断
- 从filterApples方法调用找到方法定义。
- 定义中第二个参数为Predicate类型–目标类型。
- Predicate是函数式接口,只包含一个test抽象方法,接受T类型返回布尔值,其函数描述符是 T -> boolean。
- Lambda表达式与函数描述符匹配。
// 方法调用
List<Apple> heavyApples = filterApples(inventory, (Apple apple) -> "green".equals(apple.color) && apple.getWeight() > 150);
// Apple类方法定义
public static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> predicate) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (predicate.test(apple)) {
result.add(apple);
}
}
return result;
}
// java.util.function包接口
public interface Predicate<T> {
boolean test(T var1);
}
需要注意的有
- void兼容规则。函数式接口返回类型兼容void类型,仍然要求参数列表兼容。
// Predicate返回boolean,但也兼容void
Predicate<String> p = s -> list.add(s);
- Object类虽然是超级类,但不是函数式接口,不兼容Lambda表达式。
3.2 Lambda签名类型推断
如上,编译器能从Lambda表达式上下文推断出函数式接口,也可以推断出Lambda签名,即函数式接口抽象方法的签名。
改造上面的方法调用如下形式
List<Apple> heavyApples = filterApples(inventory, apple) -> "green".equals(apple.color) && apple.getWeight() > 150);
- 参数列表中只有一个参数时,可以省略括号。
4 Lamda使用的限制
4.1 变量限制
Lambda可以没有限制地捕获(其主体中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final(只被赋值过一次)。
实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。Java8的Lambda和匿名类可以做类似于闭包的事情:它们可以作为参数传递给方法,并且可以访问其作用域之外的变量。但有一个限制:它们不能修改定义Lambda的方法的局部变量的内容。这些变量必须是隐式最终的。可以认为Lambda是对值封闭,而不是对变量封闭。这种限制存在的原因在于局部变量保存在栈上,并且隐式表示它们仅限于其所在线程。如果允许捕获可改变的局部变量,就会引发造成线程不安全的新的可能性。
4.2 this用法
此处主要区分this在Lambda和匿名内部类的区别。在Lambda中,this指向Lamba所在的外部类,而匿名内部类中this指的是匿名内部类当前对象。
5 方法引用
方法引用让你可以重复使用现有的方法定义。方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法,让你根据已有的方法实现来创建Lambda表达式。
目标引用::方法名称
方法引用一共有四种。
- 类::实例方法
@Test
public void testClassMethod() {
File[] hiddenFilesInnerClass = new File(".").listFiles(new FileFilter() {
public boolean accept(File file) {
return file.isHidden();
}
});
File[] hiddenFilesLambda = new File(".").listFiles(file -> file.isHidden());
File[] hiddenFilesMethodRef = new File(".").listFiles(File::isHidden);
}
- 类::静态方法
@Test
public void testStaticMethod() {
IntegerFunction function = Integer::parseInt;
System.out.println(method(function, "12"));
}
public Integer method(IntegerFunction integerFunction, String str) {
return integerFunction.apply(str);
}
public interface IntegerFunction {
Integer apply(String str);
}
- 实例引用::实例方法
@Test
public void testReferenceMethod(){
// 创建一个实例
MethodReferenceTest mrt = new MethodReferenceTest();
System.out.println(method(mrt::parseInt,"42"));
}
public Integer parseInt(String str) {
return new Integer(42);
}
- 构造方法引用–类名:new
@Test
public void testConstructMethod(){
Supplier<Student> supplier = Student::new;
Function<String,Student> function = Student::new;
Student student = function.apply("lzp");
}
public class Student{
private String name;
public Student() {
this.name = "lzp";
}
public Student(String name) {
this.name = name;
}
/*省略getter/setter*/
}
使用构造函数引用时,需要有与构造方法签名匹配的函数接口,如果没有现成的接口,需要自定义一个。
6 实践案例
定义Student类
public class Student {
private String name;
private Integer height;
public Student(String name, Integer height) {
this.name = name;
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getHeight() {
return height;
}
public void setHeight(Integer height) {
this.height = height;
}
}
优化过程
public class StudentSortDemo {
public static void main(String[] args) {
List<Student> students = Arrays.asList(new Student[]{new Student("Ming", 120), new Student("Ping", 160)});
// 匿名内部类
students.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getHeight().compareTo(o2.getHeight());
}
});
// Lambda
students.sort((o1, o2) -> o1.getHeight().compareTo(o2.getHeight()));
// 使用Comparator静态辅助方法与Lambda
students.sort(Comparator.comparing(o -> o.getHeight()));
// 使用Comparator静态辅助方法与方法引用
students.sort(Comparator.comparing(Student::getHeight));
}
}