Java 8 实战学习笔记

@(JAVASE)[java8, 实战, lambda]

文章目录

  • Java 8 实战学习笔记
    • 参考内容
      • Lambda表达式
        • Lambda环绕执行模式(抽离步骤)
          • 原始代码
          • 第1步 行为参数化
          • 第2步 使用函数式接口来传递行为
          • 第3步 执行一个行为
          • 第4步 传递Lambda
        • 函数接口及其原始类型特化
        • 方法引用
          • Lambda及其等效方法引用的例子
          • 方法引用主要有三类
            • 指向静态方法的方法引用
            • 指向任意类型实例方法的方法引用
            • 指向现有对象的实例方法的方法引用
          • 构造函数引用
      • 使用流
        • 归约
          • 中间操作和终端操作
        • 数值流
          • 原始类型流特化
            • 映射到数值流
            • 转换回对象流
            • 默认值OptionalInt
          • 数值范围
        • 构建流
          • 由值创建流
          • 由数组创建流
          • 由文件生成流
          • 由函数生成流:创建无限流
            • 迭代
            • 生成
      • 用流收集数据
        • Collectors类的静态工厂方法
        • 收集器接口
          • 顺序归约过程的逻辑步骤
          • 使用combiner方法来并行化归约过程
          • 案例
      • 用Optional取代null
        • Optional类的方法
        • 实战示例
          • 用Optional封装可能为null的值
          • 异常与Optional的对比
          • 把所有内容整合起来
      • CompletableFuture组合式异步编程
        • 实现异步API
        • 密集IO任务各种实现间的对比
          • 测试代码
          • JMH测试结果
          • 调整线程池的大小
          • 并行——使用流还是CompletableFutures?
        • 对多个异步任务进行流水线操作
          • JMH测试结果
          • 合并两个独立的CompletableFuture
        • 响应CompletableFuture的completion事件
      • 新的日期和时间API
        • LocalDate、LocalTime、Instant、Duration 以及Period
          • 日期-时间类中表示时间间隔的通用方法
        • 操纵、解析和格式化日期
          • 表示时间点的日期-时间类的通用方法
          • TemporalAdjuster类中的工厂方法
            • TemporalAdjuster接口
            • 定制的TemporalAdjuster
          • 打印输出及解析日期-时间对象
            • 构造一个DateTimeFormatter

参考内容

Lambda表达式

Lambda环绕执行模式(抽离步骤)

原始代码
public static String processFile() throws IOException {try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {// 行为return br.readLine();}
}
第1步 行为参数化
// 打印一行
String result = processFile((BufferedReader br) ->br.readLine());
// 打印两行
String result = processFile((BufferedReader br) ->br.readLine() + br.readLine());
第2步 使用函数式接口来传递行为
@FunctionalInterface
public interface BufferedReaderProcessor {String process(BufferedReader b) throws IOException;
}public static String processFile(BufferedReaderProcessor p) throws IOException {…
}
第3步 执行一个行为
public static String processFile(BufferedReaderProcessor p) throws IOException {try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {return p.process(br);}
}
第4步 传递Lambda
// 打印一行
String oneLine = processFile((BufferedReader br) ->br.readLine());
// 打印两行
String twoLines = processFile((BufferedReader br) ->br.readLine() + br.readLine());

函数接口及其原始类型特化

函数式接口 函数描述符 原始类型特化
Predicate<T> T->boolean IntPredicate,LongPredicate, DoublePredicate
Consumer<T> T->void IntConsumer,LongConsumer, DoubleConsumer
Function<T,R> T->R IntFunction<R>,IntToDoubleFunction,IntToLongFunction,
LongFunction<R>,LongToDoubleFunction,LongToIntFunction,
DoubleFunction<R>,ToIntFunction<T>,ToDoubleFunction<T>,
ToLongFunction<T>
Supplier<T> ()->T BooleanSupplier,IntSupplier, LongSupplier, DoubleSupplier
UnaryOperator<T> T->T IntUnaryOperator,LongUnaryOperator, DoubleUnaryOperator
BinaryOperator<T> (T,T)->T IntBinaryOperator,LongBinaryOperator, DoubleBinaryOperator
BiPredicate<L,R> (L,R)->boolean
BiConsumer<T,U> (T,U)->void ObjIntConsumer<T>,ObjLongConsumer<T>, ObjDoubleConsumer<T>
BiFunction<T,U,R> (T,U)->R ToIntBiFunction<T,U>,ToLongBiFunction<T,U>,
ToDoubleBiFunction<T,U>

方法引用

Lambda及其等效方法引用的例子
Lambda 等效的方法引用
(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
方法引用主要有三类
指向静态方法的方法引用

指向任意类型实例方法的方法引用

指向现有对象的实例方法的方法引用

构造函数引用
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
// 这就等价于
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apply(110);
// 这就等价于
Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
Apple a2 = c2.apply(110);BiFunction<String, Integer, Apple> c3 = Apple::new;
Apple c3 = c3.apply("green", 110);
// 这就等价于
BiFunction<String, Integer, Apple> c3 = (color, weight) -> new Apple(color, weight);
Apple c3 = c3.apply("green", 110);

使用流

归约

中间操作和终端操作
操作 类型 返回类型 使用的类型/函数式接口 函数描述符
filter 中间 Stream<T> Predicate<T> T -> boolean
distinct 中间(有状态-无界) Stream<T>
skip 中间(有状态-有界) Stream<T> long
limit 中间(有状态-有界) Stream<T> long
map 中间 Stream<R> Function<T,R> T -> R
flatMap 中间 Stream<R> 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

数值流

原始类型流特化

IntStreamDoubleStreamLongStream

映射到数值流
int calories = menu.stream().mapToInt(Dish::getCalories).sum();
转换回对象流
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
默认值OptionalInt
OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
int max = maxCalories.orElse(1);
数值范围

IntStreamLongStream的静态方法rangerangeClosed

IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
System.out.println(evenNumbers.count());

构建流

由值创建流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
// 空流
Stream<String> emptyStream = Stream.empty();
由数组创建流
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
由文件生成流
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.iterateStream.generate。一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。

迭代

流的第一个元素是初始值0。然后加上2来生成新的值2,再加上2来得到新的值4,以此类推。这种iterate操作基本上是顺序的,因为结果取决于前一次应用。

// (T,UnaryOperator<T>)
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
生成
// Supplier<T>
Stream.generate(Math::random).limit(5).forEach(System.out::println);

用流收集数据

Collectors类的静态工厂方法

工厂方法 返回类型 用于 使用示例
toList List<T> 把流中所有项目收集到一个List List<Dish> dishes = menuStream.collect(toList());
toSet Set<T> 把流中所有项目收集到一个Set,删除重复项 Set<Dish> dishes = menuStream.collect(toSet());
toCollection Collection<T> 把流中所有项目收集到给定的供应源创建的集合 Collection<Dish> dishes = menuStream.collect( toCollection(),ArrayList::new);
counting Long 计算流中元素的个数 long howManyDishes = menuStream.collect(counting());
summingInt Integer 对流中项目的一个整数属性求和 int totalCalories = menuStream.collect( summingInt(Dish::getCalories));
averagingInt Double 计算流中项目Integer属性的平均值 double avgCalories = menuStream.collect( averagingInt(Dish::getCalories));
summarizingInt IntSummaryStatistics 收集关于流中项目Integer属性的统计值,例如最大、最小、总和与平均值 IntSummaryStatistics menuStatistics = menuStream.collect( summarizingInt(Dish::getCalories));
joining String 连接对流中每个项目调用toString方法所生成的字符串 String shortMenu = menuStream.map(Dish::getName).collect( joining(", "));
maxBy Optional<T> 一个包裹了流中按照给定比较器选出的最大元素的Optional,或如果流为空则为Optional.empty() Optional<Dish> fattest = menuStream.collect( maxBy(comparingInt(Dish::getCalories)));
minBy Optional<T> 一个包裹了流中按照给定比较器选出的最小元素的Optional,或如果流为空则为Optional.empty() Optional<Dish> lightest = menuStream.collect( minBy(comparingInt(Dish::getCalories)));
reducing 归约操作产生的类型 从一个作为累加器的初始值开始,利用BinaryOperator与流中的元素逐个结合,从而将流归约为单个值 int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));
collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结果应用转换函数 int howManyDishes = menuStream.collect( collectingAndThen(toList(),List::size));
groupingBy Map<K, List<T>> 根据项目的一个属性的值对流中的项目作问组,并将属性值作为结果Map的键 Map<Dish.Type,List<Dish>> dishesByType = menuStream.collect( groupingBy(Dish::getType));
partitioningBy Map<Boolean,List<T>> 根据对流中每个项目应用谓词的结果来对项目进行分区 Map<Boolean,List<Dish>> vegetarianDishes = menuStream.collect( partitioningBy(Dish::isVegetarian));

收集器接口

  • T是流中要收集的项目的泛型。
  • A是累加器的类型,累加器是在收集过程中用于累积部分结果的对象。
  • R是收集操作得到的对象(通常但并不一定是集合)的类型。
public interface Collector<T, A, R> {// 建立新的结果容器Supplier<A> supplier();// 将元素添加到结果容器BiConsumer<A, T> accumulator();// 对结果容器应用最终转换Function<A, R> finisher();// 合并两个结果容器BinaryOperator<A> combiner();// UNORDERED——归约结果不受流中项目的遍历和累积顺序的影响。// CONCURRENT——accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约。// IDENTITY_FINISH——这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器A不加检查地转换为结果R是安全的。Set<Characteristics> characteristics();
}
顺序归约过程的逻辑步骤

使用combiner方法来并行化归约过程

案例
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import static java.util.stream.Collector.Characteristics.*;
public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {@Overridepublic Supplier<List<T>> supplier() {return ArrayList::new;}@Overridepublic BiConsumer<List<T>, T> accumulator() {return List::add;}@Overridepublic Function<List<T>, List<T>> finisher() {return Function.indentity();}@Overridepublic BinaryOperator<List<T>> combiner() {return (list1, list2) -> {list1.addAll(list2);return list1;};}@Overridepublic Set<Characteristics> characteristics() {return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH, CONCURRENT));}
}
// 进行自定义收集而不去实现Collector
// Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner
List<Dish> dishes = menuStream.collect(ArrayList::new,List::add,List::addAll);

用Optional取代null

Optional类的方法

方法 描述
empty 返回一个空的Optional实例
filter 如果值存在并且满足提供的谓词,就返回包含该值的Optional对象;否则返回一个空的Optional对象
flatMap 如果值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象
get 如果该值存在,将该值用Optional封装返回,否则抛出一个NoSuchElementException异常
ifPresent 如果值存在,就执行使用该值的方法调用,否则什么也不做
isPresent 如果值存在就返回true,否则返回false
map 如果值存在,就对该值执行提供的mapping函数调用
of 将指定值用Optional封装之后返回,如果该值为null,则抛出一个NullPointerException异常
ofNullable 将指定值用Optional封装之后返回,如果该值为null,则返回一个空的Optional对象
orElse 如果有值则将其返回,否则返回一个默认值
orElseGet 如果有值则将其返回,否则返回一个由指定的Supplier接口生成的值
orElseThrow 如果有值则将其返回,否则抛出一个由指定的Supplier接口生成的异常

实战示例

用Optional封装可能为null的值
Optional<Object> value = Optional.ofNullable(map.get("key"));
异常与Optional的对比
public static Optional<Integer> stringToInt(String s) {try {return Optional.of(Integer.parseInt(s));} catch (NumberFormatException e) {return Optional.empty();}
}
把所有内容整合起来
import org.junit.Test;import java.util.Optional;
import java.util.Properties;import static org.junit.Assert.assertEquals;public class OptionalTest {@Testpublic void testOptional() {Properties props = new Properties();props.setProperty("a", "5");props.setProperty("b", "true");props.setProperty("c", "-3");assertEquals(5, readDuration(props, "a"));assertEquals(0, readDuration(props, "b"));assertEquals(0, readDuration(props, "c"));assertEquals(0, readDuration(props, "d"));}private int readDuration(Properties props, String name) {return Optional.ofNullable(props.getProperty(name)).flatMap(OptionalTest::stringToInt).filter(i -> i > 0).orElse(0);}private static Optional<Integer> stringToInt(String s) {try {return Optional.of(Integer.parseInt(s));} catch (NumberFormatException e) {return Optional.empty();}}
}

CompletableFuture组合式异步编程

实现异步API

package com.switchvov.future;import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;/*** @Author Switch* @Date 2017/12/10*/
public class Shop {private String name;public Shop(String name) {this.name = name;}public Future<Double> getPriceAsyncStatic(String product) {return CompletableFuture.supplyAsync(() -> calculatePrice(product));}public Future<Double> getPriceAsync(String product) {CompletableFuture<Double> futurePrice = new CompletableFuture<>();new Thread(() -> {try {double price = calculatePrice(product);futurePrice.complete(price);} catch (Exception e) {futurePrice.completeExceptionally(e);}}).start();return futurePrice;}public double getPrice(String product) {return calculatePrice(product);}private double calculatePrice(String product) {delay();return ThreadLocalRandom.current().nextDouble() * product.charAt(0) + product.charAt(1);}private static void delay() {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
package com.switchvov.future;import org.junit.Test;import java.util.concurrent.Future;/*** @Author Switch* @Date 2017/12/10*/
public class ShopTest {@Testpublic void testShop() {Shop shop = new Shop("BestShop");long start = System.nanoTime();// Future<Double> futurePrice = shop.getPriceAsync("my favorite produce");Future<Double> futurePrice = shop.getPriceAsyncStatic("my favorite produce");long invocationTime = (System.nanoTime() - start) / 1_000_000;System.out.println("Invocation returned after " + invocationTime + " msecs");// doSomethingElse();try {double price = futurePrice.get();System.out.printf("Price is %.2f%n", price);} catch (Exception e) {throw new RuntimeException(e);}long retrievalTime = (System.nanoTime() - start) / 1_000_000;System.out.println("Price returned after " + retrievalTime + " msecs");}
}

密集IO任务各种实现间的对比

测试代码
package com.switchvov.future;import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;/*** @Author Switch* @Date 2017/12/10*/
public class Shop {private String name;public Shop(String name) {this.name = name;}public double getPrice(String product) {return calculatePrice(product);}private double calculatePrice(String product) {delay();return ThreadLocalRandom.current().nextDouble() * product.charAt(0) + product.charAt(1);}private static void delay() {try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
package com.switchvov.future;import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.IntStream;/*** @Author Switch* @Date 2017/12/10*/
@BenchmarkMode(Mode.AverageTime)
@State(Scope.Benchmark)
@Warmup(iterations = 1)
@Measurement(iterations = 2)
public class ShopMeasure {private final String SHOP_PREFIX = "Shop ";private final int SHOP_NUMBER = Runtime.getRuntime().availableProcessors();
//    private final int SHOP_NUMBER = Runtime.getRuntime().availableProcessors() * 2 + 1;private final String PRODUCT_NAME = "Java 8实战";@Benchmarkpublic void measureSequential() {System.out.println(findPricesSequential(PRODUCT_NAME));}@Benchmarkpublic void measureParallelStream() {System.out.println(findPricesParallelStream(PRODUCT_NAME));}@Benchmarkpublic void measureCompletableFuture() {System.out.println(findPricesCompletableFuture(PRODUCT_NAME));}@Benchmarkpublic void measureCustomCompletableFuture() {System.out.println(findPricesCustomCompletableFuture(PRODUCT_NAME));}private List<String> findPricesSequential(String product) {return getShops(SHOP_NUMBER).stream().map(shop -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product))).collect(Collectors.toList());}private List<String> findPricesParallelStream(String product) {return getShops(SHOP_NUMBER).parallelStream().map(shop -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product))).collect(Collectors.toList());}private List<String> findPricesCompletableFuture(String product) {List<CompletableFuture<String>> priceFutures = getShops(SHOP_NUMBER).stream().map(shop -> CompletableFuture.supplyAsync(() -> shop.getName() + " price is " + shop.getPrice(product))).collect(Collectors.toList());return priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());}private final Executor executor = Executors.newFixedThreadPool(Math.min(getShops(SHOP_NUMBER).size(), 100), runnable -> {Thread thread = new Thread(runnable);thread.setDaemon(true);return thread;});private List<String> findPricesCustomCompletableFuture(String product) {List<CompletableFuture<String>> priceFutures = getShops(SHOP_NUMBER).stream().map(shop -> CompletableFuture.supplyAsync(() -> shop.getName() + " price is " + shop.getPrice(product), executor)).collect(Collectors.toList());return priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());}private List<Shop> getShops(int shopNumber) {return IntStream.range(0, shopNumber).mapToObj(index -> new Shop(SHOP_PREFIX + index)).collect(Collectors.toList());}public static void main(String[] args) throws RunnerException {Options options = new OptionsBuilder().forks(1).include(ShopMeasure.class.getSimpleName()).build();new Runner(options).run();}
}
JMH测试结果
  • 商店数:Runtime.getRuntime().availableProcessors()
Benchmark Mode Cnt(次数) Score Error Units
ShopMeasure.measureCompletableFuture avgt 2 1.001 s/op
ShopMeasure.measureCustomCompletableFuture avgt 2 0.501 s/op
ShopMeasure.measureParallelStream avgt 2 0.502 s/op
ShopMeasure.measureSequential avgt 2 4.004 s/op
  • 商店数:Runtime.getRuntime().availableProcessors() * 2 + 1
Benchmark Mode Cnt(次数) Score Error Units
ShopMeasure.measureCompletableFuture avgt 2 1.502 s/op
ShopMeasure.measureCustomCompletableFuture avgt 2 0.502 s/op
ShopMeasure.measureParallelStream avgt 2 1.502 s/op
ShopMeasure.measureSequential avgt 2 8.506 s/op

由此可以看出在IO密集任务中,定制执行器的CompletableFuture效率是最高的。

调整线程池的大小

《Java并发编程实战》一书中,Brian Goetz和合著者们为线程池大小的优化提供了不少中肯的建议。这非常重要,如果线程池中线程的数量过多,最终它们会竞争稀缺的处理器和内存资源,浪费大量的时间在上下文切换上。反之,如果线程的数目过少,正如你的应用所面临的情况,处理器的一些核可能就无法充分利用。Brian Goetz建议,线程池大小与处理器的利用率之比可以使用下面的公式进行估算:
Nthreads=NCPU∗UCPU∗(1+WC)N_{threads} = N_{CPU} * U_{CPU} * (1 + \frac{W}{C})Nthreads​=NCPU​∗UCPU​∗(1+CW​)

其中:

  • NCPUN_{CPU}NCPU​是处理器的核的数目,可以通过Runtime.getRuntime().availableProcessors()得到
  • UCPUU_{CPU}UCPU​是期望的CPU利用率(该值应该介于0和1之间)
  • WC\frac{W}{C}CW​是等待时间与计算时间的比率,比如说IO操作即为等待时间,计算处理即为计算时间
并行——使用流还是CompletableFutures?

对集合进行并行计算有两种方式:要么将其转化为并行流,利用map这样的操作开展工作,要么枚举出集合中的每一个元素,创建新的线程,在CompletableFuture内对其进行操作。后者提供了更多的灵活性,可以调整线程池的大小,而这能帮助确保整体的计算不会因为线程都在等待I/O而发生阻塞。

使用这些API的建议如下:

  • 如果进行的是计算密集型的操作,并且没有I/O,那么推荐使用Stream接口,因为实现简单,同时效率也可能是最高的(如果所有的线程都是计算密集型的,那就没有必要创建比处理器核数更多的线程)。
  • 反之,如果并行的工作单元还涉及等待I/O的操作(包括网络连接等待),那么使用CompletableFuture灵活性更好,可以依据等待/计算,或者WC\frac{W}{C}CW​的比率设定需要使用的线程数。这种情况不使用并行流的另一个原因是,处理流的流水线中如果发生I/O等待,流的延迟特性很难判断到底什么时候触发了等待。

对多个异步任务进行流水线操作

package com.switchvov.future;import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;/*** @Author Switch* @Date 2017/12/10*/
public class Shop {private String name;public Shop(String name) {this.name = name;}public String getPrice(String product) {double price = calculatePrice(product);Discount.Code code = Discount.Code.values()[ThreadLocalRandom.current().nextInt(Discount.Code.values().length)];return String.format("%s:%.2f:%s", name, price, code);}private double calculatePrice(String product) {delay();return ThreadLocalRandom.current().nextDouble() * product.charAt(0) + product.charAt(1);}private static void delay() {try {TimeUnit.MILLISECONDS.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
package com.switchvov.future;/*** @Author Switch* @Date 2017/12/10*/
public class Quote {private final String shopName;private final double price;private final Discount.Code discountCode;public Quote(String shopName, double price, Discount.Code discountCode) {this.shopName = shopName;this.price = price;this.discountCode = discountCode;}public static Quote parse(String quoteString) {String[] split = quoteString.split(":");String shopName = split[0];double price = Double.parseDouble(split[1]);Discount.Code discountCode = Discount.Code.valueOf(split[2]);return new Quote(shopName, price, discountCode);}public String getShopName() {return shopName;}public double getPrice() {return price;}public Discount.Code getDiscountCode() {return discountCode;}
}
package com.switchvov.future;import java.util.concurrent.TimeUnit;/*** @Author Switch* @Date 2017/12/10*/
public class Discount {public enum Code {NONE(0), SILVER(5), GOLD(10), PLATINUM(15), DIAMOND(20);private final int percentage;Code(int percentage) {this.percentage = percentage;}}public static String applyDiscount(Quote quote) {return quote.getShopName() + " price is " + Discount.applyDiscount(quote.getPrice(), quote.getDiscountCode());}private static double applyDiscount(double price, Code code) {delay();return price * (100 - code.percentage) / 100;}private static void delay() {try {TimeUnit.MILLISECONDS.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
package com.switchvov.future;import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.IntStream;/*** @Author Switch* @Date 2017/12/10*/
@BenchmarkMode(Mode.AverageTime)
@State(Scope.Benchmark)
@Warmup(iterations = 1)
@Measurement(iterations = 2)
public class ShopMeasure {private final String SHOP_PREFIX = "Shop ";private final int SHOP_NUMBER = Runtime.getRuntime().availableProcessors();//    private final int SHOP_NUMBER = Runtime.getRuntime().availableProcessors() * 2 + 1;private final String PRODUCT_NAME = "Java 8实战";@Benchmarkpublic void measureSequential() {System.out.println(findPricesSequential(PRODUCT_NAME));}@Benchmarkpublic void measureCustomCompletableFuture() {System.out.println(findPricesCustomCompletableFuture(PRODUCT_NAME));}private List<String> findPricesSequential(String product) {return getShops(SHOP_NUMBER).stream().map(shop -> shop.getPrice(product)).map(Quote::parse).map(Discount::applyDiscount).collect(Collectors.toList());}private final Executor executor = Executors.newFixedThreadPool(Math.min(getShops(SHOP_NUMBER).size(), 100), runnable -> {Thread thread = new Thread(runnable);thread.setDaemon(true);return thread;});private List<String> findPricesCustomCompletableFuture(String product) {List<CompletableFuture<String>> priceFutures = getShops(SHOP_NUMBER).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());}private List<Shop> getShops(int shopNumber) {return IntStream.range(0, shopNumber).mapToObj(index -> new Shop(SHOP_PREFIX + index)).collect(Collectors.toList());}public static void main(String[] args) throws RunnerException {Options options = new OptionsBuilder().forks(1).include(ShopMeasure.class.getSimpleName()).build();new Runner(options).run();}
}
JMH测试结果
  • 商店数:Runtime.getRuntime().availableProcessors()
Benchmark Mode Cnt(次数) Score Error Units
ShopMeasure.measureCustomCompletableFuture avgt 2 2.002 s/op
ShopMeasure.measureSequential avgt 2 16.006 s/op
合并两个独立的CompletableFuture
    private Future<Double> fromCNYToUSD(Shop shop, String product) {return CompletableFuture.supplyAsync(() -> shop.getPrice(product)).thenApply(productString -> Quote.parse(productString).getPrice()).thenCombine(CompletableFuture.supplyAsync(() -> getRate(Money.CNY, Money.USD)), (price, rate) -> price * rate);}enum Money {CNY(1), USD(0.15);private double rate;Money(double rate) {this.rate = rate;}}private double getRate(Money from, Money to) {delay();return to.rate / from.rate;}private static void delay() {try {TimeUnit.MILLISECONDS.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}

响应CompletableFuture的completion事件

package com.switchvov.future;import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;/*** @Author Switch* @Date 2017/12/10*/
public class Shop {private String name;public Shop(String name) {this.name = name;}public String getPrice(String product) {double price = calculatePrice(product);Discount.Code code = Discount.Code.values()[ThreadLocalRandom.current().nextInt(Discount.Code.values().length)];return String.format("%s:%.2f:%s", name, price, code);}private double calculatePrice(String product) {randomDelay();return ThreadLocalRandom.current().nextDouble() * product.charAt(0) + product.charAt(1);}private static void randomDelay() {int delay = 500 + ThreadLocalRandom.current().nextInt(2000);try {TimeUnit.MILLISECONDS.sleep(delay);} catch (InterruptedException e) {throw new RuntimeException(e);}}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
package com.switchvov.future;import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;/*** @Author Switch* @Date 2017/12/10*/
@BenchmarkMode(Mode.AverageTime)
@State(Scope.Benchmark)
@Warmup(iterations = 1)
@Measurement(iterations = 2)
public class ShopMeasure {private final String SHOP_PREFIX = "Shop ";private final int SHOP_NUMBER = Runtime.getRuntime().availableProcessors();//    private final int SHOP_NUMBER = Runtime.getRuntime().availableProcessors() * 2 + 1;private final String PRODUCT_NAME = "Java 8实战";@Benchmarkpublic void measureFindPricesCustomCompletableFutureStream() {long start = System.currentTimeMillis();CompletableFuture[] futures = findPricesCustomCompletableFutureStream(PRODUCT_NAME).map(future -> future.thenAccept(string ->System.out.println(string + " (done in " + (System.currentTimeMillis() - start) + " msecs)"))).toArray(CompletableFuture[]::new);CompletableFuture.allOf(futures).join();System.out.println("All shops have now responded in " + (System.currentTimeMillis() - start) + " msecs");}private final Executor executor = Executors.newFixedThreadPool(Math.min(getShops(SHOP_NUMBER).size(), 100), runnable -> {Thread thread = new Thread(runnable);thread.setDaemon(true);return thread;});private Stream<CompletableFuture<String>> findPricesCustomCompletableFutureStream(String product) {return getShops(SHOP_NUMBER).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)));}private List<Shop> getShops(int shopNumber) {return IntStream.range(0, shopNumber).mapToObj(index -> new Shop(SHOP_PREFIX + index)).collect(Collectors.toList());}public static void main(String[] args) throws RunnerException {Options options = new OptionsBuilder().forks(1).include(ShopMeasure.class.getSimpleName()).build();new Runner(options).run();}
}

其余类未更改,详见对多个异步任务进行流水线操作

新的日期和时间API

LocalDate、LocalTime、Instant、Duration 以及Period

LocalDate date = LocalDate.of(2017, 12, 11);
System.out.println(date); // 2017-12-11
System.out.println(date.getYear()); // 2017
System.out.println(date.getMonth()); // DECEMBER
System.out.println(date.getDayOfMonth()); // 11
System.out.println(date.getDayOfWeek()); // MONDAY
System.out.println(date.lengthOfMonth()); // 31
System.out.println(date.isLeapYear()); // falseLocalDate today = LocalDate.now(); // 获取今天Dateint year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);
System.out.println(year + " " + month + " " + day); // 2017 12 11LocalTime time = LocalTime.of(20, 14, 30);
System.out.println(time.getHour()); // 20
System.out.println(time.getMinute()); // 14
System.out.println(time.getSecond()); // 30date = LocalDate.parse("2017-12-11");
System.out.println(date); // 2017-12-11
time = LocalTime.parse("20:14:30");
System.out.println(time); // 20:14:30LocalDateTime dt1 = LocalDateTime.of(2017, Month.DECEMBER, 11, 20, 14, 30);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(20, 14, 30);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);date = dt1.toLocalDate();
time = dt1.toLocalTime();System.out.println(Instant.ofEpochSecond(3)); // 1970-01-01T00:00:03Z
System.out.println(Instant.ofEpochSecond(3, 1)); // 1970-01-01T00:00:03.000000001Z
System.out.println(Instant.ofEpochSecond(2, 1_000_000_000)); // 1970-01-01T00:00:03Z
System.out.println(Instant.ofEpochSecond(4, -1_000_000_000)); // 1970-01-01T00:00:03ZDuration d1 = Duration.between(time, time.plus(1, ChronoUnit.MINUTES));
System.out.println(d1); // PT1M
Duration d2 = Duration.between(dt1, dt1.plus(1, ChronoUnit.DAYS));
System.out.println(d2); // PT24H
Duration d3 = Duration.between(dt1, dt1.minus(1, ChronoUnit.DAYS));
System.out.println(d3); // PT-24H
Duration d4 = Duration.between(Instant.ofEpochSecond(0), Instant.ofEpochSecond(2, 1));
System.out.println(d4); // PT2.000000001SSystem.out.println(Duration.ofMinutes(3)); // PT3M
System.out.println(Duration.of(3, ChronoUnit.MINUTES)); // PT3M
System.out.println(Period.ofDays(10)); // P10D
System.out.println(Period.ofWeeks(3)); // P21D
System.out.println(Period.of(2, 6, 1)); // P2Y6M1D
日期-时间类中表示时间间隔的通用方法
方法名 是否是静态方法 方法描述
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

操纵、解析和格式化日期

表示时间点的日期-时间类的通用方法
方法名 是否是静态方法 方法描述
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 date1 = LocalDate.of(2017, 12, 11);
System.out.println(date1); // 2017-12-11
LocalDate date2 = date1.withYear(2014);
System.out.println(date2); // 2014-12-11
LocalDate date3 = date2.withDayOfMonth(10);
System.out.println(date3); // 2014-12-10
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 5);
System.out.println(date4); // 2014-05-10LocalDate date5 = date4.plusWeeks(1);
System.out.println(date5); // 2014-05-17
LocalDate date6 = date5.minusYears(3);
System.out.println(date6); // 2011-05-17
LocalDate date7 = date6.plus(6, ChronoUnit.MONTHS);
System.out.println(date7); // 2011-11-17
TemporalAdjuster类中的工厂方法
方法名 描述
dayOfWeekInMonth 创建一个新的日期,它的值为同一个月中每一周的第几天
firstDayOfMonth 创建一个新的日期,它的值为当月的第一天
firstDayOfNextMonth 创建一个新的日期,它的值为下月的第一天
firstDayOfNextYear 创建一个新的日期,它的值为明年的第一天
firstDayOfYear 创建一个新的日期,它的值为当年的第一天
firstInMonth 创建一个新的日期,它的值为同一个月中,第一个符合星期几要求的值
lastDayOfMonth 创建一个新的日期,它的值为当月的最后一天
lastDayOfNextMonth 创建一个新的日期,它的值为下月的最后一天
lastDayOfNextYear 创建一个新的日期,它的值为明年的最后一天
lastDayOfYear 创建一个新的日期,它的值为今年的最后一天
lastInMonth 创建一个新的日期,它的值为同一个月中,最后一个符合星期几要求的值
next/previous 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期
nextOrSame/previousOrSame 创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期,如果该日期已经符合要求,直接返回该对象
LocalDate date8 = date7.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
System.out.println(date8); // 2011-11-20
LocalDate date9 = date8.with(TemporalAdjusters.lastDayOfMonth());
System.out.println(date9); // 2011-11-30
TemporalAdjuster接口
@FunctionalInterface
public interface TemporalAdjuster {Temporal adjustInto(Temporal temporal);
}
定制的TemporalAdjuster
public class NextWorkingDay implements TemporalAdjuster {@Overridepublic Temporal adjustInto(Temporal temporal) {DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));int dayToAdd = 1;if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;else if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;return temporal.plus(dayToAdd, ChronoUnit.DAYS);}
}
    date = date.with(temporal -> {DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));int dayToAdd = 1;if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;else if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;return temporal.plus(dayToAdd, ChronoUnit.DAYS);});
    TemporalAdjuster nextWorkingDay = TemporalAdjusters.ofDateAdjuster(temporal -> {DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));int dayToAdd = 1;if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;return temporal.plus(dayToAdd, ChronoUnit.DAYS);});date =date.with(nextWorkingDay);
打印输出及解析日期-时间对象
LocalDate date = LocalDate.of(2017, 12, 11);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); // 20171211
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2017-12-11
LocalDate date1 = LocalDate.parse("20171211", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2017-12-11", DateTimeFormatter.ISO_LOCAL_DATE);DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2017, 12, 11);
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);
构造一个DateTimeFormatter
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder().appendText(ChronoField.DAY_OF_MONTH).appendLiteral(". ").appendText(ChronoField.MONTH_OF_YEAR).appendLiteral(" ").appendText(ChronoField.YEAR).parseCaseInsensitive().toFormatter(Locale.ITALIAN);

-----------参考《Java 8 实战》

Java 8 实战学习笔记相关推荐

  1. 小马哥的 Java 项目实战学习笔记

    自从Java发布以来,基本数据类型就是Java语言的一部分,分别是byte, short, int, long, char, float, double, boolean. 其中: 整型:byte, ...

  2. java后验条件_JAVA并发实战学习笔记——3,4章~

    JAVA并发实战学习笔记 第三章 对象的共享 失效数据: java程序实际运行中会出现①程序执行顺序对打乱:②数据对其它线程不可见--两种情况 上述两种情况导致在缺乏同步的程序中出现失效数据这一现象, ...

  3. Linux性能优化实战学习笔记:第四十六讲=====实战分析

    Linux性能优化实战学习笔记:第四十六讲 一.上节回顾 不知不觉,我们已经学完了整个专栏的四大基础模块,即 CPU.内存.文件系统和磁盘 I/O.以及网络的性能分析和优化.相信你已经掌握了这些基础模 ...

  4. java jdk 8学习笔记,Java JDK 8学习笔记 PDF_源雷技术空间

    资源名称:Java JDK 8学习笔记 PDF 内容简介: ●本书是作者多年来教学实践经验的总结,汇集了学员在学习课程或认证考试中遇到的概念.操作.应用等问题及解决方案 ●针对Java SE 8新功能 ...

  5. 第71课:Spark SQL窗口函数解密与实战学习笔记

    第71课:Spark SQL窗口函数解密与实战学习笔记 本期内容: 1 SparkSQL窗口函数解析 2 SparkSQL窗口函数实战 窗口函数是Spark内置函数中最有价值的函数,因为很多关于分组的 ...

  6. java/android 设计模式学习笔记(1)--- 单例模式

    前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使 ...

  7. java.util.List学习笔记

    概述 在Java中,List是一种特殊的集合结构,即:List是一种有序的集合.在List接口的实现中,需要提供根据列表下表对元素进行操作的方法,包括:插入,删除,查询和修改等: List一般允许重复 ...

  8. Java快速入门学习笔记9 | Java语言中的方法

    有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...

  9. Java快速入门学习笔记8 | Java语言中的数组

    有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...

最新文章

  1. 高级软件工程的第一次作业:回顾自己本科设计
  2. 没有熙熙攘攘,百度VR在世界大会的一场奇妙之旅
  3. 彻底解决 intellij IDEA 卡顿 优化笔记
  4. 【Go API 开发实战】Go API 开发实战教程简介(1-7)
  5. SVN本地代码未提交而被覆盖
  6. NodeJS开发环境配置
  7. 将k个有序链表合并成一个有序链表
  8. Linux编程练习 --多线程4--条件变量
  9. 学习总结-《父与子的编程之旅》chapter 10
  10. 五 Zabbix全网监控
  11. IOS – OpenGL ES 图像侵蚀边缘色彩模糊 GPUImageRGBErosionFilter
  12. 企业级POS收银系统源码(客户端+后台)
  13. libmaxminddb
  14. 12x12怎么速算_12x12怎么速算_12x12怎样巧算
  15. JavaScript中this的指向总结
  16. 领英常见问题—如何提高邀请通过率与账号曝光量
  17. SMAA算法详解 - SMAADetectVerticalCornerPattern
  18. IDEA使用database时,连接MySQL后schemas不显示数据库名的情况
  19. [架构之路-164]-《软考-系统分析师》-3-操作系统基本原理-文件系统(文件的逻辑组织、文件的物理组织、硬盘空间管理、分布式文件系统)
  20. 思考深度学习的泛化能力

热门文章

  1. 七牛云图片--Java文档
  2. MQTT在线测试网站
  3. HTML网站右键禁用F12代码 屏蔽审查元素 防止修改页面代码
  4. 机器学习相关的一些术语
  5. 怎么让前端项目运行起来_如何立即使您的前端项目看起来更好
  6. 面试官问面向对象特点_最好的面试官有什么共同点?
  7. 从零学web前端_从零到前端英雄(第1部分)
  8. 1秒获取Power BI Pro帐号
  9. java switch case多个条件_JAVA基础程序设计之判断与循环
  10. python @符号_Python金三角!python初学者很难绕过的坑,附教程资料