• 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);
    
        }
    }

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

  • 相关阅读:
    GitLab版本管理
    Failed to add reference to 'System.Net.Http'. Please make sure that it is in the Global Assembly Cache.
    在CentOS 6.3中安装拼音输入法
    Yum Error Another app is currently holding the yum lock; waiting for it to exit
    Centos使用光盘作为本地yum源
    Linux操作系统桌面环境GNOME和KDE的切换
    CentOS安装VMware Tools
    CentOS 加载/挂载光驱
    svn Couldn't open rep-cache database
    linux内存排查工具valgrind
  • 原文地址:https://www.cnblogs.com/phdeblog/p/13940912.html
Copyright © 2020-2023  润新知