java8学习记录

java8学习

JAVA8 IN ACTOIN 学习

第一部分 基础知识

第一章:为什么要关心Java8

1.1 Java在变化

1,JAVA编写的代码更加简洁:
before:

	Collections.sort(inventory, new Comparator<Apple>() {
		public int compare(Apple a1, Apple a2){
			return a1.getWeight().compareTo(a2.getWeight());
		}
	});

after:

	inventory.sort(comparing(Apple::getWeight));

2,java8可以显式地使用计算机地多核cpu
以前大多数地Java程序都是只使用计算机地一个核;除非使用线程,但线程的使用难度大,效果差;
3,更多java8的优点

  1. Stream API
  2. 向方法传递代码的技巧
  3. 接口中的默认方法
    1,流,它支持许多处理的数据的并行操作,并且在多核CPU上执行所需的成本更低;
    2,行为的参数化
    3,函数式编程
1.1.1 java在编程语言生态系统中的位置

如今需要处理更多的数据,需要利用多核计算机或计算机集群在有效地处理。这意味着需要使用并行处理—java以前对此并不支持。

1.1.2 流处理

流可以并行地处理数据,就像工厂地流水线;
基于这一思想,java8在java.util.stream中添加了一个Stream API;Stream<T>就是一系类T类型地项目。可以链接形成一个复杂地流水线。这样做地好处是你可以在更高的抽象层次上写java8程序了,使用并行而不用一次只处理一个项目;另外一个好处就是,java8可以透明拿到几个CPU内核去分别执行你的Stream流水线。

1.1.3 用行为参数化把代码传递给方法

在java8之前必须采用传递对象,在对象内部实现方法。而java8支持把你的行为代码传递给另外一个方法。被称为行为参数化。它的重要之处在于,Stream API就是构建在通过传递代码使操作行为实现参数化;

1.1.4 并行与共享的可变数据

不需要使用synchronized,且Stream的行为就像是一个简单的函数,没有可见的副作用

1.2 java中的函数
1.2.1 方法和Lambda作为一等公民

一等公民(可以被函数操作的值)。让方法作为值也构成了其他若干java8功能的基础。java8的方法传递一般写成类名 :方法名
Lambda–匿名函数
除了允许函数称为一等值外,java8还体现了更广义的将函数作为值的思想,包括Lambda;

1.2.2 传递代码:一个例子

传递一个条件:Predicate<T>

	package lambdasinaction.chap1;

import java.util.*;
import java.util.function.Predicate;

public class FilteringApples{

    public static void main(String ... args){

        List<Apple> inventory = Arrays.asList(new Apple(80,"green"),
                                              new Apple(155, "green"),
                                              new Apple(120, "red"));	

        // [Apple{color='green', weight=80}, Apple{color='green', weight=155}]
        List<Apple> greenApples = filterApples(inventory, FilteringApples::isGreenApple);
        System.out.println(greenApples);
        
        // [Apple{color='green', weight=155}]
        List<Apple> heavyApples = filterApples(inventory, FilteringApples::isHeavyApple);
        System.out.println(heavyApples);
        
        // [Apple{color='green', weight=80}, Apple{color='green', weight=155}]
        List<Apple> greenApples2 = filterApples(inventory, (Apple a) -> "green".equals(a.getColor()));
        System.out.println(greenApples2);
        
        // [Apple{color='green', weight=155}]
        List<Apple> heavyApples2 = filterApples(inventory, (Apple a) -> a.getWeight() > 150);
        System.out.println(heavyApples2);
        
        // []
        List<Apple> weirdApples = filterApples(inventory, (Apple a) -> a.getWeight() < 80 || 
                                                                       "brown".equals(a.getColor()));
        System.out.println(weirdApples);
    }
	//java8之前我们会使用到的方法
    public static List<Apple> filterGreenApples(List<Apple> inventory){
        List<Apple> result = new ArrayList<>();
        for (Apple apple: inventory){
            if ("green".equals(apple.getColor())) {
                result.add(apple);
            }
        }
        return result;
    }

    public static List<Apple> filterHeavyApples(List<Apple> inventory){
        List<Apple> result = new ArrayList<>();
        for (Apple apple: inventory){
            if (apple.getWeight() > 150) {
                result.add(apple);
            }
        }
        return result;
    }
	
	//java8我们使用的方法
    public static boolean isGreenApple(Apple apple) {
        return "green".equals(apple.getColor()); 
    }

    public static boolean isHeavyApple(Apple apple) {
        return apple.getWeight() > 150;
    }

    public static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p){
        List<Apple> result = new ArrayList<>();
        for(Apple apple : inventory){
            if(p.test(apple)){
                result.add(apple);
            }
        }
        return result;
    }       

    public static class Apple {
        private int weight = 0;
        private String color = "";

        public Apple(int weight, String color){
            this.weight = weight;
            this.color = color;
        }

        public Integer getWeight() {
            return weight;
        }

        public void setWeight(Integer weight) {
            this.weight = weight;
        }

        public String getColor() {
            return color;
        }

        public void setColor(String color) {
            this.color = color;
        }

        public String toString() {
            return "Apple{" +
                   "color='" + color + '\'' +
                   ", weight=" + weight +
                   '}';
        }
    }

}

1.2.3 从传递方法到Lambda

上一节代码中我们代码中isHeavyAppleisGreenApple的方法我们使用一次,就要去声明一次,感觉特别麻烦。此时我们可以使用Lamabda表达式。
上面的方法可以写为

//绿色苹果
	filterApples(inventory,(Apple a) -> "green".equals(a.getColor());
//重苹果
	filterApples(inventory, (Apple a) -> a.getWeight() > 150 );
//甚至可以写成
	filterApples(inventory, (Apple a) -> a.getWeight() < 80 || "brown".equals(a.getColor()) );

所以你甚至都不需要为只用一次的方法写定义;代码更干净、更清晰;但Lambda的长度较长,难以一目了然,因此还是建议使用描述性的方法。
但是java8的设计师并没有就此打住,他们还要使用CPU的多个内核。后面引入流的使用,就可以使用计算机的多核。类似于filter的方法还有map,reduce等,还有我们接下来讨论的在Collections和Streams之间的转换;

1.3流

用流可以大大简化我们对集合的处理。
例如集合的过滤和分类:

	import static java.util.stream.Collectors.toList;
	Map<Currency,List<Transaction>>transactionsByCurrencies =
		 transactions.stream()
		.filter((Transaction t) -> t.getPrice() > 1000)
		.collect(Collectors.groupingBy(Transaction::getCurrency));

多线程并非易事
Stream API设计的原因:1,有反复出现的数据处理模式。2,这类操作常常何以并行化。调用集合的parallelStream();

	import static java.util.stream.Collectors.toList;
	List<Apple> heavyApples =
	inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150)
	.collect(toList());
默认方法(接口)

java8中加入默认方法主要是为了支持库设计者,让他们能够写出过呢个容易改进的接口。增加默认方法而不用全部修改其所有的显现类。例如java8之前集合的排序通过调用Collections.sort来实现,而现在List接口中添加了sort的默认方法,这样使用更加方便

	default void sort(Comparator<? super E> c) {
		Collections.sort(this, c);
	}

但是一个类可以实现多个接口,当多个接口的方法名相同时,这事就要制定使用那个接口的方法。

1.5 来自函数式编程的其他好思想

前面几节介绍了java中函数式编程中引入的两个核心思想:将方法和Lambda作为一等值,以及在没有可变共享状态时,函数或方法可以有效、安全地并行执行。
java8里面有一个Optional类,如果能一致使用它地话,就可以帮助我们比秒出现地NullPointer异常。
模式匹配:对于一个复杂地数据类型,模式匹配可以比if-elseif更加简明。对于这种数据类型,你可以使用多态或者时方法地重载来代替if-else。不幸的是,java8对模式匹配的支持还并不完全。

第2章 通过行为参数化传递代码

行为参数化就是可以帮助你处理频繁变更的需求的一种软件开发模式。

2.1 应对不断变化的需求
2.2 行为参数化(切入点,把方法定义在接口中)

让我们后退一步来看看更高层次的抽象,需要根据T的某些属性返回一个boolean值。我们把它称作谓词(一个返回boolean的函数)

2.3 对付啰嗦

若提供一个接口方法,我们就要根据多种不同的情况,定义多个接口的实现类。有很大可能这些实现类仅仅使用了一次。这个过程很复杂啰嗦。

2.3.1 匿名类

使用匿名类,可以有效地减少实现类。但匿名类不够好:第一,它往往比较笨重;第二,很多程序员觉得它用起来比较费解。

2.3.3 尝试使用Lambda表达式
2.3.4 将List类型进行抽象化

为了方便,我们可以一如类型参数T,提供一个更通用的方法:

	public static <T> List<T> filter(List<T> list,Predicate<T> p){
	List<T> result = new ArrayList<>();	
	for(T e : list){
		if(p.test(e)){
			result.add(e);
		}
	}	
}
2.4 正式的例子

Java API中的很多方法都可以用不同的行为来参数化;

2.4.1 用Comparator来排序
2.4.2 用Runnable执行代码块

第3章 Lambda表达式

lambda,它基本上就是没有声明名称的方法,但和匿名类一样,他也可以作为参数传递给一个方法。

3.1 Lambda管中窥豹

Lambda表达式的格式:

	(parameters) -> expression //或
	(parameters) -> {statements}
3.2 在哪里以及如何使用Lambda

当参数列表的参数式函数式接口时,可以使用函数式接口

3.2.1 函数式接口

一言以蔽之,函数式接口就是只定义一个抽象方法的接口;
函数式接口已经接触到的有 Comparator Runnable Predicate
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体来说,是函数式接口的一个实例)

3.2.2 函数描述符

函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们把这个抽象方法称为函数描述符。
@FunctionalInterface标注的接口是函数式接口,当接口中有多个抽象方法时,编译不会通过。
####3.3 把Lambda付诸实践:环绕执行模式
java7中的一个新特性:使用带资源的try语句时不用显式地关闭资源;

try(BufferedReader br = new BufferedReader(new fileReader("data.txt"))){
	return br.readLine();
}

3.4使用函数式接口

之前我们遇到地函数式接口有Comparable、Runnable和Callable。
java8地库设计师帮你在java.util.function包中引入了几个新地函数式接口。接下来会介绍Predicate、Consumer、Function。

3.4.1Predicate
	@FunctionalInterface
	public interface Predicate<T>{
		boolean test(T t);
	}
3.4.2 Consumer
	@FunctionalInterface
	public interface Consumer<T>{
		void accept(T t);
	}
3.4.3 Function
	@FunctionalInterface
	public interface Function<T, R>{
		R apply(T t);
	}

原始类型特化

上面的三个泛型函数式接口:Predicate、Consumer和Function<T,R>都带有泛型,但Java的基本数据类型,不能使用,因此有原始类型特化,例如:IntPredicate等,避免自动开装箱,浪费资源。
原始类型特化1
原始类型特化2

请注意,任何函数式接口都不允许抛出受检异常(checked exception)。如果你需要Lambda表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个 try/catch 块中。
例如:

	@FunctionalInterface
	public interface BufferedReaderProcessor {
		String process(BufferedReader b) throws IOException;
	}
	BufferedReaderProcessor p = (BufferedReader br) -> br.readLine();
	Function<BufferedReader, String> f = (BufferedReader b) -> {
		try {
			return b.readLine();
		} catch(IOException e) {
			throw new RuntimeException(e);
		}
	};

3.5 类型检查、类型推断以及限制
3.5.1 类型检查

Lambda的类型式从使用Lambda的上下文中推断出来的;

3.5.2 同样的Lambda,不同的函数式接口

同一个Lambda可用于多个不同的函数式接口

3.5.3 类型推断

Lambda根据目标函数推断出变量的类型

3.5.4 使用局部变量

Lambda表达式能够使用局部变量,但局部变量必须定义为final,或者事实上式final的。

3.6 方法引用

方法的引用让你可以重复使用现有的方法定义,并像Lambda一样床底它们。在一些情况下,比起使用Lambda表达式,他们似乎更易读,感觉更自然。

	inventory.sort((Apple a1, Apple a2)-> a1.getWeight().compareTo(a2.getWeight()));
	//之后(使用方法引用和 java.util.Comparator.comparing ):
	inventory.sort(comparing(Apple::getWeight));

3.6.1 方法引用

方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。更易读。其实际作用就是Lambda的快捷写法,并没有真正地去调用。
如何构建方法地引用:

  1. 指向静态方法地方法引用(Integer::parseInt);
  2. 指向任意类型实例地方法引用(String::length);
  3. 指向现有对象地实例方法引用(apple(局部变量)::getWeight)
3.6.2 构造函数的引用

对于一个现有的构造函数,可以利用它的名称和关键字new来创建它的一个引用:ClassName::new。它的功能与指向静态方法的引用类似。
1,假如有一个无参构造,它适合Supplier的签名。

	Supplier<Apple> c1 = Apple::new;
	Apple a1 = c1.get();

2,如果你的构造签名式Apple(Integer weight),那么它就适合Function接口的签名,于是你可以这样写:

	Function<Integer,Apple> c2 = Apple::new;
	Apple a2 = c2.apply(110);

比较器的使用
由于Comparator中有静态方法:

	public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)

所以我们需要比较器时可以直接使用这个静态方法,代码会更易读。

3.7符合Lambda表达式的有用方法

java8中能够传递Lambda表达式的ComParator、Function和Predicate都提供了允许你进行复合的方法。
比如说两个谓词之间做一个or的操作,组成一个更强大的谓词。而且你还可以让一个函数的结果称为另外一个函数的输入。(函数式接口的其他方法都式默认方法)

3.7.1 比较器的复合

1,逆序

	inventory.sort(comparing(Apple::getWeight).reversed);//按照重量递减排序

2,比较器链
如果发现两个苹果的重量一样,你可能需要把这两个苹果再次进行排序(如按照颜色,生产地等等)。thenComparing方法就是做这个用的。它接受一个函数作为参数就像coparing函数一样,如果第一个排序排不出的话就由后边的进行排序。

	inventory.sort(comparing(Apple::getWeight)
	.reversed()
	.thenComparing(Apple::getCountry));
3.7.2 谓词的符合

谓词接口包括三个方法:negate(非)、and和or,让你可以重用已有的Predicate来常见更复杂的谓词。
1,使用negate方法来返回一个predicate的非

	Predicate<Apple> notRedApple = redApple.negate();

2,你可能想要把两个Lambda用and方法组合起来,不如获取既是红色苹果,又较重的苹果;

	Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);

3,你可以进一步组合谓词,表达要么是重的红苹果,要么是绿苹果;

	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。

3.8.3 函数复合

最后,你还可以把 Function 接口所代表的Lambda表达式复合起来。 Function 接口为此配了 andThen 和 compose 两个默认方法,它们都会返回 Function 的一个实例。
1,andThen方法会返回一个函数,它先对输入应用一个给定的函数,再对输出应用另一个函数。比如假设有一个函数 f 给数字加1 (x -> x + 1) ,另一个函数 g 给数字乘2,你可以将它们组成一个函数 h ,先给数字加1,再给结果乘2:

	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);//最终result的值是4

2,你也可是类似地使用compost方法,这时先执行compost函数中的内容:

	Function<Integer, Integer> f = x -> x + 1;
	Function<Integer, Integer> g = x -> x * 2;
	Function<Integer, Integer> h = f.compost(g);
	int result = h.apply(1);//最终result的值是3

第二部分 函数式数据处理

本部分内容将深入探索Stream API
第4章介绍了流的概念
第5章详细讨论了表达复杂数据处理查询可以使用的流操作
第6章介绍了收集器
第7章了解流为何可以自动并行执行,并利用多核框架的优势。
###第4章 引入流
流的优点:
可以像操作数据库一样操作集合;
可以并行处理,并利用多核框架;

4.1 流是什么

流是Java API的新成员,它允许你以声明性方式处理数据集合;
普通使用流的方式是集合.Stream()
利用多核架构并行执行集合.parallelStream()
基础代码:

	//集合的定义
	List<Dish> menu = Arrays.asList(
	new Dish("pork", false, 800, Dish.Type.MEAT),
	new Dish("beef", false, 700, Dish.Type.MEAT),
	new Dish("chicken", false, 400, Dish.Type.MEAT),
	new Dish("french fries", true,530,Dish.Type.OTHER),
	new Dish("rice", true, 350, Dish.Type.OTHER),
	new Dish("season fruit", true, 120, Dish.Type.OTHER),
	new Dish("pizza", true, 550, Dish.Type.OTHER),
	new Dish("prawns", false, 300, Dish.Type.FISH),
	new Dish("salmon", false, 450, Dish.Type.FISH) );
	//Dish类的定义
	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 }
	}

下一章我们会谈到很多模式,如筛选、切片、查找、匹配、映射、和归约。

4.2 流简介

java8 中的集合支持一个新的stream方法,它会返回一个流。后续会介绍更多得到流的方法。
流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中
的常用操作,如 filter 、 map 、 reduce 、 find 、match 、 sort 等。流操作可以顺序执行,也可并行执行。

4.3 流与集合

粗略地说,集合和流之间地差异就在于什么时候进行计算。集合是一个内存中的数据结构,它包含数据结构中的所有的值— 集合中的每个元素都得计算出来才能添加到集合中。(你可以往集合里加东西或者删东西,但是不管什么时候,集合中的每个元素都是放在内存里的,元素都得先算出来才能成为集合的一部分。)
相比之下,流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计算的。从另外一个角度来说,流就像一个延迟创建的集合:只有在消费者要求的时候才会计算值。

4.3.1 只能遍历一次

流只能消费一次。

4.3.2 外部迭代与内部迭代

使用Collection接口需要用户去做迭代,这称为外部迭代。相反,Streams库使用内部迭代。

4.4 流操作

流的操作可以分为中间操作和终端操作。

4.4.1 中间操作

诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理

4.4.2 终端操作

终端操作会从流的流水线生成结果。其结果不是流,值为List、Integer甚至时void。
forEach返回的就是void。
count()返回一个long。

4.4.3 使用流

总而言之,流的使用一般包括三件事:

  1. 一个数据源(如集合)来执行一个查询;
  2. 一个或者多个中间操作,形成一条流的流水线;
  3. 一个终端操作,执行流水线,并能生成结果。
    中间操作和终端操作
    ###第5章 使用流
    在本章你能看到Stream API支持的许多操作。如筛选、切片、映射、查找、匹配和规约。
    后边会介绍一些特殊的流:数值流、来自文件和数组等多种来源的流,最后是无限流;
5.1 筛选和切片
5.1.1用谓词进行筛选

Streams接口支持filter方法进行筛选。

5.1.2 筛选各异的元素

流支持一个叫distinct的方法,它会返回一个元素各异(根据流所生成元素的hashCode和equals方法实现(对与我们自定义的类要注意))的流。

5.1.3 截断流

流支持limit(n)方法,该方法会返回一个不超过给定长度的流。

5.1.4 跳过元素

流还支持skip(n)方法,返回一个扔掉前n个元素的流。如果流中元素个数不足n个,则返回一个空流。

5.2 映射

一个常见的操作是从某些对象中选出对象的某些属性。Stream API通过map和flatMap方法提供了类似的工具。

5.2.1 对流中的每一个元素应用函数

流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。

5.2.2 流的扁平化

需求:给定单词列表[“Hello”,“world”],返回不同的字符。
由于map(word -> word.split(""))返回的是一个String[]的流。所以不符合要求。
1,尝试使用map和Arrays.stream()
首先,你需要一个字符流,而不是数组流。有一个叫做**Arrays.stream()**的方法可以接受一个数据并产生一个流,
这时

	words.stream()
		.map(word -> word.split(""))
		.map(Arrays::stream)
		.distinct()
		.collect(toList());

这样操作仍然搞不定,这是因为,你现在得到的是一个流的列表而不是字符的列表(Arrays::stream 得到的是流),这样得到是流的流。
2,使用flatMap

	List<String> uniqueCharacters =
		words.stream()
			.map(w -> w.split(""))
			.flatMap(Arrays::stream)
			.distinct()
			.collect(Collectors.toList());

使用flatMap方法的效果是,各个数组并不是分别映射程一个流,而是映射成流的内容。所有使用Arrays::stream生成的单个流都被合并起来,即扁平化为一个流。
一言以蔽之,flatMap方法可以把流中生成的流连接在一起,称为一个流;

5.3 查找和匹配

另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。Stream API通过allMatch、anyMatch、noneMatch、findFirst、findAny方法提供了这样的工具。

5.3.1 检查谓词是否至少匹配一个元素

anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。

	if(menu.stream().anyMatch(Dish::isVegetarian)){
		System.out.println("The menu is (somewhat) vegetarian friendly!!");
	}

该方法返回一个boolean,因此是一个终端操作。

5.3.2 检查谓词是否匹配所有元素

allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词。
noneMatch方法相对的是noneMatch。它可以确保流中所有元素与给定的谓词都不匹配。

5.3.3 查找元素

findAny方法将返回当前流中的任意元素,可以与其他操作结合使用。比如:

	Optional<Dish> dish = menu.stream()
							.filter(Dish::isVegetarian)
							.findAny();

这时流水线在后台进行优化使其只走一边,并在找到一个合适的元素时,立即短路返回。
Option<T>
Option类是一个容器类,代表一个值存在或者不存在。
主要方法

  1. boolean isPresent()
  2. void ifPresent(Consumer<T> block)
  3. T get()
  4. T orElse(T other)
    #####5.3.4 查找第一个元素
    findFirst()用于查询流中第一个(流必须是有序的,如List的流)合适的元素;它的工作方式类似于findAny。

findAny和findFirst该使用哪一个呢?
答案是并行。找到第一个元素在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为他在使用并行流时限制较少。

5.4 规约

在本节你将看到如何把一个流中的元素组合起来,使用reduce操作来表达更复杂的查询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的菜是哪一个”。

5.4.1 元素的求和

reduce的使用

	List<Integer> nums = Arrays.asList(1,2,3,4,5,6);
	Integer reduce = nums.stream().reduce(0, Integer::sum);
	System.out.println(reduce);

reduce接受两个参数

  • 一个初始值,这里是0;
  • 一个BinaryOperator 来将两个元素结合起来产生一个新值,这里我们使用的是(a,b)-> a+b;
    你也很容易得到所有数的乘积,不过此时初始值应该设置为1.
    reduce的无初始值
    reduce还有一个无初始值的重载变体,它不接受初始值,但会返回一个Optional对象。
    #####5.4.2 最大值和最小值
    使用reduce可以很轻松地获取最大值以及最小值。

最大值和最小值还可以用min和max方法代替,它们接受一个Comparator的参数。
####5.6 数值流
#####5.6.1原始类型流特化
java8引入了三个原始类型特化接口来解决这些问题:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。并且每个几口都带了常用的规约方法,例如sum(Stream接口中没有),max带来了进行常用数值规约的新方法。
1,映射到数值流
将流转换为特化版的常用方法是mapToInt、mapToDouble和mapToLong。
2,转换回对象流
boxed();例如intStream.boxed
3,默认值OptionalInt
当计算IntStream中的最大值时,由于我们不知道流中是不是有元素。Optional的原始类型特化版本有:OptionalInt,OptionnalDouble和OptionalLong。
例如要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:

	OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();

如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值;

	int max = maxCalories.orElse(1);
5.6.2 数值范围

java8引入了两个可以用于 IntStream 和 LongStream 的静态方法,帮助生成这种范围:range 和 rangeClosed 。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但range是不包含结束值的,而 rangeClosed 则包含结束值。

	IntStream evenNumbers = IntStream.rangeClosed(1, 100);//流中的数据范围是[1,100]
	IntStream evenNumbers = IntStream.range(1, 100);//流中的数据范围是[1,100);
5.6.3 数值流的应用

IntStream中的map方法只能为流中的每个元素返回另外一个int,如果你想生成对象类型的话,就需要在map之前调用box()方法,或者改map()mapToObj()

5.7 构建流

前面我们已经介绍了从集合生成流和用数值范围生成流,接下来会介绍更多生成流的方法。
#####5.7.1 由值生成流

	Stream.of("Java 8","Lambda","In","Action");
5.7.2 由数组创建流
	int[] numbers = {2,3,4,5,6,7};
	int sum = Arrays.stream(numbers).sum();
5.7.3 由文件生成流

java中用于处理文件等I/O操作的NIO API(非阻塞I/O)已更新,以便使用Stream API。java.nio.file.Files中由很多静态方法都会返回一个流。例如,一个很有用的方法是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){
	}
5.7.4 由函数生成流:创建无限流

Stream API提供了两个静态方法来从函数生成流:Stream.iterateStream.generate。这两个操作可以创建所谓的无限流,一般需要使用limit(n);
1,迭代

	Stream.iterate(0, n -> n + 2)
				.limit(5)
				.forEach(System.out::println);

参数:
1,初始值,在这里是0;
2,UnaryOperator。这里我们使用的是lambda。返回的是前一个元素加上2;
2,生成
generate不是依次对每个新生成的值应用函数的。它接受一个Supplier类型的Lambda提供新的值。我们来看一个简单的用法:

	Stream.generate(Math::random)
				.limit(5)
				.forEach(System.out::println);

第6章 用流收集数据

6.1 收集器简介

函数式编程需要你做的是,指出你需要得到的结果。可以指定收集器来获取你想要的结果。

6.1.1 收集器用作高级归约

一般来说Collector会对元素应用一个转换函数(例如不体现任何效果的toList),将结果累积在数据结构中。

6.1.2 预定义收集器

本章内容主要讲Collectors提供的工厂方法创建的收集器。它们主要提供了三大功能:

  • 将流元素归约和汇总为一个值;
  • 元素分组
  • 元素分区(使用谓词作为分组函数)
6.2 规约和汇总

查询数量

	long howManyDishes = menu.stream().collect(Collectors.counting()); //查询菜单里有多少种菜
	//你也可以写得更加直接
	long howManyDishes = menu.stream().count();

一般使用收集器时,我们会导入Collectors类的所有静态工厂方法,这样使用更加明了;

	import static java.util.stream.Collectors.*;

以下代码均直接调用静态工厂方法

6.2.1 查找流中的最大值和最小值

假设你想要找出菜单中热量最高的菜。你可以使用两个收集器, Collectors.maxByCollectors.minBy ,来计算流中的最大或最小值。

	Optional<Dish> collect = menu.stream()
			.collect(maxBy(Comparator.comparing(Dish::getCalories)));
		System.out.println(collect.get());
6.2.2 汇总

Collectors类专门为汇总提供了了一些工厂方法:Collectors.summingInt,Collectors.summingLongCollectors.summingDouble,用来收集成所需数据类型的值。
计算菜单列表中的总热量:

 	int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));

但汇总不仅仅时求和,还有averagingInt,连同对应的averagingLong和averagingDouble。
可以计算出平均值。

	double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));

但有时你想一下子获取到流中的最大值,最小值以及总和和平均值。这时候你可以使用summarizingInt工厂方法返回的收集器。例如,通过一次summarizing操作你就可以算出菜单中元素的个数,并得到菜单热零总和、平均值、最大值和最小值。

	IntSummaryStatistics collect = menu.stream().collect(summarizingInt(Dish::getCalories));
	System.out.println(collect + "" + collect.getAverage());

同样,相应的summerizingLong和summarizingDouble工厂方法和有关的LongSummaryStatistics和DoubleSummaryStatics类型,适用于收集的属性是原始类型long或double情况。

6.2.3 连接字符串

joining工厂方法返回的收集器,会把流中所有元素的toString方法得到的字符串连接在一起。我们可以把菜肴的名字连接在一起,如下所示:

	String shortMenu = menu.stream().map(Dish::getName).collect(joining());

如果Dish有返回菜名的toString方法也可以

	String shortMenu = menu.stream().collect(joining());

两者返回的值相同。
都返回porkbeefchickenfrench friesriceseason fruitpizzaprawnssalmon的结果。但这样字符串的可读性不好。
joining工厂方法有一个重载方法可以接受元素之间的分隔符。

	String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));

这时结果是pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon

6.2.4 广义的归约汇总

事实上,上面所讨论的收集器,都是reduceing工厂方法定义的归约过程的特殊情况而已。Collectors.reducing工厂方法是所有这些特殊情况的一般化。
现在用该方法计算所有菜肴的总热量:

	int totalCalories = menu.stream().collect(reducing(0,Dish::getCalories,(i,j)-> i + j));

它需要三个参数:

  1. 第一个参数是归约操作的起始值,也就是流中没有元素时的返回值,所以很显然对于数值和而言0是一个合适的值。
  2. 第二个参数就是你在6.2.2节使用的函数,将菜肴转化成一个表示热量的int。
  3. 第三个参数是一个BinaryOperator,将两个项目累积程一个同类型的值。这里就是int的和。
    同样,你可以reducing来找到热量最高的菜。
	Optional<Dish> mostCalorieDish =
		menu.stream().collect(reducing(
			(d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));

你可以把单参数的reducing方法创建的收集器看作三个参数方法的特殊情况,它把流中的第一个项目作为起始值,把恒等函数(即函数仅仅是返回其输入参数)作为一个转换函数。这意味着要是空流时,收集器就没有起点,因此返回一个Optional对象。
收集和归约
reduce方法和collect方法的不同。reduce方法旨在把两个值结合成一个新值,它是一个不可变的归约;而collect方法的设计就是要改变容器,从而累积输出的结果。

选择合适的方法,例如计算所有菜肴的热量的总和:

	int totalCalories = menu.stream().mapToInt(Dish::getCalories).sum();

这个方法最为合适,因为它省去了拆装箱等过程。

6.3 分组

Collectors.groupingBy工厂方法返回的收集器就可以轻松地完成分组。

	Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));

结果是下面的map:
{FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza], MEAT=[pork, beef, chicken]}

groupingBy后的是Function,我们称之为分类函数。分类函数有时候不是引用已有的方法,我们想更加灵活,比如我们把菜肴分为低热量(diet)、普通热量(normal)和高热量(fat)。

	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;
																					} ));

现在我们可以按菜肴的类型和热量进行分组,但是我们想同时按照这两种标准进行分类该怎么办呢?

6.3.1 多级分组

我们可以使用双参数版本的Collectors.groupingBy

	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]}}

这样就生成了一个双层Map

6.3.2 按子组收集数据

在上一节中,我们看到可以把第二个 groupingBy 收集器传递给外层收集器来实现多级分组。但进一步说,传递给第一个 groupingBy 的第二个收集器可以是任何类型,而不一定是另一个 groupingBy 。例如,要数一数菜单中每类菜有多少个,可以传递 counting 收集器作为groupingBy 收集器的第二个参数:

	Map<Dish.Type, Long> typesCount = menu.stream().collect(groupingBy(Dish::getType, counting()));

返回的结果是:{MEAT=3, FISH=2, OTHER=4}
实际上普通的groupingBy(f)是groupingBy(f,toList())的简便写法。
再举一个例子:

	Map<Dish.Type, Optional<Dish>> mostCaloricByType =
		menu.stream()
			.collect(groupingBy(Dish::getType,
				maxBy(comparingInt(Dish::getCalories))));

结果为:{FISH=Optional[salmon], OTHER=Optional[pizza], MEAT=Optional[pork]}
1,把收集器的结果转换为另一种类型
如上所示,因为分组操作的Map结果中的每个值都包装了Optional没有什么用,所以想把它去掉。这时要把收集器返回的结果转换为另一种类型,你可以使用Collectors.collectionAndThen工厂方法返回的收集器。

	Map<Type, Dish> collect = menu.stream().collect(
				groupingBy(Dish::getType,collectingAndThen(
				maxBy(Comparator.comparingInt(Dish::getCalories)),
				Optional::get)));

Collectors.collectingAndThen这个工厂方法接受两个参数—要转换的收集器以及转换函数,并返回另一个收集器。这个收集器相当于旧收集器的一个包装。这个操作在这里是安全的,因为收集器永远不会返回Optional.empty();
返回的是{FISH=salmon, OTHER=pizza, MEAT=pork}
2,与grupingBy联合使用的其他收集器
常常和groupingBy联合使用的另一个收集器是mapping方法生成的。这个方法接受两个参数:

  1. 元素的变换
  2. 将元素的结果收集起来;
    例子:
	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() )));

结果:{OTHER=[DIET, NORMAL], MEAT=[DIET, NORMAL, FAT], FISH=[DIET, NORMAL]}
toSet收集器对返回的Set是什么类型没有任何保证,你可以使用toCollection来保证返回的类型,比如返回的是HashSet,

	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) )));
6.4 分区

分区是分组的特殊情况:由一个谓词作为分类函数,他被称为分区函数。这意味这生成的Map的键是类型是Boolean,于是它最多可以分为两组。

	Map<Boolean, List<Dish>> partitionedMenu =
		menu.stream().collect(partitioningBy(Dish::isVegetarian));

返回下面的map:
{false=[pork, beef, chicken, prawns, salmon],true=[french fries, rice, season fruit, pizza]}

6.4.1 分区的优势

分区的好处在于保留了分区函数返回 true 或 false 的两套流元素列表。
而且就像你在分组中看到的, partitioningBy工厂方法有一个重载版本,可以传递第二个收集器。

	Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType =
		menu.stream().collect(
			partitioningBy(Dish::isVegetarian,
			groupingBy(Dish::getType)));

这将产生一个二级Map:{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)));
6.4.2 将数字按质数和非质数分区

只需要添加判断是否是质数的方法即可;
总结
在这里插入图片描述
在这里插入图片描述
[

6.5 收集器接口

collector接口包含了一系列的方法,为实现具体的归约操作提供了范本。我们也可以为Collector接口提供自己的实现。
首先让我们看看Collector接口的定义,它列出了接口的签名以及五个方法;

	public interface Collector<T,A,R>{
		Supplier<A> supplier();
		BiConsumer<A,T> accumulator();
		Function<A,R> finisher();
		BinaryOperator<A> combiner();
		Set<Characteristics> charactertics();
	}

本列表适用于一下定义。

  • T是流中要收集的项目的泛型。
  • A是累加器的类型,累加器是在收集过程中用于累积部分结果的对象。
  • R是收集器操作得到的对象的类型(通常但不一定是集合)
    例如,你可以实现一个ToListCollector类,将Stream中的所有元素收集到一个List里,它的签名如下:
	public class ToListCollector<T> implements Collector<T, List<T>, List<T>>

我们很快就会澄清,这里用于累积的对象也将是收集过程的最终结果。

6.5.1 理解Collector接口声明的方法

编写上边的ToListCollector
1,建立新的结果容器:supplier 方法
supplier方法返回一个结果为空的Supplier,也就是一个无参构造函数,在调用时它会创建一个空的累加器实例。

	@Override
	public Supplier<List<T>> supplier() {
		return ArrayList::new;
	}

2,将元素添加到结果容器:accumulator方法

	@Override
	public BiConsumer<List<T>, T> accumulator() {
		return List::add;
	}

3,将结果容器应用最终转换:finisher方法
在遍历完流后,finisher方法必须返回在累积过程的最后要调用一个函数,以便将累加器对象转换为整个集合操作的最终结果。在本实例中累加器结果刚好是最终结果,因此无需进行转换。所以finisher方法只需要返回identity函数:

	@Override
	public Function<List<T>, List<T>> finisher() {
		return Function.identity();
	}

这三个方法已经足以对流进行顺序归约.但实际细节可能还要复杂一些,一方面是因为流的延迟性质;另一方面则是理论上要进行并行归约.
4,合并两个结果容器: combiner 方法
四个方法的最后一个,它定义了对流的各个子部分进行并行处理时,各个字部份归约所得到的累加器要如何合并.本例子是List的合并.(个人感觉应该放在3之前进行)

	@Override
	public BinaryOperator<List<T>> combiner() {		
		return (list1,list2) -> {
			list1.addAll(list2);
			return list1;
		};
	}

有了第四个方法,就可以对流进行并行归约了.它会用到Java7中引入的分支/合并框架和Spliterator抽象(下章介绍).
5,characteristics 方法
返回一个不可变的Characteristics集合,它定义了收集器的行为,尤其是关于流是否可以并行归约,以及可以使用那些优化的提示.做相关的优化.
Characteristics是一个包含三个项目的枚举.

  • UNORDERED–归约结果不受流中项目的遍历和累积顺序的影响.
  • CONCURRENT–accumulator函数可以从多个线程同时调用,其该收集器可以并行归约流.如果收集器没有标记为UNORDERED,那它仅在用于无序数据源时才可以并行归约.
  • IDENTITY_FINISH–这表明完成器方法返回的函数是一个恒等函数,可以跳过.这种情况下,累加器对象将会直接用做最终结果.这也意味着,将累加器A不加检查地转换为结果R是安全的.
    目前我们的这个实例时IDENTITY_FINISH,因为累积的结构就是最终的结果;但它不是UNORDERED,因为在有序流上的时候,我们还是希望顺序能够保留在List中;最后,它是CONCURRENT的,但我们刚才说过了,仅仅在背后的数据是无须的时候才会并行处理.
	@Override
	public Set<Characteristics> characteristics() {
		return Collections.unmodifiableSet( //返回一个不可变的Set集合
				EnumSet.of(Characteristics.IDENTITY_FINISH,  //枚举集合Characteristics.CONCURRENT)
				);
	}

现在来收集菜单中的所有Dish:

	List<Dish> dishes = menuStream.collect(new ToListCollector<Dish>());

标准的是:

	List<Dish> dishes = menuStream.collect(toList());

这两个方法并不完全相同,主要的优化的一个主要方面是 java API所提供的收集器在需要返回空列表时使用了Collections.empty()这个单例.

进行自定义收集而不去实现Collector
对于 IDENTITY_FINISH 的收集操作,还有一种方法可以得到同样的结果而无需从头实现新的 Collectors 接口.。 Stream 有一个重载的 collect 方法可以接受另外三个函数—— supplier 、
accumulator 和 combiner.
我们可以这样写:

	List<Dish> dishes = menuStream.collect(
							ArrayList::new,
							List::add,
							List::addAll)
6.6 开发你自己的收集器一获得更好的性能

收集质数

	package com.cheng.learningjava8.chap6;

import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.IntStream;

public class CustomCollectorTest {
	
	public static boolean isPrime(List<Integer> primes,int candidate) {
		int cadidateRoot = (int)Math.sqrt(candidate);
		return takeWhile(primes, i -> i <= cadidateRoot)
				.stream()
				.noneMatch(p -> candidate % p == 0);
	}
	
	public static <A> List<A> takeWhile(List<A> list,Predicate<A> p){
		int i = 0;
		for(A item : list) {
			if(!p.test(item)) {
				return list.subList(0, i);
			}
			i++;
		}
		return list;
	}
	
	public Map<Boolean, List<Integer>> partitionPrimesWithCustomCollector(int n) {
		return IntStream.rangeClosed(2, n).boxed().collect(new PrimeNumbersCollector());
	}

	public static void main(String[] args) {
		Map<Boolean, List<Integer>> partitionPrimesWithCustomCollector = new CustomCollectorTest().partitionPrimesWithCustomCollector(10);
		System.out.println(partitionPrimesWithCustomCollector);
	}
}

	package com.cheng.learningjava8.chap6;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

public class PrimeNumbersCollector implements Collector<Integer, Map<Boolean, List<Integer>>,
Map<Boolean, List<Integer>>>{

	@Override
	public Supplier<Map<Boolean, List<Integer>>> supplier() {
		return () -> {
			HashMap<Boolean, List<Integer>> result = new HashMap<Boolean, List<Integer>>();
			result.put(Boolean.TRUE, new ArrayList<Integer>());
			result.put(Boolean.FALSE, new ArrayList<Integer>());
			return result;
		};
	}

	@Override
	public BiConsumer<Map<Boolean, List<Integer>>, Integer> accumulator() {
		return (Map<Boolean, List<Integer>> acc, Integer candidate) -> {
			acc.get( CustomCollectorTest.isPrime(acc.get(true), candidate) )
			.add(candidate);
			};
	}

	@Override
	public BinaryOperator<Map<Boolean, List<Integer>>> combiner() {
		return (Map<Boolean, List<Integer>> map1,
				Map<Boolean, List<Integer>> map2) -> {
					map1.get(true).addAll(map2.get(true));
					map1.get(false).addAll(map2.get(false));
					return map1;
					};
	}

	@Override
	public Function<Map<Boolean, List<Integer>>, Map<Boolean, List<Integer>>> finisher() {
		return Function.identity();
	}

	@Override
	public Set<Characteristics> characteristics() {
		return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH));
	}

}

6.6.2 比较收集器的性能

自定义的收集器性能确实比原来的性能高一些;


第7章 并行数据与性能

7.1 并行流

在第4章中我们简单地说过,通过对收集源调用ParallelStream方法来把集合转换为并行流.

7.1.1 将顺序流转换为并行流

可以显式地调用parallel方法把顺序流改为并行流;

	public static long parallelSum(long n) {
	return Stream.iterate(1L, i -> i + 1)
		.limit(n)
		.parallel()
		.reduce(0L, Long::sum);
	}

如果想把并行流该为顺序流,只需要调用sequential方法.parallelStream和sequential不能同时调用,最终是否并行主要看最后调用地哪个(这两个方法并不能改变流,而是改变流地一个标志(类似于booelan));
例如:

	stream.parallel()
		.filter(...)
		.sequential()
		.map(...)
		.parallel()
		.reduce();

最后是并行处理,因为最后调用的是paralle;
并行流默认使用的是ForkJoinPool,它默认的线程数量是你的处理器数.
iterate函数使用并行流会更慢,因为每次运行都要依赖前面的结果.
而使用range方法就可以有效地使用并行流.
并行化并不是没有代价的。并行化过程本身需要对流做递归划分,把每个子流的归纳操作分配到不同的线程,然后把这些操作的结果合并成一个值。iterate运行之间耦合较大,分流会影响效率.
在多个内核之间移动数据的代价也可能比你想的要大,所以很重要的一点是要保证在内核中并行执行工作的时间比在内核之间传输数据的时间长.
#####7.13 正确使用并行流
错用并行流而产生错误的首要原因,就是使用的算法改变了某些共享状态。
#####7.1.4 高效使用并行流
什么时候使用并行流呢?数据量达到多少使用好呢?

  1. 如果有疑问,测试;
  2. 留意装箱;
  3. 有写操作本身在并行流上的性能就很差:limit和findFirst等依赖于元素顺序的操作,它们在并行流上执行的代价非常大.但是你可以调用unordered方法把有序流变为无序流,这个时候调用limit时效率会更高一点.
  4. 还要考虑流的操作流水线的总计算成本.设N是要处理的元素的总数,Q是一个元素通过的大致成本.Q值较高就意味着使用并行流时性能好的可能性比较大.
  5. 对于较小的数据量,选择并行流不是一个好的选择.
  6. 要考虑流背后的数据结构是否易于分解.ArrayList比LinkedList更易于拆解.range工厂创建的原始类型也可以快速分解.
  7. 流自身的特点
  8. 考虑终端操作中合并步骤的代价的大小.
    流的数据源和可分解性:
    源 可分解性
    ArrayList 极佳
    LinkedList 差
    IntStream.range 极佳
    Stream.iterate 差
    HashSet 好
    TreeSet 好
7.2 分支/合并框架
7.3 Spliterator

Part3 高效java8编程

第8章 重构、测试和调试

8.1为改善可读性和灵活性重构代码
8.1.2 从匿名类到Lambda表达式的转换

匿名类和Lambda的最大不同之处:
1,this 在匿名类中表示匿名类的对象,而在Lambda中表示Lambda表达式所在类的对象;
2,匿名类可以屏蔽包含的类的变量,而Lambda表达式不能;
例如

	int a = 10;
	Runnable r1 = () -> {
	int a = 2;//此时编译报错
	System.out.println(a);
	};

3,改为Lambda表达式可能会更难懂;

8.1.3 从Lambda表达式到方法的引用的转换

改为方法的引用会使代码更加易懂

8.14 从命令式的数据处理切换到Stream
8.1.5 增加代码的灵活性

1,才用函数接口

2,有条件的延迟执行

第10章用Optional取代null

例如Person类下的Car属性改为Optional

10.3.1 创建Optional对象

1,声明一个空的optional

	Optional<Car> = Optional.enpty();

2,依据一个非空值创建 Optional

    Optional<Car> optCar = Optional.of(car);
	//如果car为null时,或报空指针异常

3,可接受null的Optional

	Optional<Car> optCar = Optional.ofNullable(car);
10.3.2 使用 map 从 Optional 对象中提取和转换值
	Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
	Optional<String> name = optInsurance.map(Insurance::getName);
10.3.3 多层对象时使用flatMap
10.3.4 默认行为引用Optional对象
  1. get() 最简单但又最不安全的.如果存在就返回,如果不存在就报空指针异常;
  2. orElse(T t) 不存在时提供一个默认值
  3. orElseGet(Supplier) 是orElse的延迟调用版
  4. orElseThrow(Supplier) 不存在时定制一个异常类型
  5. ifPresent (Consumer)

第12章 新的日期和时间API

12.1 LocalDate、LocalTime、Instant、Duration以及Period
12.1.1使用LocalDate和LocalTime

1,LocalDate
该类的实例是一个不可变对象,包含年月日,但不包含时间;
你可以创建一个LocalDate实例: 使用静态方法.of
你还可以使用工厂方法从系统中获取当前的时间.now()

	LocalDate date = LocalDate.of(2014, 3, 18);
	int year = date.getYear();
	Month month = date.getMonth();
	int day = date.getDayOfMonth();
	DayOfWeek dow = date.getDayOfWeek();
	int len = date.lengthOfMonth();
	boolean leap = date.isLeapYear()
	//你也可以通过传递TemporalFileld参数获取
	int year = date.get(ChronoField.YEAR);
	int month = date.get(ChronoField.MONTH_OF_YEAR);
	int day = date.get(ChronoField.DAY_OF_MONTH);

2,LocalTime

	LocalTime time = LocalTime.of(13, 45, 20);
	int hour = time.getHour();
	int minute = time.getMinute();
	int second = time.getSecond();

解析:

	LocalDate date = LocalDate.parse("2014-03-18");
	LocalTime time = LocalTime.parse("13:45:20");
合并时间和时间

LocalDateTime是LocalDate和LocalTime的合体,但不包含时区信息,可以直接创建,也可以通过下边的方法:

	LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20);
	LocalDateTime dt2 = LocalDateTime.of(date, time);
	LocalDateTime dt3 = date.atTime(13, 45, 20);
	LocalDateTime dt4 = date.atTime(time);
	LocalDateTime dt5 = time.atDate(date);
	LocalDate date1 = dt1.toLocalDate();
	LocalTime time1 = dt1.toLocalTime();
机器时间

Instant
方法有:

	Instant.ofEpochSecond(3);
	Instant.ofEpochSecond(3, 0);
	Instant.ofEpochSecond(2, 1_000_000_000);
	Instant.ofEpochSecond(4, -1_000_000_000);
	Instant.ofEpochMilli(System.currentTimeMillis())
	Instant.now();
定义Duration或Period

上面所有的类都实现了Temporal接口(暂时的,当时的);
Duration
Duration就是两个Temporal就是两个Temporal之间的对象。主要方法有Duration.between(temporal1,temporal2);

	Duration d1 = Duration.between(time1, time2);
	Duration d1 = Duration.between(dateTime1, dateTime2);
	Duration d2 = Duration.between(instant1, instant2);

Duration类主要是用于以秒和纳秒衡量时间的长短。适用的主要有LocalTime,LocalDateTime和Instant。
LocalDateTime和Instant不能混用;
Period
如果你要以年月日的方式对时间建模,可以使用类Period类.使用Period.between(LocalDate1,LocalDate2);

当然这两个类提供了了很多方便的静态方法.
如:

	Duration threeMinutes = Duration.ofMinutes(3);
	Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
	Period tenDays = Period.ofDays(10);
	Period threeWeeks = Period.ofWeeks(3);
	Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);

在这里插入图片描述

12.2 操纵、解析和格式化日期

上面介绍的时间对象都是不可修改的。
以直观的方式操作LocalDate的属性
修改属性最直接的方法是withAttribute方法;此时会生成一个新的对象;

	LocalDate date1 = LocalDate.of(2014, 3, 18);
	LocalDate date2 = date1.withYear(2011);
	LocalDate date3 = date2.withDayOfMonth(25);
	LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);

with方法声明于Temporal接口,任何实现类都能够使用;
以相对的方式修改LocalDate对象的属性

	LocalDate date1 = LocalDate.of(2014, 3, 18);
	LocalDate date2 = date1.plusWeeks(1);
	LocalDate date3 = date2.minusYears(3);
	LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);

与我们刚才介绍的get和with方法类似,plus方法和minus也都定义在Temporal接口中,属于通用方法.

在这里插入图片描述

12.2.1 使用TemporalAdjuster

到目前为止,我们修改时间都是比较直接的(增加一天产生一个对象),有时候我们需要更复杂的操作.比如说把日期调整到下周日,本月的最后一天.
这是你可以使用with方法,后边传递一个TemporalAdjuster对象,可以更灵活的处理时间.
例如:

	import static java.time.temporal.TemporalAdjusters.*;
	LocalDate date1 = LocalDate.of(2014, 3, 18);
	LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY));
	LocalDate date3 = date2.with(lastDayOfMonth());

在这里插入图片描述

12.2.2 打印输出及解析日期-时间对象

java.time.format包下的DateTimeFormatter类就是用于时间格式化的.
可以通过器静态工厂或者静态常量获取格式化对象.
格式化

	LocalDate date = LocalDate.of(2014, 3, 18);
	String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);//20140318
	String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);//2014-03-18

解析

	LocalDate date1 = LocalDate.parse("20140318",
		DateTimeFormatter.BASIC_ISO_DATE);
	LocalDate date2 = LocalDate.parse("2014-03-18",
		DateTimeFormatter.ISO_LOCAL_DATE);

创建本地化的DateTimeFormatter

	DateTimeFormatter italianFormatter =
	DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
	LocalDate date1 = LocalDate.of(2014, 3, 18);
	String formattedDate = date.format(italianFormatter); // 18. marzo 2014
	LocalDate date2 = LocalDate.parse(formattedDate, italianFormatter);
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值