lambda表达式——Java8新特性之一

基础概念

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 注释,他的使用规则:

  1. 必须注释在接口上。
  2. 被注解的接口有且只有一个抽象方法。
  3. 被注解的接口可以有默认方法/静态方法,或者重写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表达式反而会造成阅读障碍,所以请大家谨慎使用。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值