连接:https://www.jianshu.com/p/936d97ba0362javascript
连接:https://www.jianshu.com/p/41de7b5ac7b9php
本文主要总结了《Java8实战》,适用于学习 Java8 的同窗,也能够做为一个 API 手册文档适用,平时使用时可能因为不熟练,忘记 API 或者语法。css
Lambda 表达式,也可称为闭包,它是推进 Java 8 发布的最重要新特性。
Lambda 容许把函数做为一个方法的参数(函数做为参数传递进方中)。
使用 Lambda 表达式可使代码变的更加简洁紧凑。
如下是lambda表达式的重要特征:java
// jdk1.8接口 @FunctionalInterface public interface Supplier <T> { T get(); } public class Car { //Supplier是jdk1.8的接口,这里和lamda一块儿使用了 public static Car create(final Supplier <Car> supplier) { return supplier.get(); } }
构造器引用
它的语法是Class::new,或者更通常的Class< T >::new实例以下:python
final Car car = Car.create(Car::new); final List <Car> cars = Arrays.asList(car);
无参构造程序员
Supplier <Apple> c1 = Apple::new; Apple a1 = c1.get();
一个参数构造算法
Function <Integer, Apple> c2 = Apple::new; Apple a2 = c2.apply(110);
两个参数构造数据库
BiFunction <String, Integer, Apple> c3 = Apple::new; Apple c3 = c3.apply("green", 110);
多个参数构造
可自定义Function实现,如编程
public interface TriFunction <T, U, V, R> { R apply(T t, U u, V v); }
静态方法引用
它的语法是Class::static_method,实例以下:数组
cars.forEach(Car::collide);
特定类的任意对象的方法引用
它的语法是Class::method实例以下:
cars.forEach(Car::repair);
特定对象的方法引用
它的语法是instance::method实例以下:
final Car police = Car.create(Car::new); cars.forEach(police::follow);
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,可是能够有多个非抽象方法的接口。
函数式接口能够被隐式转换为lambda表达式。
函数式接口能够现有的函数友好地支持 lambda。
JDK 1.8以前已有的函数式接口:
java.util.function 它包含了不少类,用来支持 Java的 函数式编程,该包中的函数式接口有:
接口名 | 参数 | 返回值 | 用途 |
---|---|---|---|
Predicate | T | boolean | 断言 |
Consumer | T | void | 消费 |
Function<T,R> | T | R | 函数 |
Supplier | None | T | 工厂方法 |
UnaryOperator | T | T | 逻辑非 |
BinaryOperator | (T,T) | T | 二元操做 |
函数式接口各种介绍:
接口 | 描述 |
---|---|
BiConsumer<T,U> | 表明了一个接受两个输入参数的操做,而且不返回任何结果 |
BiFunction<T,U,R> | 表明了一个接受两个输入参数的方法,而且返回一个结果 |
BinaryOperator<T> | 表明了一个做用于于两个同类型操做符的操做,而且返回了操做符同类型的结果 |
BiPredicate<T,U> | 表明了一个两个参数的boolean值方法 |
BooleanSupplier | 表明了boolean值结果的提供方 |
Consumer<T> | 表明了接受一个输入参数而且无返回的操做 |
DoubleBinaryOperator | 表明了做用于两个double值操做符的操做,而且返回了一个double值的结果 |
DoubleConsumer | 表明一个接受double值参数的操做,而且不返回结果 |
DoubleFunction<R> | 表明接受一个double值参数的方法,而且返回结果 |
DoublePredicate | 表明一个拥有double值参数的boolean值方法 |
DoubleSupplier | 表明一个double值结构的提供方 |
DoubleToIntFunction | 接受一个double类型输入,返回一个int类型结果 |
DoubleToLongFunction | 接受一个double类型输入,返回一个long类型结果 |
DoubleUnaryOperator | 接受一个参数同为类型double,返回值类型也为double |
Function<T,R> | 接受一个输入参数,返回一个结果 |
IntBinaryOperator | 接受两个参数同为类型int,返回值类型也为int |
IntConsumer | 接受一个int类型的输入参数,无返回值 |
IntFunction<R> | 接受一个int类型输入参数,返回一个结果 |
IntPredicate | 接受一个int输入参数,返回一个布尔值的结果 |
IntSupplier | 无参数,返回一个int类型结果 |
IntToDoubleFunction | 接受一个int类型输入,返回一个double类型结果 |
IntToLongFunction | 接受一个int类型输入,返回一个long类型结果 |
IntUnaryOperator | 接受一个参数同为类型int,返回值类型也为int |
LongBinaryOperator | 接受两个参数同为类型long,返回值类型也为long |
LongConsumer | 接受一个long类型的输入参数,无返回值 |
LongFunction<R> | 接受一个long类型输入参数,返回一个结果 |
LongPredicate | R接受一个long输入参数,返回一个布尔值类型结果 |
LongSupplier | 无参数,返回一个结果long类型的值 |
LongToDoubleFunction | 接受一个long类型输入,返回一个double类型结果 |
LongToIntFunction | 接受一个long类型输入,返回一个int类型结果 |
LongUnaryOperator | 接受一个参数同为类型long,返回值类型也为long |
ObjDoubleConsumer<T> | 接受一个object类型和一个double类型的输入参数,无返回值 |
ObjIntConsumer<T> | 接受一个object类型和一个int类型的输入参数,无返回值 |
ObjLongConsumer<T> | 接受一个object类型和一个long类型的输入参数,无返回值。 |
Predicate<T> | 接受一个输入参数,返回一个布尔值结果 |
Supplier<T> | 无参数,返回一个结果 |
ToDoubleBiFunction<T,U> | 接受两个输入参数,返回一个double类型结果 |
ToDoubleFunction<T> | 接受一个输入参数,返回一个double类型结果 |
ToIntBiFunction<T,U> | 接受两个输入参数,返回一个int类型结果 |
ToIntFunction<T> | 接受一个输入参数,返回一个int类型结果 |
ToLongBiFunction<T,U> | 接受两个输入参数,返回一个long类型结果 |
ToLongFunction<T> | 接受一个输入参数,返回一个long类型结果 |
UnaryOperator<T> | 接受一个参数为类型T,返回值类型也为T |
@FunctionalInterface
这个标注用于表示该接口会设计成一个函数式接口。若是你用 @FunctionalInterface 定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示缘由的错误。例如,错误消息多是“Multiple non-overriding abstract methods found in interface Foo” , 代表存在多个抽象方法。 请注意, @FunctionalInterface 不是必需的, 但对于为此设计的接口而言, 使用它是比较好的作法。 它就像是 @Override标注表示方法被重写了。
// 定义函数式接口 @FunctionalInterface public interface BufferedReaderProcessor { String process(BufferedReader b) throws IOException; } // 定义方法 public static String processFile(BufferedReaderProcessor p) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { return p.process(br); } } // 调用 String result = processFile(br -> br.readLine() + br.readLine());
Predicate
Predicate <T> 接口是一个函数式接口,它接受一个输入参数 T,返回一个布尔值结果。
该接口包含多种默认方法来将Predicate组合成其余复杂的逻辑(好比:与,或,非)。
该接口用于测试对象是 true 或 false。
与:predicate.and()
或:predicate.or()
非:predicate.negate()
a.or(b).and(c) 能够看做 (a || b) && c
咱们能够经过如下实例(Java8Tester.java)来了解函数式接口 Predicate <T> 的使用:
public class Java8Tester { public static void main(String args[]) { List < Integer > list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); // Predicate<Integer> predicate = n -> true // n 是一个参数传递到 Predicate 接口的 test 方法 // n 若是存在则 test 方法返回 true System.out.println("输出全部数据:"); // 传递参数 n eval(list, n -> true); // Predicate<Integer> predicate1 = n -> n%2 == 0 // n 是一个参数传递到 Predicate 接口的 test 方法 // 若是 n%2 为 0 test 方法返回 true System.out.println("输出全部偶数:"); eval(list, n -> n % 2 == 0); // Predicate<Integer> predicate2 = n -> n > 3 // n 是一个参数传递到 Predicate 接口的 test 方法 // 若是 n 大于 3 test 方法返回 true System.out.println("输出大于 3 的全部数字:"); eval(list, n -> n > 3); } public static void eval(List < Integer > list, Predicate < Integer > predicate) { for (Integer n: list) { if (predicate.test(n)) { System.out.println(n + " "); } } } }
Consumer
java.util.function.Consumer<T> 定义了一个名叫 accept 的抽象方法,它接受泛型 T 的对象,没有返回( void ) 。你若是须要访问类型 T 的对象,并对其执行某些操做,就可使用这个接口。
public static <T> void forEach(List <T> list, Consumer <T> c) { for (T i: list) { c.accept(i); } } forEach(Arrays.asList(1, 2, 3, 4, 5), System.out::println);
Function
java.util.function.Function<T, R> 接口定义了一个叫做 apply 的方法,它接受一个泛型 T 的对象,并返回一个泛型 R 的对象。
public static <T, R> List <R> map(List <T> list, Function <T, R> f) { List <R> result = new ArrayList < > (); for (T s: list) { result.add(f.apply(s)); } return result; } // [7, 2, 6] List <Integer> l = map(Arrays.asList("lambdas", "in", "action"), String::length);
Function 接口所表明的Lambda表达式复合起来。 Function 接口为此配了 andThen 和 compose 两个默认方法,它们都会返回 Function 的一个实例。
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); // 4
数学上会写做 g(f(x)) 或(g o f)(x)
compose 方法
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); // 3
数学上会写做 f(g(x)) 或 (f o g)(x)
Stream主要用于操做集合,更方便的去处理数据。
java.util.stream.Stream 中的 Stream 接口定义了许多操做。它们能够分为两大类:能够链接起来的流操做称为中间操做,关闭流的操做称为终端操做。能够理解为有返回值是Stream的方法是中间操做,返回值非Stream的方法是终端操做。
中间操做会返回另外一个流,可是若是没有终端操做,中间操做不会执行任何处理。
Stream调用了终端操做以后,若是再调用,抛出如下异常:
java.lang.IllegalStateException: stream has already been operated upon or closed
Stream只能被消费一次!!!
外部迭代与内部迭代
使用 Collection 接口须要用户去作迭代(好比用 for-each ) ,这称为外部迭代。 相反,Streams库使用内部迭代。
外部迭代:外部迭代实际是使用Iterator对象。
开发中如何选择两种迭代方式:
流的使用通常包括三件事
筛选(filter)
该方法会接受一个谓词(Predicate)(一个返回boolean 的函数)做为参数,并返回一个包括全部符合谓词(Predicate)的元素的流。
List <Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());
切片(limit)
该方法会返回一个不超过给定长度的流。所需的长度做为参数传递给 limit 。若是流是有序的,则最多会返回前 n 个元素。若是流是无序的,limit的结果不会以任务顺序排列。
List <Dish> dishes = menu.stream().filter(d -> d.getCalories() > 300).limit(3).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);
跳过元素(skip)
返回一个扔掉了前 n 个元素的流。若是流中元素不足 n 个,则返回一个空流。请注意, limit(n) 和 skip(n) 是互补的!
List <Dish> dishes = menu.stream().filter(d -> d.getCalories() > 300).skip(2).collect(toList());
映射(map和flatMap)
map:
一个很是常见的数据处理套路就是从某些对象中选择信息。好比在SQL里,你能够从表中选择一列。
能够改变原有流的类型,从新返回一个新的类型集合。
List <String> dishNames = menu.stream().map(Dish::getName).collect(toList());
flatMap:
一个用于把 Stream<String[]> 转换成Stream<String> 的操做方法,将流扁平化。
Arrays.stream() 的方法能够接受一个数组并产生一个流。
若是须要把一个String[] arrayOfWords = {"Hello", "World"};转换成[G, o, o, d, b, y, e, W, o, r, l, d]
String[] arrayOfWords = {"Hello", "World"}; Stream <String> streamOfwords = Arrays.stream(arrayOfWords); streamOfwords.map(word -> word.split("")).flatMap(Arrays::stream).collect(Collectors.toList());
匹配( allMatch和anyMatch和noneMatch )
检查是否至少匹配一个元素(anyMatch ):
boolean isVegetarian = menu.stream().anyMatch(Dish::isVegetarian);
检查是否匹配全部元素(allMatch):
boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);
检查是否全部元素不匹配(noneMatch ):
boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);
查找(findFirst和findAny)
返回当前流中的任意元素(findAny):
Optional <Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();
获取集合中第一个元素(findFirst):
Optional <Dish> dish = menu.stream().filter(Dish::isVegetarian).findFirst();
什么时候使用 findFirst 和 findAny
你可能会想,为何会同时有 findFirst 和 findAny 呢?答案是并行。找到第一个元素在并行上限制更多。若是你不关心返回的元素是哪一个,请使用 findAny ,由于它在使用并行流时限制较少。
归约(reduce)
reduce 接受两个参数:
求和
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
等同于
int sum = numbers.stream().mapToInt(n -> n).sum();
最大值
Optional <Integer> max = numbers.stream().reduce(Integer::max);
等同于
int max = numbers.stream().mapToInt(n -> n).max();
最小值
Optional <Integer> min = numbers.stream().reduce(Integer::min);
等同于
int min = numbers.stream().mapToInt(n -> n).min();
执行原理:
0 做为Lambda( a )的第一个参数,从流中得到 4 做为第二个参数( b ) 。 0 + 4 获得 4 ,它成了新的累积值。而后再用累积值和流中下一个元素 5 调用Lambda,产生新的累积值 9 。接下来,再用累积值和下一个元素 3调用Lambda,获得 12 。最后,用 12 和流中最后一个元素 9 调Lambda,获得最终结果 21 。
ps:reduce若是不设置初始值,会返回一个 Optional 对象。
流操做:无状态和有状态
无状态:操做集合数据时,每个元素之间数据不相互影响,如map或者filter等操做。
有状态:操做集合数据时,元素之间数据有影响,如sort或者distinct等操做,须要知道每一个元素值才能执行处理。
操 做 | 类 型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream<T> | Predicate<T> | T -> boolean |
distinct | 中间 (有状态-无界) | Stream<T> | ||
skip | 中间 (有状态-有界) | Stream<T> | long | |
limit | 中间 (有状态-有界) | Stream<T> | long | |
map | 中间 | Stream<T> | Function<T, R> | T -> R |
flatMap | 中间 | Stream<T> | Function<T, Stream<R>> | T -> Stream<R> |
sorted | 中间 (有状态-无界) | Stream<T> | Comparator<T> | (T, T) -> int |
anyMatch | 终端 | boolean | Predicate<T> | T -> boolean |
noneMatch | 终端 | boolean | Predicate<T> | T -> boolean |
allMatch | 终端 | boolean | Predicate<T> | T -> boolean |
findAny | 终端 | Optional<T> | ||
findFirst | 终端 | Optional<T> | ||
forEach | 终端 | void | Consumer<T> | T -> void |
collect | 终端 | R | Collector<T, A, R> | |
reduce | 终端 (有状态-有界) | Optional<T> | BinaryOperator<T> | (T, T) -> T |
count | 终端 | long |
原始类型流转换
IntStream 、 DoubleStream 和 LongStream ,分别将流中的元素特化为 int 、 long 和 double,从而避免了暗含的装箱成本。转换的缘由并不在于流的复杂性,而是装箱形成的复杂性——即相似 int 和 Integer 之间的效率差别。将流转换为转换的经常使用方法是 mapToInt 、 mapToDouble 和 mapToLong 。
转换回对象流:
要把原始流转换成通常流(每一个 int 都会装箱成一个Integer ) ,可使用 boxed 方法。
IntStream intStream = menu.stream().mapToInt(Dish::getCalories); Stream <Integer> stream = intStream.boxed();
默认值 OptionalInt:
Optional 能够用Integer 、 String 等参考类型来参数化。对于三种原始流特化,也分别有一个 Optional 原始类型特化版本: OptionalInt 、 OptionalDouble 和 OptionalLong 。
OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
int max = maxCalories.orElse(1);
数值范围(range 和 rangeClosed):
range 和 rangeClosed这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但
range 是不包含结束值的,而 rangeClosed 则包含结束值。
由值建立流(Stream.of):
Stream.of 经过显式值建立一个流,它能够接受任意数量的参数。
Stream <String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
也能够建立一个空流:
Stream <String> emptyStream = Stream.empty();
由数组建立流(Arrays.stream):
Arrays.stream 从数组建立一个流,它接受一个数组做为参数。
int[] numbers = {2, 3, 5, 7, 11, 13}; int sum = Arrays.stream(numbers).sum();
由文件生成流:
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) {}
由函数生成流:建立无限流:
Stream API提供了两个静态方法来从函数生成流: Stream.iterate 和 Stream.generate 。这两个操做能够建立所谓的无限流:不像从固定集合建立的流那样有固定大小的流。由 iterate和 generate 产生的流会用给定的函数按需建立值,所以能够无穷无尽地计算下去!通常来讲,应该使用 limit(n) 来对这种流加以限制,以免打印无穷多个值。
Stream.iterate:
Stream.iterate(0, n - > n + 2).limit(10).forEach(System.out::println);
Stream.generate:
Stream.generate(Math::random).limit(5).forEach(System.out::println);
收集器
汇总(Collectors.counting 、 Collectors.summingInt、 Collectors.summingLong 、Collectors.summingDouble、 Collectors.averagingInt、 Collectors.averagingLong 、Collectors.averagingDouble、Collectors.summarizingInt、Collectors.summarizingLong、Collectors.summarizingDouble):
总数:
Collectors.counting:
long howManyDishes = menu.stream().collect(Collectors.counting());
等同于
long howManyDishes = menu.stream().count();
求和:
Collectors.summingInt:
int totalCalories = menu.stream().collect(Collectors.summingInt(Dish::getCalories));
等同于
int sum = menu.stream().mapToInt(Dish::getCalories).sum();
Collectors.summingLong:
long totalCalories = menu.stream().collect(Collectors.summingLong(Dish::getCalories));
等同于
long sum = menu.stream().mapToLong(Dish::getCalories).sum();
Collectors.summingDouble:
double totalCalories = menu.stream().collect(Collectors.summingDouble(Dish::getCalories));
等同于
double sum = menu.stream().mapToDouble(Dish::getCalories).sum();
平均数:
Collectors.averagingInt:
int avgCalories = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
Collectors.averagingLong:
long avgCalories = menu.stream().collect(Collectors.averagingLong(Dish::getCalories));
Collectors.averagingDouble:
double avgCalories = menu.stream().collect(Collectors.averagingDouble(Dish::getCalories));
汇总(总和、平均值、最大值和最小值):
Collectors.summarizingInt:
IntSummaryStatistics menuStatistics = menu.stream().collect(Collectors.summarizingInt(Dish::getCalories));
Collectors.summarizingLong:
LongSummaryStatistics menuStatistics = menu.stream().collect(Collectors.summarizingLong(Dish::getCalories));
Collectors.summarizingDouble:
DoubleSummaryStatistics menuStatistics = menu.stream().collect(Collectors.summarizingDouble(Dish::getCalories));
查找流中的最大值和最小值(Collectors.maxBy 和 Collectors.minBy):
Collectors.maxBy :
Comparator <Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories); Optional <Dish> mostCalorieDish = menu.stream().collect(Collectors.maxBy(dishCaloriesComparator));
链接字符串(Collectors.joining):
joining 工厂方法返回的收集器会把对流中每个对象应用 toString 方法获得的全部字符串链接成一个字符串。 joining 在内部使用了 StringBuilder 来把生成的字符串逐个追加起来。 joining 工厂方法有一个重载版本能够接受元素之间的分界符。
String shortMenu = menu.stream().map(Dish::getName).collect(Collectors.joining());
分隔符:
String shortMenu = menu.stream().map(Dish::getName).collect(Collectors.joining(”, “));
分组(Collectors.groupingBy):
普通的单参数 groupingBy(f) (其中 f 是分类函数)其实是 groupingBy(f, toList()) 的简便写法。
第一个参数是指定以什么分组
第二个参数是指定使用的Map
第三个参数是指定Collector
Map <Dish.Type, List <Dish>> dishesByType = menu.stream().collect(Collectors.groupingBy(Dish::getType));
多级分组:
Map<Dish.Type, Map<Long, List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.groupingBy(Dish::getCalories)));
分组汇总:
Map <Dish.Type, Long> typesCount = menu.stream().collect(Collectors.groupingBy(Dish::getType, counting()));
分组汇总最大值:
Map Dish.Type, Optional <Dish>> mostCaloricByType = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))));
分组结果包装Optional转换具体值(Collectors.collectingAndThen)
Map <Dish.Type, Dish> mostCaloricByType = menu.stream().collect(Collectors.groupingBy(Dish::getType,Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)));
分组类型转换(Collectors.mapping):
这个方法接受两个参数:一个函数对流中的元素作变换,另外一个则将变换的结果对象收集起来。
Map <Dish.Type, Set <CaloricLevel>> caloricLevelsByType = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.mapping( dish - > { if (dish.getCalories() <= 400) return CaloricLevel.DIET; else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; else return CaloricLevel.FAT; }, Collectors.toSet())));
分区(Collectors.partitioningBy)
分区是分组的特殊状况:由一个谓词(返回一个布尔值的函数)做为分类函数,它称分区函数。分区函数返回一个布尔值,这意味着获得的分组 Map 的键类型是 Boolean ,因而它最多能够分为两组—— true 是一组, false 是一组。
Map <Boolean, List <Dish>> partitionedMenu = menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian));
排序(Comparator )
Comparator 接口如今同时包含了默认方法和静态方法。可使用静态方法 Comparator.comparing 返回一个 Comparator 对象,该对象提供了一个函数能够提取排序关键字。
并行流
使用并行流能够经过parallelStream或者parallel方法。对顺序流调用 parallel 方法并不意味着流自己有任何实际的变化。它在内部实际上就是设了一个 boolean 标志,表示你想让调用 parallel 以后进行的全部操做都并行执行。并行流转换成顺序流使用sequential方法。
并行化并非没有代价的。并行化过程自己须要对流作递归划分,把每一个子流的概括操做分配到不一样的线程,而后把这些操做的结果合并成一个值。但在多个内核之间移动数据的代价也可能比你想的要大, 因此很重要的一点是要保证在内核中并行执行工做的时间比在内核之间传输数据的时间长。总而言之,不少状况下不可能或不方便并行化。然而,在使用并行 Stream 加速代码以前,你必须确保用得对;若是结果错了,算得快就毫无心义了。让咱们来看一个常见的陷阱。
分支/合并框架
分支/合并框架的目的是以递归方式将能够并行的任务拆分红更小的任务,而后将每一个子任务的结果合并起来生成总体结果。它是 ExecutorService 接口的一个实现,它把子任务分配给线程池(称为 ForkJoinPool )中的工做线程。首先来看看如何定义任务和子任务。
RecursiveTask:
要把任务提交到这个池, 必须建立 RecursiveTask<R> 的一个子类, 其中 R 是并行化任务 (以及全部子任务)产生的结果类型,或者若是任务不返回结果,则是 RecursiveAction 类型(固然它可能会更新其余非局部机构) 。要定义 RecursiveTask, 只需实现它惟一的抽象方法compute :
protected abstract R compute();
这个方法同时定义了将任务拆分红子任务的逻辑,以及没法再拆分或不方便再拆分时,生成单个子任务结果的逻辑。正因为此,这个方法的实现相似于下面的伪代码:
if (任务足够小或不可分) { 顺序计算该任务 } else { 将任务分红两个子任务 递归调用本方法,拆分每一个子任务,等待全部子任务完成 合并每一个子任务的结果 }
使用分支/合并框架的最佳作法:
虽然分支/合并框架还算简单易用,不幸的是它也很容易被误用。如下是几个有效使用它的最佳作法。
Spliterator:
Spliterator 是Java 8中加入的另外一个新接口;这个名字表明“可分迭代器” (splitable iterator) 。和 Iterator 同样, Spliterator 也用于遍历数据源中的元素,但它是为了并行执行而设计的。虽然在实践中可能用不着本身开发 Spliterator ,但了解一下它的实现方式会让你对并行流的工做原理有更深刻的了解。Java 8已经为集合框架中包含的全部数据结构提供了一个默认的 Spliterator 实现。 集合实现了 Spliterator 接口, 接口提供了一个 spliterator 方法。
public interface Spliterator<T> { boolean tryAdvance(Consumer<? super T> action); Spliterator<T> trySplit(); long estimateSize(); int characteristics(); }
与往常同样, T 是 Spliterator 遍历的元素的类型。 tryAdvance 方法的行为相似于普通的Iterator ,由于它会按顺序一个一个使用 Spliterator 中的元素,而且若是还有其余元素要遍历就返回 true 。 但 trySplit 是专为 Spliterator 接口设计的, 由于它能够把一些元素划出去分给第二个 Spliterator (由该方法返回) ,让它们两个并行处理。 Spliterator 还可经过estimateSize 方法估计还剩下多少元素要遍历,由于即便不那么确切,能快速算出来是一个值也有助于让拆分均匀一点。
流拆分过程:
将 Stream 拆分红多个部分的算法是一个递归过程,第一步是对第一个Spliterator 调用 trySplit ,生成第二个 Spliterator 。第二步对这两个 Spliterator 调用trySplit ,这样总共就有了四个 Spliterator 。这个框架不断对 Spliterator 调用 trySplit直到它返回 null ,代表它处理的数据结构不能再分割,最后,这个递归拆分过程到第四步就终止了,这时全部的 Spliterator 在调用 trySplit 时都返回了 null 。
这个拆分过程也受 Spliterator 自己的特性影响,而特性是经过 characteristics 方法声明的,它将返回一个 int ,表明 Spliterator 自己特性集的编码。使用 Spliterator 的客户能够用这些特性来更好地控制和优化它的使用。
Future
Future 接口在Java 5中被引入,设计初衷是对未来某个时刻会发生的结果进行建模。它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。在Future 中触发那些潜在耗时的操做把调用线程解放出来,让它能继续执行其余有价值的工做,再也不须要呆呆等待耗时的操做完成。
ExecutorService executor = Executors.newCachedThreadPool();
Future < Double > future = executor.submit(new Callable <Double> () { public Double call() { return doSomeLongComputation(); } }); doSomethingElse(); try { Double result = future.get(1, TimeUnit.SECONDS); } catch (ExecutionException ee) { // 计算抛出一个异常 } catch (InterruptedException ie) { // 当前线程在等待过程当中被中断 } catch (TimeoutException te) { // 在Future对象完成以前超过已过时 }
这种编程方式让你的线程能够在 ExecutorService 以并发方式调用另外一个线程执行耗时操做的同时,去执行一些其余的任务。
局限性:
Future 接口提供了方法来检测异步计算是否已经结束(使用isDone 方法) ,等待异步操做结束,以及获取计算的结果。
CompletableFuture
CompletableFuture 的 completeExceptionally 方法将致使 CompletableFuture 内发生问题的异常抛出。客户端如今会收到一个 ExecutionException 异常,该异常接收了一个包含失败缘由的Exception 参数。
使用工厂方法 supplyAsync 建立 CompletableFuture:
public Future <Double> getPriceAsync(String product) { return CompletableFuture.supplyAsync(() -> calculatePrice(product)); }
supplyAsync 方法接受一个生产者( Supplier )做为参数,返回一个 CompletableFuture对象, 该对象完成异步执行后会读取调用生产者方法的返回值。 生产者方法会交由 ForkJoinPool池中的某个执行线程( Executor )运行,可是你也可使用 supplyAsync 方法的重载版本,传递第二个参数指定不一样的执行线程执行生产者方法。通常而言,向 CompletableFuture 的工厂方法传递可选参数,指定生产者方法的执行线程是可行的。
CompletableFuture和stream组合使用:
public List <String> findPrices(String product) { List < CompletableFuture <String>> priceFutures = shops.stream() .map(shop -> CompletableFuture.supplyAsync(() - > shop.getName() + " price is " + shop.getPrice(product))) .collect(Collectors.toList()); return priceFutures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); }
利用 CompletableFutures 向其提交任务执行是个不错的主意。处理需大量使用异步操做的状况时,这几乎是最有效的策略。
构造同步和异步操做:
public List <String> findPrices(String product) { List <CompletableFuture <String>> priceFutures = shops.stream() .map(shop -> CompletableFuture.supplyAsync( () -> shop.getPrice(product), executor)) .map(future -> future.thenApply(Quote::parse)) .map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync( () -> Discount.applyDiscount(quote), executor))) .collect(Collectors.toList()); return priceFutures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); }
Java 8的 CompletableFuture API提供了名为 thenCompose 的方法,它就是专门为这一目的而设计的, thenCompose 方法容许你对两个异步操做进行流水线,第一个操做完成时,将其结果做为参数传递给第二个操做。建立两个 CompletableFutures 对象,对第一个 CompletableFuture 对象调用 thenCompose ,并向其传递一个函数。当第一个CompletableFuture 执行完毕后,它的结果将做为该函数的参数,这个函数的返回值是以第一个 CompletableFuture 的返回作输入计算出的第二个 CompletableFuture 对象。thenCompose 方法像 CompletableFuture 类中的其余方法同样,也提供了一个以 Async 后缀结尾的版本 thenComposeAsync 。一般而言,名称中不带 Async的方法和它的前一个任务同样,在同一个线程中运行;而名称以 Async 结尾的方法会将后续的任务提交到一个线程池,因此每一个任务是由不一样的线程处理的。
方法名 | 描述 |
---|---|
allOf(CompletableFuture<?>... cfs) | 等待全部任务完成,构造后CompletableFuture完成 |
anyOf(CompletableFuture<?>... cfs) | 只要有一个任务完成,构造后CompletableFuture就完成 |
runAsync(Runnable runnable) | 使用ForkJoinPool.commonPool()做为它的线程池执行异步代码 |
runAsync(Runnable runnable, Executor executor) | 使用指定的thread pool执行异步代码 |
supplyAsync(Supplier<U> supplier) | 使用ForkJoinPool.commonPool()做为它的线程池执行异步代码,异步操做有返回值 |
supplyAsync(Supplier<U> supplier,Executor executor) | 使用指定的thread pool执行异步代码,异步操做有返回值 |
complete(T t) | 完成异步执行,并返回future的结果 |
completeExceptionlly(Throwable ex) | 异步执行不正常的结束 |
cancel(boolean mayInterruptIfRunning) | 取消任务的执行。参数指定是否当即中断任务执行,或者等等任务结束 |
isCancelled() | 任务是否已经取消,任务正常完成前将其取消,则返回 true |
isDone() | 任务是否已经完成。须要注意的是若是任务正常终止、异常或取消,都将返回true |
get() | throws InterruptedException, ExecutionException 等待任务执行结束,而后得到V类型的结果。InterruptedException 线程被中断异常, ExecutionException任务执行异常,若是任务被取消,还会抛出CancellationException |
get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException | 同上面的get功能同样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。若是计 算超时,将抛出TimeoutException |
thenApply(Function<? super T,? extends U> fn) | 转换一个新的CompletableFuture对象 |
thenApplyAsync(Function<? super T,? extends U> fn) | 异步转换一个新的CompletableFuture对象 |
thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) | 使用指定的thread pool执行异步代码,异步转换一个新的CompletableFuture对象 |
thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) | 在异步操做完成的时候对异步操做的结果进行一些操做,而且仍然返回CompletableFuture类型 |
thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) | 在异步操做完成的时候对异步操做的结果进行一些操做,而且仍然返回CompletableFuture类型。使用ForkJoinPool |
thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) | 在异步操做完成的时候对异步操做的结果进行一些操做,而且仍然返回CompletableFuture类型。使用指定的线程池 |
thenAccept(Consumer<? super T> action) | 当CompletableFuture完成计算结果,只对结果执行Action,而不返回新的计算值 |
thenAcceptAsync(Consumer<? super T> action) | 当CompletableFuture完成计算结果,只对结果执行Action,而不返回新的计算值,使用ForkJoinPool |
thenAcceptAsync(Consumer<? super T> action,Executor executor) | 当CompletableFuture完成计算结果,只对结果执行Action,而不返回新的计算值 |
thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) | 当两个CompletableFuture都正常完成后,执行提供的fn,用它来组合另一个CompletableFuture的结果 |
thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) | 当两个CompletableFuture都正常完成后,执行提供的fn,用它来组合另一个CompletableFuture的结果,使用ForkJoinPool |
thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor) | 当两个CompletableFuture都正常完成后,执行提供的fn,用它来组合另一个CompletableFuture的结果,使用指定的线程池 |
thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action) | 当两个CompletableFuture都正常完成后,执行提供的action,用它来组合另一个CompletableFuture的结果 |
thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action) | 当两个CompletableFuture都正常完成后,执行提供的action,用它来组合另一个CompletableFuture的结果,使用ForkJoinPool |
thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action, Executor executor) | 当两个CompletableFuture都正常完成后,执行提供的action,用它来组合另一个CompletableFuture的结果,使用指定的线程池 |
whenComplete(BiConsumer<? super T,? super Throwable> action) | 当CompletableFuture完成计算结果时对结果进行处理,或者当CompletableFuture产生异常的时候对异常进行处理 |
whenCompleteAsync(BiConsumer<? super T,? super Throwable> action) | 当CompletableFuture完成计算结果时对结果进行处理,或者当CompletableFuture产生异常的时候对异常进行处理,使用ForkJoinPool |
whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor) | 当CompletableFuture完成计算结果时对结果进行处理,或者当CompletableFuture产生异常的时候对异常进行处理,使用指定的线程池。 |
handle(BiFunction<? super T, Throwable, ? extends U> fn) | 当CompletableFuture完成计算结果或者抛出异常的时候,执行提供的fn |
handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) | 当CompletableFuture完成计算结果或者抛出异常的时候,执行提供的fn,使用ForkJoinPool |
handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) | 当CompletableFuture完成计算结果或者抛出异常的时候,执行提供的fn,使用指定的线程池 |
Clock
Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,能够用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可使用Instant类来表示,Instant类也能够用来建立老的java.util.Date对象。
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant);
LocalDate
该类的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息。另外,它也不附带任何与时区相关的信息。经过静态工厂方法 of 建立一个 LocalDate 实例。 LocalDate 实例提供了多种方法来读取经常使用的值,好比年份、月份、星期几等。
LocalDate date = LocalDate.of(2018, 10, 1); int year = date.getYear(); Month month = date.getMonth(); int day = date.getDayOfMonth(); DayOfWeek dow = date.getDayOfWeek(); int len = date.lengthOfMonth(); boolean leap = date.isLeapYear();
等同于
int year = date.get(ChronoField.YEAR); int month = date.get(ChronoField.MONTH_OF_YEAR); int day = date.get(ChronoField.DAY_OF_MONTH);
获取当前时间:
LocalDate today = LocalDate.now();
LocalTime
一天中的时间,好比13:45:20,可使用 LocalTime 类表示。你可使用 of 重载的两个工厂方法建立 LocalTime 的实例。 第一个重载函数接收小时和分钟, 第二个重载函数同时还接收秒。同 LocalDate 同样, LocalTime 类也提供了一些 getter 方法访问这些变量的值。
LocalTime time = LocalTime.of(13, 45, 20); int hour = time.getHour(); int minute = time.getMinute(); int second = time.getSecond();
LocalDate 和 LocalTime 均可以经过解析表明它们的字符串建立。使用静态方法 parse:
LocalDate date = LocalDate.parse("2018-03-18"); LocalTime time = LocalTime.parse("13:45:20");
能够向 parse 方法传递一个 DateTimeFormatter 。该类的实例定义了如何格式化一个日
期或者时间对象。它是替换老版 java.util.DateFormat 的推荐替代品。一旦传递的字符串参数没法被解析为合法的 LocalDate 或 LocalTime 对象, 这两个 parse 方法都会抛出一个继承自 RuntimeException 的 DateTimeParseException 异常。
LocalDateTime
这个复合类名叫 LocalDateTime ,是 LocalDate 和 LocalTime 的合体。它同时表示了日期和时间, 但不带有时区信息, 你能够直接建立, 也能够经过合并日期和时间对象构造。
LocalDateTime dt1 = LocalDateTime.of(2018, 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);
经过它们各自的 atTime 或者 atDate 方法,向 LocalDate 传递一个时间对象,或者向LocalTime 传递一个日期对象的方式,你能够建立一个 LocalDateTime 对象。你也可使用toLocalDate 或者 toLocalTime 方法,从 LocalDateTime 中提取 LocalDate 或者 LocalTime组件:
LocalDate date1 = dt1.toLocalDate(); LocalTime time1 = dt1.toLocalTime();
Instant
能够经过向静态工厂方法 ofEpochSecond 传递一个表明秒数的值建立一个该类的实例。 静态工厂方法 ofEpochSecond 还有一个加强的重载版本,它接收第二个以纳秒为单位的参数值,对传入做为秒数的参数进行调整。重载的版本会调整纳秒参数,确保保存的纳秒分片在0到999 999999之间。
Instant.ofEpochSecond(3); Instant.ofEpochSecond(3, 0); Instant.ofEpochSecond(2, 1_000_000_000); // 2秒 之 后 再 加上100万纳秒(1秒) Instant.ofEpochSecond(4, -1_000_000_000); // 4秒以前的100万纳秒(1秒)
修改操做:
若是你已经有一个 LocalDate 对象, 想要建立它的一个修改版, 最直接也最简单的方法是使用 withAttribute 方法。 withAttribute 方法会建立对象的一个副本,并按照须要修改它的属性。
LocalDate date1 = LocalDate.of(2014, 3, 18); // 2014-03-18 LocalDate date2 = date1.withYear(2011); // 2011-03-18 LocalDate date3 = date2.withDayOfMonth(25); // 2011-03-25 LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9); // 2011-09-25 LocalDate date1 = LocalDate.of(2014, 3, 18); // 2014-03-18 LocalDate date2 = date1.plusWeeks(1); // 2014-03-25 LocalDate date3 = date2.minusYears(3); // 2011-03-25 LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS); // 2011-09-25
LocalDate 、 LocalTime 、 LocalDateTime 以及 Instant通用方法
方法名 | 是不是静态方法 | 描述 |
---|---|---|
from | 是 | 依据传入的 Temporal 对象建立对象实例 |
now | 是 | 依据系统时钟建立 Temporal 对象 |
of | 是 | 由 Temporal 对象的某个部分建立该对象的实例 |
parse | 是 | 由字符串建立 Temporal 对象的实例 |
atOffset | 否 | 将 Temporal 对象和某个时区偏移相结合 |
atZone | 否 | 将 Temporal 对象和某个时区相结合 |
format | 否 | 使用某个指定的格式器将 Temporal 对象转换为字符串 ( Instant 类不提供该方法) |
get | 否 | 读取 Temporal 对象的某一部分的值 |
minus | 否 | 建立 Temporal 对象的一个副本, 经过将当前 Temporal 对象的值减去必定的时长建立该副本 |
plus | 否 | 建立 Temporal 对象的一个副本, 经过将当前 Temporal 对象的值加上必定的时长建立该副本 |
with | 否 | 以该 Temporal 对象为模板,对某些状态进行修改建立该对象的副本 |
LocalDate date = LocalDate.of(2014, 3, 18); date = date.with(ChronoField.MONTH_OF_YEAR, 9); date = date.plusYears(2).minusDays(10); date.withYear(2011);
答案: 2016-09-08 。
每一个动做都会建立一个新的 LocalDate 对象,后续的方法调用能够操纵前一方法建立的对象。这段代码的最后一句不会产生任何咱们能看到的效果,由于它像前面的那些操做同样,会建立一个新的 LocalDate 实例,不过咱们并无将这个新建立的值赋给任何的变量。
Duration
用于比较LocalTime之间的时间差, Duration 类主要用于以秒和纳秒衡量时间的长短。
LocalTime time1 = LocalTime.now(); LocalTime time2 = LocalTime.of(11, 0, 0); Duration d1 = Duration.between(time1, time2);
Period
用于比较LocalDate之间的时间差, Period类主要用于以年月日衡量时间的长短。
Period tenDays = Period.between(LocalDate.of(2014, 3, 8), LocalDate.of(2014, 3, 18));
Duration和Period通用方法
方法名 | 是不是静态方法 | 方法描述 |
---|---|---|
between | 是 | 建立两个时间点之间的 interval |
from | 是 | 由一个临时时间点建立 interval |
of | 是 | 由它的组成部分建立 interval的实例 |
parse | 是 | 由字符串建立 interval 的实例 |
addTo | 否 | 建立该 interval 的副本,并将其叠加到某个指定的 temporal 对象 |
get | 否 | 读取该 interval 的状态 |
isNegative | 否 | 检查该 interval 是否为负值,不包含零 |
isZero | 否 | 检查该 interval 的时长是否为零 |
minus | 否 | 经过减去必定的时间建立该 interval 的副本 |
multipliedBy | 否 | 将 interval 的值乘以某个标量建立该 interval 的副本 |
negated | 否 | 以忽略某个时长的方式建立该 interval 的副本 |
plus | 否 | 以增长某个指定的时长的方式建立该 interval 的副本 |
subtractFrom | 否 | 从指定的 temporal 对象中减去该 interval |
TemporalAdjuster
将日期调整到下个周日、下个工做日,或者是本月的最后一天。这时,你可使用重载版本的 with 方法, 向其传递一个提供了更多定制化选择的 TemporalAdjuster 对象,更加灵活地处理日期。
LocalDate date1 = LocalDate.of(2014, 3, 18); LocalDate date2 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)); LocalDate date3 = date2.with(TemporalAdjusters.lastDayOfMonth());
方法名 | 描述 |
---|---|
dayOfWeekInMonth | 建立一个新的日期,它的值为同一个月中每一周的第几天 |
firstDayOfMonth | 建立一个新的日期,它的值为当月的第一天 |
firstDayOfNextMonth | 建立一个新的日期,它的值为下月的第一天 |
firstDayOfNextYear | 建立一个新的日期,它的值为明年的第一天 |
firstDayOfYear | 建立一个新的日期,它的值为当年的第一天 |
firstInMonth | 建立一个新的日期,它的值为同一个月中,第一个符合星期几要求的值 |
lastDayOfMonth | 建立一个新的日期,它的值为当月的最后一天 |
lastDayOfNextMonth | 建立一个新的日期,它的值为下月的最后一天 |
lastDayOfNextYear | 建立一个新的日期,它的值为明年的最后一天 |
lastDayOfYear | 建立一个新的日期,它的值为今年的最后一天 |
lastInMonth | 建立一个新的日期,它的值为同一个月中,最后一个符合星期几要求的值 |
next/previous | 建立一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期 |
nextOrSame/previousOrSame | 建立一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期,若是该日期已经符合要求,直接返回该对象 |
DateTimeFormatter
处理日期和时间对象时,格式化以及解析日期时间对象是另外一个很是重要的功能。新的java.time.format 包就是特别为这个目的而设计的。这个包中,最重要的类是 DateTime-Formatter 。 建立格式器最简单的方法是经过它的静态工厂方法以及常量。 像 BASIC_ISO_DATE和 ISO_LOCAL_DATE 这 样 的 常 量 是 DateTimeFormatter 类 的 预 定 义 实 例 。 所 有 的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);
和老的 java.util.DateFormat 相比较,全部的 DateTimeFormatter 实例都是线程安全的。因此,你可以以单例模式建立格式器实例,就像 DateTimeFormatter 所定义的那些常量,并能在多个线程间共享这些实例。 DateTimeFormatter 类还支持一个静态工厂方法,它能够按照某个特定的模式建立格式器。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy") LocalDate date1 = LocalDate.of(2014, 3, 18); String formattedDate = date1.format(formatter); LocalDate date2 = LocalDate.parse(formattedDate, formatter);
若是还须要更加细粒度的控制, DateTimeFormatterBuilder 类还提供了更复杂的格式器,你能够选择恰当的方法,一步一步地构造本身的格式器。另外,它还提供了很是强大的解析功能,好比区分大小写的解析、柔性解析(容许解析器使用启发式的机制去解析输入,不精确地匹配指定的模式) 、填充,以及在格式器中指定可选节。
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder() .appendText(ChronoField.DAY_OF_MONTH) .appendLiteral(". ") .appendText(ChronoField.MONTH_OF_YEAR) .appendLiteral(" ") .appendText(ChronoField.YEAR) .parseCaseInsensitive() .toFormatter(Locale.ITALIAN);
ZoneId
以前看到的日期和时间的种类都不包含时区信息。时区的处理是新版日期和时间API新增长的重要功能,使用新版日期和时间API时区的处理被极大地简化了。新的 java.time.ZoneId类是老版 java.util.TimeZone 的替代品。它的设计目标就是要让你无需为时区处理的复杂和繁琐而操心,好比处理日光时(Daylight Saving Time,DST)这种问题。跟其余日期和时间类同样, ZoneId 类也是没法修改的。
ZoneId romeZone = ZoneId.of("Europe/Rome");
地区ID都为 “{区域}/{城市}” 的格式, 这些地区集合的设定都由英特网编号分配机构 (IANA)的时区数据库提供。你能够经过Java 8的新方法 toZoneId 将一个老的时区对象转换为 ZoneId :
ZoneId zoneId = TimeZone.getDefault().toZoneId();
一旦获得一个 ZoneId 对象,你就能够将它与 LocalDate 、 LocalDateTime 或者是 Instant对象整合起来,构造为一个 ZonedDateTime 实例,它表明了相对于指定时区的时间点。
LocalDate date = LocalDate.of(2014, Month.MARCH, 18); ZonedDateTime zdt1 = date.atStartOfDay(romeZone); LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); ZonedDateTime zdt2 = dateTime.atZone(romeZone); Instant instant = Instant.now(); ZonedDateTime zdt3 = instant.atZone(romeZone);
ZonedDateTime
将 LocalDateTime 转换为 Instant :
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); Instant instantFromDateTime = dateTime.toInstant(romeZone);
将 Instant 转换为 LocalDateTime :
Instant instant = Instant.now(); LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);
计算时区
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
日历系统
Java 8中另外还提供了4种其余的日历系统。这些日历系统中的每个都有一个对应的日志类,分别是 ThaiBuddhistDate 、MinguoDate 、 JapaneseDate 以及 HijrahDate 。全部这些类以及 LocalDate 都实现了ChronoLocalDate 接口,可以对公历的日期进行建模。利用 LocalDate 对象,你能够建立这些类的实例。
LocalDate date = LocalDate.of(2014, Month.MARCH, 18); JapaneseDate japaneseDate = JapaneseDate.from(date);
等同于
Chronology japaneseChronology = Chronology.ofLocale(Locale.JAPAN); ChronoLocalDate now = japaneseChronology.dateNow();
@Repeatable
若是一个注解在设计之初就是可重复的,你能够直接使用它。可是,若是你提供的注解是为用户提供的,那么就须要作一些工做,说明该注解能够重复。
新增方法
类/接口 | 新方法 |
---|---|
Map | getOrDefault , forEach , compute , computeIfAbsent , computeIfPresent , merge ,putIfAbsent , remove(key,value) , replace , replaceAll |
Iterable | forEach , spliterator |
Iterator | forEachRemaining |
Collection | removeIf , stream , parallelStream |
List | replaceAll , sort |
BitSet | stream |
Map
forEach 该方法签名为void forEach(BiConsumer<? super K,? super V> action),做用是对Map中的每一个映射执行action指定的操做,其中BiConsumer是一个函数接口,里面有一个待实现方法void accept(T t, U u)。
java8以前写法:
HashMap<Integer, String> map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); for (Map.Entry<Integer, String> entry : map.entrySet()) { System.out.println(entry.getKey() + "=" + entry.getValue()); }
java8:
HashMap<Integer, String> map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); map.forEach((k, v) -> System.out.println(k + "=" + v));
getOrDefault 方法就能够替换如今检测 Map 中是否包含给定键映射的惯用方法。若是 Map 中不存在这样的键映射,你能够提供一个默认值,方法会返回该默认值。
java8以前写法:
Map<String, Integer> carInventory = new HashMap<>(); Integer count = 0; if (map.containsKey("Aston Martin")) { count = map.get("Aston Martin"); }
java8:
Integer count = map.getOrDefault("Aston Martin", 0);
putIfAbsent 方法签名为V putIfAbsent(K key, V value),做用是只有在不存在key值的映射或映射值为null时,才将value指定的值放入到Map中,不然不对Map作更改.该方法将条件判断和赋值合二为一,使用起来更加方便。
remove(Object key, Object value)方法,只有在当前Map中key正好映射到value时才删除该映射,不然什么也不作。
replace 在Java7及之前,要想替换Map中的映射关系可经过put(K key, V value)方法实现,该方法老是会用新值替换原来的值.为了更精确的控制替换行为,Java8在Map中加入了两个replace()方法,分别以下:
replaceAll 该方法签名为replaceAll(BiFunction<? super K,? super V,? extends V> function),做用是对Map中的每一个映射执行function指定的操做,并用function的执行结果替换原来的value,其中BiFunction是一个函数接口,里面有一个待实现方法R apply(T t, U u)。
java8以前写法:
HashMap<Integer, String> map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); for (Map.Entry<Integer, String> entry : map.entrySet()) { entry.setValue(entry.getValue().toUpperCase()); }
java8:
HashMap<Integer, String> map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); map.replaceAll((k, v) -> v.toUpperCase());
merge 该方法签名为merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction),做用是:
若是Map中key对应的映射不存在或者为null,则将value(不能是null)关联到key上;
不然执行remappingFunction,若是执行结果非null则用该结果跟key关联,不然在Map中删除key的映射。
Map<String, String> myMap = new HashMap<>(); myMap.put("A", "str01A"); myMap.merge("A", "merge01", String::concat); // str01A merge01
compute 该方法签名为compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) ,若是map里有这个key,那么remappingFunction输入的v就是如今的值,返回的是对应value,若是没有这个key,那么输入的v是null。
map.compute(key, (k, v) -> v == null ? newMsg : v.concat(newMsg));
computeIfAbsent 该方法签名为V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction),做用是:只有在当前Map中不存在key值的映射或映射值为null时,才调用mappingFunction,并在mappingFunction执行结果非null时,将结果跟key关联。
java8以前写法:
Map<Integer, Set<String>> map = new HashMap<>(); if (map.containsKey(1)) { map.get(1).add("one"); } else { Set<String> valueSet = new HashSet<String>(); valueSet.add("one"); map.put(1, valueSet); }
java8:
map.computeIfAbsent(1, v -> new HashSet<String>()).add("yi");
computeIfPresent 该方法签名为V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction),做用跟computeIfAbsent()相反,即,只有在当前Map中存在key值的映射且非null时,才调用remappingFunction,若是remappingFunction执行结果为null,则删除key的映射,不然使用该结果替换key原来的映射。
Collection
removeIf 该方法签名为boolean removeIf(Predicate<? super E> filter),做用是删除容器中全部知足filter指定条件的元素,其中Predicate是一个函数接口,里面只有一个待实现方法boolean test(T t),一样的这个方法的名字根本不重要,由于用的时候不须要书写这个名字。
java8以前写法:
// 使用迭代器删除列表元素 ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().length()>3) { // 删除长度大于3的元素 it.remove(); } }
java8:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); // 删除长度大于3的元素 list.removeIf(str -> str.length() > 3);
replaceAll 该方法签名为void replaceAll(UnaryOperator<E> operator),做用是对每一个元素执行operator指定的操做,并用操做结果来替换原来的元素。其中UnaryOperator是一个函数接口,里面只有一个待实现函数T apply(T t)。
java8以前写法:
// 使用下标实现元素替换 ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); for (int i=0; i<list.size(); i++) { String str = list.get(i); if (str.length()>3) { list.set(i, str.toUpperCase()); } }
java8:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.replaceAll(str -> { if (str.length() > 3) { return str.toUpperCase(); } return str; });
sort 该方法定义在List接口中,方法签名为void sort(Comparator<? super E> c),该方法根据c指定的比较规则对容器元素进行排序。Comparator接口咱们并不陌生,其中有一个方法int compare(T o1, T o2)须要实现,显然该接口是个函数接口。
java8以前写法:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); Collections.sort(list, new Comparator<String>() { @Override public int compare(String str1, String str2) { return str1.length() - str2.length(); } });
java8:
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.sort((str1, str2) -> str1.length() - str2.length());
spliterator 方法签名为Spliterator<E> spliterator(),该方法返回容器的可拆分迭代器。从名字来看该方法跟iterator()方法有点像,咱们知道Iterator是用来迭代容器的,Spliterator也有相似做用,但两者有以下不一样:
stream和parallelStream 分别返回该容器的Stream视图表示,不一样之处在于parallelStream()返回并行的Stream。Stream是Java函数式编程的核心类。
原子操做
java.util.concurrent.atomic 包提供了多个对数字类型进行操做的类,好比 AtomicInteger 和 AtomicLong ,它们支持对单一变量的原子操做。这些类在Java 8中新增了更多的方法支持。
Adder 和 Accumulator:
多线程的环境中,若是多个线程须要频繁地进行更新操做,且不多有读取的动做(好比,在统计计算的上下文中) ,Java API文档中推荐你们使用新的类 LongAdder 、 LongAccumulator 、DoubleAdder 以及 DoubleAccumulator ,尽可能避免使用它们对应的原子类型。这些新的类在设计之初就考虑了动态增加的需求,能够有效地减小线程间的竞争。
LongAddr 和 DoubleAdder 类都支持加法操做,而 LongAccumulator 和 DoubleAccumulator 可使用给定的方法整合多个值。
LongAdder adder = new LongAdder(); adder.add(10); long sum = adder.sum();
等同于
LongAccumulator acc = new LongAccumulator(Long::sum, 0); acc.accumulate(10); long result = acc.get();
ConcurrentHashMap
ConcurrentHashMap 类的引入极大地提高了 HashMap 现代化的程度,新引入的ConcurrentHashMap 对并发的支持很是友好。 ConcurrentHashMap 容许并发地进行新增和更新操做,由于它仅对内部数据结构的某些部分上锁。所以,和另外一种选择,即同步式的 Hashtable 比较起来,它具备更高的读写性能。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); Optional<Integer> maxValue = Optional.of(map.reduceValues(1, Integer::max));
注意,对 int 、 long 和 double ,它们的 reduce 操做各有不一样(好比 reduceValuesToInt 、reduceKeysToLong 等) 。
Arrays
使用 parallelSort
parallelSort 方法会以并发的方式对指定的数组进行排序,你可使用天然顺序,也能够为数组对象定义特别的 Comparator 。
使用 setAll 和 parallelSetAll
setAll 和 parallelSetAll 方法能够以顺序的方式也能够用并发的方式,使用提供的函数计算每个元素的值,对指定数组中的全部元素进行设置。该函数接受元素的索引,返回该索引元素对应的值。因为 parallelSetAll 须要并发执行,因此提供的函数必须没有任何反作用。
int[] evenNumbers = new int[10]; Arrays.setAll(evenNumbers, i -> i * 2); // 0, 2, 4, 6...
使用 parallelPrefix
parallelPrefix 方法以并发的方式, 用用户提供的二进制操做符对给定数组中的每一个元素进行累积计算。
int[] ones = new int[10];
Arrays.fill(ones, 1);
Arrays.parallelPrefix(ones, (a, b) -> a + b);
Number
Number 类中新增的方法以下。
Math
若是 Math 中的方法在操做中出现溢出, Math 类提供了新的方法能够抛出算术异常。支持这一异常的方法包括使用 int 和 long 参数的 addExact 、 subtractExact 、 multipleExact 、incrementExact 、 decrementExact 和 negateExact 。此外, Math 类还新增了一个静态方法toIntExact , 能够将 long 值转换为 int 值。 其余的新增内容包括静态方法 floorMod 、 floorDiv和 nextDown 。
Files
Files 类最引人注目的改变是,你如今能够用文件直接产生流。经过 Files.lines 方法你能够以延迟方式读取文件的内容,并将其做为一个流。此外,还有一些很是有用的静态方法能够返回流。
Reflection
Relection 接口的另外一个变化是新增了能够查询方法参数信息的API,好比,你如今可使用新增的 java.lang.reflect.Parameter 类查询方法参数的名称和修饰符,这个类被新的java.lang.reflect.Executable 类所引用, 而 java.lang.reflect.Executable 通用函数和构造函数共享的父类。
String
String 类也新增了一个静态方法,名叫 join 。你大概已经猜出它的功能了,它能够用一个分隔符将多个字符串链接起来。
String authors = String.join(", ", "Raoul", "Mario", "Alan");
泛型
Java类型要么是引用类型(好比 Byte 、 Integer 、 Object 、 List ) ,要么是原始类型(好比 int 、 double 、 byte 、 char ) 。可是泛型(好比 Consumer<T> 中的 T )只能绑定到引用类型。这是由泛型内部的实现方式形成的。所以,在Java里有一个将原始类型转换为对应的引用类型的机制。这个机制叫做装箱(boxing)。相反的操做,也就是将引用类型转换为对应accept 方法的实现Lambda是 Function接口的 apply 方法的实现的原始类型,叫做拆箱(unboxing) 。Java还有一个自动装箱机制来帮助程序员执行这一任务:装箱和拆箱操做是自动完成的。
工具类库
Guava、Apache和lambdaj
广义归约( Collectors.reducing)
它须要三个参数。
第一个参数是归约操做的起始值,也是流中没有元素时的返回值,因此很显然对于数值和而言 0 是一个合适的值。
第二个参数就是转换成一个表示其所含热量的 int 。
第三个参数是一个 BinaryOperator ,将两个项目累积成一个同类型的值。
求和:
int totalCalories = menu.stream().collect(Collectors.reducing(0, Dish::getCalories, (i, j) -> i + j));
找出集合中最大值:
Optional <Dish> mostCalorieDish = menu.stream().collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));
Collectors 类的静态工厂方法
工厂方法 | 返回类型 | 描 述 | 使用示例 |
---|---|---|---|
toList | List<T> | 把流中全部项目收集到一个 List | List<Dish> dishes = menuStream.collect(Collectors.toList()); |
toSet | Set<T> | 把流中全部项目收集到一个 Set ,删除重复项 | Set<Dish> dishes = menuStream.collect(Collectors.toSet()); |
toMap | Map<T, K> | 把流中全部项目收集到一个 Map ,删除重复项,默认状况下,出现重复数据会报错 | Map<Long, Dish> dishesMap = menuStream.collect(Collectors.toMap(Dish::getCalories));若有重复数据,能够设置使用哪个数据 Map<Long, Dish> dishesMap = menuStream.collect(Collectors.toMap(Dish::getCalories, d -> d, (d1, d2) -> d1, LinkedHashMap::new)); |
toCollection | Collection<T> | 把流中全部项目收集到给定的供应源建立的集合 | Collection<Dish> dishes = menuStream.collect(Collectors.toCollection(), ArrayList::new); |
counting | Long | 计算流中元素的个数 | long howManyDishes = menuStream.collect(Collectors.counting()); |
summingInt | Integer | 对流中项目的一个整数属性求和 | int totalCalories = menuStream.collect(Collectors.summingInt(Dish::getCalories)); |
averagingInt | Integer | 计算流中项目 Integer 属性的平均值 | int avgCalories = menuStream.collect(Collectors.averagingInt(Dish::getCalories)); |
summarizingInt | IntSummaryStatistics | 收集关于流中项目 Integer 属性的统计值,例如最大、最小、总和与平均值 | IntSummaryStatistics menuStatistics = menuStream.collect(Collectors.summarizingInt(Dish::getCalories)); |
joining | String | 链接对流中每一个项目调用 toString 方法所生成的字符串 | String shortMenu = menuStream.map(Dish::getName).collect(Collectors.joining(", ")); |
maxBy | Optional<T> | 一个包裹了流中按照给定比较器选出的最大元素的 Optional ,或若是流为空则为 Optional.empty() | Optional<Dish> fattest = menuStream.collect(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))); |
minBy | Optional<T> | 一个包裹了流中按照给定比较器选出的最小元素的 Optional ,或若是流为空则为 Optional.empty() | Optional<Dish> lightest = menuStream.collect(Collectors.minBy(Comparator.comparingInt(Dish::getCalories))); |
reducing | 归约操做产生的类型 | 从一个做为累加器的初始值开始,利用 BinaryOperator 与流中的元素逐个结合,从而将流归约为单个值 | int totalCalories = menuStream.collect(Collectors.reducing(0, Dish::getCalories, Integer::sum)); |
collectingAndThen | 转换函数返回的类型 | 包裹另外一个收集器,对其结果应用转换函数 | int howManyDishes = menuStream.collect(Collectors.collectingAndThen(toList(), List::size)); |
groupingBy | Map<K, List<T>> | 根据项目的一个属性的值对流中的项目分组组,并将属性值分组结果 Map 的键 | Map<Dish.Type,List<Dish>> dishesByType = menuStream.collect(Collectors.groupingBy(Dish::getType)); |
partitioningBy | Map<Boolean,List<T>> | 根据对流中每一个项目应用谓词的结果来对项目进行分区 | Map<Boolean,List<Dish>> vegetarianDishes = menuStream.collect(Collectors.partitioningBy(Dish::isVegetarian)); |
Optional介绍
Optional<T> 类( java.util.Optional )是一个容器类,表明一个值存在或不存在。
线程个数计算方式
若是线程池中线程的数量过多,最终它们会竞争稀缺的处理器和内存资源,浪费大量的时间在上下文切换上。反之,若是线程的数目过少,正如你的应用所面临的状况,处理器的一些核可能就没法充分利用。Brian Goetz建议,线程池大小与处理器的利用率之比可使用下面的公式进行估算:
N threads = N CPU * U CPU * (1 + W/C)
其中:
并行——使用parallelStream仍是 CompletableFutures ?
目前为止, 你已经知道对集合进行并行计算有两种方式: 要么将其转化为parallelStream, 利用map这样的操做开展工做,要么枚举出集合中的每个元素,建立新的线程,在 Completable-Future 内对其进行操做。后者提供了更多的灵活性,你能够调整线程池的大小,而这能帮助你确保总体的计算不会由于线程都在等待I/O而发生阻塞。
咱们对使用这些API的建议以下。
若是你进行的是计算密集型的操做,而且没有I/O,那么推荐使用 Stream 接口,由于实现简单,同时效率也多是最高的(若是全部的线程都是计算密集型的,那就没有必要建立比处理器核数更多的线程) 。
反之,若是你并行的工做单元还涉及等待I/O的操做(包括网络链接等待) ,那么使用CompletableFuture 灵活性更好,你能够像前文讨论的那样,依据等待/计算,或者W/C的比率设定须要使用的线程数。这种状况不使用并行流的另外一个缘由是,处理流的流水线中若是发生I/O等待, 流的延迟特性会让咱们很难判断到底何时触发了等待。
配置并行流使用的线程池
并行流内部使用了默认的 ForkJoinPool,它默认的线程数量就是你的处理器数量 , 这个值是由Runtime.getRuntime().available-Processors() 获得的。
可是能够通 过系统属性java.util.concurrent.ForkJoinPool.common.parallelism 来改变线程池大小,以下所示:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
这是一个全局设置,所以它将影响代码中全部的并行流。反过来讲,目前还没法专为某个
并行流指定这个值。通常而言,让 ForkJoinPool 的大小等于处理器数量是个不错的默认值,
除非你有很好的理由,不然咱们强烈建议你不要修改它。
测量流性能
咱们声称并行求和方法应该比顺序和迭代方法性能好。然而在软件工程上,靠猜绝对不是什么好办法!特别是在优化性能时,你应该始终遵循三个黄金规则:测量,测量,再测量。
可分解性总结了一些流数据源适不适于并行:
源 | 可分解性 |
---|---|
ArrayList | 极佳 |
LinkedList | 差 |
IntStream.range | 极佳 |
Stream.iterate | 差 |
HashSet | 好 |
TreeSet | 好 |