Java8之使用Stream流
第四章 引入流(stream)
1、基础概念
1)流和集合
- 流是按需加载的,就像流媒体视频一样,是按需下载播放的
- 集合是急切创建的,所有都放在内存
2)流只能遍历一次
- 遍历完之后,我们就说这个流已经被消费掉了。
- 例如,以下代码会抛出一个异常,说流已被消 费掉了:
集合和流的另一个关键区别在于它们遍历数据的方式
3)外部迭代和内部迭代
- 外部迭代(用for-each)
- 流:内部迭代
List<String> names = menu.stream()
.map(Dish::getName)
.collect(toList());
-
帮助理解区别
-
以下是for-each迭代
-
你:“索菲亚,我们把玩具收起来吧。地上还有玩具吗?”
索菲亚:“有,球。”
你:“好,把球放进盒子里。还有吗?”索菲亚:“有,那是我的娃娃。”
你:“好,把娃娃放进盒子里。还有吗?”
索菲亚:“有,有我的书。”
你:“好,把书放进盒子里。还有吗?”
索菲亚:“没了,没有了。”
你:“好,我们收好啦。”
-
-
以下是流迭代
-
你:“索菲亚,我们把玩具收起来吧。把你身边的玩具都给我”
索菲亚:“好的。”
你:“好,我们收好啦。”
-
4)流操作
- filter、map和limit可以连成一条流水线;
- collect触发流水线执行并关闭它。关闭流的操作称为终端操作。
第五章 使用流
1、筛选与切片
1)谓词筛选-filter
-
Streams接口支持filter方法(你现在应该很熟悉了)。该操作会接受一个谓词(一个返回 boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。
-
例如
List<Dish> vegetarianMenu =
menu.stream().filter(Dish::isVegetarian).collect(toList());
2)筛选各异的元素(其实就是去重)-distinct
- 例如-筛选出列表中所有的偶数,并确保没有 重复
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
3)截短流(其实就类似于mysql的分页)-limit
- 例如,选出了符合谓词的头三个元素, 然后就立即返回了结果。
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
4)跳过元素-skip- 和limit互补
- skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一 个空流。请注意,limit(n)和skip(n)是互补的!
- 例如,下面的代码将跳过超过300卡路里的头 两道菜,并返回剩下的
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
2、映射
1)流的扁平化-flatMap
- 使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所 有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流
List<String> uniqueCharacters =
words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
- 和map对比图
- flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接 起来成为一个流。
3、查找和匹配
1)anyMatch-流中是否有一个元素能匹配给定的谓词
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
2)allMatch-看流中的元素是否都能匹配给定的谓词
3)noneMatch-和allMatch相对,即都不能匹配
4)findAny-返回当前流中的任意元素
- 它可以与其他流操作结合使用。
- 可以结合使用filter和findAny方法来实现查询
Optional<Dish> dish =
menu.stream()
.filter(Dish::isVegetarian)
.findAny();
- Optional返回值
isPresent()将在Optional包含值的时候返回true, 否则返回false。
ifPresent(Consumer block)会在值存在的时候执行给定的代码块。它让你传递一个接收T类型参数,并返回void的Lambda 表达式。
T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。
T orElse(T other)会在值存在时返回值,否则返回一个默认值。
5)findFirst-查找第一个元素
- 给定一个数字列表,下面的代码能找出第一个平方 能被3整除的数:
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =
someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst(); // 9
4、归约-reduce
1)元素求和
- Java7 写法
int sum = 0;
for (int x : numbers) {
sum += x;
}
- java8写法
private static void useReduce() {
logger.info("[useReduce]用法");
List<Integer> nums = Arrays.asList(1,2,3,6);
//int sum = nums.parallelStream().reduce(0,(a,b) -> a + b);
int sum = nums.parallelStream().reduce(0,Integer::sum);
int product = nums.parallelStream().reduce(1,(a,b) -> a * b);
System.out.println(sum);
System.out.println(product);
}
2) 最大值和最小值
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
3) 算出集合中有多少元素
Integer count = nums.parallelStream()
.map(d -> 1)
.reduce(0, Integer::sum);
System.out.println(count);
- 可以把流中每个元素都映射成数字1,然后用reduce求和
4) 总结
5、付诸实践
- 练习
(1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
(2) 交易员都在哪些不同的城市工作过?
(3) 查找所有来自于剑桥的交易员,并按姓名排序。
(4) 返回所有交易员的姓名字符串,按字母顺序排序。
(5) 有没有交易员是在米兰工作的?
(6) 打印生活在剑桥的交易员的所有交易额。
(7) 所有交易中,最高的交易额是多少? (8) 找到交易额最小的交易。
- Traders和Transactions列表
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario","Milan");
Trader alan = new Trader("Alan","Cambridge");
Trader brian = new Trader("Brian","Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
- Trader和Transaction类
public class Trader{
private final String name;
private final String city;
public Trader(String n, String c){
this.name = n;
this.city = c;
}
public String getName(){
return this.name;
}
public String getCity(){
return this.city;
}
public String toString(){
return "Trader:"+this.name + " in " + this.city;
}
}
public class Transaction{
private final Trader trader;
private final int year;
private final int value;
public Transaction(Trader trader, int year, int value){
this.trader = trader;
this.year = year;
this.value = value;
}
public Trader getTrader(){
return this.trader;
}
public int getYear(){
return this.year;
}
public int getValue(){
return this.value;
}
public String toString(){
return "{" + this.trader + ", " +
"year: "+this.year+", " +
"value:" + this.value +"}";
}
}
6、数值流
6.1 原始类型流特化
1、映射到数值流
这里,mapToInt会从每道菜中提取热量(用一个Integer表示),并返回一个IntStream (而不是一个Stream)。然后你就可以调用IntStream接口中定义的sum方法,对卡 路里求和了!请注意,如果流是空的,sum默认返回0。IntStream还支持其他的方便方法,如 max、min、average等。
2、转换回对象流
一旦有了数值流,你可能会想把它转换回非特化流。例如,IntStream上的操作只能 产生原始整数: IntStream 的 map 操作接受的 Lambda 必须接受 int 并返回 int (一个 IntUnaryOperator)。但是你可能想要生成另一类值,比如Dish。为此,你需要访问Stream 接口中定义的那些更广义的操作。要把原始流转换成一般流(每个int都会装箱成一个 Integer),可以使用boxed方法
3、默认值OptionalInt
求和的那个例子很容易,因为它有一个默认值:0。但是,如果你要计算IntStream中的最 大元素,就得换个法子了,因为0是错误的结果。如何区分没有元素的流和最大值真的是0的流呢? Optional可以用 Integer、String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类 型特化版本:OptionalInt、OptionalDouble和OptionalLong。
- 要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalories)
.max();
//如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:
int max = maxCalories.orElse(1);
6.2 数值范围
假设你想要生成1和100之间的所有数字。Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围: range和rangeClosed。
6.3 数值流应用:勾股数
Stream<int[]> pythagoreanTriples =
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a ->
IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.mapToObj(b ->
new int[]{a, b, (int)Math.sqrt(a * a + b * b)})
);
pythagoreanTriples.limit(5)
.forEach(t ->
System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
7、构建流
7.1 由值创建流-Stream.of/empty
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
Stream<String> emptyStream = Stream.empty();
7.2 由数组创建流-Arrays.stream
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
//这里stream里面必须放int[]才能调出sum()方法
7.3 由文件生成流java.nio.file.Files
- 看一个文件中有多少各不相同的词
long uniqueWords = 0;
try (Stream<String> lines =
Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
} catch (IOException e) {
}
你可以使用Files.lines得到一个流,其中的每个元素都是给定文件中的一行。然后,你 可以对line调用split方法将行拆分成单词。应该注意的是,你该如何使用flatMap产生一个扁 平的单词流,而不是给每一行生成一个单词流。最后,把distinct和count方法链接起来,数 数流中有多少各不相同的单词。
7.4 由函数生成流:创建无限流
1、迭代-Stream.iterate
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
-
一般来说,在需要依次生成一系列值的时候应该使用iterate,比如一系列日期:1月31日, 2月1日,依此类推。
-
测验5.4:斐波纳契元组序列
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55…
Stream.iterate(new int[]{0, 1},
t -> new int[]{t[1],t[0] + t[1]})
.limit(10)
.map(t -> t[0])
.forEach(System.out::println);
2、生成-Stream.generate
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);