第三章 Stream流

关注公众号(CoderBuff)回复“stream”获取《Java8 Stream编码实战》PDF完整版。

《Java8 Stream编码实战》的代码全部在https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/stream-coding,一定要配合源码阅读,并且不断加以实践,才能更好的掌握Stream。

对于初学者,必须要声明一点的是,Java8中的Stream尽管被称作为“流”,但它和文件流、字符流、字节流完全没有任何关系。Stream流使程序员得以站在更高的抽象层次上对集合进行操作[1]。也就是说Java8中新引入的Stream流是针对集合的操作。

3.1 迭代

我们在使用集合时,最常用的就是迭代。

public int calcSum(List<Integer> list) {
int sum = 0;
for (int i = 0; i < list.size(); i++) {
sum += list.get(i);
}
return sum;
}
com.coderbuff.chapter3_stream.chapter3_1.ForDemo#calcSum

例如,我们可能会对集合中的元素累加并返回结果。这段代码由于for循环的样板代码并不能很清晰的传达程序员的意图。也就是说,实际上除了方法名叫“计算总和”,程序员必须阅读整个循环体才能理解。你可能觉得一眼就能理解上述代码的意图,但如果碰上下面的代码,你还能一眼理解吗?

public Map<Long, List<Student>> useFor(List<Student> students) {
Map<Long, List<Student>> map = new HashMap<>();
for (Student student : students) {
List<Student> list = map.get(student.getStudentNumber());
if (list == null) {
list = new ArrayList<>();
map.put(student.getStudentNumber(), list);
}
list.add(student);
}
return map;
}

阅读完这个循环体以及包含的if判断条件,大概可以知道这是想使用“studentNumber”对“Student”对象分组。这段代码在Stream进行重构后,将会变得非常简洁和易读

public Map<Long, List<Student>> useStreamByGroup(List<Student> students) {
Map<Long, List<Student>> map = students.stream().collect(Collectors.groupingBy(Student::getStudentNumber));
return map;
}

当第一次看到这样的写法时,可能会认为这样的代码可读性不高,不容易测试。我相信,当你在学习掌握Stream后会重新改变对它的看法。

3.2 Stream

3.2.1 创建

要想使用Stream,首先要创建一个流,创建流最常用的方式是直接调用集合的stream方法。

/**
* 通过集合构造流
*/
private void createByCollection() {
List<Integer> list = new ArrayList<>();
Stream<Integer> stream = list.stream();
}
com.coderbuff.chapter3_stream.chapter3_2.StreamCreator#createByCollection

也能通过数组构造一个流。

/**
* 通过数组构造流
*/
private void createByArrays() {
Integer[] intArrays = {1, 2, 3};
Stream<Integer> stream = Stream.of(intArrays);
Stream<Integer> stream1 = Arrays.stream(intArrays);
}
com.coderbuff.chapter3_stream.chapter3_2.StreamCreator#createByArrays

学习Stream流,掌握集合创建流就足够了。

3.2.2 使用

对于Stream流操作共分为两个大类:惰性求值及时求值

所谓惰性求值,指的是操作最终不会产生新的集合。及时求值,指的是操作会产生新的集合。举以下示例加以说明:

/**
* 通过for循环过滤元素返回新的集合
* @param list 待过滤的集合
* @return 过滤后的集合
*/
private List<Integer> filterByFor(List<Integer> list) {
List<Integer> filterList = new ArrayList<>(); for (Integer number : list) {
if (number > 1) {
filterList.add(number);
}
}
return filterList;
}
com.coderbuff.chapter3_stream.chapter3_3.Example#filterByFor

通过for循环过滤元素返回新的集合,这里的“过滤”表示排除不符合条件的元素。我们使用Stream流过滤并返回新的集合:

/**
* 通过Stream流过滤元素返回新的集合
* @param list 待过滤的集合
* @return 新的集合
*/
private List<Integer> filterByStream(List<Integer> list) {
return list.stream()
.filter(number -> number > 1)
.collect(Collectors.toList());
}
com.coderbuff.chapter3_stream.chapter3_3.Example#filterByStream

Stream操作时,先调用了filter方法传入了一个Lambda表达式代表过滤规则,后调用了collect方法表示将流转换为List集合。

按照常理来想,一个方法调用完后,接着又调用了一个方法,看起来好像做了两次循环,把问题搞得更复杂了。但实际上,这里的filter操作是惰性求值,它并不会返回新的集合,这就是Stream流设计精妙的地方。既能在保证可读性的同时,也能保证性能不会受太大影响。

所以使用Stream流的理想方式就是,形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果。

我们不需要去记哪些方法是惰性求值,如果方法的返回值是Stream那么它代表的就是惰性求值。如果返回另外一个值或空,那么它代表的就是及早求值。

3.2.3 常用的Stream操作

map

map操作不好理解,它很容易让人以为这是一个转换为Map数据结构的操作。实际上他是将集合中的元素类型,转换为另外一种数据类型。

例如,你想将“学生”类型的集合转换为只有“学号”类型的集合,应该怎么做?

/**
* 通过for循环提取学生学号集合
* @param list 学生对象集合
* @return 学生学号集合
*/
public List<Long> fetchStudentNumbersByFor(List<Student> list) {
List<Long> numbers = new ArrayList<>();
for (Student student : list) {
numbers.add(student.getStudentNumber());
}
return numbers;
}
com.coderbuff.chapter3_stream.chapter3_4.StreamMapDemo#fetchStudentNumbersByFor

这是只借助JDK的“传统”方式。如果使用Stream则可以直接通过map操作来获取只包含学生学号的集合。

/**
* 通过Stream map提取学生学号集合
* @param list 学生对象集合
* @return 学生学号集合
*/
public List<Long> fetchStudentNumbersByStreamMap(List<Student> list) {
return list.stream()
.map(Student::getStudentNumber)
.collect(Collectors.toList());
}
com.coderbuff.chapter3_stream.chapter3_4.StreamMapDemo#fetchStudentNumbersByStreamMap

map传入的是一个方法,同样可以理解为传入的是一个“行为”,在这里我们传入方法“getStudentNumber”表示将通过这个方法进行转换分类。

“Student::getStudentNumber”叫方法引用,它是“student -> student.getStudentNumber()”的简写。表示直接引用已有Java类或对象的方法或构造器。在这里我们是需要传入“getStudentNumber”方法,在有的地方,你可能会看到这样的代码“Student::new”,new调用的就是构造方法,表示创建一个对象。方法引用,可以将我们的代码变得更加紧凑简洁。

我们再举一个例子,将小写的字符串集合转换为大写字符串集合。

/**
* 通过Stream map操作将小写的字符串集合转换为大写
* @param list 小写字符串集合
* @return 大写字符串集合
*/
public List<String> toUpperByStreamMap(List<String> list) {
return list.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
}
com.coderbuff.chapter3_stream.chapter3_4.StreamMapDemo#toUpperByStreamMap

filter

filter,过滤。这里的过滤含义是“排除不符合某个条件的元素”,也就是返回true的时候保留,返回false排除。

我们仍然以“学生”对象为例,要排除掉分数低于60分的学生。

/**
* 通过for循环筛选出分数大于60分的学生集合
* @param students 待过滤的学生集合
* @return 分数大于60分的学生集合
*/
public List<Student> fetchPassedStudentsByFor(List<Student> students) {
List<Student> passedStudents = new ArrayList<>();
for (Student student : students) {
if (student.getScore().compareTo(60.0) >= 0) {
passedStudents.add(student);
}
}
return passedStudents;
}
com.coderbuff.chapter3_stream.chapter3_4.StreamFilterDemo#fetchPassedStudentsByFor

这是我们通常的实现方式,通过for循环能解决“一切”问题,如果使用Stream filter一行就搞定。

/**
* 通过Stream filter筛选出分数大于60分的学生集合
* @param students 待过滤的学生集合
* @return 分数大于60分的学生集合
*/
public List<Student> fetchPassedStudentsByStreamFilter(List<Student> students) {
return students.stream()
.filter(student -> student.getScore().compareTo(60.0) >= 0)
.collect(Collectors.toList());
}
com.coderbuff.chapter3_stream.chapter3_4.StreamFilterDemo#fetchPassedStudentsByStreamFilter

sorted

排序,也是日常最常用的操作之一。我们常常会把数据按照修改或者创建时间的倒序、升序排列,这步操作通常会放到SQL语句中。但如果实在是遇到要对集合进行排序时,我们通常也会使用Comparator.sort静态方法进行排序,如果是复杂的对象排序,还需要实现Comparator接口。

/**
* 通过Collections.sort静态方法 + Comparator匿名内部类对学生成绩进行排序
* @param students 待排序学生集合
* @return 排好序的学生集合
*/
private List<Student> sortedByComparator(List<Student> students) {
Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student student1, Student student2) {
return student1.getScore().compareTo(student2.getScore());
}
});
return students;
}
com.coderbuff.chapter3_stream.chapter3_4.StreamSortedDemo#sortedByComparator

关于Comparator可以查看这篇文章《似懂非懂的Comparable与Comparator》。简单来讲,我们需要实现Compartor接口的compare方法,这个方法有两个参数用于比较,返回1代表前者大于后者,返回0代表前者等于后者,返回-1代表前者小于后者。

当然我们也可以手动实现冒泡算法对学生成绩进行排序,不过这样的代码大多出现在课堂教学中。

/**
* 使用冒泡排序算法对学生成绩进行排序
* @param students 待排序学生集合
* @return 排好序的学生集合
*/
private List<Student> sortedByFor(List<Student> students) {
for (int i = 0; i < students.size() - 1; i++) {
for (int j = 0; j < students.size() - 1 - i; j++) {
if (students.get(j).getScore().compareTo(students.get(j + 1).getScore()) > 0) {
Student temp = students.get(j);
students.set(j, students.get(j + 1));
students.set(j + 1, temp);
}
}
}
return students;
}
com.coderbuff.chapter3_stream.chapter3_4.StreamSortedDemo#sortedByFor

在使用Stream sorted后,你会发现代码将变得无比简洁。

/**
* 通过Stream sorted对学生成绩进行排序
* @param students 待排序学生集合
* @return 排好序的学生集合
*/
private List<Student> sortedByStreamSorted(List<Student> students) {
return students.stream()
.sorted(Comparator.comparing(Student::getScore))
.collect(Collectors.toList());
}
com.coderbuff.chapter3_stream.chapter3_4.StreamSortedDemo#sortedByStreamSorted

简洁的后果就是,代码变得不那么好读,其实并不是代码的可读性降低了,而只是代码不是按照你的习惯去写的。而大部分人恰好只习惯墨守成规,而不愿意接受新鲜事物。

上面的排序是按照从小到大排序,如果想要从大到小应该如何修改呢?

Compartor.sort方法和for循环调换if参数的位置即可。

return student1.getScore().compareTo(student2.getScore());
修改为
return student2.getScore().compareTo(student1.getScore());
if (students.get(j).getScore().compareTo(students.get(j + 1).getScore()) > 0)
修改为
if (students.get(j).getScore().compareTo(students.get(j + 1).getScore()) < 0)

这改动看起来很简单,但如果这是一段没有注释并且不是你本人写的代码,你能一眼知道是按降序还是升序排列吗?你还能说这是可读性强的代码吗?

如果是Stream操作。

return students.stream()
.sorted(Comparator.comparing(Student::getScore))
.collect(Collectors.toList());
修改为
return students.stream()
.sorted(Comparator.comparing(Student::getScore).reversed())
.collect(Collectors.toList());

这就是声明式编程,你只管叫它做什么,而不像命令式编程叫它如何做。

reduce

reduce是将传入一组值,根据计算模型输出一个值。例如求一组值的最大值、最小值、和等等。

不过使用和读懂reduce还是比较晦涩,如果是简单最大值、最小值、求和计算,Stream已经为我们提供了更简单的方法。如果是复杂的计算,可能为了代码的可读性和维护性还是建议用传统的方式表达。

我们来看几个使用reduce进行累加例子。

/**
* Optional<T> reduce(BinaryOperator<T> accumulator);
* 使用没有初始值对集合中的元素进行累加
* @param numbers 集合元素
* @return 累加结果
*/
private Integer calcTotal(List<Integer> numbers) {
return numbers.stream()
.reduce((total, number) -> total + number).get();
}
com.coderbuff.chapter3_stream.chapter3_4.StreamReduceDemo#calcTotal

reduce有3个重载方法,

第一个例子调用的是Optional<T> reduce(BinaryOperator<T> accumulator);它只有BinaryOperator一个参数,这个接口是一个函数接口,代表它可以接收一个Lambda表达式,它继承自BiFunction函数接口,在BiFunction接口中,只有一个方法:

@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}

这个方法有两个参数。也就是说,传入reduce的Lambda表达式需要“实现”这个方法。如果不理解这是什么意思,我们可以抛开Lambda表达式,从纯粹传统的接口角度去理解。

首先,Optional<T> reduce(BinaryOperator<T> accumulator);方法接收BinaryOperator类型的对象,而BinaryOperator是一个接口并且继承自BiFunction接口,而在BiFunction中只有一个方法定义 R apply(T t, U u),也就是说我们需要实现apply方法。

其次,接口需要被实现,我们不妨传入一个匿名内部类,并且实现apply方法。

private Integer calcTotal(List<Integer> numbers) {
return numbers.stream()
.reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
}).get();
}

最后,我们在将匿名内部类改写为Lambda风格的代码,箭头左边是参数,右边是函数主体。

private Integer calcTotal(List<Integer> numbers) {
return numbers.stream()
.reduce((total, number) -> total + number).get();
}

至于为什么两个参数相加最后就是不断累加的结果,这就是reduce的内部实现了。

接着看第二个例子:

/**
* T reduce(T identity, BinaryOperator<T> accumulator);
* 赋初始值为1,对集合中的元素进行累加
* @param numbers 集合元素
* @return 累加结果
*/
private Integer calcTotal2(List<Integer> numbers) {
return numbers.stream()
.reduce(1, (total, number) -> total + number);
}
com.coderbuff.chapter3_stream.chapter3_4.StreamReduceDemo#calcTotal2

第二个例子调用的是reduceT reduce(T identity, BinaryOperator<T> accumulator);重载方法,相比于第一个例子,它多了一个参数“identity”,这是进行后续计算的初始值,BinaryOperator和第一个例子一样。

第三个例子稍微复杂一点,前面两个例子集合中的元素都是基本类型,而现实情况是,集合中的参数往往是一个对象我们常常需要对对象中的某个字段做累加计算,比如计算学生对象的总成绩。

我们先来看for循环怎么做的:

/**
* 通过for循环对集合中的学生成绩字段进行累加
* @param students 学生集合
* @return 分数总和
*/
private Double calcTotalScoreByFor(List<Student> students) {
double total = 0;
for (Student student : students) {
total += student.getScore();
}
return total;
}
com.coderbuff.chapter3_stream.chapter3_4.StreamReduceDemo#calcTotalScoreByFor

要按前文的说法,“这样的代码充斥了样板代码,除了方法名,代码并不能直观的反应程序员的意图,程序员需要读完整个循环体才能理解”,但凡事不是绝对的,如果换做reduce操作:

/**
* <U> U reduce(U identity,
* BiFunction<U, ? super T, U> accumulator,
* BinaryOperator<U> combiner);
* 集合中的元素是"学生"对象,对学生的"score"分数字段进行累加
* @param students 学生集合
* @return 分数总和
*/
private Double calcTotalScoreByStreamReduce(List<Student> students) {
return students.stream()
.reduce(Double.valueOf(0),
(total, student) -> total + student.getScore(),
(aDouble, aDouble2) -> aDouble + aDouble2);
}
com.coderbuff.chapter3_stream.chapter3_4.StreamReduceDemo#calcTotalScoreByStreamReduce

这样的代码,已经不是样板代码的问题了,是大部分程序员即使读十遍可能也不知道要表达什么含义。但是为了学习Stream我们还是要硬着头皮去理解它。

Lambda表达式不好理解,过于简洁的语法,也代表更少的信息量,我们还是先将Lambda表达式还原成匿名内部类。

private Double calcTotalScoreByStreamReduce(List<Student> students) {
return students.stream()
.reduce(Double.valueOf(0), new BiFunction<Double, Student, Double>() {
@Override
public Double apply(Double total, Student student) {
return total + student.getScore();
}
}, new BinaryOperator<Double>() {
@Override
public Double apply(Double aDouble, Double aDouble2) {
return aDouble + aDouble2;
}
});
}

reduce的第三个重载方法<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);一共有3个参数,与第一、二个重载方法不同的是,第一、第二个重载方法参数和返回类型都是泛型“T”,意思是入参和返回都是同一种数据类型。但在第三个例子中,入参是Student对象,返回却是Double,显然不能使用第一、二个重载方法。

第三个重载方法的第一个参数类型是泛型“U”,它的返回类型也是泛型“U”,所以第一个参数类型,代表了返回的数据类型,我们必须将第一个类型定义为Double例子中的入参是Double.valueOf(0)表示了累加的初始值为0,且返回值是Double类型。第二个参数可以简单理解为“应该如何计算,累加还是累乘”的计算模型。最难理解的是第三个参数,因为前两个参数类型看起来已经能满足我们的需求,为什么还有第三个参数呢?

当我在第三个参数中加上一句输出时,发现它确实没有用。

private Double calcTotalScoreByStreamReduce(List<Student> students) {
return students.stream()
.reduce(Double.valueOf(0), new BiFunction<Double, Student, Double>() {
@Override
public Double apply(Double total, Student student) {
return total + student.getScore();
}
}, new BinaryOperator<Double>() {
@Override
public Double apply(Double aDouble, Double aDouble2) {
System.out.println("第三个参数的作用");
return aDouble + aDouble2;
}
});
}

控制台没有输出“第三个参数的作用”,改变它的返回值最终结果也没有任何改变,这的确表示它真的没有用

第三个参数在这里的确没有用,这是因为我们目前所使用的Stream流是串行操作,它在并行Stream流中发挥的是多路合并的作用,在下一章会继续介绍并行Stream流,这里就不再多做介绍。

对于reduce操作,我的个人看法是,不建议在现实中使用。如果你有累加、求最大值、最小值的需求,Stream封装了更简单的方法。如果是特殊的计算,不如直接按for循环实现,如果一定要使用Stream对学生成绩求和也不妨换一个思路。

前面提到map方法可以将集合中的元素类型转换为另一种类型,那我们就能把学生的集合转换为分数的集合,再调用reduce的第一个重载方法计算总和:

/**
* 先使用map将学生集合转换为分数的集合
* 再使用reduce调用第一个重载方法计算总和
* @param students 学生集合
* @return 分数总和
*/
private Double calcTotalScoreByStreamMapReduce(List<Student> students) {
return students.stream()
.map(Student::getScore)
.reduce((total, score) -> total + score).get();
}
com.coderbuff.chapter3_stream.chapter3_4.StreamReduceDemo#calcTotalScoreByStreamMapReduce

min

min方法能返回集合中的最小值。它接收一个Comparator对象,Java8对Comparator接口提供了新的静态方法comparing,这个方法返回Comparator对象,以前我们需要手动实现compare比较,现在我们只需要调用Comparator.comparing静态方法即可。

/**
* 通过Stream min计算集合中的最小值
* @param numbers 集合
* @return 最小值
*/
private Integer minByStreamMin(List<Integer> numbers) {
return numbers.stream()
.min(Comparator.comparingInt(Integer::intValue)).get();
}
com.coderbuff.chapter3_stream.chapter3_4.StreamMinDemo#minByStreamMin

Comparator.comparingInt用于比较int类型数据。因为集合中的元素是Integer类型,所以我们传入Integer类型的iniValue方法。如果集合中是对象类型,我们直接调用Comparator.comparing即可。

/**
* 通过Stream min计算学生集合中的最低成绩
* @param students 学生集合
* @return 最低成绩
*/
private Double minScoreByStreamMin(List<Student> students) {
Student minScoreStudent = students.stream()
.min(Comparator.comparing(Student::getScore)).get();
return minScoreStudent.getScore();
}
com.coderbuff.chapter3_stream.chapter3_4.StreamMinDemo#minScoreByStreamMin

max

min的用法相同,含义相反取最大值。这里不再举例。

summaryStatistics

求和操作也是常用的操作,利用reduce会让代码晦涩难懂,特别是复杂的对象类型。

好在Streaam提供了求和计算的简便方法——summaryStatistics,这个方法并不是Stream对象提供,而是 IntStream,可以把它当做处理基本类型的流,同理还有LongStreamDoubleStream

summaryStatistics方法也不光是只能求和,它还能求最小值、最大值。

例如我们求学生成绩的平均分、总分、最高分、最低分。

/**
* 学生类型的集合常用计算
* @param students 学生
*/
private void calc(List<Student> students) {
DoubleSummaryStatistics summaryStatistics = students.stream()
.mapToDouble(Student::getScore)
.summaryStatistics();
System.out.println("平均分:" + summaryStatistics.getAverage());
System.out.println("总分:" + summaryStatistics.getSum());
System.out.println("最高分:" + summaryStatistics.getMax());
System.out.println("最低分:" + summaryStatistics.getMin());
}
com.coderbuff.chapter3_stream.chapter3_4.StreamSummaryStatisticsDemo#calc

返回的summaryStatistics包含了我们想要的所有结果,不需要我们单独计算。mapToDouble方法将Stream流按“成绩”字段组合成新的DoubleStream流,summaryStatistics方法返回的DoubleSummaryStatistics对象为我们提供了常用的计算。

灵活运用好summaryStatistics,一定能给你带来更少的bug和更高效的编码。

3.3 Collectors

前面的大部分操作都是以collect(Collectors.toList())结尾,看多了自然也大概猜得到它是将流转换为集合对象。最大的功劳当属Java8新提供的类——Collectors收集器。

Collectors不但有toList方法能将流转换为集合,还包括toMap转换为Map数据类型,还能分组

/**
* 将学生类型的集合转换为只包含名字的集合
* @param students 学生集合
* @return 学生姓名集合
*/
private List<String> translateNames(List<Student> students) { return students.stream()
.map(Student::getStudentName)
.collect(Collectors.toList());
}
com.coderbuff.chapter3_stream.chapter3_4.StreamCollectorsDemo#translateNames
/**
* 将学生类型的集合转换为Map类型,key=学号,value=学生
* @param students 学生集合
* @return 学生Map
*/
private Map<Long, Student> translateStudentMap(List<Student> students) {
return students.stream()
.collect(Collectors.toMap(Student::getStudentNumber, student -> student));
}
com.coderbuff.chapter3_stream.chapter3_4.StreamCollectorsDemo#translateStudentMap
/**
* 按学生的学号对学生集合进行分组返回Map,key=学生学号,value=学生集合
* @param students 学生集合
* @return 按学号分组的Map
*/
private Map<Long, List<Student>> studentGroupByStudentNumber(List<Student> students) {
return students.stream()
.collect(Collectors.groupingBy(Student::getStudentNumber));
}
com.coderbuff.chapter3_stream.chapter3_4.StreamCollectorsDemo#studentGroupByStudentNumber

关注公众号(CoderBuff)回复“stream”抢先获取PDF完整版。

近期教程:

《ElasticSearch6.x实战教程》

《Redis5.x入门教程》

《Java8 编码实战》

这是一个能给程序员加buff的公众号 (CoderBuff)

  1. 《Java 8函数式编程》 ↩︎

Java8 Stream流的更多相关文章

  1. 【转】Java8 Stream 流详解

      当我第一次阅读 Java8 中的 Stream API 时,说实话,我非常困惑,因为它的名字听起来与 Java I0 框架中的 InputStream 和 OutputStream 非常类似.但是 ...

  2. 关于Java8 Stream流的利与弊 Java初学者,大神勿喷

    题目需求: 1:第一个队伍只要名字为3个字成员的姓名,存储到新集合 2:第一个队伍筛选之后只要前3人:存储到一个新集合 3:第2个队伍只要姓张的成员姓名:存储到一个新集合 4:第2个队伍不要前2人,存 ...

  3. Java8 Stream流API常用操作

    Java版本现在已经发布到JDK13了,目前公司还是用的JDK8,还是有必要了解一些JDK8的新特性的,例如优雅判空的Optional类,操作集合的Stream流,函数式编程等等;这里就按操作例举一些 ...

  4. Java8——Stream流式操作的一点小总结

    我发现,自从我学了Stream流式操作之后,工作中使用到的频率还是挺高的,因为stream配合着lambda表达式或者双冒号(::)使用真的是优雅到了极致!今天就简单分(搬)享(运)一下我对strea ...

  5. 【JDK8】Java8 Stream流API常用操作

    Java版本现在已经发布到JDK13了,目前公司还是用的JDK8,还是有必要了解一些JDK8的新特性的,例如优雅判空的Optional类,操作集合的Stream流,函数式编程等等;这里就按操作例举一些 ...

  6. Java8 Stream流方法

    流是Java API的新成员,它允许以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现).就现在来说,可以把它们看成遍历数据集的高级迭代器.此外,流还可以透明地并行处理,无需写任何多 ...

  7. Java8 - Stream流:让你的集合变得更简单!

    前段时间,在公司熟悉新代码,发现好多都是新代码,全是 Java8语法,之前没有了解过,一直在专研技术的深度,却忘了最初的语法,所以,今天总结下Stream ,算是一份自己理解,不会很深入,就讲讲常用的 ...

  8. java8 stream 流 例子

    Trader raoul = new Trader("Raoul", "Cambridge"); Trader mario = new Trader(" ...

  9. Java8 Stream 流使用场景和常用操作

    JAVA8内置的函数式编程接口应用场景和方式 pojo类对象和默认创建list的方法 import lombok.AllArgsConstructor; import lombok.Data; imp ...

随机推荐

  1. 修改 commit message

    本文为原创文章,转载请标明出处 目录 修改上一条提交的 commit message 修改之前提交的 commit message 1. 修改上一条提交的 commit message git com ...

  2. Java Design Patterns(2)

    1.Factory Design pattern 工厂设计模式的优点 (1)工厂设计模式提供了接口而不是实现的代码方法. (2)工厂模式从客户端代码中删除实际实现类的实例化.工厂模式使我们的代码更健壮 ...

  3. ReadHub项目Kotlin版开发指南(三、MVP架构)

    ReadHub项目Kotlin版转换指南(一.环境搭建) ReadHub项目Kotlin版转换指南(二.数据库和网络请求) ReadHub项目Kotlin版转换指南(三.MVP架构) Android ...

  4. AndroidImageSlider

    最核心的类是SliderLayout,他继承自相对布局,包含了可以左右滑动的SliderView,以及页面指示器PagerIndicator.这两部分都可以自定义. AndroidImageSlide ...

  5. Leetcode 239题 滑动窗口最大值(Sliding Window Maximum) Java语言求解

    题目链接 https://leetcode-cn.com/problems/sliding-window-maximum/ 题目内容 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧 ...

  6. java单链表的实现自己动手写一个单链表

    单链表:单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素.链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是 ...

  7. jvm的运行参数

    1.我们为什么要对jvm做优化? 在本地开发环境中我们很少会遇到需要对jvm进行优化的需求,但是到了生产环境,我们可能将有下面的需求: 运行的应用“卡住了”,日志不输出,程序没有反应 服务器的CPU负 ...

  8. 挖SRC逻辑漏洞心得分享

    文章来源i春秋 白帽子挖洞的道路还漫长的很,老司机岂非一日一年能炼成的. 本文多处引用了 YSRC 的 公(qi)开(yin)漏(ji)洞(qiao).挖SRC思路一定要广!!!!漏洞不会仅限于SQL ...

  9. 7-44 jmu-python-区间数之和 (10 分)

    输入一个区间,计算里面能被3整除或被5整除的数和. 输入格式: 每行输入一个数据,代表区间左界和右界.区间包含左界和右界.数据必须是整数. 输出格式: 满足条件数和. 输入样例: 2 10 输出样例: ...

  10. 前端基础知识之HTML

    [1: What does a doctype do?] 1: doctype是html文件的第一行代码,意味着它的前面有注释都不行.所以要要写在<html>标签前面,而且它不属于html ...