java8(一)不断进化的java

作者: 我犟不过你 | 来源:发表于2021-10-15 17:40 被阅读0次

    当前软件行业的气候正在不断地变化。数据量的爆炸式增长,导致程序员在开发时不得不去面对存在的各种效率问题,并希望利用多核计算机或计算集群来有效地处理。这意味着需要使用并行处理,而曾经的java对于并行的支持并不好,这使得其不得不进行进化,以适应当前的行业气候。

    Java 8中开发出并行和编写更简洁通用代码的功能,下面我们一起学习。

    一、java8有哪些变化

    1.1 流处理

    Java 8在 java.util.stream 中添加了一个Stream API。通过这个流处理有两个较为直观的好处:

    1)把这样的流变成那样的流。

    什么意思?简单举个小例子,在Stream提供了很多方法,此处以mapToInt举例:

    IntStream mapToInt(ToIntFunction<? super T> mapper);
    

    用法:

        public static void main(String[] args) {
            // 有如下的字符串列表
            List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
            // 打印出每个字符串的长度
            list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
            // 经过Lambda替换方法后,尽量的简化代码
            list.stream().mapToInt(String::length).forEach(System.out::println);
        }
    

    结果:

    4
    3
    3
    6
    6
    

    在上面的代码我们看到只通过一行代码就可以完成操作,最大化的简化了代码。比传统的for循环更容易查看。

    2)集合处理
    几乎每个Java应用都会制造和处理集合。但集合用起来并不总是那么理想。比方说,你需要从一个列表中筛选金额较高的交易,然后按货币分组。你需要写一大堆套路化的代码来实现这个数据处理命令。

    通过Stream API 可以简单的完成上面的步骤,这里举个例子:

        Map<Currency, List<Transaction>> transactionsByCurrencies =
                transactions.stream()
                        .filter((Transaction t) -> t.getPrice() > 1000)//筛选金额较高的
                        .collect(groupingBy(Transaction::getCurrency));//按照金额分组
    

    相比于使用集合Collection,使用Stream API我们能获得两点直观的优点:
    1)不需要自己去通过Collection API去迭代处理复杂的过程(外部迭代),使用流式处理的内部迭代,不需要操心循环的事情。
    2)如果数据量非常庞大,那么使用集合的方式,处理将会非常慢;引入多线程将会是原本就复杂的集合迭代变得更加困难,使用Steam API可以较好的解决这一问题,看3)中的介绍。

    3)在不同cpu上执行Stream操作,不需要Thread
    Stream提供了并行操作,通过下面的例子简单看下:

        public static void main(String[] args) {
            // 有如下的字符串列表
            List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
            // 打印出每个字符串的长度
            //list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
            // 经过Lambda替换方法后,尽量的简化代码
            long startTime = System.currentTimeMillis();
            list.stream().mapToInt(String::length).forEach(l -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(l);
            });
            System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
        }
    

    结果:

    4
    3
    3
    6
    6
    耗时:5104
    

    这时我们添加上并行操作parallel,代码如下:

        public static void main(String[] args) {
            // 有如下的字符串列表
            List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
            // 打印出每个字符串的长度
            //list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
            // 经过Lambda替换方法后,尽量的简化代码
            long startTime = System.currentTimeMillis();
            list.stream().mapToInt(String::length).parallel().forEach(l -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(l);
            });
            System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
        }
    

    结果:

    3
    6
    4
    3
    6
    耗时:1096
    

    总体耗时基本是五分之一,或者可以使用parallelStream():

    list.parallelStream().mapToInt(String::length).forEach....
    

    关于具体的内容后面会详细讲解。

    1.2 用行为参数化把代码(方法)传递给方法

    Java 8增加了把方法(你的代码)作为参数传递给另一个方法的能力。我们把这一概念称为行为参数化

    下面会简单讲解时什么意思。

    1.2.1 函数

    编程语言中的函数一词通常是指方法,尤其是静态方法。

    Java 8中新增了函数:值的一种新形式

    编程语言的本质就是操作值。按照传统的编程语言历史,我们以java举例:例如int,long,double这些可以直接被作为参数传递的值,都是一等值(一等公民);而还有很多涉及到结构的内容,例如方法和类等,不能作为参数传递,这些被称为二等公民。

    java8中增加了新的功能,将二等公民添加到运行时传递,则二等公民就成为了一等公民。

    1.2.1.1 方法和 Lambda 作为一等公民

    Java 8的设计者决定允许方法作为值,让编程更轻松。此外,让方法作为值也构成了其他若干Java 8功能(如 Stream )的基础。

    我们介绍的Java 8的第一个新功能是方法引用

    举个例子,你想要筛选一个目录中的所有隐藏文件。 File类里面有一个叫作 isHidden 的方法。我们可以把它看作一个函数,接受一个 File ,返回一个布尔值。但要用它做筛选,你需要把它包在一个 FileFilter 对象里,然后传递给 File.listFiles方法,如下所示:

        public void test(){
            File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
                @Override
                public boolean accept(File file) {
                    return file.isHidden();
                }
            });
        }
    

    在java8当中我们可以使用如下的方式:

        public void test(){
            File[] hiddenFiles = new File(".").listFiles(File::isHidden);
        }
    

    你已经有了函数 isHidden ,因此只需用Java 8的方法引用 :: 语法(即“把这个方法作为值”)将其传给 listFiles 方法,我们也开始用函数代表方法了。

    当使用File::isHidden时,其实创建了一个方法引用,与对象引用类似,我们就可以将其作为一等公民传递。

    除了允许(命名)函数成为一等值外,Java 8还体现了更广义的将函数作为值的思想,包括Lambda(匿名函数)。直白说,将匿名函数作为一等值

    将匿名函数传递有什么好处呢?写代码的时候相信都会遇到一种情况,当你只需要一个简单的运算的时候,还需要去重新定义一个类或者方法吗?如果没有方便的类或者方法可用的话,lambda就能帮助你使语法更简洁。

    1.2.1.2 使用案例

    假设有一个手机仓库,里面放了很多品牌的手机,包括苹果、华为等。现在我们需要筛选出华为手机有多少。那么需要些如下的代码:

        public List<Phone> brandFilter(){
            List<Phone> phones = new ArrayList<>();
            List<Phone> results = new ArrayList<>();
            for (Phone phone : phones) {
                if ("华为".equals(phone.getBrand())){
                    results.add(phone);
                }
            }
            return results;
        }
    

    如果又需要筛选黑色的手机,那么还需要写下面的方法:

        public List<Phone> colorFilter(){
            List<Phone> phones = new ArrayList<>();
            List<Phone> results = new ArrayList<>();
            for (Phone phone : phones) {
                if ("黑色".equals(phone.getColor())){
                    results.add(phone);
                }
            }
            return results;
        }
    

    这样一来重复代码就会很多了,如果我们使用java8,可以像下面这样写:

    package com.cloud.bssp.java8.stream;
    
    import lombok.Data;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.function.Predicate;
    
    /**
     * @description: 挑选手机
     * @author:weirx
     * @date:2021/10/15 16:02
     * @version:3.0
     */
    public class ChoicePhone {
    
        public static void main(String[] args) {
            List<Phone> list = new ArrayList<>();
            list.add(new Phone("苹果", "黑色"));
            list.add(new Phone("华为", "黑色"));
            List<Phone> blackPhones = phoneFilter(list, ChoicePhone::isBlack);
            System.out.println(blackPhones.toString());
            List<Phone> huaweiPhones = phoneFilter(list, ChoicePhone::isHuawei);
            System.out.println(huaweiPhones.toString());
        }
    
        @Data
        static class Phone {
            private String brand;
    
            public Phone(String brand, String color) {
                this.brand = brand;
                this.color = color;
            }
    
            private String color;
        }
    
        /**
         * 筛选华为方法
         */
        public static boolean isHuawei(Phone phone) {
            return "华为".equals(phone.getBrand());
        }
    
        /**
         * 筛选黑色方法
         */
        public static boolean isBlack(Phone phone) {
            return "黑色".equals(phone.getColor());
        }
    
        /**
         * 手机过滤方法,参数是手机list和Predicate<T>函数
         */
        public static List<Phone> phoneFilter(List<Phone> phones, Predicate<Phone> p) {
            List<Phone> results = new ArrayList<>();
            for (Phone phone : phones) {
                if (p.test(phone)) {
                    results.add(phone);
                }
            }
            return results;
        }
    }
    

    前面的代码传递了方法 Apple::isGreenApple (它接受参数 Apple 并返回一个boolean )给 filterApples ,后者则希望接受一个 Predicate<Apple> 参数。

    Java 8也会允许你写 Function<Apple,Boolean>。

    关于上面的代码请同学们细细体会啊,一遍看不懂就多看几遍。

    前面就简单介绍了lambda的作用,像我们前面的例子中,只是一个判断的方法实在没有必要单独提供一个方法去维护,所以我们可以使用lambda的方式进行优化,就不需要单独定义判断方法isBlack和isHuawei了,使代码更简洁,如下所示:

    List<Phone> blackPhones = phoneFilter(list, (Phone p) -> p.getColor().equals("黑色"));
    List<Phone> huaweiPhones = phoneFilter(list, (Phone p) -> p.getBrand().equals("华为"));
    
    List<Phone> blackPhones = phoneFilter(list, (Phone p) -> 
      p.getColor().equals("黑色") || p.getBrand().equals("华为")
    );
    

    1.3 默认方法

    在java8之前的版本当中,接口被一个类实现,必须要求这个类实现该接口的全部方法,如果一个接口增加一个方法,那么所有的实现类都需要增加这个方法的实现。那么如何解决这个问题呢?

    既然不想让实现类自己实现这个方法,那么只能由接口自己来实现了。这就给开发者提供了一个扩充接口的方式,而不会破坏现有的代码。在java8中使用关键字default来表示这个关键点。

    比如在java8当中,我们可以直接调用List接口中的sort方法:

        default void sort(Comparator<? super E> c) {
            Object[] a = this.toArray();
            Arrays.sort(a, (Comparator) c);
            ListIterator<E> i = this.listIterator();
            for (Object e : a) {
                i.next();
                i.set((E) e);
            }
        }
    

    这意味着 List 的任何实体类都不需要显式实现 sort。


    本篇主要针对java的变化有个简单的认识,后续文章会逐渐深入到细节。

    如果对您有帮助,给点个赞吧,感谢!!

    相关文章

      网友评论

        本文标题:java8(一)不断进化的java

        本文链接:https://www.haomeiwen.com/subject/jjjmoltx.html