public class Apple {
private String color;
private Double weight;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Double getWeight() {
return weight;
}
public void setWeight(Double weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Apple [color=" + color + ", weight=" + weight + "]";
}
}
1.对列表排序
List<Apple> inventory = new ArrayList<>();
//方法引用
inventory.sort(Comparator.comparing(Apple :: getWeight));
之前可以这样:
public class AppleComparator implements Comparator<Apple> {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
}
List<Apple> inventory = new ArrayList<>();
inventory.sort(new AppleComparator());
或者这样
List<Apple> inventory = new ArrayList<>();
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
使用 Lambda 表达式:
inventory.sort((Apple a1, Apple a2)
-> a1.getWeight().compareTo(a2.getWeight())
);
Lambda根据上下文来推断Lambda表达式参数的类型:
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
利用Comparator的静态辅助方法comparing:
Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());
最终:
import static java.util.Comparator.comparing;
List<Apple> inventory = new ArrayList<>();
inventory.sort(comparing((a) -> a.getWeight()));
//方法引用
ventory.sort(comparing(Apple::getWeight));
1.2.比较器复合
Java 8的好几个函数式接口都有为方便而设计的方法。具体而言,许多函数式接口,比如用于传递Lambda表达式的Comparator、Function和Predicate都提供了允许你进行复合的方法。这意味着你可以把多个简单的Lambda复合成复杂的表达式。比如,你可以让两个谓词之间做一个or操作,组合成一个更大的谓词。而且,你还可以让一个函数的结果成为另一个函数的输入。
Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
List<Apple> inventory = new ArrayList<>();
(1) 逆序
inventory.sort(comparing(Apple::getWeight).reversed()); //按重量递减排序
(2) 比较器链
inventory.sort(comparing(Apple::getWeight)
.reversed()//按重量递减排序
.thenComparing(Apple::getColor));//一样重时按颜色排序
(3) 谓词复合
Predicate<Apple> redApple = a -> "red".equals(a.getColor());
// 可以使用negate方法来返回一个Predicate的非,比如苹果不是红的
Predicate<Apple> notRedApple = redApple.negate();//产生现有Predicate对象redApple的非
//可能想要把两个Lambda用and方法组合起来,比如一个苹果既是红色又比较重:
Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
//可以进一步组合谓词,表达要么是重(150克以上)的红苹果,要么是绿苹果
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150)
.or(a -> "green".equals(a.getColor())
);
注意, and和or方法是按照在表达式链中的位置,从左向右确定优先级的。因此, a.or(b).and(d)可以看作(a || b) && d。
1.3.函数复合
Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。
-
andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数。数学上会写作g(f(x))
Function<Integer, Integer> f = x -> x + 1;//函数f给数字加1 Function<Integer, Integer> g = x -> x * 2;//函数g给数字乘2 f&g 组合成一个函数h Function<Integer, Integer> h = f.andThen(g);//先给数字加1,再给结果乘2 int result = h.apply(1);//return 4
-
compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果。数学会写作f(g(x))
Function<Integer, Integer> f = x -> x + 1;//函数f给数字加1 Function<Integer, Integer> g = x -> x * 2;//函数g给数字乘2 //f&g 组合成一个函数h Function<Integer, Integer> h = f.compose(g); //先给数字乘2,在给结果加1 int result = h.apply(1); //return 3
例:用String表示的一封信做文本转换:
public class Letter{
public static String addHeader(String text){
return "From Raoul, Mario and Alan: " + text;
}
public static String addFooter(String text){
return text + " Kind regards";
}
public static String checkSpelling(String text){
return text.replaceAll("labda", "lambda");
}
}
Function<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline = addHeader.andThen(Letter::checkSpelling)
.andThen(Letter::addFooter);
2. stream流
注意,和迭代器类似,流只能遍历一次
List<String> title = Arrays.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);//java.lang.IllegalStateException:流已被操作或关闭
流只能消费一次!
2.1. 流操作
List<Apple> inventory = new ArrayList<>();
List<String> colors = inventory.stream()//获取流
.filter(d -> d.getWeight() > 300)//中间操作,过滤
.map(Apple::getColor)//中间操作,提取颜色
.limit(3)//中间操作,截断流
.collect(toList());//将stream转换为List
- filter、 map和limit可以连成一条流水线;
- collect触发流水线执行并关闭它。
可以连接起来的流操作称为中间操作,关闭流的操作称为终端操作。如下图所示:
2.2.使用流
流的使用一般包括三件事:
- 一个数据源(如集合)来执行一个查询;
- 一个中间操作链,形成一条流的流水线;
- 一个终端操作,执行流水线,并能生成结果。
中间操作
操 作 | 类 型 | 返回类型 | 操作参数 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream< T> | Predicate< T> | T -> boolean |
map | 中间 | Stream< R> | Function<T, R> | T -> R |
limit | 中间 | Stream< T> | ||
sorted | 中间 | Stream< T> | Comparator< T> | (T, T) -> int |
distinct | 中间 | Stream< T> |
终端操作
操 作 | 类 型 | 目 的 |
---|---|---|
forEach | 终端 | 消费流中的每个元素并对其应用 Lambda。这一操作返回 void |
count | 终端 | 返回流中元素的个数。这一操作返回 long |
collect | 终端 | 把流归约成一个集合,比如 List、 Map 甚至是 Integer |
collect()终端操作用于collect(toList())的特殊情况。这一操作会创建一个与流具有相同元素的列表。
2.2.1.筛选和切片
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
@Override
public String toString() {
return name;
}
public enum Type { MEAT, FISH, OTHER }
}
- 用谓词筛选
Streams接口支持filter方法
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(Collectors.toList())
Streams接口支持filter方法
流还支持一个叫作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);
截短流
流支持limit(n)方法,该方法会返回一个不超过给定长度的流。
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(Collectors.toList());
跳过元素
流还支持skip(n)方法,返回一个扔掉了前n个元素的流。
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(Collectors.toList());
2.2.2.映射
Stream API通过map和flatMap方法从某些对象中选择信息
对流中每一个元素应用函数
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(Dish::getName)
// .map(String::length)//再链接上一个map
.collect(Collectors.toList());
流的扁平化
例 如 , 给 定 单 词 列 表[“Hello”,“World”],你想要返回列表[“H”,“e”,“l”, “o”,“W”,“r”,“d”]。
第一个版本可能是这样的
List<String> words = Arrays.asList("Hello","World");
words.stream()
.map(word -> word.split(""))
.distinct()
.collect(Collectors.toList());
这个方法的问题在于,传递给map方法的Lambda为每个单词返回了一个String[](String列 表 )。 因 此 , map 返 回 的 流 实 际 上 是 Stream<String[]> 类 型 的 。 你 真 正 想 要 的 是 用Stream来表示一个字符流。如下图:
尝试使用map和Arrays.stream()
List<String> words = Arrays.asList("Hello","World");
words.stream()
.map(word -> word.split(""))
.map(Arrays :: stream)
.distinct()
.collect(Collectors.toList());
现在得到的是一个流的列表(更准确地说是Stream< String>),先是把每个单词转换成一个字母数组,然后把每个数组变成了一个独立的流
使用flatMap
List<String> words = Arrays.asList("Hello","World");
words.stream()
.map(word -> word.split(""))
.flatMap(Arrays :: stream)
.distinct()
.collect(Collectors.toList());
使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。如下图所示:
flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
2.2.3.查找和匹配
查找数据集中的某些元素是否匹配一个给定的属性。 StreamAPI通过allMatch、 anyMatch、 noneMatch、 findFirst和findAny方法提供了这样的工具。
检查谓词是否至少匹配一个元素 anyMatch方法
anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。比如,你可以用它来看看菜单里面是否有素食可选择
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
anyMatch方法返回一个boolean,因此是一个终端操作。
检查谓词是否匹配所有元素 allMatch和noneMatch方法
*allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词。比如,你可以用它来看看菜品是否有利健康
boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);
和allMatch相对的是noneMatch。它可以确保流中没有任何元素与给定的谓词匹配。比如,
你可以用noneMatch重写前面的例子
boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);
anyMatch、 allMatch和noneMatch这三个操作都用到了我们所谓的短路,就是Java中&&和||运算符短路在流中的版本。
查找元素 findAny方法
findAny方法将返回当前流中的任意元素。
List<String> words = Arrays.asList("Hello","World");
words.stream()
.map(word -> word.split(""))
.flatMap(Arrays :: stream)
.distinct()
.findAny()
.ifPresent(System.out::println);
查找第一个元素 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
findFirst找到第一个元素在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少
2.2.4.归约 reduce操作
元素求和
对流中所有的元素求和:
List<Integer> numbers = Arrays.asList(4, 5, 3, 9);
int product = numbers.stream().reduce(0, (a, b) -> a * b);
对流中所有的元素相乘:
List<Integer> numbers = Arrays.asList(4, 5, 3, 9);
int product = numbers.stream().reduce(1, (a, b) -> a * b);
reduce接受两个参数:
- 一个初始值,这里是0;
- 一个BinaryOperator< T>来将两个元素结合起来产生一个新值,这里我们用的是 lambda (a, b) -> a + b。
reduce求和操作如下图:
也可以使用Integer类静态的sum方法
int sum = numbers.stream().reduce(0, Integer::sum);
无初始值
reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象:
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
最大值和最小值
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
long count = numbers.stream().count();
2.2.5.数值流
原始类型流特化
Java 8引入了三个原始类型特化流接口: IntStream、 DoubleStream和LongStream,分别将流中的元素特化为int、 long和double,从而避免了暗含的装箱成本。(主要解决拆装箱造成的复杂性和效率差异)
- 映射到数值流
将流转换为特化版本的常用方法是mapToInt、 mapToDouble和mapToLong。(返回的是一个特化流,而不是Stream< T>) - 转换回对象流 boxed方法
有了数值流,你可能会想把它转换回非特化流.
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
- 默认值OptionalInt
对于三种原始流特化,也分别有一个Optional原始类型特化版本: OptionalInt、 OptionalDouble和OptionalLong。()
例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt(区分没有元素的流和最大值真的是0的流):
OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
现在,如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:
int max = maxCalories.orElse(1);//如果没有最大值的话,显式提供一个默认最大值
数值范围 range和rangeClosed方法
range是不包含结束值,rangeClosed包含结束值。
IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
System.out.println(evenNumbers.count());
例子(复合勾股定理的数组):
Stream<double[]> pythagoreanTriples2 = IntStream.rangeClosed(1, 100).boxed()
.flatMap(a ->
IntStream.rangeClosed(a, 100)
.mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)})
.filter(t -> t[2] % 1 == 0)
);
pythagoreanTriples2.limit(5)
.forEach(t ->
System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
2.2.6.构建流
由值创建流 静态方法Stream.of
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
//可以使用empty得到一个空流,
Stream<String> emptyStream = Stream.empty();
由数组创建流 Arrays.stream
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
由文件生成流
例看看一个文件中有多少各不相同的词:
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) {
}
由函数生成流:创建无限流 Stream.iterate和Stream.generate。
由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。
- 迭代
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
- 生成
generate方法也可让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一个Supplier类型的Lambda提供新的值。
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
3.用流收集数据(Collectors类)
收集器用作高级归约
例:
List<Transaction> transactions = transactionStream.collect(Collectors.toList());
归约和汇总
例:
long howManyDishes = menu.stream().collect(Collectors.counting());
//简写
long howManyDishes = menu.stream().count();
或者
import static java.util.stream.Collectors.*;
long howManyDishes = menu.stream().collect(counting());
1.最大值和最小值
Collectors.maxBy()
Collectors.minBy()
2.汇总
求和
Collectors.summingInt。它可接受一个把对象映射为求和所需int的函数,并返回一个收集器
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
Collectors.summingLong和Collectors.summingDouble方法的作用完全一样
平均数:
Collectors.averagingInt,averagingLong和averagingDouble可以计算数值的平均数
double avgCalories = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
Collectors.summarizing可以同时获得总和、平均值、最大值和最小值,相应的summarizingLong和summarizingDouble。
3.连接字符串 joining
//joining在内部使用了StringBuilder来把生成的字符串逐个追加起来
String shortMenu = menu.stream().map(Dish::getName).collect(joining());
joining工厂方法有一个重载版本可以接受元素之间的分界符
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
//生成pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon
4.广义的归约汇总 reducing
int totalCalories = menu.stream().collect(Collectors.reducing(0, Dish::getCalories, (i, j) -> i + j));
它需要三个参数:
- 第一个参数是归约操作的起始值,也是流中没有元素时的返回值,所以很显然对于数值和而言0是一个合适的值。
- 第二个参数函数,将菜肴转换成一个表示其所含热量的int。
- 第三个参数是一个BinaryOperator,将两个项目累积成一个同类型的值。这里它就是对两个int求和。
例,取最高:
Optional<Dish> mostCalorieDish = menu.stream().collect(reducing(
(d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));
用reduce方法来实现toListCollector所做的工作:
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
List<Integer> numbers = stream.reduce(
new ArrayList<Integer>(),
(List<Integer> l, Integer e) -> {
l.add(e);
return l;
},
(List<Integer> l1, List<Integer> l2) -> {
l1.addAll(l2);
return l1; }
);
分组 Collectors.groupingBy
- 多级分组
把一个内层groupingBy传递给外层groupingBy,并定义一个为流中项目分类的二级标准
public enum CaloricLevel { DIET, NORMAL, FAT }
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect(
groupingBy(Dish::getType,groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}
)
)
);
/**
输出
{
MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]},
FISH={DIET=[prawns], NORMAL=[salmon]},
OTHER={DIET=[rice, seasonal fruit], NORMAL=[french fries, pizza]}
}
*/
- 按子组收集数据
传递给第一个groupingBy的第二个收集器可以是任何类型.
/**
*return {MEAT=3, FISH=2, OTHER=4}
*/
Map<Dish.Type, Long> typesCount = menu.stream().collect(groupingBy(Dish::getType, counting()));
/**
*return Optional<Dish> {FISH=Optional[salmon], OTHER=Optional[pizza], MEAT=Optional[pork]}
*/
Map<Dish.Type, Optional<Dish>> mostCaloricByType = menu.stream().collect(
groupingBy(Dish::getType,
maxBy(comparingInt(Dish::getCalories)))
);
普通的单参数groupingBy(f)(其中f是分类函数)实际上是groupingBy(f,toList())的简便写法.
其他例子:
Map<Dish.Type, Integer> totalCaloriesByType = menu.stream().collect(groupingBy(Dish::getType, summingInt(Dish::getCalories)));
Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType = menu.stream().collect(
groupingBy(Dish::getType, mapping(
dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}, toSet() )));
分区 partitioningBy
区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函数。分区函数返回一个布尔值,意味着得到的分组Map的键类型是Boolean,于是它最多可以分为两组——true是一组, false是一组.
//return {false=[pork, beef, chicken, prawns, salmon],true=[french fries, rice, season fruit, pizza]}
Map<Boolean, List<Dish>> partitionedMenu = menu.stream().collect(partitioningBy(Dish::isVegetarian));
也可以传递第二个收集器
/**
return: 二级map:
{false={FISH=[prawns, salmon], MEAT=[pork, beef, chicken]},
true={OTHER=[french fries, rice, season fruit, pizza]}}
*/
Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType = menu.stream().collect(
partitioningBy(
Dish::isVegetarian,groupingBy(Dish::getType))
);
Collectors类的静态工厂方法
工厂方法 | 返回类型 | 用 于 | 使用示例 |
---|---|---|---|
toList | List< T> | 把流中所有项目收集到一个 List | List< Dish> dishes = menuStream.collect(toList()); |
toSet | Set< T> | 把流中所有项目收集到一个 Set,删除重复项 | Set< Dish> dishes = menuStream.collect(toSet()); |
toCollection | Collection< T> | 把流中所有项目收集到给定的供应源创建的集合 | Collection< Dish> dishes = menuStream.collect(toCollection(),ArrayList::new); |
counting | Long | 计算流中元素的个数 | long howManyDishes = menuStream.collect(counting()); |
summingInt | Integer | 对流中项目的一个整数属性求和 | int totalCalories =menuStream.collect(summingInt(Dish::getCalories)); |
averagingInt | Double | 计算流中项目 Integer 属性的平均值 | double avgCalories =menuStream.collect(averagingInt(Dish::getCalories)); |
summarizingInt | IntSummaryStatistics | 收集关于流中项目 Integer 属性的统计值,例如最大、最小、总和与平均值 | IntSummaryStatistics menuStatistics = menuStream.collect(summarizingInt(Dish::getCalories)); |
joining | String | 连接对流中每个项目调用 toString 方法所生成的字符串 | String shortMenu = menuStream.map(Dish::getName).collect(joining(", ")); |
maxBy | Optional< T> | 一个包裹了流中按照给定比较器选出的最大元素的 Optional,或如果流为空则为 Optional.empty() | Optional< Dish> fattest = menuStream.collect(maxBy(comparingInt(Dish::getCalories))); |
minBy | Optional< T> | 一个包裹了流中按照给定比较器选出的最小元素的 Optional,或如果流为空则为 Optional.empty() | Optional< Dish> lightest = menuStream.collect(minBy(comparingInt(Dish::getCalories))); |
reducing | 归约操作产生的类型 | 从一个作为累加器的初始值开始,利用 BinaryOperator 与流中的元素逐个结合,从而将流归约为单个值 | int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum)); |
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果应用转换函数 | int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size)); |
groupingBy | Map<K, List< T>> | 根据项目的一个属性的值对流中的项目作问组,并将属性值作为结果 Map 的键 | Map<Dish.Type,List< Dish>> dishesByType = menuStream.collect(groupingBy(Dish::getType)); |
partitioningBy | Map<Boolean,List< T>> | 根据对流中每个项目应用谓词的结果来对项目进行分区 | Map<Boolean,List< Dish>> vegetarianDishes = menuStream.collect(partitioningBy(Dish::isVegetarian)); |
收集器接口 Collector
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
Function<A, R> finisher();
BinaryOperator<A> combiner();
Set<Characteristics> characteristics();
}
列表适用以下定义。
- T是流中要收集的项目的泛型。
- A是累加器的类型,累加器是在收集过程中用于累积部分结果的对象。
- R是收集操作得到的对象(通常但并不一定是集合)的类型。
例如,你可以实现一个ToListCollector< T>类,将Stream< T>中的所有元素收集到一个
List里,它的签名如下:
public class ToListCollector< T> implements Collector<T, List< T>, List< T>>
建立新的结果容器: supplier方法
public Supplier<List<T>> supplier() {
return () -> new ArrayList<T>();
}
public Supplier<List<T>> supplier() {
return ArrayList::new;
}
将元素添加到结果容器: accumulator方法
public BiConsumer<List<T>, T> accumulator() {
return (list, item) -> list.add(item);
}
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}
对结果容器应用最终转换: finisher方法
public Function<List<T>, List<T>> finisher() {
return Function.identity();
}
合并两个结果容器: combiner方法
public BinaryOperator<List<T>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1; }
}
characteristics方法
characteristics会返回一个不可变的Characteristics集合,它定义了收集器的行为.
Characteristics是一个包含三个项目的枚举。
- UNORDERED——归约结果不受流中项目的遍历和累积顺序的影响。
- CONCURRENT——accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约
- IDENTITY_FINISH——这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器A不加检查地转换为结果R是安全的。
例子
public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {
@Override
//创建集合操作的起始点
public Supplier<List<T>> supplier() {
return ArrayList::new;
}
@Override
//累 积 遍 历 过 的项目,原位修改累加器
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}
@Override
//恒 等函数
public Function<List<T>, List<T>> finisher() {
return Function.indentity();
}
@Override
//修 改 第 一 个 累 加器,将其与第二个累加器的内容合并
public BinaryOperator<List<T>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;//返回修改后的第一个累加器
};
}
@Override
public Set<Characteristics> characteristics() {
//为收集器添加IDENTITY_FINISH和CONCURRENT标志
return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH, CONCURRENT));
}
}
3.并行数据处理与性能
3.1.并行流 parallelStream
并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
3.1.1.将顺序流转换为并行流
public static long parallelSum(long n) {
return Stream.iterate(1L, i -> i + 1)
.limit(n)
.parallel()//将流转换为并行流
.reduce(0L, Long::sum);
}
对顺序流调用parallel方法并不意味着流本身有任何实际的变化。它在内部实际上就是设了一个boolean标志,表示你想让调用parallel之后进行的所有操作都并行执行。类似地,你只需要对并行流调用sequential方法就可以把它变成顺序流。
请注意,你可能以为把这两个方法结合起来,就可以更细化地控制在遍历流时哪些操作要并行执行,哪些要顺序执行。
例如
stream.parallel()
.filter(...)
.sequential()
.map(...)
.parallel()
.reduce();
但最后一次parallel或sequential调用会影响整个流水线。在本例中,流水线会并行执行,因为最后调用的是它
配置并行流使用的线程池
并行流内部使用了默认的ForkJoinPool(7.2节会进一步讲到分支/合并框架),它默认的线 程 数 量 就 是 你 的 处 理 器 数 量 , 这 个 值 是 由 Runtime.getRuntime().availableProcessors()得到的。
但 是 你 可 以 通 过 系 统 属 性 java.util.concurrent.ForkJoinPool.common.parallelism来改变线程池大小,如下所示:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
这是一个全局设置,因此它将影响代码中所有的并行流。反过来说,目前还无法专为某个并行流指定这个值。一般而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值,除非你有很好的理由,否则强烈建议你不要修改它。
Spliterator
Spliterator是Java 8中加入的另一个新接口;这个名字代表“可分迭代器”(splitable iterator)。和Iterator一样, Spliterator也用于遍历数据源中的元素,但它是为了并行执行而设计的。