基础概念
lambda表达式——Java8新特性之一
本质上讲是一个匿名方法。
只能实现函数式接口:即一个接口中,要求实现类必须实现的抽象方法,有且只有一个。
Lambda表达式基础语法:
(type1 arg1, type2 arg2...) -> { body }
-
()参数部分:方法的参数列表,要求和实现的接口中的方法参数部分一致,包括参数的数量和类型。
-
{}方法体部分:方法的实现部分,如果接口中定义的方法有返回值,则在实现时,注意返回值的返回。
-
-> :分隔参数部分和方法体部分。
// 无参数无返回值
() -> System.out.println("test");
// 无参数有返回值
() -> {
int a = 1;
int b = 2;
return a * b;
};
// 有参数无返回值
(String s) -> {System.out.println(s);};
// 有参数有返回值
(int a, int b) -> {return a + b;};
// 只有一个参数,可省略类型和小括号
e -> {System.out.println(e);};
// 方法体只有一句话,可省略大括号
e -> System.out.println(e);
案例
public static void main(String[] args) {
StudyDemo studyDemo = new StudyDemo();
studyDemo.study();
}
public static class StudyDemo implements Learn {
@Override
public void study() {
System.out.println("我爱学习,学习爱我!");
}
}
public interface Learn {
void study();
}
上面举了一个最基础的例子,一个Learn接口,用一个StudyDemo类实现这个接口并且实现study方法,然后创建一个实例对象,调用对象的study方法。
如果StudyDemo类只是为了实现 Learn接口而存在的,且仅被使用了一次,我们就可以使用匿名内部类来简化这一操作:
匿名内部类
public static void main(String[] args) {
Learn learn = new Learn() {
@Override
public void study() {
System.out.println("我爱学习,学习爱我!");
}
};
learn.study();
}
public interface Learn {
void study();
}
虽然使用匿名内部类后代码量有所减少,但还是不够简洁,所以java8给我们带来了lambda表达式来优化代码
lambda表达式
public static void main(String[] args) {
Learn learn1 = () -> System.out.println("我爱学习,学习爱我!");
learn1.study();
}
@FunctionalInterface
public interface Learn {
void study();
}
因为Learn接口中只有一个方法,编译器通过类型推断从上下文中推导出这些类型和方法,所以我们可以省去study的方法名和new Learn。
上面使用了一个 @FunctionalInterface 注释,他的使用规则:
- 必须注释在接口上。
- 被注解的接口有且只有一个抽象方法。
- 被注解的接口可以有默认方法/静态方法,或者重写Object的方法
注:上述2中,值得注意的是默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
使用场景
用于某一特定的场景,有不同的处理逻辑的时候。(感觉用处不大),举个例子来理解:
我们需要分别找出颜色为绿色的苹果和重量大于15的苹果,我们就可以分别写两个方法来筛选。
public static void main(String[] args) {
List<Apple> apples = new ArrayList<Apple>() {};
apples.add(new Apple("green", 13));
apples.add(new Apple("red", 16));
apples.add(new Apple("red", 20));
compareColor(apples, "green");
compareWeight(apples, 15);
}
public static void compareColor(List<Apple> apples, String color) {
for (Apple apple : apples) {
if (color.equals(apple.getColor())) {
System.out.println(apple);
}
}
}
public static void compareWeight(List<Apple> apples, int weight) {
for (Apple apple : apples) {
if (apple.getWeight() > weight) {
System.out.println(apple);
}
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Apple {
private String color;
private int weight;
}
PS:偷了个小懒,Apple 类上面使用了三个注解 @Data、@AllArgsConstructor、@NoArgsConstructor ,是在maven里面引用了lombok,作用是帮我们创建了get、set、有参与无参构造方法。
你会发现这两个方法就参数与判断有一点区别,其他并无不同,这样无疑会造成代码重复的问题,于是我们就可以把这种(筛选苹果的)行为抽象成一个接口。
public static void main(String[] args) {
List<Apple> apples = new ArrayList<Apple>() {};
apples.add(new Apple("green", 13));
apples.add(new Apple("red", 16));
apples.add(new Apple("red", 20));
filterApples(apples, new GreenFilter());
filterApples(apples, new WeightGt15Filter());
}
public static class GreenFilter implements AppleFilter {
@Override
public boolean filter(Apple apple) {
return "green".equals(apple.getColor());
}
}
public static class WeightGt15Filter implements AppleFilter {
@Override
public boolean filter(Apple apple) {
return apple.getWeight() > 15;
}
}
public static void filterApples(List<Apple> apples, AppleFilter filter) {
for (Apple apple : apples) {
if (filter.filter(apple)) {
System.out.println(apple);
}
}
}
public interface AppleFilter {
boolean filter(Apple apple);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Apple {
private String color;
private int weight;
}
现在,代码重复的问题解决了,当有新需求的时候,只需要再添加一个类来实现这个AppleFilter接口就行了。
但是,代码量一点都没减少,最后我们用lambda表达式来简化。
public static void main(String[] args) {
List<Apple> apples = new ArrayList<Apple>() {};
apples.add(new Apple("green", 13));
apples.add(new Apple("red", 16));
apples.add(new Apple("red", 20));
filterApples(apples, apple -> "green".equals(apple.getColor()));
filterApples(apples, apple -> apple.getWeight() > 15);
}
public static void filterApples(List<Apple> apples, AppleFilter filter) {
for (Apple apple : apples) {
if (filter.filter(apple)) {
System.out.println(apple);
}
}
}
public interface AppleFilter {
boolean filter(Apple apple);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Apple {
private String color;
private int weight;
}
lambda表达式经常与方法引用一起使用,下面举个栗子来理解:
public static void main(String[] args) {
Student[] students = {
new Student("张三", 18),
new Student("李四", 20),
new Student("王五", 19)};
//匿名内部类写法
Comparator<Student> compare = new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return s1.getAge() - s2.getAge();
}
};
Arrays.sort(students, compare);
//lambda表达式写法,去掉接口名和方法名
Comparator<Student> compare1 = (Student s1, Student s2) -> s1.getAge() - s2.getAge();
Arrays.sort(students, compare1);
//省略写法,去掉参数类型,只保留返回值
Comparator<Student> compare2 = (s1, s2) -> s1.getAge() - s2.getAge();
Arrays.sort(students, compare2);
//使用方法引用
Comparator<Student> compare3 = Comparator.comparingInt(Student::getAge);
Arrays.sort(students, compare3);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Student {
private String name;
private int age;
}
有人会注意到使用方法引用时,我们用的是comparingInt方法,和上面我们用的的compare发放不同。大家可以把代码复制到idea上面点进去看看,comparingInt是Java8自己封装的方法,里面使用了lambda表达式和compare方法。
关于方法引用的具体使用,我在网上找了个例子,大家可以参考参考来理解:
public static void main(String[] args) {
// 第一种:构造器引用。语法:Class::new,或者更一般的形式:Class<T>::new
final Car car = Car.create(Car::new);
final List<Car> cars = Arrays.asList(car);
// 第二种:静态方法引用.语法:Class::static_method
cars.forEach(Car::collide);
// 第三种:成员方法引用。语法:Class::method
cars.forEach(Car::repair);
// 实例对象的成员方法引用。语法:instance::method
final Car police = Car.create(Car::new);
cars.forEach(police::follow);
}
public static class Car {
public static Car create(final Supplier<Car> supplier) {
return supplier.get();
}
public static void collide(final Car car) {
System.out.println("Collided " + car.toString());
}
public void follow(final Car another) {
System.out.println("Following the " + another.toString());
}
public void repair() {
System.out.println("Repaired " + this.toString());
}
}
下面再给两个演变流程,让大家体会一下使用lambda表达式和方法引用带来的便利:
// forEach
Arrays.asList("a", "b", "d").forEach((String e) -> System.out.print(e + " "));
Arrays.asList("a", "b", "d").forEach(e -> System.out.print(e + " "));
// 如果只是单纯输出e,可以直接使用方法引用
Arrays.asList("a", "b", "d").forEach(System.out::println);
// sort
Arrays.asList("a", "b", "d").sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
Arrays.asList("a", "b", "d").sort((o1, o2) -> o1.compareTo(o2));
Arrays.asList("a", "b", "d").sort(String::compareTo);
最后,虽然lambda表达式回省略很多代码,但是当方法比较复杂时,使用lambda表达式反而会造成阅读障碍,所以请大家谨慎使用。