java8(一)不断进化的java 一、java8有哪些变化 1.1 流处理

当前软件行业的气候正在不断地变化。数据量的爆炸式增长,导致程序员在开发时不得不去面对存在的各种效率问题,并希望利用多核计算机或计算集群来有效地处理。这意味着需要使用并行处理,而曾经的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的变化有个简单的认识,后续文章会逐渐深入到细节。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章