java8 stream lambda

记录Java8的stream操作,供自己复习。

创建Stream

Employee类

class Employee{
    public int id;
    public String name;
    public double salary;

    public Employee(int id, String name, double salary){
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                '}';
    }

    public void salaryIncrement(double add){
        this.salary += add;
    }
}

创建stream方法

 Employee[] arrayOfEms = {
                new Employee(1, "Jeff Bezos", 100000.0),
                new Employee(2, "Bill Gates", 200000.0),
                new Employee(3, "Mark Zuckerberg", 300000.0)
        };
//1
Stream.of(arrayOfEms);
//2
List<Employee> empList = Arrays.asList(arrayOfEms);
empList.stream();
//3
Stream.of(arrayOfEms[0], arrayOfEms[1], arrayOfEms[2]);
//4
Stream.Builder<Employee> employeeBuilder = Stream.builder();
employeeBuilder.accept(arrayOfEms[0]);
employeeBuilder.accept(arrayOfEms[1]);
employeeBuilder.accept(arrayOfEms[2]);
Stream<Employee> empStream = employeeBuilder.build();

forEach

forEach就是对stream进行遍历,但是不保证遍历的顺序。forEach是一个终端操作,流管道被视为已消耗,不能在使用。所以在forEach后不再跟其他操作。

empList.stream().forEach(e -> e.salaryIncrement(10.0));
empList.stream().forEach(e -> System.out.println(e));

map

map和Python里面的意思一样,就是将每个元素应用同样的操作。根据empIds里面的id将对应的元素找出来。

class employeeRepository{
    public static Employee findById(int id){
        Employee[] arrayOfEms = {
                new Employee(1, "Jeff Bezos", 100000.0),
                new Employee(2, "Bill Gates", 200000.0),
                new Employee(3, "Mark Zuckerberg", 300000.0)
        };
        List<Employee> employees = Arrays.asList(arrayOfEms).stream().filter(e -> e.getId() == id).collect(Collectors.toList());
        if(employees.size() > 0)
            return employees.get(0);
        else
            return null;
    }
}
Integer[] empIds = {1, 3};
List<Employee> employees = Stream.of(empIds)
                .map(employeeRepository::findById)
                .collect(Collectors.toList());
employees.forEach(e -> System.out.println(e));

collect

就像前面的map处理通过collect操作获得List结果一样,collect将流管道中的元素重新组合起来。

List<Employee> employees = empList.stream().collect(Collectors.toList());
employees.forEach(e -> System.out.println(e));

filter

就是对stream中的元素进行过滤。下面的操作表示先使用map选出empIds中的元素,然后在将工资小于200000的过滤掉。

Integer[] empIds = {1, 2, 3, 4};
List<Employee> employees = Stream.of(empIds)
                .map(employeeRepository::findById)
                .filter(e -> e != null)
                .filter(e -> e.getSalary() > 200000)
                .collect(Collectors.toList());
employees.forEach(e -> System.out.println(e));

findFirst

顾名思义,找到第一个符合条件的数据。在指定id中找到第一个工资大于100000的元素。

Integer[] empIds = {1, 2, 3, 4};
Employee employee = Stream.of(empIds)
        .map(employeeRepository::findById)
        .filter(e -> e != null)
        .filter(e -> e.getSalary() > 100000)
        .findFirst()
        .orElse(null);
System.out.println(employee);

toArray

将管道流中的元素转变为数组,提供了一种列表转数组的方法,其中Employee[]::new表示创建一个空数组,用流中的元素将其填满。

Employee[] employees = empList.stream().toArray(Employee[]::new);
System.out.println(Arrays.toString(employees));

flatMap

将复杂的数据结构拉平

List<List<String>> namesNested = Arrays.asList(
        Arrays.asList("Jeff", "Bezos"),
        Arrays.asList("Bill", "Gates"),
        Arrays.asList("Mark", "Zuckerberg")
);
List<String> namesFlatStream = namesNested.stream()
        .flatMap(Collection::stream)
        .collect(Collectors.toList());
System.out.println(namesFlatStream);

peek

前面已经说了forEach是个终端操作,也就是后面不能再有其他操作,但是我想对一个元素多次操作怎么办呢?peek可以对流中的元素执行指定的操作,然后返回一个可以继续使用的新流,peek是一个中间操作。

Employee[] arrayOfEmps = {
        new Employee(1, "Jeff Bezos", 1000.0),
        new Employee(2, "bill Gates", 2000.0),
        new Employee(3, "Mark Zuckerberg", 3000.0)
};
List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream()
        .peek(e -> e.salaryIncrement(10.0))
        .peek(System.out::println)
        .collect(Collectors.toList());

System.out.println(empList);

Method types and Pipelines

正如前面所说的,流操作分为中间操作和终端操作,例如filter之类的中间操作返回一个新的流,可以对其做进一步的处理,终端操作例如forEach将流标记为已使用,此后无法在对其进行任何操作。

流管道包括一个stream source、多个中间操作以及一个终端操作。

long empCount = empList.stream()
                .filter(e -> e.getSalary() > 200000.0)
                .count();
System.out.println("empCount " + empCount);

Stream<Integer> infiniteStream = Stream.iterate(2, i -> i*2);
List<Integer> collect = infiniteStream.skip(3)
        .limit(5)
        .collect(Collectors.toList());
System.out.println("collect " + collect);

惰性计算

流的重要特征之一是可以通过惰性计算进行重要优化。仅在启动终端操作的时候才会对源数据进行计算,并且仅在需要的时候才使用源元素。所有的中间操作都是惰性计算,因此只有在实际需要处理结果的时候它们才会执行。
想一想之前的findFirst例子,map操作一共执行了几次?是4次吗?因为我们的输入empIds数组长度为4。

Integer[] empIds = {1, 2, 3, 4};
Employee employee = Stream.of(empIds)
        .map(employeeRepository::findById)
        .filter(e -> e != null)
        .filter(e -> e.getSalary() > 100000)
        .findFirst()
        .orElse(null);
System.out.println(employee);

一个元素一次执行一个map和两个filter操作。它首先对id 1执行所有操作。由于id 1的薪水不大于100000,因此处理移至下一个元素。id 2满足两个filter,因此流终端操作findFirst()执行并返回结果。id 3和id 4没有执行任何操作。懒惰处理数据流可以避免在不必要时检查所有数据。当输入流是无限或者很大时,这种特点就更加重要。

基于流的比较操作

sorted

流元素的比较基于我们传递进去的比较器

List<Employee> employees = empList.stream()
        .sorted((e1, e2) -> e1.getName().compareTo(e2.getName()))
        .collect(Collectors.toList());
System.out.println(employees);

min & max

顾名思义,返回流中的最大值或者最小值基于我们传递进去的比较器。返回结果是optional,因为可能存在可能不存在(例如由于filter)

Employee firstEmp = empList.stream()
        .min((e1, e2) -> e1.getId() - e2.getId())
        .orElseThrow(NoSuchElementException::new);
System.out.println(firstEmp);

我们还可以使用Comparator.comparing定义比较器

Employee maxSalEmp = empList.stream()
        .max(Comparator.comparing(Employee::getSalary))
        .orElseThrow(NoSuchElementException::new);
System.out.println(maxSalEmp);

distinct

distinct不需要传递参数,返回流中不同的元素,它使用元素的equals判断元素是否相等

List<Integer> intList = Arrays.asList(2,5,3,2,4,3);
List<Integer> distinctIntList = intList.stream()
        .distinct()
        .collect(Collectors.toList());
System.out.println(distinctIntList);

match

allMatch都要符合,anyMatch有一个符合即可,noneMatch都不能符合。

List<Integer> intList = Arrays.asList(2,4,5,6,8);
boolean allEven = intList.stream().allMatch(i -> i%2 == 0);
boolean oneEven = intList.stream().anyMatch(i -> i%2 == 0);
boolean noneMultipleOfThree = intList.stream().noneMatch(i -> i%3 == 0);
System.out.println("allEven: " + allEven + " oneEven: " + oneEven + " noneMultipleOfThree: " + noneMultipleOfThree);

stream specializations(流定制化,不知道这么翻译对不对)

根据到目前为止的讨论,Stream是对象引用的流。但是,还有IntStream,LongStream和DoubleStream –它们分别是int,long和double的重要定制。这些在处理许多数值型元素时非常方便。
这些定制流并不是扩展了Stream而是扩展了BaseStrean


stream

因此,并非所有Stream支持的操作都出现在这些Stream实现中。例如,标准min()和max()使用比较器,而专用流则不需要。

creation

最常用的创建IntStream流的方法就是对现有流使用mapToInt()。使用mapToInt创建了IntStream,然后使用max找出最大的id。

Integer latestEmpId = empList.stream()
        .mapToInt(Employee::getId)
        .max()
        .orElseThrow(NoSuchElementException::new);
System.out.println("latestEmpId: " + latestEmpId);

还可以使用IntStream.of(1,2,3)IntStream.range(10,20)进行创建
在进行下面的学习之前需要注意一个重要的区别。Stream.of(1,2,3)返回的不是IntStream而是Stream<Integer>。同样地使用map而不是mapToInt返回的也不是IntStream,empList.stream().map(Employee::getId);返回的是Stream<Integer>。

Specialized Operations

先比较一般的流,这些定制流可以很方面的进行数值型的操作,比如sum()、average()、range(),因为都是数值嘛。

Double avgSal = empList.stream()
        .mapToDouble(Employee::getSalary)
        .average()
        .orElseThrow(NoSuchElementException::new);
System.out.println("avgSal: " + avgSal);

Reduction Operations

学过Python的应该对reduce操作很熟悉,就是将第一个元素与第二个元素按照指定操作进行处理,然后将处理结果与第三个元素进行处理,以此类推直到最后一个元素,前面讲的findFirst(), min() and max()都是这样的操作。对empList中的薪资进行求和,第一个操作数是0.0然后后面一直执行sum操作,直到最后一个元素。

Double sumVal = empList.stream()
        .map(Employee::getSalary)
        .reduce(0.0, Double::sum);
System.out.println("sumVal: " + sumVal);

高级collect

joining

String empNames = empList.stream()
                .map(Employee::getName)
                .collect(Collectors.joining(", "))
                .toString();
System.out.println(empNames);

toSet

Set<String> empNames = empList.stream()
        .map(Employee::getName)
        .collect(Collectors.toSet());
System.out.println(empNames);

toCollection

Vector<String> empNames = empList.stream()
                .map(Employee::getName)
                .collect(Collectors.toCollection(Vector::new));
System.out.println(empNames);

summarizingDouble

有点像pandas的summary,给出count、min、max、average等统计信息

DoubleSummaryStatistics stats = empList.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));
stats = empList.stream()
        .mapToDouble(Employee::getSalary)
        .summaryStatistics();
System.out.println("getCount: " + stats.getCount()
+"\ngetSum: " + stats.getSum() + "\ngetMin: " + stats.getMin()
+"\ngetMax: " + stats.getMax() + "\ngetAverage: " + stats.getAverage());

partitioningBy

对流中的元素基于某个条件判断进行分区

List<Integer> intList = Arrays.asList(2,4,5,6,8);
Map<Boolean, List<Integer>> isEven = intList.stream()
        .collect(Collectors.partitioningBy(i -> i % 2 == 0));
System.out.println(isEven.get(true));
System.out.println(isEven.get(false));

groupingBy

如果学习过SQL语句就很好理解了,是一种更高级的分区,可以将流中元素分成不止两个分区。它采用一个分类函数作为参数,这个分类函数被应用到流中每一个元素。这个分类函数的返回值就是Map的key。根据Employee的首字母将列表中的元素分组。

Map<Character, List<Employee>> groupByAlphabet = empList.stream()
        .collect(Collectors.groupingBy(e -> new Character(e.getName().charAt(0))));
for(HashMap.Entry<Character, List<Employee>> item:groupByAlphabet.entrySet())
    System.out.println(item.getKey() + ": " + item.getValue());

mapping

对groupingBy的结果再进行抽取处理,直接看例子可能更好一些。

Map<Character, List<Integer>> idGroupedByAlphabet = empList.stream()
        .collect(Collectors.groupingBy(e -> new Character(e.getName().charAt(0)),
                Collectors.mapping(Employee::getId, Collectors.toList())));
for(HashMap.Entry<Character, List<Integer>> item:idGroupedByAlphabet.entrySet())
    System.out.println(item.getKey() + ": " + item.getValue());

reducing

下面是两个例子,第二个操作是找到每个分组的最长的名字。

Double percentage = 10.0;
Double salIncrOverhead = empList.stream()
        .collect(Collectors.reducing(0.0, e -> e.getSalary()*percentage/100,
                (s1,s2) -> s1+s2));
System.out.println(salIncrOverhead);

Comparator<Employee> byNameLength = Comparator.comparing(Employee::getName);
Map<Character, Optional<Employee>> longestNameByAlphabet = empList.stream()
        .collect(Collectors.groupingBy(e -> new Character(e.getName().charAt(0)),
                Collectors.reducing(BinaryOperator.maxBy(byNameLength))));
for(HashMap.Entry<Character, Optional<Employee>> item:longestNameByAlphabet.entrySet())
    System.out.println(item.getKey() + ": " + item.getValue());

并行流

只需要加上parallel即可

Employee[] arrayOfEmps = {
        new Employee(1, "Jeff Bezos", 100000.0),
        new Employee(2, "Bill Gates", 200000.0),
        new Employee(3, "Mark Zuckerberg", 300000.0)
};
List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream().parallel().forEach(e -> e.salaryIncrement(10.0));
System.out.println(empList);

无限流

Stream.generate(Math::random)
        .limit(5)
        .forEach(System.out::println);
Stream<Integer> evenNumStream = Stream.iterate(2, i -> i*2);
List<Integer> collect = evenNumStream
        .limit(5)
        .collect(Collectors.toList());
System.out.println(collect);

文件操作

String[] words = {
        "heelo",
        "refer",
        "world",
        "level"
};
try(PrintWriter pw = new PrintWriter(
        Files.newBufferedWriter(Paths.get("./file.txt")))){
    Stream.of(words).forEach(pw::println);
} catch (IOException e) {
    e.printStackTrace();
}

List<String> str = null;
try {
    str = Files.lines(Paths.get("./file.txt"))
            .filter(s -> s.length() == 5)
            .filter(s -> s.compareToIgnoreCase(new StringBuffer(s).reverse().toString()) == 0)
            .collect(Collectors.toList());
} catch (IOException e) {
    e.printStackTrace();
}
System.out.println(str);

stream在Java9中的改进

takeWhile

当流中的元素符合(while)某个条件的时候,就把元素取走(take)。

Stream.iterate(1, i -> i + 1)
                .takeWhile(n -> n <= 10)
                .map(x -> x * x)
                .forEach(System.out::println);

这个takeWhile看着和filter很类似,区别是什么?下面的例子很清楚了说明了区别,takeWhile在遇到条件不符合的时候就终止了,而filter会处理每一个元素。

Stream.of(1,2,3,4,5,6,7,8,9,0,9,8,7,6,5,4,3,2,1,0)
                .takeWhile(x -> x <= 5)
                .forEach(System.out::println);
# 输出
1
2
3
4
5
Stream.of(1,2,3,4,5,6,7,8,9,0,9,8,7,6,5,4,3,2,1,0)
                .filter(x -> x <= 5)
                .forEach(System.out::println);
# 输出
1
2
3
4
5
0
5
4
3
2
1

dropWhile

与takeWhile类似,只是作用相反。

iterate

java8中iterate是个无限流,没有终止条件。Java9中增加了一个判断条件。

Stream.
    iterate(1, i -> i < 256, i -> i * 2)
    .forEach(System.out::println);

与下面for循环作用一样。

for (int i = 1; i < 256; i*=2) {
    System.out.println(i);
}

ofNullable

Stream<Integer> result = number != null
        ? Stream.of(number)
        : Stream.empty();

上面的number可能来源于网络、前端或者其他不信任的地方。所以它可能是null,我们不想创建一个null的流,所以需要对number进行判断,然后返回一个empty的流。
但是上面这个例子是经过精心设计的,时间情况可能非常复杂,需要进行判断处理。这个时候可以利用ofNullable进行处理。

Stream<Integer> result = Stream.ofNullable(number);

ofNullable当接收number为null时候,可以返回空的optional,必须程序报错。就行下面代码所示。

Integer number = null;
Stream<Integer> result = Stream.ofNullable(number);
result.map(x -> x * x).forEach(System.out::println);

参考资料

https://stackify.com/streams-guide-java-8/#

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 转自: Java 8 中的 Streams API 详解 为什么需要 Stream Stream 作为 Java ...
    普度众生的面瘫青年阅读 2,913评论 0 11
  • 一、什么是Stream流(WHAT) 在Java中,集合和数组是我们经常会用到的数据结构,需要经常对他们做增、删、...
    GeekerLou阅读 988评论 0 11
  • Stream 允许你以声明性方式处理数据集合,流还可以透明地并行处理,你就无需写任何多线程代码了。和迭代器类似,流...
    PawsUp阅读 793评论 0 3
  • 1、Stream简介 Stream作为Java 8的一大亮点,是对集合(Collection)、数组对象功能的增强...
    Albert_Yu阅读 6,773评论 1 21
  • 了解Stream ​ Java8中有两个最为重要的改变,一个是Lambda表达式,另一个就是Stream AP...
    龙历旗阅读 3,301评论 3 4