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 方法将集合转换为并行流,并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的内容,这样一来,就可以自动把给定操作的工作负荷分配给多核处理器的所有内核。

  

 

posted @ 2017-07-17 18:05  yangfei969  阅读(889)  评论(0编辑  收藏  举报