Java8新特性-Stream

    • Stream 详解
  • Collect 详解

简介


Stream 作为 Java8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念,Stream 是对集合的包装,通常和lambda一起使用。使用stream可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。

Stream 的特性


Stream 主要具有如下三点特性

  • stream不存储数据

  • stream不改变源数据

  • stream的延迟执行特性

通常我们在数组或集合的基础上创建stream,stream不会专门存储数据,对stream的操作也不会影响到创建它的数组和集合,对于stream的聚合、消费或收集操作只能进行一次,再次操作会报错

@Test

public void test1(){

int[] arr = new int[]{1, 2, 3};

IntStream intStream = Arrays.stream(arr);

intStream.forEach(System.out::println);

intStream.forEach(System.out::println);

}

输出结果

1

2

3

java.lang.IllegalStateException: stream has already been operated upon or closed

stream的操作是延迟执行的,在列出字符串长度大于3的例子中,在collect方法执行之前,filter、sorted、map方法还未执行,只有当collect方法执行时才会触发之前转换操作

public boolean filter(String s) {

System.out.println(“begin compare”);

return s.length() > 3;

}

@Test

public void test2() {

List strs = new ArrayList() {

{

add(“abc”);

add(“abcd”);

}

};

Stream stream = strs.stream().filter(this::filter);

System.out.println(“split-------------------------------------”);

List list = stream.collect(Collectors.toList());

System.out.println(list);

}

打印结果如下:

split-------------------------------------

begin compare

begin compare

[abcd]

由此可以看出,在执行完filter时,没有实际执行filter中的方法,而是等到执行collect时才会执行,即是延迟执行的。

注意

  • 由于stream的延迟执行特性,在聚合操作执行前修改数据源是允许的。

  • 当我们操作一个流的时候,并不会修改流底层的集合(即使集合是线程安全的),如果想要修改原有的集合,就无法定义流操作的输出。

/**

  • 延迟执行特性,在聚合操作之前都可以添加相应元素

*/

@Test

public void test3() {

List wordList = new ArrayList() {

{

add(“a”);

add(“b”);

}

};

Stream words = wordList.stream();

wordList.add(“END”);

long n = words.distinct().count();

System.out.println(n);

}

输出结果

3

延迟执行特性,会产生干扰

@Test

public void test4(){

List wordList = new ArrayList() {

{

add(“a”);

add(“b”);

}

};

Stream words1 = wordList.stream();

words1.forEach(s -> {

System.out.println(“s->”+s);

if (s.length() < 4) {

System.out.println(“select->”+s);

wordList.remove(s);

System.out.println(wordList);

}

});

}

输出结果

s->a

select->a

[b]

s->null

java.lang.NullPointerException

创建Stream


要进行流操作首先要获取流,有6中方法可以获取流。

通过集合创建流

@Test

public void testCollectionStream() {

List strs = Arrays.asList(“a”, “b”, “c”, “d”);

//创建普通流

Stream stream = strs.stream();

//创建并行流(即多个线程处理)

// Stream stream1 = strs.parallelStream();

stream.forEach(System.out::println);

}

输出结果

a

b

c

d

通过数组创建流

@Test

public void testArrayStream() {

int[] arr = new int[]{1, 2, 3, 4};

IntStream intStream = Arrays.stream(arr);

intStream.forEach(System.out::println);

System.out.println(“========”);

Integer[] arr2 = new Integer[]{1, 2, 3, 4};

Stream stream = Arrays.stream(arr2);

stream.forEach(System.out::println);

}

输出结果

1

2

3

4

========

1

2

3

4

通过Stream.of方法创建流

@Test

public void testStreamOf() {

Stream stream = Stream.of(1, 2, 3);

stream.forEach(System.out::println);

}

输出结果

1

2

3

创建规律的无限流

@Test

public void testUnlimitStream() {

Stream stream = Stream.iterate(0, x -> x + 2).limit(3);

stream.forEach(System.out::println);

}

输出结果

0

2

4

创建无限流

@Test

public void testUnlimitStream2() {

Stream stream = Stream.generate(() -> “number” + new Random().nextInt()).limit(3);

stream.forEach(System.out::println);

}

输出结果

number1042047526

number-155761434

number-1605164634

创建空流

@Test

public void testEmptyStream() {

Stream stream = Stream.empty();

stream.forEach(System.out::println);

}

Stream操作分类


  • 中间操作又可以分为无状态(Stateless)与有状态(Stateful)操作,

  • 无状态是指元素的处理不受之前元素的影响,

  • 有状态是指该操作只有拿到所有元素之后才能继续下去。

  • 终结操作又可以分为短路(Short-circuiting)与非短路(Unshort-circuiting)操作,

  • 短路是指遇到某些符合条件的元素就可以得到最终结果,

  • 非短路是指必须处理完所有元素才能得到最终结果。

  • 我们通常还会将中间操作称为懒操作,也正是由这种懒操作结合终结操作数据源构成的处理管道(Pipeline)实现了 Stream 的高效

中间操作


无状态

  • filter:过滤流,过滤流中的元素

  • map:转换流,将一种类型的流转换为另外一种类型的流

  • flapMap:拆解流,将流中每一个元素拆解成一个流

filter

filter接收一个Predicate函数接口参数,boolean test(T t);即接收一个参数,返回boolean类型。

@Test

public void testFilter() {

Integer[] arr = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

Arrays.stream(arr).filter(x -> x > 3 && x < 8).forEach(System.out::println);

}

输出结果

4

5

6

7

map

map接收一个Function<T, R>函数接口,R apply(T t);即接收一个参数,并且有返回值。

@Test

public void testMap() {

String[] arr = new String[]{“yes”, “YES”, “no”, “NO”};

Arrays.stream(arr).map(x -> x.toLowerCase()).forEach(System.out::println);

}

输出结果

yes

yes

no

no

flapMap

flatMap接收一个Function<T, R>函数接口: Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);即入参为集合类型,返回Stream类型。

@Test

public void testFlatMap() {

String[] arr1 = {“a”, “b”};

String[] arr2 = {“e”, “f”};

String[] arr3 = {“h”, “j”};

// Stream.of(arr1, arr2, arr3).flatMap(x -> Arrays.stream(x)).forEach(System.out::println);

Stream.of(arr1, arr2, arr3).flatMap(Arrays::stream).forEach(System.out::println);

}

输出结果

a

b

e

f

h

j

有状态

  • distinct:元素去重

  • sorted:元素排序

  • limit:获取前面的指定数量的元素

  • skip:跳过前面指定数量的元素,获取后面的元素

  • concat:把两个stream合并成一个stream

distinct

@Test

public void testDistinct() {

List list = new ArrayList() {

{

add(“user1”);

add(“user2”);

add(“user2”);

add(“user2”);

}

};

list.stream().distinct().forEach(System.out::println);

}

输出结果

user1

user2

sorted
sorted

@Test

public void testSorted1() {

String[] arr1 = {“abc”, “a”, “bc”, “abcd”};

// 按照字符长度排序

System.out.println(“lambda表达式”);

Arrays.stream(arr1).sorted((x, y) -> {

if (x.length() > y.length())

return 1;

else if (x.length() < y.length())

return -1;

else

return 0;

}).forEach(System.out::println);

// Comparator.comparing是一个键提取的功能

System.out.println(“方法引用”);

Arrays.stream(arr1).sorted(Comparator.comparing(String::length)).forEach(System.out::println);

}

输出结果

lambda表达式

a

bc

abc

abcd

方法引用

a

bc

abc

abcd

reversed

/**

  • 倒序

  • reversed(),java8泛型推导的问题,所以如果comparing里面是非方法引用的lambda表达式就没办法直接使用reversed()

  • Comparator.reverseOrder():也是用于翻转顺序,用于比较对象(Stream里面的类型必须是可比较的)

  • Comparator.naturalOrder():返回一个自然排序比较器,用于比较对象(Stream里面的类型必须是可比较的)

*/

@Test

public void testSorted2_() {

String[] arr1 = {“abc”, “a”, “bc”, “abcd”};

System.out.println(“reversed(),这里是按照字符串长度倒序排序”);

Arrays.stream(arr1).sorted(Comparator.comparing(String::length).reversed()).forEach(System.out::println);

System.out.println(“Comparator.reverseOrder(),这里是按照首字母倒序排序”);

Arrays.stream(arr1).sorted(Comparator.reverseOrder()).forEach(System.out::println);

System.out.println(“Comparator.naturalOrder(),这里是按照首字母顺序排序”);

Arrays.stream(arr1).sorted(Comparator.naturalOrder()).forEach(System.out::println);

}

输出结果

reversed(),这里是按照字符串长度倒序排序

abcd

abc

bc

a

Comparator.reverseOrder(),这里是按照首字母倒序排序

bc

abcd

abc

a

Comparator.naturalOrder(),这里是按照首字母顺序排序

a

abc

abcd

bc

thenComparing

/**

  • thenComparing

  • 先按照首字母排序

  • 之后按照String的长度排序

*/

@Test

public void testSorted3() {

String[] arr1 = {“abc”, “a”, “bc”, “abcd”};

Arrays.stream(arr1).sorted(Comparator.comparing(this::firstChar).thenComparing(String::length)).forEach(System.out::println);

}

public char firstChar(String x) {

return x.charAt(0);

}

输出结果

a

abc

abcd

bc

limit

/**

  • limit,限制从流中获得前n个数据

*/

@Test

public void testLimit() {

Stream.iterate(1, x -> x + 2).limit(3).forEach(System.out::println);

}

输出结果

1

3

5

skip

/**

  • skip,跳过前n个数据

*/

@Test

public void testSkip() {

Stream.iterate(1, x -> x + 2).skip(1).limit(3).forEach(System.out::println);

}

输出结果

3

5

7

concat

/**

  • 可以把两个stream合并成一个stream(合并的stream类型必须相同)

  • 只能两两合并

*/

@Test

public void testConcat(){

// 1,3,5

Stream stream1 = Stream.iterate(1, x -> x + 2).limit(3);

// 3,5,7

Stream stream2 = Stream.iterate(1, x -> x + 2).skip(1).limit(3);

Stream.concat(stream1,stream2).distinct().forEach(System.out::println);

}

输出结果

1

3

5

7

终结操作


非短路操作

  • forEach:遍历

  • toArray:将流转换为Object数组

  • reduce : 归约,可以将流中的元素反复结合起来,得到一个值

  • collect:收集,将流装换为其他形式,比如List,Set,Map

  • max:返回流的最大值,无方法参数

  • min:返回流中的最小值,无方法参数

  • count:返回流中的元素总个数,无方法参数

  • summaryStatistics:获取汇总统计数据,比如最大值,最小值,平均值等

forEach

@Test

public void testForEach() {

List list = new ArrayList() {

{

add(“a”);

add(“b”);

}

};

list.stream().forEach(System.out::println);

}

输出结果

a

b

reduce

@Test

public void testReduce() {

Optional optional = Stream.of(1, 2, 3).filter(x -> x > 1).reduce((x, y) -> x + y);

System.out.println(optional.get());

}

输出结果

5

collect

收集是非常常用的一个操作。 将流装换为其他形式。接收到一个Collector接口的实现,用于给Stream中的元素汇总的方法。用collect方法进行收集。方法参数为Collector。Collector可以由Collectors中的toList(),toSet(),toMap(Function(T,R) key,Function(T,R) value)等静态方法实现。

  • toList() 返回一个 Collector,它将输入元素到一个新的 List 。

  • toSet() 返回一个 Collector,将输入元素到一个新的 Set 。

  • toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) 返回一个 Collector ,它将元素累加到一个 Map ,其键和值是将所提供的映射函数应用于输入元素的结果。

用户

@Data

@AllArgsConstructor

@ToString

public class User {

private String name;

private Integer age;

private Integer salary;

}

toList,toSet,toSet

@Test

public void testCollect() {

List users = Arrays.asList(new User(“张三”, 19, 1000),

new User(“张三”, 58, 2000),

new User(“李四”, 38, 3000),

new User(“赵五”, 48, 4000)

);

List collect = users.stream().map(x -> x.getName()).collect(Collectors.toList());

Set collect1 = users.stream().map(x -> x.getName()).collect(Collectors.toSet());

Map<Integer, String> collect2 = users.stream().collect(Collectors.toMap(x -> x.getAge(), x -> x.getName()));

System.out.println(collect);

System.out.println(collect1);

System.out.println(collect2);

}

输出结果

[张三, 张三, 李四, 赵五]

[李四, 张三, 赵五]

{48=赵五, 19=张三, 38=李四, 58=张三}

groupingBy

Collectors.groupingBy()方法根据分类功能分组元素。这个是非常常用的操作。 比如你要对名字相同的进行分组。groupingBy(Function<? super T,? extends K> classifier)

@Test

public void testGroupby() {

List users = Arrays.asList(new User(“张三”, 19, 1000),

new User(“张三”, 58, 2000),

new User(“李四”, 38, 3000),

new User(“赵五”, 48, 4000)

);

Map<String, List> collect3 = users.stream().collect(Collectors.groupingBy(x -> x.getName()));

System.out.println(collect3);

}

输出结果

{李四=[StreamTest.User(name=李四, age=38, salary=3000)], 张三=[StreamTest.User(name=张三, age=19, salary=1000), StreamTest.User(name=张三, age=58, salary=2000)], 赵五=[StreamTest.User(name=赵五, age=48, salary=4000)]}

partitioningBy

如果只有两类,使用partitioningBy会比groupingBy更有效率,按照工资是否大于2500分组

@Test

public void testPartitioningBy() {

List users = Arrays.asList(new User(“张三”, 19, 1000),

new User(“张三”, 58, 2000),

new User(“李四”, 38, 3000),

new User(“赵五”, 48, 4000)

);

Map<Boolean, List> map = users.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 2500));

map.forEach((x, y) -> System.out.println(x + “->” + y));

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。

如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获的事了。


《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
ningBy

如果只有两类,使用partitioningBy会比groupingBy更有效率,按照工资是否大于2500分组

@Test

public void testPartitioningBy() {

List users = Arrays.asList(new User(“张三”, 19, 1000),

new User(“张三”, 58, 2000),

new User(“李四”, 38, 3000),

new User(“赵五”, 48, 4000)

);

Map<Boolean, List> map = users.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 2500));

map.forEach((x, y) -> System.out.println(x + “->” + y));

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-Cja8BM7B-1712596254947)]

[外链图片转存中…(img-1Rlafzyk-1712596254947)]

[外链图片转存中…(img-D8XpVJv1-1712596254947)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。

如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获的事了。

[外链图片转存中…(img-YjiCS9qa-1712596254948)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值