java 8 in action
1. 基础知识
通过行为参数化传递代码 - 处理频繁变更的需求。类实现接口,不同的类接口方法的实现不同,作为谓词进行传递处理不同的业务。
List自带了一个sort方法(你也可以使用Collections.sort)。sort的参数类型为函数式接口,所以sort的行为可以用java.util.Comparator对象来参数化:
inventory.sort(new Comparator<Apple>() { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } });
inventory.sort(
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
线程执行代码块:
// java.lang.Runnable public interface Runnable{ public void run(); }
// java.lang.Runnable
public interface Runnable{
public void run();
}
Thread t = new Thread(() -> System.out.println("Hello world"));
GUI事件处理:
Button button = new Button("Send"); button.setOnAction(new EventHandler<ActionEvent>() { public void handle(ActionEvent event) { label.setText("Sent!!"); } }); button.setOnAction((ActionEvent event) -> label.setText("Sent!!"));
Lambda表达式可以在用到函数式接口的地方使用,接口还可以拥有默认方法既在类没有对方法进行实现时,其主体方法提供默认实现的方法,哪怕有很多默认方法,只要接口只定义了一个抽象方法,它仍然是一个函数式接口。Lambda表达式允许使用内联的方式为函数式接口的抽象方法提供实现,使用匿名内部类也可以实现同样的事情。
Predicate 断言:
java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。
Consumer:
java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。
Function:
java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。
使用局部变量:
不是参数,而是外层作用域中定义的变量,lambda可以没有限制的捕获实例变量和静态变量,但是局部变量必须显示声明为final,或事实是final,其实也就是lambda只能捕获指派给他们的局部变量一次。
下边的代码无法编译,因为局部变量被赋值两次。
int portNumber = 1337; Runnable r = () -> System.out.println(portNumber); portNumber = 31337;
为什么局部变量会有这些限制呢?因为局部变量保存在栈上,实例变量保存在堆上。如果lambda可以直接访问局部变量,而且lambda是在一个线程中使用,则使用lambda的线程,可能会在分配该变量的线程将这个变量回收之后去访问该变量。因此java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量,如果局部变量仅仅赋值一次那就没什么区别了,因此有了这个限制。使用外部变量的命令式编程会阻碍很容易做到的并行处理。lambda是対值封闭,而不是对变量封闭,这种限制存在的原因在于局部变量保存在栈上,并且隐式表示他们仅限于其所在线程,如果允许改变可改变的局部变量,就会引发造成线程不安全的可能性,实例变量可以,因为它们保存在堆中,而堆是在线程之间共享的。
方法引用 -- lambda的快捷写法
重复使用现有方法,并向lambda一样传递它们。根据已有的方法实现来创建lambda表达式。一般用来替换lambda表达式函数体中只有一句函数体的lambda,类型 + 分隔符 + 方法。
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())); 之后(使用方法引用和java.util.Comparator.comparing): inventory.sort(comparing(Apple::getWeight));
(Apple a) -> a.getWeight() Apple::getWeight
() -> Thread.currentThread().dumpStack() Thread.currentThread()::dumpStack
(str, i) -> str.substring(i) String::substring
(String s) -> System.out.println(s) System.out::println
List<String> str = Arrays.asList("a","b","A","B");
str.sort((s1, s2) -> s1.compareToIgnoreCase(s2));
List<String> str = Arrays.asList("a","b","A","B");
str.sort(String::compareToIgnoreCase);
lambda和方法引用实战:
法法引用
inventory.sort(comparing(Apple::getWeight));
传递代码
public class AppleComparator implements Comparator<Apple> { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } } inventory.sort(new AppleComparator());
匿名内部类
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表达式
比较器复合
Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
逆序
nventory.sort(comparing(Apple::getWeight).reversed());
比较器链
inventory.sort(comparing(Apple::getWeight)
.reversed()
.thenComparing(Apple::getCountry));
谓词复合
谓词接口包括三个方法:negate,and,or
Predicate<Apple> notRedApple = redApple.negate(); Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150); Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150) .or(a -> "green".equals(a.getColor()));
and 和 or方法是按照在表达式链中的位置,从左向右确定优先级的,因此,a.or(b).and(c)可以看做(a || b ) && c
复合函数
复合函数说的是可以将function接口所代表的lambda表达式复合起来,function接口为此提供了andThen和compose两个默认方法,他们都会返回function的一个实例。
Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x * 2; Function<Integer, Integer> h = f.andThen(g); int result = h.apply(1); Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x * 2; Function<Integer, Integer> h = f.compose(g); int result = h.apply(1);
Function<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline
= addHeader.andThen(Letter::checkSpelling)
.andThen(Letter::addFooter);
2. 函数式数据处理
流:
流可以允许你以声明式的方式处理集合(通过查询语句来表达,而不是临时表写一个实现)要处理大量元素,提高性能,需要并行处理,利用多核架构,无需写任何多线程代码。
流处理集合可以避免垃圾变量(作为一次性中间容器),实现细节放在了它本该归属的库里了。代码以声明式的方式编写而不是如何实现一个操作,将几个基础操作链接起来,来表达复杂的数据处理流水线。
import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toList; List<String> lowCaloricDishesName = menu.stream() .filter(d -> d.getCalories() < 400) .sorted(comparing(Dish::getCalories)) .map(Dish::getName) .collect(toList()); 为了利用多核架构并行执行这段代码,你只需要把stream()换成parallelStream(): List<String> lowCaloricDishesName = menu.parallelStream() .filter(d -> d.getCalories() < 400) .sorted(comparing(Dishes::getCalories)) .map(Dish::getName) .collect(toList());
中间操作,终端操作
返回一个非Stream的值为终端,返回一个Stream的值为中间操作。
使用流:
一个数据源(如集合)来执行一个查询,一个中间操作链形成一条流的流水线,一个终端操作执行流水线并生成结果。
筛选
filter方法接收一个谓词作为参数,返回一个包裹所有复合谓词的元素的流。
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
筛选各异的元素
distinct方法,他会返回一个元素各异(hashCode,equals方法)的流。
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<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); numbers.stream() .filter(i -> i % 2 == 0) .distinct() .forEach(System.out::println);
跳过元素
skip(n),返回一个扔掉钱n哥元素的流,如果流元素不足n个,则返回一个空流。limit和skip是互补的。
List<Dish> dishes = menu.stream() .filter(d -> d.getCalories() > 300) .skip(2) .collect(toList());
映射
map方法,接收一个函数作为参数,这个函数会被应用到每个元素上,并将其映射为一个新的元素
List<String> dishNames = menu.stream() .map(Dish::getName) .collect(toList()); List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action"); List<Integer> wordLengths = words.stream() .map(String::length) .collect(toList());
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(toList());
流的扁平化
单词列表["Hello","World"],你想要返回列表["H","e","l", "o","W","r","d"]
words.stream() .map(word -> word.split("")) .distinct() .collect(toList());
flatMap的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容,所有使用map(Arrays::stream)时生成的单个流都被合并起来,既扁化为一个流,其实就是对流使用合并流。
List<String> uniqueCharacters = words.stream() .map(w -> w.split("")) .flatMap(Arrays::stream) .distinct() .collect(Collectors.toList());
给定列表[1, 2, 3]和列表[3, 4],应该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。为简单起见,你可以用有两个元素的数组来代表数对。
你可以使用两个map来迭代这两个列表,并生成数对。但这样会返回一个Stream<Stream<Integer[]>>。你需要让生成的流扁平化,以得到一个Stream<Integer[]>。这
正是flatMap所做的:
List<Integer> numbers1 = Arrays.asList(1, 2, 3); List<Integer> numbers2 = Arrays.asList(3, 4); List<int[]> pairs = numbers1.stream() .flatMap(i -> numbers2.stream() .map(j -> new int[]{i, j}) ) .collect(toList());
查找和匹配
检查谓词是否至少匹配一个元素
if(menu.stream().anyMatch(Dish::isVegetarian)){ System.out.println("The menu is (somewhat) vegetarian friendly!!"); } anyMatch方法返回一个boolean,因此是一个终端操作。
检查谓词是否匹配所有元素
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方法将返回当前流中的任意元素。它可以与其他流操作结合使用。比如,你可能想找到一道素食菜肴。你可以结合使用filter和findAny方法来实现这个查询:
Optional<Dish> dish =
menu.stream()
.filter(Dish::isVegetarian)
.findAny();
menu.stream() .filter(Dish::isVegetarian) .findAny() .ifPresent(d -> System.out.println(d.getName())
查找第一个元素
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
归约
将流中所有元素反复结合起来,得到一个值,这样的查询可以被称为归约操作,将流归约成一个值。
元素求和
int sum = 0; for (int x : numbers) { sum += x; } int sum = numbers.stream().reduce(0, (a, b) -> a + b); 一个BinaryOperator<T>来将两个元素结合起来产生一个新值,这里我们用的是 lambda (a, b) -> a + b。 int product = numbers.stream().reduce(1, (a, b) -> a * b);
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);
map和reduce的链接通常称为map-reduce模式,因google用它进行搜索而出名,同时很容易并行化。map-reduce模式可以用filter对A字段过滤,map对B字段映射,reduce
int count = menu.stream() .map(d -> 1) .reduce(0, (a, b) -> a + b); long count = menu.stream().count();
找出2011年的所有交易并按交易额排序(从低到高)
List<Transaction> tr2011 =
transactions.stream()
.filter(transaction -> transaction.getYear() == 2011)
.sorted(comparing(Transaction::getValue))
.collect(toList());
交易员都在哪些不同的城市工作过
List<String> cities = transactions.stream() .map(transaction -> transaction.getTrader().getCity()) .distinct() .collect(toList());
Set<String> cities =
transactions.stream()
.map(transaction -> transaction.getTrader().getCity())
.collect(toSet());
查找所有来自于剑桥的交易员,并按姓名排序
List<Trader> traders = transactions.stream() .map(Transaction::getTrader) .filter(trader -> trader.getCity().equals("Cambridge")) .distinct() .sorted(comparing(Trader::getName)) .collect(toList());
返回所有交易员的姓名字符串,按字母顺序排序
String traderStr = transactions.stream() .map(transaction -> transaction.getTrader().getName()) .distinct() .sorted() .reduce("", (n1, n2) -> n1 + n2);
String traderStr =
transactions.stream()
.map(transaction -> transaction.getTrader().getName())
.distinct()
.sorted()
.collect(joining());
有没有交易员是在米兰工作的
boolean milanBased = transactions.stream() .anyMatch(transaction -> transaction.getTrader() .getCity() .equals("Milan"))
打印生活在剑桥的交易员的所有交易 -- filter-map-forEach
transactions.stream() .filter(t -> "Cambridge".equals(t.getTrader().getCity())) .map(Transaction::getValue) .forEach(System.out::println);
所有交易中,最高的交易额是多少 -- reduce
Optional<Transaction> smallestTransaction = transactions.stream() .reduce((t1, t2) -> t1.getValue() < t2.getValue() ? t1 : t2);
流支持min,max方法,他们接受一个Comparator作为参数,指定计算最小或做大值是比较哪个键值
Optional<Transaction> smallestTransaction =
transactions.stream()
.min(comparing(Transaction::getValue));
数值流
reduce计算流元素的总和。
int calories = menu.stream() .map(Dish::getCalories) .reduce(0, Integer::sum);
这段代码暗含装箱成本,每个Integer都必须拆箱成一个原始类型,在进行求和,下边这种方法是错误的因为map方法会生成一个Stream<T>,而Stream接口中没有定义sum方法,但是Stream api中提供了原始类型流特化,专门支持处理数值流的方法
int calories = menu.stream()
.map(Dish::getCalories)
.sum();
原始类型流特化
IntStream,DoubleStream,LongStream 分别将流中元素转换为int,double,long,从而避免了暗含的装箱成本。么个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。
映射到数值流
将流转换为特化版本的常用方法是mapToInt,mapToDouble,mapToLong,这些方法和前边说的map方法的工作方式一样,只是他们返回的是一个特化流,而不是Stream<T>
int calories = menu.stream() .mapToInt(Dish::getCalories) //返回一个IntStream .sum();
转换为对象流
一旦有了数值流,可能会想将它转换回非特化流,例如,IntStream上的操作只能产生原始整数:IntStream的map操作接受的lambda必须接受int并返回int,但是如果想要生成另一类值,如Dish,因此,你需要访问的Stream接口中定义的那些更广义的操作,要将原始流转换成一般流(每个int都会装箱成一个Integer),可以使用boxed()方法。
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
默认值OptionalInt
求和的例子很容易,因为他有一个默认值0,但是,如果计算IntStream中的最大值就得换个法子了,因为0是错误的结果,如何区分没有元素的流和最大值真是0的流呢,前边说的Optional类,是一个可以表示值存在或不存在的容器,Optional可以用Integer,String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt,OptionalDouble和OptionalLong,例如求IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalories)
.max();
如果没有最大值的话,就可以显示处理OptionalInt去定义一个默认值了:
int max = maxCalories.orElse(1);
数值范围
java8引入两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rnageClosed,这两个方法都是第一个参数接收起始值,第二个参数接收结束值,但range是不包含结束值得,而rangeClosed则包含结束值
IntStream evenNumbers = IntStream.rangeClosed(1, 100) .filter(n -> n % 2 == 0); System.out.println(evenNumbers.count());
生成100个Dish对象
List<Dish> result = IntStream.rangeClosed(1, 100).boxed().map(n->{ Dish dish = new Dish(n.toString(), n%2 == 0?true:false, n, Type.FISH); return dish; }).collect(Collectors.toList());
数值流应用:勾股数
Stream<int[]> test = 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)}) );
对每个给定的a值创建一个三元数流,要把a的只映射到三元数流的话,就会得到一个由流构成的流,flatmap方法在做映射的同时,还会把所有生成的三元数流扁平化成一个流,既这个流中的数据在下一个流中使用也就是将由流构成的流扁平化。而IntStream中的map方法只能为流中的每一个元素返回一个int,可以使用mapToObj方法返回一个对象值流。
求两次平方根,如果让代码更加紧凑的一种可能方法是,先生成所有的三元数,然后在筛选复合条件的:
Stream<double[]> test2 = 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));
由值创建流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action"); stream.map(String::toUpperCase).forEach(System.out::println); 你可以使用empty得到一个空流,如下所示: Stream<String> emptyStream = Stream.empty();
由数组创建流
int[] numbers = {2, 3, 5, 7, 11, 13}; int sum = Arrays.stream(numbers).sum();
由文件生成流
Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
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 API提供了两个静态方法从函数来生成流:Stream.iterate,Stream.generate这两个操作可以创建所谓的无限流,不像是从固定集合创建的流那样有固定大小的流。这两个函数产生的流会用给定的函数按需创建值,并且可以无穷地计算下去,一般情况下要用limit(n)来对这种流加以限制,来避免打印无穷多个值。
Stream.iterate(0, n -> n + 2).forEach(System.out::println);
iterate方法接收一个初始值,和一个依次应用在每个产生的新值上的lambda(unaryOperator<T>类型,一元运算符)。然后调用forEach来消费流。
斐波纳契元组序列::(0, 1),(1, 1), (1, 2), (2, 3), (3, 5), (5, 8), (8, 13), (13, 21) …
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0]+t[1]}) .limit(20) .forEach(t -> System.out.println("(" + t[0] + "," + t[1] +")"));
用流收集数据
流可以看做花哨又懒惰的数据集迭代器,支持两种类型的操作:中间操作(filter或map)和终端操作(count,findFirst,forEach,reduce)。
由Transaction构成的List,并且想按照名义货币进行分组。目标map,源集合,遍历集合,map里边拿数据,不能拿到则创建,添加到map,小集合增加数据。
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>(); for (Transaction transaction : transactions) { Currency currency = transaction.getCurrency(); List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency); if (transactionsForCurrency == null) { transactionsForCurrency = new ArrayList<>(); transactionsByCurrencies .put(currency, transactionsForCurrency); } transactionsForCurrency.add(transaction); }
Map<Currency, List<Transaction>> transactionsByCurrencies =
transactions.stream().collect(groupingBy(Transaction::getCurrency));
这里的groupingBy是Collectior接口的一个实现。
预定义收集器:
预定义收集器也就是那些可以从Collectors类提供的工厂方法创建的收集器主要提供了三大功能:将流元素归约和汇总为一个值,元素分组,元素分区。
归约和汇总:菜单里有多少菜种:
long howManyDishes = menu.stream().collect(Collectors.counting());
也可以这样写:
long howManyDishes = menu.stream().collect(Collectors.counting());
查询流中的最大值和最小值:Collectors.maxBy,Collectors.minBy这两个收集器接收一个Comparator参数来比较流中的元素。
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories); Optional<Dish> mostCalorieDish = menu.stream() .collect(maxBy(dishCaloriesComparator));
汇总:Collectors.summingInt,它可以接收一个把对象映射为求和所需int的函数,并返回一个收集器。
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
汇总不仅仅是求和,还有Collectors.averagingInt,averagingLong,averagingDouble等可以计算平均值:
double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));
通过收集器给流中的元素计数,可以找到这些元素值属性的最大值,最小值,总和,平均数。如果想只需一次操作就可以完成上述所有功能,可以使用summarizingInt工厂方法返回的收集器。
IntSummaryStatistics menuStatistics =
menu.stream().collect(summarizingInt(Dish::getCalories));
这个收集器会把所有这些信息收集到一个叫作IntSummaryStatistics的类里,它提供了方便的取值(getter)方法来访问结果。打印menuStatisticobject会得到以下输出:
IntSummaryStatistics{count=9, sum=4300, min=120,average=477.777778, max=800}
同样,相应的summarizingLong和summarizingDouble工厂方法也有相关的LongSummaryStatistics和DoubleSummaryStatistics类型。
链接字符串
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
广义的归约汇总
事实上,我们已经讨论的所有收集器,都是一个可以用reducing工厂方法定义的归约过程的特少情况而已。Collectors.reducing工厂方法是所有这些特殊情况的一般化。例如用reducing方法创建收集器来计算你菜单的总热量:
int totalCalories = menu.stream().collect(reducing( 0, Dish::getCalories, (i, j) -> i + j));
第一个参数时归约操作的起始值,也就是流中没有元素时的返回值,对于数值而言0是一个合适值。
int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, Integer::sum));
第二个参数是将菜肴转换成一个表示其所含热量的int。
第三个参数是一个BinaryOperator,将两个项目累积成一个同类型的值,这里就是对两个int求和。
特殊情况,将流中的第一个项目作为起点,返回一个Optional<Dish>对象。
Optional<Dish> mostCalorieDish =
menu.stream().collect(reducing(
(d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));
reduce(Integer::sum)反悔的不是int而是Optional<Integer>,以便在空流的情况下安全地执行归约操作。然后使用Optional对象中的get方法来提取里边的值,如果确认流不为空这种使用get方法是安全的。orElse或orElseGet来解开Optional中包含的值更安全。
int totalCalories = menu.stream().map(Dish::getCalories).reduce(Integer::sum).get();
int totalCalories = menu.stream().mapToInt(Dish::getCalories).sum();
String shortMenu = menu.stream() .collect( reducing( (d1, d2) -> d1.getName() + d2.getName() ) ).get(); String shortMenu = menu.stream() .collect( reducing( "",Dish::getName, (s1, s2) -> s1 + s2 ) );
第一个无法编译,因为reducing接受的参数是一个BinaryOperator<t>也就是一个BiFunction<T,T,T>,这就意味着他需要的函数必须接受两个参数,然后返回一个相同类型的值,这里的lambda表达式接受的参数是两个菜,返回的确是一个字符串。
分组
Collectors.groupingBy工厂方法返回的收集器可以轻松分组。
Map<Dish.Type, List<Dish>> dishesByType =
menu.stream().collect(groupingBy(Dish::getType));
这里给groupingBy方法传递一个Function(以方法引用的形式)。
分类不一定像方法引用那样可用,因为你想用以分类的条件可能比简单的属性访问器更加复杂,例如:将热量不到400的菜划分为低热量,400-700划分为普通,>700划分为高热量,因为Dish类中没有将这个操作写成一个方法,所以你无法使用方法引用,但是可以将这个逻辑写成lambda表达式:
public enum CaloricLevel { DIET, NORMAL, FAT } Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect( groupingBy(dish -> { if (dish.getCalories() <= 400) return CaloricLevel.DIET; else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; else return CaloricLevel.FAT; } ));
多级分组:map嵌套map
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; } ) ) );
按子组收集数据
Map<Dish.Type, Long> typesCount = menu.stream().collect(
groupingBy(Dish::getType, counting()));
其结果是下面的Map:
{MEAT=3, FISH=2, OTHER=4}
Map<Dish.Type, Optional<Dish>> mostCaloricByType =
menu.stream()
.collect(groupingBy(Dish::getType,
maxBy(comparingInt(Dish::getCalories))));
这个分组的结果显然是一个map,以Dish类型作为键,以包装了该类型中热量最高的Dish的Optional<Dish>作为值:{FISH=Optional[salmon], OTHER=Optional[pizza], MEAT=Optional[pork]}
把收集器的结果转换为另一种类型
Map<Dish.Type, Dish> mostCaloricByType = menu.stream() .collect(groupingBy(Dish::getType, collectingAndThen( maxBy(comparingInt(Dish::getCalories)), Optional::get)));
Map<Dish.Type, Integer> totalCaloriesByType =
menu.stream().collect(groupingBy(Dish::getType,
summingInt(Dish::getCalories)));
groupingBy常常联合使用的另一个收集器是mapping方法。这个发方法接收两个参数,一个函数对流中的元素做变化,另一个则将变换的结果对象收集起来
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() )));
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; }, toCollection(HashSet::new) )));
分区:
分区是分组的特殊情况,由一个谓词作为分类函数,它称为分区函数,这意味着得到分组map的键类型是Bollean,于是它最多分两组。
Map<Boolean, List<Dish>> partitionedMenu =
menu.stream().collect(partitioningBy(Dish::isVegetarian));
Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType =
menu.stream().collect(
partitioningBy(Dish::isVegetarian,
groupingBy(Dish::getType)));
{false={FISH=[prawns, salmon], MEAT=[pork, beef, chicken]},true={OTHER=[french fries, rice, season fruit, pizza]}}
Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = menu.stream().collect( partitioningBy(Dish::isVegetarian, collectingAndThen( maxBy(comparingInt(Dish::getCalories)), Optional::get)));
{false=pork, true=pizza}
Collectors类的静态工厂方法:toList,toSet,toCollection,counting,sumingInt,averagingInt,sumarizingInt,joining,maxBy,minByh,reducing,collectingAndThen,groupingBy,partioningBy;
七:并行数据处理与性能
并行流:
parallelStream 方法将集合转换为并行流,并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的内容,这样一来,就可以自动把给定操作的工作负荷分配给多核处理器的所有内核。