java8新特性stream深入解析

Owen Jia 2019年02月12日 145次浏览

继续java8源码的发烧热,越看越是有充实的感觉。

数据时代下的产物

Java顺应时代的发展推出的高效处理大量数据能力的api,它专注于对集合对象进行各种非常便利、高效的聚合操作,借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,可以很方便地写出高性能的并发程序。

Stream可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

核心概念理解

一个流的操作 = 创建流 + 中间操作 + 结果转换。

  1. 创建流:指从集合转换过滤,数值转换过滤、I/O转换、创建等等;
  2. 中间操作:指对流中的数据进行聚合,如filter\map\sorted\reduce、distinct等等;
  3. 结果转换:指将流进行输出,打印、转换成array、转换成collection等;

Stream重要点:

  1. 流是不存储值的,只是一个管道过程
  2. 单个流只能被使用一次,再次被使用报错
  3. 流是支持函数式调用

测试样例定义

static List<ICar> cars = new ArrayList<>();

static Map<Integer,String> kv = new HashMap<>();

public static void init(){
    for(int i = 1; i <= 10; i++){
        WeiLaiCar car = new WeiLaiCar();
        car.setCarWheel(i);
        cars.add(car);

        kv.put(i,i+"-value");
    }
}

源码深度

java.util.stream

在java.util.stream目录下可以看出有多种stream可以使用。Stream是支持泛型的对象的流,另外还提供了double\int\long常用数据类型的流,但他们提供的方法基本类似。

1、流是管道内部单向不可逆操作

Random random = new Random();
IntStream intStream = random.ints(10);
intStream.limit(6).forEach(PrintUtil::printTest);
intStream.toArray();

这里的第二个intStream.toArray()执行会报错误:IllegalStateException: stream has already been operated upon or closed,流创建流到结束流单个管道内只能顺序一次使用,想要再次使用就得从源头重新创建流重新开始;

Random random = new Random();
IntStream intStream = random.ints(10);
intStream.limit(6).forEach(PrintUtil::printTest);
random.ints(15).toArray();

2、Stream的串行和并行

流的计算提供了两种模式:串行流和并行流,认是创建串行流。

  • 提供流方法parallel()和sequential()在串并流间相互转换。
  • 在Collection接口上添加了两个方法stream()\parallelStream(),parallel是并行计算的。这两个方法的内部实现皆是通过StreamSupport.stream(xx,aa)来实现,该方法的aa参数是boolean代表是否开启并行流;

3、collection与stream转换

流提供有方法toArray()转换成数组,也提供了方法collect()转换为list\set\Collection集合;同时Arrays.stream()方法可以把数组转换为流。

//list -> stream
Stream<ICar> carStream = cars.parallelStream();
carStream.filter(a -> a.getWheelCount() > 5).map(a-> a.hashCode()+"|"+a.getWheelCount()).forEach(PrintUtil::printTest);
//list转换为并发stream并打印符合条件的对象的hashcode和wheel数量:730140433|8

//stream -> list
Stream<ICar> tmp = cars.stream();
List<ICar> carList = tmp.collect(Collectors.toList());
carList.forEach(PrintUtil::printTest);
//通过collect方法把stream转换为list并打印对象全称:com.ts.util.optional.WeiLaiCar@4e515669

//array -> stream
ICar[] carss = {new WeiLaiCar(1),new WeiLaiCar(2)};
Arrays.stream(carss).filter(a -> a.getWheelCount() >2).forEach(PrintUtil::printTest);
//最终输出8个wheel大于2个的对象,并打印对象全称:com.ts.util.optional.WeiLaiCar@4dcbadb4

//stream -> array
Object[] tmps = cars.stream().filter(a ->a.getWheelCount()>7).toArray();
PrintUtil.printTest(tmps.length);
//从cars中转换stream选出wheel大于7个的,转换为数值最终输出数量为:3

4、stream的排序

默认的排序要求排序对象是自然的有些值,一般dubble\int\long等;其他的类型需要主动定义comparator接口比较逻辑。

//默认sorted()要求是值natural order,即是自然顺序的
cars.stream().map(a->a.getWheelCount()).sorted().forEach(PrintUtil::printTest);
//先将对象的int类型的wheel聚合成集合,再使用默认排序(顺序)进行打印:1 2 3 4 5 6 ...10

//自定义comparator,也可以在对象之间实现Comparator接口
cars.stream().sorted(Comparator.comparingInt(ICar::getWheelCount)).map(a->a.getWheelCount()).forEach(PrintUtil::printTest);
//自定义使用ICar的int类型wheel字段顺序比较,之后再将对象的wheel字段聚合进行打印:1 2 3 4 5 6 ...10
cars.stream().sorted(Comparator.comparingInt(ICar::getWheelCount).reversed()).map(a->a.getWheelCount()).forEach(PrintUtil::printTest);
//自定义使用ICar的int类型wheel字段顺序比较后再逆转,之后再将对象的wheel字段聚合进行打印
//与上面的顺序正好相反:10 9 8 7 ... 1

流包下重要的工具类

  1. Collectors 提供了很多 reduction 操作,都是静态的方法,如聚合成集合或者其他类型,官网例子如下:
// Accumulate names into a List
List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());

// Accumulate names into a TreeSet
Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));

// Convert elements to strings and concatenate them, separated by commas
String joined = things.stream().map(Object::toString).collect(Collectors.joining(", "));

// Compute sum of salaries of employee
int total = employees.stream().collect(Collectors.summingInt(Employee::getSalary)));

// Group employees by department
Map<Department, List<Employee>> byDept = employees.stream()
					.collect(Collectors.groupingBy(Employee::getDepartment));

// Compute sum of salaries by department
Map<Department, Integer> totalByDept = employees.stream()
                    .collect(Collectors.groupingBy(Employee::getDepartment,
                                                   Collectors.summingInt(Employee::getSalary)));

// Partition students into passing and failing
Map<Boolean, List<Student>> passingFailing = students.stream()
                 .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
  1. StreamSupport 是stream的底层创建操作方法实现类,让我们查看一下Stream类的部分方法源码:
    /**
     * Returns an empty sequential {@code Stream}.
     *
     * @param <T> the type of stream elements
     * @return an empty sequential stream
     */
    public static<T> Stream<T> empty() {
        return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);
    }

    /**
     * Returns a sequential {@code Stream} containing a single element.
     *
     * @param t the single element
     * @param <T> the type of stream elements
     * @return a singleton sequential stream
     */
    public static<T> Stream<T> of(T t) {
        return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
    }
  1. 其他创建stream的方式,通过Stream的静态方法或者StreamSupport类:
Stream<String> stringStream = Stream.of("1","a","c");
stringStream.forEach(PrintUtil::printTest);
// 1 a c

Stream<Integer> integerStream = Stream.empty();
PrintUtil.printTest("count is " + integerStream.count());
//count is 0

double[] tm = {22D,1.11D,33D,20.12D,11.02D,9.34D};
DoubleStream doubleStream = StreamSupport.doubleStream(Spliterators.spliterator(tm,0,5,1),true);
doubleStream.filter(a -> a < 20).forEach(PrintUtil::printTest);
//1.11 11.02

要主动拥抱新技术的出现

看着公司年轻的工程师一个比一个年轻,我变的更加努力了。要鞭策自己在项目中多尝试使用新API各种类和方法以锻炼自己,这些东西就是需要多用你才能发现区别并提升你的技术能力。

就算是写业务的也能写出不一样的水平。时常告诉自己去学习,追求进步不要做茫茫的小白。 对于一个工程师来说学习能力是你在这个行业必备的能力。

测试用例地址:play-java-sample


作者:Owen Jia

欢迎关注他的博客:Owen Blog

坚持写博客成了我的一种寄托