lambda表达式是stream的基础,初学者建议先学习lambda表达式,
1.初识stream
先来一个总纲:
东西就是这么多啦,stream是java8中加入的一个非常实用的功能,最初看时以为是io中的流(其实一点关系都没有),让我们先来看一个小例子感受一下:
@Before
public void init() {
random = new Random();
stuList = new ArrayList() {
{
for (int i = 0; i < 100; i++) {
add(new Student("student" + i, random.nextInt(50) + 50));
}
}
};
}
public class Student {
private String name;
private Integer score;
//-----getters and setters-----
}
//1列出班上超过85分的学生姓名,并按照分数降序输出用户名字
@Test
public void test1() {
List studentList = stuList.stream()
.filter(x->x.getScore()>85)
.sorted(Comparator.comparing(Student::getScore).reversed())
.map(Student::getName)
.collect(Collectors.toList());
System.out.println(studentList);
}
列出班上分数超过85分的学生姓名,并按照分数降序输出用户名字,在java8之前我们需要三个步骤:
1)新建一个List newList,在for循环中遍历stuList,将分数超过85分的学生装入新的集合中
2)对于新的集合newList进行排序操作
3)遍历打印newList
这三个步骤在java8中只需要两条语句,如果紧紧需要打印,不需要保存新生产list的话实际上只需要一条,是不是非常方便。
2.stream的特性
我们首先列出stream的如下三点特性,在之后我们会对照着详细说明
1.stream不存储数据
2.stream不改变源数据
3.stream的延迟执行特性
通常我们在数组或集合的基础上创建stream,stream不会专门存储数据,对stream的操作也不会影响到创建它的数组和集合,对于stream的聚合、消费或收集操作只能进行一次,再次操作会报错,如下代码:
@Test
public void test1(){
Stream stream = Stream.generate(()->"user").limit(20);
stream.forEach(System.out::println);
stream.forEach(System.out::println);
}
程序在正常完成一次打印工作后报错。
stream的操作是延迟执行的,在列出班上超过85分的学生姓名例子中,在collect方法执行之前,filter、sorted、map方法还未执行,只有当collect方法执行时才会触发之前转换操作
看如下代码:
public boolean filter(Student s) {
System.out.println("begin compare");
return s.getScore() > 85;
}
@Test
public void test() {
Stream stream = Stream.of(stuArr).filter(this::filter);
System.out.println("split-------------------------------------");
List studentList = stream.collect(toList());
}
我们将filter中的逻辑抽象成方法,在方法中加入打印逻辑,如果stream的转换操作是延迟执行的,那么split会先打印,否则后打印,代码运行结果为
可见stream的操作是延迟执行的。
TIP:
当我们操作一个流的时候,并不会修改流底层的集合(即使集合是线程安全的),如果想要修改原有的集合,就无法定义流操作的输出。
由于stream的延迟执行特性,在聚合操作执行前修改数据源是允许的。
List wordList;
@Before
public void init() {
wordList = new ArrayList() {
{
add("a");
add("b");
add("c");
add("d");
add("e");
add("f");
add("g");
}
};
}
/
延迟执行特性,在聚合操作之前都可以添加相应元素
/
@Test
public void test() {
Stream words = wordList.stream();
wordList.add("END");
long n = words.distinct().count();
System.out.println(n);
}
最后打印的结果是8
如下代码是错误的
/
延迟执行特性,会产生干扰
nullPointException
/
@Test
public void test2(){
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);
}
});
}
结果报空指针异常
3.创建stream
1)通过数组创建
/**
通过数组创建流
/
@Test
public void testArrayStream(){
//1.通过Arrays.stream
//1.1基本类型
int【】 arr = new int【】{1,2,34,5};
IntStream intStream = Arrays.stream(arr);
//1.2引用类型
Student【】 studentArr = new Student【】{new Student("s1",29),new Student("s2",27)};
Stream studentStream = Arrays.stream(studentArr);
//2.通过Stream.of
Stream stream1 = Stream.of(1,2,34,5,65);
//注意生成的是int【】的流
Stream[span style="color: rgba(0, 0, 255, 1)">int【】> stream2 = Stream.of(arr,arr);
stream2.forEach(System.out::println);
}
2)通过集合创建流
/**
通过集合创建流
/
@Test
public void testCollectionStream(){
List strs = Arrays.asList("11212","dfd","2323","dfhgf");
//创建普通流
Stream stream = strs.stream();
//创建并行流
Stream stream1 = strs.parallelStream();
}
3)创建空的流
@Test
public void testEmptyStream(){
//创建一个空的stream
Stream stream = Stream.empty();
}
4)创建无限流
@Test
public void testUnlimitStream(){
//创建无限流,通过limit提取指定大小
Stream.generate(()->"number"+new Random().nextInt()).limit(100).forEach(System.out::println);
Stream.generate(()->new Student("name",10)).limit(20).forEach(System.out::println);
}
5)创建规律的无限流
/**
产生规律的数据
/
@Test
public void testUnlimitStream1(){
Stream.iterate(0,x->x+1).limit(10).forEach(System.out::println);
Stream.iterate(0,x->x).limit(10).forEach(System.out::println);
//Stream.iterate(0,x->x).limit(10).forEach(System.out::println);与如下代码意思是一样的
Stream.iterate(0, UnaryOperator.identity()).limit(10).forEach(System.out::println);
}
4.对stream的操作
1)最常使用
map:转换流,将一种类型的流转换为另外一种流
/**
map把一种类型的流转换为另外一种类型的流
将String数组中字母转换为大写
/
@Test
public void testMap() {
String【】 arr = new String【】{"yes", "YES", "no", "NO"};
Arrays.stream(arr).map(x -> x.toLowerCase()).forEach(System.out::println);
}
filter:过滤流,过滤流中的元素
@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);
}
flapMap:拆解流,将流中每一个元素拆解成一个流
/
flapMap:拆解流
/
@Test
public void testFlapMap1() {
String【】 arr1 = {"a", "b", "c", "d"};
String【】 arr2 = {"e", "f", "c", "d"};
String【】 arr3 = {"h", "j", "c", "d"};
// 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);
}
sorted:对流进行排序
String【】 arr1 = {"abc","a","bc","abcd"};
/
Comparator.comparing是一个键提取的功能
以下两个语句表示相同意义
/
@Test
public void testSorted1_(){
/**
按照字符长度排序
/
Arrays.stream(arr1).sorted((x,y)->{
if (x.length()>y.length())
return 1;
else if (x.length()[span style="color: rgba(0, 0, 0, 1)">y.length())
return -1;
else
return 0;
}).forEach(System.out::println);
Arrays.stream(arr1).sorted(Comparator.comparing(String::length)).forEach(System.out::println);
}
/**
倒序
reversed(),java8泛型推导的问题,所以如果comparing里面是非方法引用的lambda表达式就没办法直接使用reversed()
Comparator.reverseOrder():也是用于翻转顺序,用于比较对象(Stream里面的类型必须是可比较的)
Comparator. naturalOrder():返回一个自然排序比较器,用于比较对象(Stream里面的类型必须是可比较的)
/
@Test
public void testSorted2(){
Arrays.stream(arr1).sorted(Comparator.comparing(String::length).reversed()).forEach(System.out::println);
Arrays.stream(arr1).sorted(Comparator.reverseOrder()).forEach(System.out::println);
Arrays.stream(arr1).sorted(Comparator.naturalOrder()).forEach(System.out::println);
}
/*
thenComparing
先按照首字母排序
之后按照String的长度排序
*/
@Test
public void testSorted3(){
Arrays.stream(arr1).sorted(Comparator.comparing(this::com1).thenComparing(String::length)).forEach(System.out::println);
}
public char com1(String x){
return x.charAt(0);
}
2)提取流和组合流
@Before
public void init(){
arr1 = new String【】{"a","b","c","d"};
arr2 = new String【】{"d","e","f","g"};
arr3 = new String【】{"i","j","k","l"};
}
/
limit,限制从流中获得前n个数据
/
@Test
public void testLimit(){
Stream.iterate(1,x->x+2).limit(10).forEach(System.out::println);
}
/
skip,跳过前n个数据
/
@Test
public void testSkip(){
// Stream.of(arr1).skip(2).limit(2).forEach(System.out::println);
Stream.iterate(1,x->x+2).skip(1).limit(5).forEach(System.out::println);
}
/
可以把两个stream合并成一个stream(合并的stream类型必须相同)
只能两两合并
*/
@Test
public void testConcat(){
Stream stream1 = Stream.of(arr1);
Stream stream2 = Stream.of(arr2);
Stream.concat(stream1,stream2).distinct().forEach(System.out::println);
}
3)聚合操作
@Before
public void init(){
arr = new String【】{"b","ab","abc","abcd","abcde"};
}
/
max、min
最大最小值
/
@Test
public void testMaxAndMin(){
Stream.of(arr).max(Comparator.comparing(String::length)).ifPresent(System.out::println);
Stream.of(arr).min(Comparator.comparing(String::length)).ifPresent(System.out::println);
}
/**
count
计算数量
/
@Test
public void testCount(){
long count = Stream.of(arr).count();
System.out.println(count);
}
/
findFirst
查找第一个
*/
@Test
public void testFindFirst(){
//代码效果参考:http://www.lyjsj.net.cn/wz/art_24105.html
String str = Stream.of(arr).parallel().filter(x->x.length()>3).findFirst().orElse("noghing");System.out.println(str);
}
/
findAny
找到所有匹配的元素
对并行流十分有效
只要在任何片段发现了第一个匹配元素就会结束整个运算
/
@Test
public void testFindAny(){
Optional optional = Stream.of(arr).parallel().filter(x->x.length()>3).findAny();
optional.ifPresent(System.out::println);
}
/**
anyMatch
是否含有匹配元素
/
@Test
public void testAnyMatch(){
Boolean aBoolean = Stream.of(arr).anyMatch(x->x.startsWith("a"));
System.out.println(aBoolean);
}
@Test
public void testStream1() {
Optional optional = Stream.of(1,2,3).filter(x->x>1).reduce((x,y)->x+y);
System.out.println(optional.get());
}
4)Optional类型
通常聚合操作会返回一个Optional类型,Optional表示一个安全的指定结果类型,所谓的安全指的是避免直接调用返回类型的null值而造成空指针异常,调用optional.ifPresent()可以判断返回值是否为空,或者直接调用ifPresent(Consumer consumer)在结果部位空时进行消费操作;调用optional.get()获取返回值。通常的使用方式如下:
@Test
public void testOptional() {
List list = new ArrayList() {
{
a