Java8 Lambda表达式的使用

参考

官网文档:https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

博客:https://www.cnblogs.com/franson-2016/p/5593080.html

菜鸟教程: https://www.runoob.com/java/java8-functional-interfaces.html

为什么要用Lambda表达式?

Lambda表达式可以让我们用较少的代码来实现更多的功能,尤其在Java这种相对来说比较“啰嗦”的语言中,是一把提高效率的利器。

演变过程

把官网上提供的demo拿过来

实体类

@Data
@AllArgsConstructor
public class Person {
    public enum Sex {
        MALE,FEMALE;
    }

    private String name;

    private LocalDate birthday;

    private Sex gender;

    private String emailAddress;

    private Integer age;

    public void printPerson() {
        System.out.println(this.toString());
    }
}

需求

现在我们需要实现一个功能,按照某种标准,从Person的集合中,挑选出符合条件的对象。

写法一

/**
 * 第一种写法,所有的判断方法耦合在一起
 */
public class PrintTest {

    //大于指定年龄即可
    public static void  printPersonOlderTan(List<Person> roster, int age) {
        for (Person person : roster) {
            if(person.getAge() > age) {
                person.printPerson();
            }
        }
    }

    //在某个年龄区间内
    public static void printPersonWithinAgeRange (List<Person> roster, int low, int high) {
        for (Person person : roster) {
            if(low <= person.getAge() && person.getAge() < high) {
                person.printPerson();
            }
        }
    }
}

根据不同的要求写多个方法,不够灵活,重复代码较多。

写法二

上述代码存在的问题是,如果筛选的条件改变了,我们就需要写一个新的方法。

所以,思考一下我们可不可以把变化的部分单独拿出来。

public interface CheckPerson {
    boolean test(Person person);
}
public class CheckPersonEligibleForSelectiveService implements CheckPerson{
    @Override
    public boolean test(Person person) {
        return person.getGender() == Person.Sex.MALE && person.getAge() >= 17 && person.getAge() <= 25;
    }
}
/**
 * 将判断条件抽象成方法
 */
public class PrintPerson {

    public static void printPerson(List<Person>roster, CheckPerson tester) {
        for (Person person : roster) {
            if(tester.test(person)) {
                person.printPerson();
            }
        }
    }

将筛选条件封装成表示算法的类,当有新的需求下来的时候,可以根据不同的需要编写对应的类,实现 CheckPerson接口,重写test方法即可。

将不同的算法封装起来,实现同一个接口,利用java的多态,根据不同的需求指定对应的算法,这就是策略设计模式。

调用该方法

public class PrintPersonTest {
    public static void main(String[] args) {
        Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 12);
        Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 13);
        Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 14);
        List<Person> rosters = Arrays.asList(person1, person2, person3);

        //实例化算法类
         CheckPersonEligibleForSelectiveService checkService = new CheckPersonEligibleForSelectiveService();
         PrintPerson.printPerson(rosters, checkService)

    }
}

写法三

       有一种算法就需要定义一个类,如果说这个算法经常使用,倒还是值得的。

       但如果这个算法只用一次,还要特地定义一个类,未免有些太不划算了。

       所以这个时候就想起了jdk1.8之前就存在的匿名内部类。

public class PrintPersonTest {
    public static void main(String[] args) {
        Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 12);
        Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 13);
        Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 14);
        List<Person> rosters = Arrays.asList(person1, person2, person3);
        //匿名内部类
        PrintPerson.printPerson(rosters, new CheckPerson() {
            @Override
            public boolean test(Person p) {
                return  p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25;
            }
        });
    }
}

写法四

Lambda表达式可以在匿名类的基础上,进一步简化写法。

public class PrintPersonTest {
    public static void main(String[] args) {
        Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 12);
        Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 13);
        Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 14);
        List<Person> rosters = Arrays.asList(person1, person2, person3);

        //lambda表达式
        PrintPerson.printPerson(rosters, p -> p.getGender() == Person.Sex.MALE
                && p.getAge() >= 18
                && p.getAge() <= 25);

    }
}

Lambda写法汇总

没有参数

public interface SayHello0 {
    void say();
}
/**
 * 函数没有参数
 */
public class SayTest {
    public static void main(String[] args) {
        //写法一
        SayHello0 sayHello0 = ()->{
            System.out.println("hello");
        };
        //写法二,可以省略大括号
        SayHello0 sayHello01 = () -> System.out.println("hello2") ;

        //方法里面的内容多余一行的时候,必须要加大括号
        SayHello0 sayHello02 = () -> {
            System.out.println("hello3");
            System.out.println("hello3");
        };
    }
}

一个参数

public interface SayHello1 {
    void say(String msg);
}
/**
 * 函数有1个参数
 */
public class SayTest1 {
    public static void main(String[] args) {
        //写法一
        SayHello1 sayHello1 = (msg)->{
            System.out.println(msg);
        };
        //写法二,简写
        SayHello1 sayHello11 = msg -> System.out.println(msg);

        //写法三:再简写(这种写法的解释在后面)
        SayHello1 sayHello12 = System.out::println;
    }
}

两个及以上

public interface SayHello2 {
    void say(String name, String msg);
}
/**
 * 函数有2个参数
 */
public class SayTest2 {

    public static void main(String[] args) {
        //多个参数时,小括号不能省略
        SayHello2 sayHello2 = (name, msg) -> {
            System.out.println("name" + "msg");
        };
    }
}

System.out::println

双冒号(::)也是1.8才有的运算符,用于lambda表达式中。

看一个demo

public class LambdaTest {

    public static void main(String[] args) {
        Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 10);
        Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 40);
        Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 35);
        Person person4 = new Person("test4", null, Person.Sex.MALE, "qwert@gmail.com", 28);
        Person person5 = new Person("test5", null, Person.Sex.MALE, "qwert@gmail.com", 91);
        List<Person> personList = Arrays.asList(person1, person2, person3, person4, person5);

        // personList.forEach(person -> System.out.println(person));
        personList.forEach(System.out::println);

    }
}

顺序输出集合中的所有元素

      personList.forEach(person -> System.out.println(person));
      personList.forEach(System.out::println);

这两种写法的效果是等价的,

先看forEach()方法需要的参数是什么

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
Consumer是java8的内置函数式接口
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

标注了@FunctionalInterface接口,符合函数式接口的规范。函数式接口要求接口只能有一个方法,这里的接口为啥会有两个呢?

第二个方法有个default关键字,所以不需要给他初始化,当你不实现他的时候,jvm会采用默认的实现方式。

。。。。。。

所以这个forEach方法是干嘛的

forEach遍历集合,把每一个元素都传给一个方法处理,而这个方法是用Consumer来接收的,所以必须符合Lambda表达式的规范。

所以

personList.forEach(System.out::println);

含义是,将personList里面的每一个元素都传给System.out这个对象的println方法,供这个方法消费(consumer)。

自定义一个类,来说明这么写的含义,可能会更好一点。

public class ShowService {
    public void show(Person person) {
        System.out.println(person);
    }
}

测试类

public class LambdaTest {

    public static void main(String[] args) {
        Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 10);
        Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 40);
        Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 35);
        Person person4 = new Person("test4", null, Person.Sex.MALE, "qwert@gmail.com", 28);
        Person person5 = new Person("test5", null, Person.Sex.MALE, "qwert@gmail.com", 91);
        List<Person> personList = Arrays.asList(person1, person2, person3, person4, person5);

ShowService showService = new ShowService(); personList.forEach(showService::show); //或者把show改为静态方法 public static void show(Person person) // personList.forEach(ShowService::show); } }

至于forEach以及Stream等java8新特性,后面会陆续总结。

内置函数式接口

1.8新增的内置函数接口有很多,在java.util.function这个包下。

可以参考:.https://www.runoob.com/java/java8-functional-interfaces.html

我们可以根据具体的需求,去使用不同的接口

例如

消费型接口(Consumer),有参数,返回值

public void test01(){
    //Consumer
    Consumer<Integer> consumer = (x) -> System.out.println("消费型接口" + x);
    //test
    consumer.accept(100);
}

提供型接口(supplier),无参数,有返回值

public void test02(){
    List<Integer> list = new ArrayList<>();
    List<Integer> integers = Arrays.asList(1,2,3); 
    list.addAll(integers);
    //Supplier<T>
    Supplier<Integer> supplier = () -> (int)(Math.random() * 10);
    list.add(supplier.get());
    System.out.println(supplier);
    for (Integer integer : list) {
        System.out.println(integer);
    }
}

函数型接口 Function<K,V>,有参数,有返回值

public void test03(){
    //Function<T, R>
    String oldStr = "abc123456xyz";
    Function<String, String> function = (s) -> s.substring(1, s.length()-1);
    //test
    System.out.println(function.apply(oldStr));
}

断言型接口 (Predicate),有参数,返回值为布尔型

public void test04(){
    //Predicate<T>
    Integer age = 35;
    Predicate<Integer> predicate = (i) -> i >= 35;
    if (predicate.test(age)){
        System.out.println("你该退休了");
    } else {
        System.out.println("我觉得还OK啦");
    }
}

本节demo来自:https://blog.csdn.net/weixin_45225595/article/details/106203264

实战

public class CompareTest {

    public static void main(String[] args) {
        Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 10);
        Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 40);
        Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 35);
        Person person4 = new Person("test4", null, Person.Sex.MALE, "qwert@gmail.com", 28);
        Person person5 = new Person("test5", null, Person.Sex.MALE, "qwert@gmail.com", 91);

        //匿名内部类写法
        List<Person> personList = Arrays.asList(person1, person2, person3, person4, person5);
        Collections.sort(personList, new Comparator<Person>() {
            //从小到大排序
            @Override
            public int compare(Person p1, Person p2) {
                return p1.getAge() - p2.getAge();
            }
        });
        System.out.println(personList);

        //正则表达式写法
        Collections.sort(personList, (p1, p2)->{
            return  p1.getAge() - p2.getAge();
        });

        //1.8以后的Comparator提供了专门的方法
        Collections.sort(personList, Comparator.comparingInt(Person::getAge));

        //还可以这么用,BiFunction也是内置函数
        BiFunction<Person,Person, Integer> function= (p1, p2)-> p1.getAge() - p2.getAge();
        function.apply(person1, person2);

    }
}

如有错误,恳请批评指正!

原文地址:https://www.cnblogs.com/phdeblog/p/13940912.html