1. 基础知识
通过行为参数化传递代码 - 处理频繁变更的需求。类实现接口,不同的类接口方法的实现不同,作为谓词进行传递处理不同的业务。
List自带了一个sort方法(你也可以使用Collections.sort)。sort的参数类型为函数式接口,所以sort的行为可以用java.util.Comparator对象来参数化:
inventory.sort(new Comparator() {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.Runnablepublic 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() {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 Liststr = Arrays.asList("a","b","A","B"); str.sort((s1, s2) -> s1.compareToIgnoreCase(s2)); List str = Arrays.asList("a","b","A","B"); str.sort(String::compareToIgnoreCase);
lambda和方法引用实战:
法法引用
inventory.sort(comparing(Apple::getWeight));
传递代码
public class AppleComparator implements Comparator{public int compare(Apple a1, Apple a2){return a1.getWeight().compareTo(a2.getWeight());}}inventory.sort(new AppleComparator());
匿名内部类 inventory.sort(new Comparator() {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表达式
比较器复合
Comparatorc = Comparator.comparing(Apple::getWeight);
逆序
nventory.sort(comparing(Apple::getWeight).reversed());
比较器链
inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));
谓词复合
谓词接口包括三个方法:negate,and,or
PredicatenotRedApple = redApple.negate();Predicate redAndHeavyApple =redApple.and(a -> a.getWeight() > 150);Predicate 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的一个实例。
Functionf = x -> x + 1;Function g = x -> x * 2;Function h = f.andThen(g);int result = h.apply(1);Function f = x -> x + 1;Function g = x -> x * 2;Function h = f.compose(g);int result = h.apply(1); Function addHeader = Letter::addHeader; Function transformationPipeline = addHeader.andThen(Letter::checkSpelling) .andThen(Letter::addFooter);
2. 函数式数据处理
流:
流可以允许你以声明式的方式处理集合(通过查询语句来表达,而不是临时表写一个实现)要处理大量元素,提高性能,需要并行处理,利用多核架构,无需写任何多线程代码。
流处理集合可以避免垃圾变量(作为一次性中间容器),实现细节放在了它本该归属的库里了。代码以声明式的方式编写而不是如何实现一个操作,将几个基础操作链接起来,来表达复杂的数据处理流水线。
import static java.util.Comparator.comparing;import static java.util.stream.Collectors.toList;ListlowCaloricDishesName =menu.stream().filter(d -> d.getCalories() < 400).sorted(comparing(Dish::getCalories)).map(Dish::getName).collect(toList());为了利用多核架构并行执行这段代码,你只需要把stream()换成parallelStream():List lowCaloricDishesName =menu.parallelStream().filter(d -> d.getCalories() < 400).sorted(comparing(Dishes::getCalories)).map(Dish::getName).collect(toList());
中间操作,终端操作
返回一个非Stream的值为终端,返回一个Stream的值为中间操作。
使用流:
一个数据源(如集合)来执行一个查询,一个中间操作链形成一条流的流水线,一个终端操作执行流水线并生成结果。
筛选
filter方法接收一个谓词作为参数,返回一个包裹所有复合谓词的元素的流。
ListvegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());
筛选各异的元素
distinct方法,他会返回一个元素各异(hashCode,equals方法)的流。
Listnumbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
截短流
limit(n)方法,返回一个不超过给定长度的流。
Listnumbers = 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是互补的。
Listdishes = menu.stream().filter(d -> d.getCalories() > 300).skip(2).collect(toList());
映射
map方法,接收一个函数作为参数,这个函数会被应用到每个元素上,并将其映射为一个新的元素
ListdishNames = menu.stream().map(Dish::getName).collect(toList());List words = Arrays.asList("Java 8", "Lambdas", "In", "Action");List wordLengths = words.stream().map(String::length).collect(toList()); List words = Arrays.asList("Java 8", "Lambdas", "In", "Action"); List 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)时生成的单个流都被合并起来,既扁化为一个流,其实就是对流使用合并流。
ListuniqueCharacters =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所做的:Listnumbers1 = Arrays.asList(1, 2, 3);List numbers2 = Arrays.asList(3, 4);List 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方法来实现这个查询:
Optionaldish =menu.stream().filter(Dish::isVegetarian).findAny();
menu.stream().filter(Dish::isVegetarian).findAny().ifPresent(d -> System.out.println(d.getName())
查找第一个元素
ListsomeNumbers = Arrays.asList(1, 2, 3, 4, 5);Optional 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来将两个元素结合起来产生一个新值,这里我们用的是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对象:
Optionalsum = numbers.stream().reduce((a, b) -> (a + b));
最大值和最小值
Optionalmax = numbers.stream().reduce(Integer::max); Optional 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年的所有交易并按交易额排序(从低到高)
Listtr2011 = transactions.stream() .filter(transaction -> transaction.getYear() == 2011) .sorted(comparing(Transaction::getValue)) .collect(toList());
交易员都在哪些不同的城市工作过
Listcities =transactions.stream().map(transaction -> transaction.getTrader().getCity()).distinct().collect(toList()); Set cities = transactions.stream() .map(transaction -> transaction.getTrader().getCity()) .collect(toSet());
查找所有来自于剑桥的交易员,并按姓名排序
Listtraders =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
OptionalsmallestTransaction =transactions.stream().reduce((t1, t2) ->t1.getValue() < t2.getValue() ? t1 : t2); 流支持min,max方法,他们接受一个Comparator作为参数,指定计算最小或做大值是比较哪个键值 Optional smallestTransaction = transactions.stream() .min(comparing(Transaction::getValue));
数值流
reduce计算流元素的总和。
int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum); 这段代码暗含装箱成本,每个Integer都必须拆箱成一个原始类型,在进行求和,下边这种方法是错误的因为map方法会生成一个Stream,而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);Streamstream = 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对象
Listresult = 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());
数值流应用:勾股数
Streamtest = 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方法返回一个对象值流。
求两次平方根,如果让代码更加紧凑的一种可能方法是,先生成所有的三元数,然后在筛选复合条件的:
Streamtest2 =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));
由值创建流
Streamstream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");stream.map(String::toUpperCase).forEach(System.out::println);你可以使用empty得到一个空流,如下所示:Stream emptyStream = Stream.empty();
由数组创建流
int[] numbers = { 2, 3, 5, 7, 11, 13};int sum = Arrays.stream(numbers).sum();
由文件生成流
Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
long uniqueWords = 0;try(Streamlines =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> transactionsByCurrencies =new HashMap<>();for (Transaction transaction : transactions) {Currency currency = transaction.getCurrency();List transactionsForCurrency =transactionsByCurrencies.get(currency);if (transactionsForCurrency == null) {transactionsForCurrency = new ArrayList<>();transactionsByCurrencies.put(currency, transactionsForCurrency);}transactionsForCurrency.add(transaction);}
Map> 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参数来比较流中的元素。
ComparatordishCaloriesComparator =Comparator.comparingInt(Dish::getCalories);Optional 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>对象。
OptionalmostCalorieDish =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> dishesByType =menu.stream().collect(groupingBy(Dish::getType));
这里给groupingBy方法传递一个Function(以方法引用的形式)。
分类不一定像方法引用那样可用,因为你想用以分类的条件可能比简单的属性访问器更加复杂,例如:将热量不到400的菜划分为低热量,400-700划分为普通,>700划分为高热量,因为Dish类中没有将这个操作写成一个方法,所以你无法使用方法引用,但是可以将这个逻辑写成lambda表达式:
public enum CaloricLevel { DIET, NORMAL, FAT }Map> dishesByCaloricLevel = menu.stream().collect(groupingBy(dish -> {if (dish.getCalories() <= 400) return CaloricLevel.DIET;else if (dish.getCalories() <= 700) returnCaloricLevel.NORMAL;else return CaloricLevel.FAT;} ));
多级分组:map嵌套map
Map>> 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;} )));
按子组收集数据
MaptypesCount = menu.stream().collect(groupingBy(Dish::getType, counting()));
其结果是下面的Map:
{MEAT=3, FISH=2, OTHER=4}Map> 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]}
把收集器的结果转换为另一种类型
MapmostCaloricByType =menu.stream().collect(groupingBy(Dish::getType,collectingAndThen(maxBy(comparingInt(Dish::getCalories)),Optional::get)));
MaptotalCaloriesByType =menu.stream().collect(groupingBy(Dish::getType,summingInt(Dish::getCalories)));
groupingBy常常联合使用的另一个收集器是mapping方法。这个发方法接收两个参数,一个函数对流中的元素做变化,另一个则将变换的结果对象收集起来
Map> 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> 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> partitionedMenu =menu.stream().collect(partitioningBy(Dish::isVegetarian));
Map>> 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]}}
MapmostCaloricPartitionedByVegetarian =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 方法将集合转换为并行流,并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的内容,这样一来,就可以自动把给定操作的工作负荷分配给多核处理器的所有内核。