新特性總覽
- lambda表達(dá)式
- Stream操作數(shù)組
- Optional取代null
- 簡(jiǎn)潔的并發(fā)編程
- LocalDateTime新的時(shí)間API
Lambda表達(dá)式
概念:Lambda表達(dá)式是一個(gè)匿名函數(shù),Lambda表達(dá)式基于數(shù)學(xué)中的λ演算得名,直接對(duì)應(yīng)其中的Lambda抽象(lambda abstraction),是一個(gè)匿名函數(shù),既沒有函數(shù)名的函數(shù)。Lambda表達(dá)式可以表示閉包(注意和數(shù)學(xué)傳統(tǒng)意義的不同)。你也可以理解為,簡(jiǎn)潔的表示可傳遞的匿名函數(shù)的一種方式:它沒有名稱,但它有參數(shù)列表、函數(shù)主體、返回類型,可能還有一個(gè)可以拋出異常的列表。
-
作用:既然是匿名函數(shù),那就類比于匿名內(nèi)部類的用法咯,哪些地方,會(huì)用到一些代碼量大但實(shí)際邏輯不算復(fù)雜的方法調(diào)用,就可以用到它。
什么是函數(shù)式接口?
答:僅僅只有一個(gè)抽象方法的接口。
-
語法:
() -> 表達(dá)式() -> {語句;}() -> 對(duì)象-
(Class)() -> {語句;}【指定對(duì)象類型】public static void doSomething(Runnable r) { r.run(); } public static void doSomething(Task a) { a.execute(); } .... doSomething(() -> System.out.println("DDDD")); // 為了避免隱晦的方法調(diào)用,嘗試顯式地類型轉(zhuǎn)換 doSomething((Runnable)() -> System.out.println("DDDD"));
-
使用場(chǎng)景(很多特殊場(chǎng)景都包含在內(nèi))
總的來說,只有在接受函數(shù)式接口的地方才可以使用Lambda表達(dá)式。
image.png -
一些Jdk8的lambda語法糖:
Lambda:(Apple a) -> a.getWeight() 方法引用:Apple::getWeight Lambda:() -> Thread.currentThread().dumpStack() 方法引用:Thread.currentThread()::dumpStack Lambda:(str, i) -> str.substring(i) 方法引用:String::substring Lambda:(String s) -> System.out.println(s) 方法引用:System.out::println -
構(gòu)造函數(shù)引用
- 無參構(gòu)造器
Supplier<Apple> c1 = Apple::new; Apple apple = c1.get(); // 等價(jià)于 Supplier<Apple> c2 = () -> new Apple(); - 一參構(gòu)造器
Function<Integer, Apple> c1 = Apple::new; Apple apple = c1.apply(123); - 兩參構(gòu)造器
BiFunction<Integer, String, Apple> c1 = Apple::new; Apple apple = c2.apply(120, "red");
- 無參構(gòu)造器
-
簡(jiǎn)化的數(shù)組排序
apples.sort(comparing(Apple::getWeight)); // 其中: // ArrayList.sort() since:1.2 // Comparator.comparing(Function<Apple, Integer>) since: 1.8 -
更復(fù)雜的數(shù)組排序
- 倒序
apples.sort(Comparator.comparing(Apple::getWeight).reversed()); - 多條件排序
apples.sort(Comparator.comparing(Apple::getWeight).reversed() .thenComparing(Apple::getCountry));
- 倒序
-
Predicate的復(fù)合
以下三個(gè)基本謂詞,可以配合已有的謂詞(Predicate),來制造出更加復(fù)雜的謂詞。
- negate:“非”
private static <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> result = new ArrayList<>(); for (T t : list) { if (predicate.test(t)) { result.add(t); } } return result; } // 紅蘋果 Predicate<Apple> redApplePd = a -> "red".equals(a.getColor()); // 不是紅蘋果 Predicate<Apple> notRedApplePd = a -> redApplePd.negate(); // 過濾 List<Apple> redApple = filter(rawApples, redApplePd); List<Apple> notRedApple = filter(rawApples, notRedApplePd); - and:“與”
Predicate<Apple> redHeavyApplePd = ((Predicate<Apple>) apple -> apple.color.equals("red")).and(apple -> false); - or:“或”(同理)
- negate:“非”
-
Function的復(fù)合
- andThen
f.andThen(g)相當(dāng)于g(f()),先執(zhí)行f(),后執(zhí)行g()Function<Integer, Integer> dbSelf = x -> x * 2; Function<Integer, Integer> oppositeNum = x -> -1 * x; Function<Integer, String> toStr = String::valueOf; String result = dbSelf.andThen(oppositeNum).andThen(toStr).apply(1); // "-2" - compose
f.compose(g)相當(dāng)于f(g()),先執(zhí)行g(),后執(zhí)行f()Function<Integer, String> toStr = x -> "ToString:" + x; Function<String, String> split = x -> x.split(":")[1]; Function<String, Integer> toInt = Integer::valueOf; int result = toInt.compose(split.compose(toStr)).apply(123); // 123
- andThen
Stream
- 概念:流是Java API新成員,它允許你以聲明性方式處理數(shù)據(jù)集合。
- 特點(diǎn):
- 流水線:類似“鏈?zhǔn)秸{(diào)用”,一步接一步。
- 內(nèi)部迭代:與使用迭代器顯式迭代的集合不同,流的迭代操作是在背后進(jìn)行的。
- 作用:
- 減少for循環(huán)
- 減少數(shù)組操作中可能聲明的垃圾變量的數(shù)量
- 直觀、提高可讀性
- 流和集合的區(qū)別:
- 類似于看視頻,無論點(diǎn)到視頻的哪一段,它都能很快加載出來,這就是流。而集合相當(dāng)于我要把整部電影down下來,才能點(diǎn)哪看哪。
- 集合是內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),它包含數(shù)據(jù)結(jié)構(gòu)中目前所有的值,集合中每個(gè)元素都需要事先計(jì)算好,才被放入集合。
- 流是在概念上固定的數(shù)據(jù)結(jié)構(gòu),其元素時(shí)按需計(jì)算的(懶加載)。需要多少就給多少。換一個(gè)角度,流像是一個(gè)延遲創(chuàng)建的集合:只有在消費(fèi)者要求的時(shí)候才會(huì)生成值。
- 看一個(gè)例子
如果只用Lambda表達(dá)式,那操作數(shù)組起來,也還是需要一些for循環(huán)的加持。public List<Apple> filterApple(List<Apple> apples, Predicate<Apple> criteria) { List<Apple> result = new ArrayList<>(); apples.forEach(apple -> { if (criteria.test(apple)) { result.add(apple); } }); return result; }
而有了Stream,寫起code就簡(jiǎn)單很多了。List<Apple> redHeavyApples = apples.stream() .filter(apple -> "red".equals(apple.color)) .filter(apple -> apple.weight > 120) .collect(Collectors.toList()); - 相關(guān)包、類、方法
- 包:
java.util.stream - 接口:
BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseableStream<T> extends BaseStream<T, Stream<T>>DoubleStream extends BaseStream<Double, DoubleStream>IntStream extends BaseStream<Integer, IntStream>LongStream extends BaseStream<Long, LongStream>
- 方法:
filter(Predicate<? super T>):Stream<T>map(Function<? super T, ? extends R>):Stream<R>mapToInt(ToIntFunction<? super T>):LongStreammapToDouble(ToDoubleFunction<? super T>):DoubleStreammapToLongflatMap(Function<? super T, ? super Stream<? extends R>>):Stream<R>flatMapToInt()flatMapToLong()flatMapToDouble()distinct():Stream<T>sorted():Stream<T>sorted(Comparator<? super T>):Stream<T>peek(Consumer<? super T>):Stream<T>limit(long):Stream<T>skip(long):Stream<T>forEach(Consumer<? super T>):voidforEachOrdered()toArray():Object[]toArray(IntFunction<A[]>):Object[]reduce(T, BinaryOperator<T>):Treduce(BinaryOperator<T>):Optional<T>reduce(U, BiFunction<U, ? super T, U>, BinaryOperator<U>):Ucollect(Supplier<R>, BiConsumer<R, ? super T>, BiConsumer<R,R>):Rcollect(Collector<? super T,A,R>):Rmin(Comparator<? super T>):Optional<T>maxcount():longanyMatch(Predicate<? super T>):booleanallMatchnoneMatchfindFirst():Optional<T>findAnybuilder():Builder<T>empty():Stream<T>of(T...):Stream<T>iterate(T, UnaryOperator<T>):Steram<T>generate(Supplier<T>):Stream<T>concat(Stream<? extends T>, Stream<? extends T>):Stream<T>
- 包:
- 例子:取重的綠蘋果,然后升序排序,取它的重量
List<Integer> greenHeavyAppleWeight = apples.stream() .filter(apple -> apple.weight > 120) .filter(apple -> "green".equals(apple.color)) .sorted(comparing(Apple::getWeight)) // Comparator.comparing() .map(apple -> apple.weight) .collect(toList()); // Collectors.toList() - 流只能被消費(fèi)一次
List<String> names = Arrays.asList("Java8", "Lambdas", "In", "Action"); Stream<String> s = names.stream(); s.forEach(System.out::println); // 再繼續(xù)執(zhí)行一次,則會(huì)拋出異常 s.forEach(System.out::println); - 用
flatMap()實(shí)現(xiàn)流的扁平化
沒有打平,出來的是兩個(gè)String[],我需要兩個(gè)嵌套for 循環(huán)來打印內(nèi)容。而如果用flatMap:String[] words = {"Hello", "World"}; Stream<String> streamOfWords = Arrays.stream(words); // 沒有打平,是兩個(gè) String[],我需要兩個(gè)嵌套for 循環(huán)來打印內(nèi)容 List<String[]> a = streamOfWords.map(w -> w.split("")).collect(toList()); for (String[] itemStrings: a) { System.out.println("item.length: " + itemStrings.length); for (String item: itemStrings) { System.out.print(item); } System.out.println(); }String[] words = {"Hello", "World"}; Stream<String> streamOfWords = Arrays.stream(words); // 打平,一個(gè)for循環(huán)就搞定 List<String> chars = streamOfWords .map(w -> w.split("")) .flatMap(Arrays::stream) .collect(toList()); for (String item: chars) { System.out.print(item + "-"); }
打平之后,直接操作一個(gè)數(shù)組就好。
- 例子:求最大最小值
List<Integer> numbers = Arrays.asList(2, 5, 3, 4, 1, 6, 3, 5); // way 1 Integer max = numbers.stream().max(Integer::compareTo).orElse(null); // way 2 max = numbers.stream().reduce(Integer::max).orElse(null); - 原始類型流的特化(Stream轉(zhuǎn)IntStream/LongStream/DoubleStream)
作用:直接特化為原始類型:int、long、double,避免暗含的裝箱成本。以及,有了一些額外的計(jì)算方法。
- 映射到數(shù)值流:
mapToInt/mapToLong/mapToDouble - 轉(zhuǎn)回對(duì)象流:
boxed
- 映射到數(shù)值流:
- 特化流的一下額外方法
// 獲得 1到100 的所有偶數(shù) IntStream.rangeClosed(1, 100).filter(num -> num%2 == 0).forEach(System.out::println); - 構(gòu)建流
- 方式一:由值創(chuàng)建流
Stream<String> stream = Stream.of("Java8", "Lambda", "In"); stream.map(String::toUpperCase).forEach(System.out::println); - 方式二:由數(shù)組創(chuàng)建流
int[] nums = {2,4,6,7,8,12}; int sum = Arrays.stream(nums).sum(); - 方式三:由集合創(chuàng)建流
List<Integer> nums = Arrays.asList(1,2,3,4,5); Stream<Integer> numStream = nums.stream(); Stream<Integer> parallelStream = nums.parallelStream(); - 方式四:文件 + NIO 創(chuàng)建流
利用
java.nio.file.Files中的一些靜態(tài)方法(靜態(tài)方法 since JDK1.8)都返回一個(gè)流。Filessince JDK1.7一個(gè)很有用的方法是
Files.lines,它會(huì)返回一個(gè)由指定文件中的各行構(gòu)成的字符串流。
long uniqueWords; try (Stream<String> lines = Files.lines(Paths.get(ClassLoader.getSystemResource("data.txt").toURI()), Charset.defaultCharset())) { uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))) .distinct() .count(); System.out.println("uniqueWords:" + uniqueWords); } catch (IOException e) { e.fillInStackTrace(); } catch (URISyntaxException e) { e.printStackTrace(); } - 方式五:由函數(shù)生成流
不像從固定集合創(chuàng)建的流那樣有固定大小的流。由 iterate和 generate 產(chǎn)生的流會(huì)用給定的函數(shù)按需創(chuàng)建值,因此可以無窮無盡地計(jì)算下去。以下兩個(gè)操作,可以創(chuàng)建“無限流”,一般配合limit 使用 Stream.iterate(<初始值>, <值的變化函數(shù)>) Stream.generate(Supplier<? extends Object> s)// 迭代:每次返回前一個(gè)元素加2的值 Stream.iterate(0, n -> n + 2) .limit(10) .forEach(System.out::println);
看下面這個(gè)例子,對(duì)比Lambda表達(dá)式(匿名函數(shù))和匿名內(nèi)部類:// 生成:接收一個(gè)Supplier類型的函數(shù)(有出無入的函數(shù)) Stream.generate(Math::random) .limit(5) .forEach(System.out::println);
如果這個(gè)// lambda IntStream twos = IntStream.generate(() -> 2); // 匿名內(nèi)部類 IntStream twos = IntStream.generate(new IntSupplier() { @Override public int getAsInt() { return 2; } });IntSupplier中不存在成員變量,那么,兩者等價(jià)。總的來說,匿名內(nèi)部類更加靈活,而且其output值不一定唯一不變,較為靈活。
- 方式一:由值創(chuàng)建流
- 收集器的用法
- 接口:
java.util.stream.Collector - 作用:對(duì)
Stream的處理結(jié)果做收集。 - 例子:
-
分組
List<Apple> apples = Arrays.asList( new Apple(130, "red"), new Apple(22, "red"), new Apple(60, "green"), new Apple(162, "green"), new Apple(126, "green"), new Apple(142, "green"), new Apple(117, "green") ); // 根據(jù)顏色分組 Map<String, List<Apple>> colorAppleMap = apples.stream().collect(groupingBy(apple -> apple.color)); -
多級(jí)分組
Map<Dish.Type, Map<Dish.CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect( groupingBy(Dish::getType, groupingBy(dish -> { if (dish.getCalories() <= 400) { return Dish.CaloricLevel.DIET; } else if (dish.getCalories() <= 700) { return Dish.CaloricLevel.NORMAL; } else { return Dish.CaloricLevel.FAT; } }) ) ); -
分組的一些配合操作
Stream.collect(groupingBy(Dish::getType, summingInt(Dish::getCalories))); Stream.collect(groupingBy(Dish::getType, mapping(...))); // 如: Map<Dish.Type, Set<Dish.CaloricLevel>> caloricLevelsByType = menu.stream().collect( groupingBy(Dish::getType, mapping( dish -> { if (dish.getCalories() <= 400) { return Dish.CaloricLevel.DIET; } else if (dish.getCalories() <= 700) { return Dish.CaloricLevel.NORMAL; } else { return Dish.CaloricLevel.FAT; } }, toSet()))); -
計(jì)數(shù)
long count = apples.size(); long count = apples.stream().collect(Collectors.counting()); long count = apples.stream().count(); -
匯總
// 求和 int totalWeight = apples.stream().collect(summingInt(Apple::getWeight)); totalWeight = apples.stream().mapToInt(Apple::getWeight).sum(); // 平均數(shù) double avgWeight = apples.stream().collect(averagingDouble(Apple::getWeight)); avgWeight = apples.stream().mapToDouble(Apple::getWeight).average().orElse(-1); // 匯總 IntSummaryStatistics appleStatistics = apples.stream().collect(summarizingInt(Apple::getWeight)); System.out.println(appleStatistics.getMax()); System.out.println(appleStatistics.getMin()); System.out.println(appleStatistics.getAverage()); System.out.println(appleStatistics.getCount()); System.out.println(appleStatistics.getSum()); -
連接字符串
String[] strs = {"Hello", "World"}; String result = Arrays.stream(strs).collect(joining([分隔符])); -
分區(qū)
根據(jù)Predicate條件,分成true和false兩部分集合。
Map<Boolean, List<Apple>> partitionApples = apples.stream().collect(partitioningBy(apple -> "green".equals(apple.color)));看似沒有什么特點(diǎn),但是其實(shí)和grouping類似,有一個(gè)
downStream:Collector的一個(gè)第二參數(shù),這就厲害了,擴(kuò)展性很強(qiáng)。// 先劃分了素食和非素食,然后,每一類里面,去熱量最高的一個(gè)。 Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = menu.stream().collect( partitioningBy(Dish::isVegetarian, collectingAndThen( maxBy(comparingInt(Dish::getCalories)), Optional::get )));
-
- 接口:
- 自定義流(這個(gè)就再說咯)
- 并行流
相對(duì)于
stream(),用parallelStream()就能把集合轉(zhuǎn)換為并行流。- 概念:并行流就是一個(gè)把內(nèi)容分成多個(gè)數(shù)據(jù)塊,并用不同線程分別處理每個(gè)數(shù)據(jù)塊的流。
- 方法:
切換為并行流:Stream.parallel() 切換為順序流:Stream.sequential() // 注意,誰最后調(diào)用,流就apply誰。 - 用并行流之前,要測(cè)試性能(如果遇到iterator裝包解包等的情況,實(shí)際上并行鎖會(huì)更加慢,能用特化流就盡量用特化流)
- Spliterator 定義了并行流如何拆分它要遍歷的數(shù)據(jù)
Optional
- class:
java.util.Optional - 作用:解決和避免NPE異常
- Optional對(duì)象的方法
-
get():不推薦使用。如果變量存在,它直接返回封裝的變量值,否則就拋出一個(gè)NPE或者NoSuchElementException異常。Optional方法 調(diào)用get()后拋出異常 Optional.of(null) java.lang.NullPointerException Optional.ofNullable(null) java.util.NoSuchElementException orElse(T other)-
orElseGet(Supplier<? extends T> other):orElse的延遲調(diào)用版,Supplier方法只有在Optional對(duì)象不含值時(shí)才執(zhí)行。- 適用場(chǎng)景:
- 創(chuàng)建默認(rèn)值是耗時(shí)的工作。
- 或者需要十分確定某個(gè)方法僅在Optional為空時(shí)才調(diào)用。
Person p1 = null; Optional<Person> optP1 = Optional.ofNullable(p1); Person resultP = optP1.orElseGet(() -> { Person p = new Person(); p.firstName = "Fang"; return p; }); System.out.println("resultP.firstName: " + resultP.firstName); - 適用場(chǎng)景:
-
orElseThrow(Supplier<? extends X> exceptionSupplier):定制拋出的異常。 -
ifPresent(Consumer<? super T>):當(dāng)變量值存在時(shí)執(zhí)行一個(gè)作為參數(shù)傳入的方法,否則不做任何操作。Person p1 = new Person(); Optional<Person> optP1 = Optional.ofNullable(p1); optP1.ifPresent(person -> System.out.println("Haha")); -
filterPerson p1 = new Person(); p1.firstName="Fang"; p1.lastName="Hua"; Person resP = Optional.ofNullable(p1).filter(person -> "Hua".equals(person.lastName)).orElseGet(() -> { Person newP = new Person(); newP.firstName= "Ming"; return newP; }); System.out.println(resP.firstName); // Ming - 當(dāng)然,還有一些方法與Stream接口相似,如
map和flatMap
-
- 例子:
- 對(duì)象嵌套取值
- old
@Test public void test_optional_1() { Person person = new Person(); // 當(dāng)然從重構(gòu)角度來看,這里是不對(duì)的,我們知道太多這個(gè)類內(nèi)部的東西,是需要重構(gòu)的 String name = person.getCar().getInsurance().getName(); } public class Person { private Car car; public Car getCar() { return car; } } public class Car { private Insurance insurance; public Insurance getInsurance() { return insurance; } } public class Insurance { private String name; public String getName() { return name; } } - new
public String getCarInsuranceName(Person person) { return Optional.ofNullable(person).flatMap(Person::getCar) .flatMap(Car::getInsurance) .map(Insurance::getName) .orElse("Unknown"); } public class Person { private String sex; private String firstName; private String lastName; private Optional<Car> car = Optional.empty(); public Optional<Car> getCar() { return car; } } public class Car { private Optional<Insurance> insurance = Optional.empty(); public Optional<Insurance> getInsurance() { return insurance; } } public class Insurance { private String name; public String getName() { return name; } }
- old
- 封裝可能為空的值
Object value = map.get("key"); // 加上 Optional Optional<Object> value = Optional.ofNullable(map.get("key")); // 如: Map<String, Person> map String valA = Optional.ofNullable(map.get("A")).orElse(new Person()).firstName; - 異常與Optional 去替代 if-else判斷
public static Optional<Integer> stringToInt(String s) { try { return Optional.of(Integer.parseInt(s)); } catch (NumberFormatException e) { return Optional.empty(); } } - 兩個(gè)Optional對(duì)象的組合
public Insurance findBestInsurance(Person person, Car car) { Insurance insurance = new Insurance(); insurance.name = person.firstName + person.lastName + " --insurance 01"; return insurance; } public Optional<Insurance> nullSafeFindBestInsurance(Optional<Person> person) { if (person.isPresent() && person.get().getCar().isPresent()) { Car car = person.get().getCar().get(); return Optional.of(findBestInsurance(person.get(), car)); } else { return Optional.empty(); } }
- 對(duì)象嵌套取值
- 原理:
- 變量存在時(shí),Optional類只是對(duì)類簡(jiǎn)單封裝。
- 變量不存在時(shí),缺失的值會(huì)被建模成一個(gè)“空”的Optional對(duì)象,由方法
Optional.empty()返回。 -
Optional.empty()是一個(gè)靜態(tài)工廠方法,
函數(shù)式編程(Stream+Lambda)
函數(shù)式編程 VS 命令式編程
- 命令式編程關(guān)注怎么做,而函數(shù)式編程關(guān)注做什么
- 函數(shù)式編程 可讀性強(qiáng),但運(yùn)行速度不見得更快。
例子
1. for循環(huán)取數(shù)組最小值
int[] nums = {1,3,-1,6,-20};
int min = Integer.MAX_VALUE;
for (int i:nums) {
if(i < min) {
min = i;
}
}
變成
int min2 = IntStream.of(nums).parallel().min().getAsInt();
2. 接口的實(shí)現(xiàn)/匿名內(nèi)部類轉(zhuǎn)Lambda
/// 接口實(shí)現(xiàn)
Object target = new Runnable() {
@Override
public void run() {
System.out.println("新建一個(gè)線程");
}
};
new Thread((Runnable) target).start();
/// 匿名內(nèi)部類
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("BBB");
}
}).start();
變成
Object target2 = (Runnable)() -> System.out.println("新建一個(gè)線程2");
Runnable target3 = () -> System.out.println("新建一個(gè)線程3");
System.out.println("target2 == target3 :" + (target2 == target3)); // false
new Thread((Runnable) target2).start();
new Thread(() -> System.out.println("BBB")).start()
3. Lambda創(chuàng)建自定義接口的實(shí)例對(duì)象
必備條件:
- 該接口中只能有一個(gè)抽象方法
- 在接口上加上@FunctionalInterface注解(可選:為了編譯器的校驗(yàn),有這個(gè)注解的接口,當(dāng)存在多個(gè)抽象方法時(shí),是會(huì)編譯報(bào)錯(cuò)的。)
JDK8 中,接口中可以定義靜態(tài)方法和默認(rèn)方法。
@FunctionalInterface
interface Interface1 {
int doubleNum(int i);
default int add(int x, int y) {
return x + y;
}
static int sub(int x, int y) {
return x - y;
}
}
@FunctionalInterface
interface Interface2 {
int doubleNum(int i);
default int add(int x, int y) {
return x + y;
}
}
@FunctionalInterface
interface Interface3 extends Interface1, Interface2 {
@Override
default int add(int x, int y) {
return Interface1.super.add(x, y);
}
}
@Test
public void test_lambda_1() {
Interface1 i1 = (i) -> i * 2;
System.out.println("Interface1.sub(10, 3): " + Interface1.sub(10, 3));
System.out.println("i1.add(3,7):" + i1.add(3, 7));
System.out.println("i1.doubleNum(20):" + i1.doubleNum(20));
Interface2 i2 = i -> i * 2;
Interface3 i3 = (int i) -> i * 2;
Interface3 i4 = (int i) -> {
System.out.println(".....");
return i * 2;
};
}
4. Lambda與Function
String cityName= "HongKong";
int stateCode=237;
String street = "東岸村黃皮樹下街1號(hào)";
String locationID = "";
Function<String, String> locationIDBuilder = locId -> locId + cityName; // Step 1
locationID = locationIDBuilder
.andThen(locId -> locId + ",區(qū)號(hào):" + stateCode) // Step 2
.andThen(locId -> locId+",街道:" + street).apply(locationID); // Step 3
System.out.println("locationID:" + locationID);
JDK 1.8 API包含了很多內(nèi)建的函數(shù)式接口,在老Java中常用到的比如Comparator或者Runnable接口,這些接口都增加了@FunctionalInterface注解以便能用在lambda上
| name | type | description |
|---|---|---|
| Consumer | Consumer< T > | 接收T對(duì)象,不返回值 |
| Predicate | Predicate< T > | 接收T對(duì)象并返回boolean |
| Function | Function< T, R > | 接收T對(duì)象,返回R對(duì)象 |
| Supplier | Supplier< T > | 提供T對(duì)象(例如工廠),不接收值 |
| UnaryOperator | UnaryOperator | 接收T對(duì)象,返回T對(duì)象 |
| BinaryOperator | BinaryOperator | 接收兩個(gè)T對(duì)象,返回T對(duì)象 |
Lambda 與 設(shè)計(jì)模式
策略模式
interface ValidationStrategy {
boolean execute(String s);
}
static class IsAllLowerCase implements ValidationStrategy {
@Override
public boolean execute(String s) {
return s.matches("[a-z]+");
}
}
static class IsNumeric implements ValidationStrategy {
@Override
public boolean execute(String s) {
return s.matches("\\d+");
}
}
static class Validator {
private final ValidationStrategy validationStrategy;
public Validator(ValidationStrategy validationStrategy) {
this.validationStrategy = validationStrategy;
}
public boolean validate(String s) {
return validationStrategy.execute(s);
}
}
正常來說,要new一些策略來當(dāng)參數(shù)傳。
IsNumeric isNumeric = new IsNumeric();
IsAllLowerCase isAllLowerCase = new IsAllLowerCase();
Validator validatorA = new Validator(isNumeric);
Validator validatorB = new Validator(isAllLowerCase);
使用lambda,讓new盡量少出現(xiàn)在code中。
Validator validatorA = new Validator(s -> s.matches("\\d+"));
Validator validatorB = new Validator(s -> s.matches("[a-z]+"));
模板模式(抽象類的應(yīng)用)
public abstract class AbstractOnlineBank {
public void processCustomer(int id) {
Customer customer = Database.getCustomerWithId(id);
makeCustomerHappy(customer);
}
abstract void makeCustomerHappy(Customer customer);
static class Customer {}
static class Database {
static Customer getCustomerWithId(int id) {
return new Customer();
}
}
}
...
AbstractOnlineBank bank = new AbstractOnlineBank() {
@Override
void makeCustomerHappy(Customer customer) {
System.out.println("Hello!");
}
};
bank.processCustomer(1);
bank.processCustomer(2);
用了Lambda,抽象方法都用不著了
public class AbstractOnlineBank {
public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
Customer customer = Database.getCustomerWithId(id);
makeCustomerHappy.accept(customer);
}
static class Customer {}
static class Database {
static Customer getCustomerWithId(int id) {
return new Customer();
}
}
}
...
AbstractOnlineBank bank = new AbstractOnlineBank();
bank.processCustomer(1, customer -> System.out.println("Hello"));
bank.processCustomer(2, customer -> System.out.println("Hi"));
觀察者模式
interface Observer{
void inform(String tweet);
}
private static class NYTimes implements Observer {
@Override
public void inform(String tweet) {
if (tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY!" + tweet);
}
}
}
private static class Guardian implements Observer {
@Override
public void inform(String tweet) {
if (tweet != null && tweet.contains("queen")) {
System.out.println("Yet another news in London... " + tweet);
}
}
}
private static class LeMonde implements Observer {
@Override
public void inform(String tweet) {
if(tweet != null && tweet.contains("wine")){
System.out.println("Today cheese, wine and news! " + tweet);
}
}
}
interface Subject {
void registerObserver(Observer o);
void notifyObserver(String tweet);
}
private static class Feed implements Subject {
private final List<Observer> observers = new ArrayList<>();
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void notifyObserver(String tweet) {
observers.forEach(o -> o.inform(tweet));
}
}
好的,看到了有一個(gè)觀察者接口,并且只有一個(gè)方法inform,那么,我們是不是就可以少聲明這幾個(gè)實(shí)現(xiàn)類呢?
Feed feedLambda = new Feed();
feedLambda.registerObserver((String tweet) -> {
if (tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY!" + tweet);
}
});
feedLambda.registerObserver((String tweet) -> {
if (tweet != null && tweet.contains("queen")) {
System.out.println("Yet another news in London... " + tweet);
}
});
feedLambda.notifyObserver("Money money money, give me money!");
責(zé)任鏈模式
private static abstract class AbstractProcessingObject<T> {
protected AbstractProcessingObject<T> successor;
public void setSuccessor(AbstractProcessingObject<T> successor) {
this.successor = successor;
}
public T handle(T input) {
T r = handleWork(input);
if (successor != null) {
return successor.handle(r);
}
return r;
}
protected abstract T handleWork(T input);
}
一看到,又是抽象類加不同的實(shí)現(xiàn),那就想到是不是可以用匿名函數(shù)實(shí)現(xiàn),而這里,我們使用UnaryOperator:
// 流程A
UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;
// 流程B
UnaryOperator<String> spellCheckerProcessing = (String text) -> text.replaceAll("labda", "lambda");
// A --> B
Function<String, String> pipeline = headerProcessing.andThen(spellCheckerProcessing);
String result2 = pipeline.apply("Aren't labdas really sexy?!!");
工廠模式
private interface Product {
}
private static class ProductFactory {
public static Product createProduct(String name) {
switch (name) {
case "loan":
return new Loan();
case "stock":
return new Stock();
case "bond":
return new Bond();
default:
throw new RuntimeException("No such product " + name);
}
}
}
static private class Loan implements Product {
}
static private class Stock implements Product {
}
static private class Bond implements Product {
}
簡(jiǎn)單情況下,可以用Supplier去實(shí)現(xiàn)引用方法式的構(gòu)造器調(diào)用,并且減少switch。
private static class ProductFactory {
private static final Map<String, Supplier<Product>> map = new HashMap<>();
static {
map.put("loan", Loan::new);
map.put("stock", Stock::new);
map.put("bond", Bond::new);
}
public static Product createProduct(String name) {
Supplier<Product> productSupplier = map.get(name);
if (productSupplier != null) {
return productSupplier.get();
}
throw new RuntimeException("No such product " + name);
}
}
當(dāng)然,如果工廠方法 createProduct 需要接收多個(gè)傳遞給產(chǎn)品構(gòu)造方法的參數(shù),這種方式的擴(kuò)展性不是很好。
默認(rèn)方法
Jdk8開始支持的東西
Jdk8中的接口支持在聲明方法的同時(shí)提供實(shí)現(xiàn)。
- Jdk8支持接口中有靜態(tài)方法。
interface IBoy { static String getCompany() { return "OOL"; } } - Jdk8支持接口中有默認(rèn)方法。【jdk8 新功能】
// List.sort() default void sort(Comparator<? super E> c) { Object[] a = this.toArray(); Arrays.sort(a, (Comparator) c); ListIterator<E> i = this.listIterator(); for (Object e : a) { i.next(); i.set((E) e); } } // Collection.stream() default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); }
作用
- 為接口定義默認(rèn)的方法實(shí)現(xiàn):默認(rèn)方法提供了接口的這個(gè)方法的默認(rèn)實(shí)現(xiàn),那么,在使用Lambda匿名地構(gòu)造接口實(shí)現(xiàn)時(shí),就不需要顯示重寫接口的這個(gè)方法,默認(rèn)方法自動(dòng)就會(huì)繼承過來。
-
新增的接口,只要加上
default修飾符,就可以不被顯式繼承,因此,別的地方的代碼是不用改動(dòng)的?。?/strong>看個(gè)例子:jdk8以前的
Iterator接口,實(shí)際上用戶是不在乎remove()方法的,而且當(dāng)時(shí)是沒有forEachRemaining()方法的,那么,當(dāng)時(shí)的做法,就是每次實(shí)現(xiàn)這個(gè)接口的時(shí)候,都需要顯式繼承并重寫remove()方法,很煩。到了jdk8, 有了
default修飾符,那么,我們不想顯式重寫的remove()方法就可以不用重寫了,然后,jdk8新增了forEachRemaining()方法,也不需要其他實(shí)現(xiàn)類再去修改code了,因?yàn)樗鼔焊恍枰泔@式重寫。
解決繼承鏈上的沖突的規(guī)則
public interface A {
default void hello() {
System.out.println("Hello from A");
}
}
public interface B extends A {
default void hello() {
System.out.println("Hello from B");
}
}
public class C implements A, B {
public static void main(String[] args) {
// 猜猜打印的是什么?
new C().hello();
}
}
以上問題,就是菱形繼承問題,如果父子接口有同名的default方法,那么,以上代碼編譯不通過。
我們需要重寫這個(gè)方法:
public class C implements A, B {
public static void main(String[] args) {
new C().hello();
}
@Override
public void hello() {
A.super.hello();
}
OR
@Override
public void hello() {
B.super.hello();
}
OR
@Override
public void hello() {
System.out.println("Hello from C!");
}
}
組合式異步編程
- 術(shù)語: CompletableFuture
- 背景:
- Jdk 7 中引入了“并行/合并框架”
注意:并行和并發(fā)的區(qū)別:
- 并行(parallellism):
- 并行指的是同一個(gè)時(shí)刻,多個(gè)任務(wù)確實(shí)真的在同時(shí)運(yùn)行。多個(gè)任務(wù)不搶對(duì)方資源。
- 例子:兩個(gè)人,各自一邊吃水果,吃完就吃pizza?!居卸鄠€(gè)CPU內(nèi)核時(shí),每個(gè)內(nèi)核各自不占對(duì)方的資源,各做各的事,是可以達(dá)到真正意義上的“同時(shí)”的。】
- 并發(fā)(concurrency):
- 并發(fā)是指在一段時(shí)間內(nèi)宏觀上多個(gè)程序同時(shí)運(yùn)行。多個(gè)任務(wù)互相搶資源。
- 例子:一個(gè)人,同時(shí)做多件事情?!締魏擞?jì)算器中,是不可能“同時(shí)”做兩件事的,只是時(shí)間片在進(jìn)程間的切換很快,我們感覺不到而已?!?/li>
- 并行(parallellism):
- Jdk 8 中引入了并行流
- Jdk 8 中改進(jìn)了
Future接口,并且,新增了CompletableFuture接口。 - 如果我們現(xiàn)在有一個(gè)大的耗時(shí)任務(wù)要處理,我們可以將其拆分為多個(gè)小任務(wù),讓其并行處理,最終再將處理的結(jié)果統(tǒng)計(jì)合并起來, 那么,我們可以結(jié)合 并行/合并框架 + 并行流 來快速實(shí)現(xiàn)。
- Jdk 7 中引入了“并行/合并框架”
RecursiveTask(JDK 1.7)
例子:實(shí)現(xiàn)一個(gè)100000個(gè)自然數(shù)的求和。
class SumTask extends RecursiveTask<Long> {
public static final int Flag = 50;
long[] arr;
int start;
int end;
public SumTask(long[] arr, int start, int end) {
this.arr = arr;
this.start = start;
this.end = end;
}
public SumTask(long[] arr) {
this.arr = arr;
this.start = 0;
this.end = arr.length;
}
@Override
protected Long compute() {
// 如果不能進(jìn)行更小粒度的任務(wù)分配
int length = end - start;
if (length <= Flag) {
return processSequentially();
}
//分治
int middle = (start + end) / 2;
SumTask sumTaskOne = new SumTask(arr, start, middle);
SumTask sumTaskTwo = new SumTask(arr, middle, end);
invokeAll(sumTaskOne, sumTaskTwo);
Long join1 = sumTaskOne.join();
Long join2 = sumTaskTwo.join();
return join1 + join2;
}
// 小任務(wù)具體是做什么
private long processSequentially() {
long sum = 0;
for (int i = start; i < end; i++) {
sum += arr[i];
}
return sum;
}
}
@Test
public void test() {
long[] arr = new long[1000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (long) (Math.random() * 10 + 1);
}
// 線程池(since 1.7)
ForkJoinPool forkJoinPool = new ForkJoinPool(5);
ForkJoinTask<Long> forkJoinTask = new SumTask(arr);
long result = forkJoinPool.invoke(forkJoinTask);
System.out.println(result);
}
- 總結(jié): 分支/合并框架讓你得以用遞歸方式將可以并行的任務(wù)拆分成更小的任務(wù),在不同的線程上執(zhí)行,然后將各個(gè)子任務(wù)的結(jié)果合并起來生成整體結(jié)果。
Spliterator(可分迭代器 JDK 1.8)
(一)背景
- jdk 1.8 加入,和Iterator一樣,也用于遍歷數(shù)據(jù)源元素,但它是為了并行執(zhí)行而設(shè)計(jì)的。
- jdk8已經(jīng)為集合框架中的所有數(shù)據(jù)結(jié)構(gòu)提供了一個(gè)默認(rèn)的Spliterator實(shí)現(xiàn)。
- 目的:
- 為了優(yōu)化在并行流做任務(wù)處理時(shí)的數(shù)據(jù)源拆分遍歷時(shí),使用Iterator的裝包和解包的性能開銷。
- 配合并行流更快地遍歷和處理元素。
- 內(nèi)部方法:
public interface Spliterator<T> { // 如果還有元素要遍歷, 返回true boolean tryAdvance(Consumer<? super T> action); // 把一些元素拆分給第二個(gè)Spliterator,不斷對(duì) Spliterator 調(diào)用 trySplit直到它返回 null ,表明它處理的數(shù)據(jù)結(jié)構(gòu)不能再分割 Spliterator<T> trySplit(); // 估計(jì)剩下多少元素要遍歷 long estimateSize(); // 用于影響開分過程的配置參數(shù) int characteristics(); } - 延遲綁定的Spliterator:Spliterator可以在第一次遍歷、第一次拆分或第一次查詢估計(jì)大小時(shí)綁定元素的數(shù)據(jù)源,而不是在創(chuàng)建時(shí)就綁定。這種情況下,它稱為延遲綁定(late-binding)的 Spliterator 。
(二)例子
例子:一個(gè)自定義的并行迭代器,用于處理單詞數(shù)量統(tǒng)計(jì)
// 首先,我們有一個(gè)英文句子,我們要統(tǒng)計(jì)它的單詞數(shù)量
public static final String SENTENCE =
" Nel mezzo del cammin di nostra vita " +
"mi ritrovai in una selva oscura" +
" che la dritta via era smarrita ";
- 方法一:通常手段,寫一個(gè)方法,直接實(shí)現(xiàn)統(tǒng)計(jì)邏輯
public static int countWords1(String s) { int counter = 0; boolean lastSpace = true; for (char c : s.toCharArray()) { if (Character.isWhitespace(c)) { lastSpace = true; } else { if (lastSpace) { counter ++; } lastSpace = Character.isWhitespace(c); } } return counter; } - 方法二:利用jdk8 stream的函數(shù)聲明式來簡(jiǎn)化代碼,但是要實(shí)現(xiàn)輔助對(duì)象了
實(shí)現(xiàn)了輔助對(duì)象后,實(shí)現(xiàn)一個(gè)傳stream處理的方法,去調(diào)用private static class WordCounter { private final int counter; private final boolean lastSpace; public WordCounter(int counter, boolean lastSpace) { this.counter = counter; this.lastSpace = lastSpace; } // 如何改變WordCounter的屬性狀態(tài) public WordCounter accumulate(Character c) { if (Character.isWhitespace(c)) { return lastSpace ? this : new WordCounter(counter, true); } else { return lastSpace ? new WordCounter(counter + 1, false):this; } } // 調(diào)用此方法時(shí),會(huì)把兩個(gè)子counter的部分結(jié)果進(jìn)行匯總。 // 其實(shí)就是 把內(nèi)部計(jì)數(shù)器相加 public WordCounter combine(WordCounter wordCounter) { return new WordCounter(counter + wordCounter.counter, wordCounter.lastSpace); } public int getCounter() { return counter; } }
用以上這種方式,就只能實(shí)現(xiàn)串行的處理public static int countWords2(Stream<Character> stream) { WordCounter wordCounter = stream.reduce(new WordCounter(0, true),WordCounter::accumulate, WordCounter::combine); return wordCounter.getCounter(); }@Test public void test_2() { Stream<Character> stream = IntStream.range(0, SENTENCE.length()) .mapToObj(SENTENCE::charAt); System.out.println("Found " + countWords2(stream) + " words"); } - 方法三:使用jdk8中的可分迭代器,模擬jdk7的拆分/合并模式去實(shí)現(xiàn)并行迭代處理過程:
有了自定義的可分迭代器,我們就可以用并行的處理方式了:public class WordCounterSpliterator implements Spliterator<Character> { private final String string; private int currentChar = 0; public WordCounterSpliterator(String string) { this.string = string; } /** * 把String中當(dāng)前位置的char 傳給 Consumer,并讓其位置+1, * 作為參數(shù)傳遞的Consumer是一個(gè)java內(nèi)部類,在遍歷流時(shí)將要處理的char傳給一系列要對(duì)其執(zhí)行的函數(shù)。 * 這里只有一個(gè)歸約函數(shù),即 WordCounter 類的 accumulate方法。 * 如果新的指針位置小于 String 的總長(zhǎng),且還有要遍歷的 Character ,則tryAdvance 返回 true 。 */ @Override public boolean tryAdvance(Consumer<? super Character> action) { action.accept(string.charAt(currentChar++)); return currentChar < string.length(); } @Override public Spliterator<Character> trySplit() { // 像 RecursiveTask 的 compute 方法一樣(分支/合并框架的使用方式) int currentSize = string.length() - currentChar; // 定義不再拆分的界限(不斷拆分,直到返回null) if (currentSize < 10) { return null; } for (int splitPos = currentSize / 2 + currentChar; splitPos < string.length(); splitPos++) { if (Character.isWhitespace(string.charAt(splitPos))) { // 類似RecursiveTask那樣,遞歸拆分 Spliterator<Character> spliterator = new WordCounterSpliterator(string.substring(currentChar, splitPos)); currentChar = splitPos; return spliterator; } } return null; } @Override public long estimateSize() { return string.length() - currentChar; } @Override public int characteristics() { return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE; } }@Test public void test_3() { Spliterator<Character> spliterator = new WordCounterSpliterator(SENTENCE); Stream<Character> stream = StreamSupport.stream(spliterator, true); System.out.println("Found " + countWords2(stream.parallel()) + " words"); }
CompletableFuture(Jdk 1.8)
- 翻譯:可完備的Future
- 簡(jiǎn)單來說,就是寫法更靈活、code可讀性更好的Future。
- 是除了并行流之外的另一種并行方式,只是使用場(chǎng)景不同。
(一)一個(gè)商店商品報(bào)價(jià)的例子
普通的方法去寫一個(gè)同步的計(jì)算報(bào)價(jià)的方法:
private static class Shop {
private final String name;
private final Random random;
public Shop(String name) {
this.name = name;
random = new Random(name.charAt(0) * name.charAt(1) * name.charAt(2));
}
public double getPrice(String product) {
return calculatePrice(product);
}
private double calculatePrice(String product) {
delay(1000);
return random.nextDouble()*product.charAt(0) + product.charAt(1);
}
}
......
@Test
public void test_1() {
Shop nike = new Shop("nike");
Shop adidas = new Shop("adidas");
System.out.println("nike Kobe1 price : "+ nike.getPrice("Kobe1"));
System.out.println("nike Rose3 price : "+ adidas.getPrice("Rose3"));
}
我們先用CompletableFuture來讓計(jì)算變成異步。
- 首先,用一個(gè)線程去執(zhí)行對(duì)應(yīng)邏輯,并且返回一個(gè)
CompletableFuture實(shí)例:private static class Shop { ............ public Future<Double> getPriceAsync(String product) { CompletableFuture<Double> futurePrice = new CompletableFuture<>(); new Thread(() -> { double price = calculatePrice(product); futurePrice.complete(price); }).start(); return futurePrice; } ............ } - 好,然后我們?cè)趫?zhí)行此方法時(shí),就實(shí)現(xiàn)了異步。
@Test public void test_2() throws ExecutionException, InterruptedException { Shop nike = new Shop("nike"); Shop adidas = new Shop("adidas"); Future<Double> price1 = nike.getPriceAsync("Kobe1"); Future<Double> price2 = adidas.getPriceAsync("Kobe1"); System.out.printf("nike Kobe1 price :%.2f%n",price1.get()); System.out.printf("nike Kobe1 price :%.2f%n", price2.get()); }
(二)Future的局限性
看到上面,我們發(fā)現(xiàn),其實(shí),和用Future去handle結(jié)果返回,好像差不多。
- 我們可以用
Future.get(timeout, TimeUnit)來防止一直拿不到值而等待的情況。 - 可以用
Future.isDone()來判斷當(dāng)前任務(wù)是否跑完,然后做不同的handle邏輯。
@Test
public void test_1() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newCachedThreadPool();
Future<Double> future = executor.submit(this::doSthAsync);
System.out.println("好,任務(wù)起來了,我去干別的先了");
for (int i = 0; i < 2; i++) {
System.out.println("主線程正在干活。。");
Thread.sleep(500L);
}
try {
System.out.println("異步任務(wù)返回了: " + future.get(2, TimeUnit.SECONDS));
} catch (TimeoutException e) {
System.out.println("異步任務(wù)出了異常?。。?);
e.printStackTrace();
}
}
這里,就要說一下Future的局限性了。
-
我們很難表述
Future結(jié)果之間的依賴性。比如這樣一個(gè)案例:“當(dāng)長(zhǎng)時(shí)間計(jì)算任務(wù)完成時(shí),請(qǐng)將該計(jì)算的結(jié)果通知到另一個(gè)長(zhǎng)時(shí)間運(yùn)行的計(jì)算任務(wù),這兩個(gè)計(jì)算任務(wù)都完成后,將計(jì)算的結(jié)果與另一個(gè)查詢操作結(jié)果合并”。 -
以下場(chǎng)景,
Future都難以表述:- 將兩個(gè)異步計(jì)算合并為一個(gè)——這兩個(gè)異步計(jì)算之間相互獨(dú)立,同時(shí)第二個(gè)又依賴于第一個(gè)的結(jié)果。
- 等待Future集合中的所有任務(wù)都完成。
- 僅等待Future集合中最快結(jié)束的任務(wù)完成(有可能因?yàn)樗鼈冊(cè)噲D通過不同的方式計(jì)算同一個(gè)值),并返回它的結(jié)果。
- 通過編程方式完成一個(gè)Future任務(wù)的執(zhí)行(即以手工設(shè)定異步操作結(jié)果的方式)。
- 應(yīng)對(duì)Future的完成事件(即當(dāng)Future的完成事件發(fā)生時(shí)會(huì)收到通知,并能使用Future計(jì)算的結(jié)果進(jìn)行下一步的操作,不只是簡(jiǎn)單地阻塞等待操作的結(jié)果)。
(三)CompetableFuture與Future間的關(guān)系
CompetableFuture之于Future,相當(dāng)于Stream之于Collection。
(四)CompetableFuture的一些用法
-
CompetableFuture的一些靜態(tài)方法,直接簡(jiǎn)化創(chuàng)建Thread的邏輯:
public Future<Double> getPriceAsync(String product) { CompletableFuture<Double> futurePrice = new CompletableFuture<>(); new Thread(() -> { double price = calculatePrice(product); futurePrice.complete(price); }).start(); return futurePrice; }直接變成一句話
public Future<Double> getPriceAsync(String product) { return CompletableFuture.supplyAsync(() -> calculatePrice(product)); }supplyAsync方法接受一個(gè)生產(chǎn)者(Supplier)作為參數(shù),返回一個(gè)CompletableFuture對(duì)象,該對(duì)象完成異步執(zhí)行后會(huì)讀取調(diào)用生產(chǎn)者方法的返回值。生產(chǎn)者方法會(huì)交由ForkJoinPool池中的某個(gè)執(zhí)行線程(Executor)運(yùn)行,但是你也可以使用supplyAsync方法的重載版本,傳遞第二個(gè)參數(shù)指定不同的執(zhí)行線程執(zhí)行生產(chǎn)者方法。
-
再來一個(gè)例子,之前是一家Shop做異步處理,這還不能發(fā)揮此接口的最大效用,所以,這次,來一打Shops,比較最佳售價(jià)。
private final List<Shop> shops = Lists.newArrayList(new Shop("BestPrice"), new Shop("LetsSaveBig"), new Shop("MyFavoriteShop"), new Shop("BuyItAll"));一般情況下,用并行流和CompletableFuture的異步效果是半斤八兩的。
/// 并行流的方式 public List<String> findPricesParallel(String product) { return shops.parallelStream() .map(shop -> String.format("%s 價(jià)格 %.2f", shop.getName() , shop.getPrice(product))) .collect(toList()); } // CompletableFuture的方式 public List<String> findPricesFuture(String product) { List<CompletableFuture<String>> completableFutures = shops.stream() .map(shop -> CompletableFuture.supplyAsync(() -> String.format("%s 價(jià)格 %.2f", shop.getName(), shop.getPrice(product)))) .collect(toList()); return completableFutures .stream() .map(CompletableFuture::join) .collect(toList()); }默認(rèn)
CompletableFuture.supplyAsyn()內(nèi)部使用的線程池和ParallelStream使用的是同一個(gè)線程池,是默認(rèn)的固定線程數(shù)量的線程池,這個(gè)線程數(shù)由CPU、JVM配置等決定。具體線程數(shù)取決于
Runtime.getRuntime().availableProcessors()的返回值但是,
CompletableFuture是可以配置supplyAsyn()中使用的ThreadFactory的,而ParallelStream是不能的。// 使用自定義的線程池 private final Executor executor = Executors.newFixedThreadPool(100, new ThreadFactory() { @Override public Thread newThread(Runnable runnable) { Thread t = new Thread(runnable); t.setDaemon(true); return t; } }); ... ... public List<String> findPricesFuture(String product) { return shops.stream() .map(shop -> CompletableFuture.supplyAsync(() -> "" + shop.getPrice(), executor)) .collect(toList()) .stream() .map(CompletableFuture::join) .collect(toList()); }
并發(fā),用并行流還是CompletableFuture?
| 情況 | 推薦 | 原因 |
|---|---|---|
| 如果你進(jìn)行的是計(jì)算密集型的操作,并且沒有I/O | Stream | 實(shí)現(xiàn)簡(jiǎn)單 |
| 如果你并行的工作單元還涉及等待I/O的操作(包括網(wǎng)絡(luò)連接等待) | CompletableFuture | 高靈活性 |
新的日期和時(shí)間API
背景
- Java 1.0
- 特點(diǎn):只有java.util.Date類
- 缺點(diǎn):這個(gè)類無法表示日期,只能以毫秒的精度表示時(shí)間。而且,易用性差,如:
Date date = new Date(114, 2, 18);居然表示2014年3月18日。
- Java 1.1
- 更新:Date類中的很多方法被廢棄了,取而代之的是java.util.Calendar類
- 缺點(diǎn):Calendar類同樣很難用,比如:
- 月份依舊是從0開始計(jì)算(不過,至少Calendar類拿掉了由1900年開始計(jì)算年份這一設(shè)計(jì))
- DateFormat方法也有它自己的問題,它不是線程安全的。
LocalDate和LocalTime
相關(guān)類:
java.time.LocalDatejava.time.LocalTimejava.time.LocalDateTime
三個(gè)類都實(shí)現(xiàn)了各種基本計(jì)算方法、parse方法、比較方法, 以及各種靜態(tài)方法。
LocalDate localDate = LocalDate.of(2018, 11, 25);
int year = localDate.getYear();// 2018
Month month = localDate.getMonth(); // 11
int day = localDate.getDayOfMonth(); // 25
DayOfWeek dow = localDate.getDayOfWeek(); // SUNDAY
int len = localDate.lengthOfMonth(); // 本月總天數(shù): 30
boolean leap = localDate.isLeapYear(); // 是不是閏年: false
LocalDate localDate = LocalDate.now();
int year = localDate.get(ChronoField.YEAR);
int month = localDate.get(ChronoField.MONTH_OF_YEAR);
int day = localDate.get(ChronoField.DAY_OF_MONTH);
LocalTime localTime = LocalTime.now();
int hour = localTime.get(ChronoField.HOUR_OF_DAY);
int minute = localTime.get(ChronoField.MINUTE_OF_HOUR);
int second = localTime.get(ChronoField.SECOND_OF_MINUTE);
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime); // 2018-11-25T22:10:08.721
System.out.println(localDateTime.atZone(ZoneId.of("GMT"))); // 2018-11-25T22:11:08.778Z[GMT]
System.out.println(localDateTime.atOffset(ZoneOffset.UTC)); // 2018-11-25T22:11:44.362Z
機(jī)器的日期和時(shí)間格式
從計(jì)算機(jī)的角度來看,建模時(shí)間最自然的格式是表示一個(gè)持續(xù)時(shí)間段上某個(gè)點(diǎn)的單一大整型數(shù)。
-
相關(guān)類:
java.time.Instant
建模方式:以Unix元年時(shí)間(傳統(tǒng)的設(shè)定為UTC時(shí)區(qū)1970年1月1日午夜時(shí)分)開始所經(jīng)歷的秒數(shù)進(jìn)行計(jì)算。
作用:適用于計(jì)算機(jī)做高精度運(yùn)算。
-
用法實(shí)例
- Instant.ofEpochSecond(秒/long, 納秒/long)
Instant.ofEpochSecond(3); // 1970-01-01T00:00:03Z Instant.ofEpochSecond(3, 0); Instant.ofEpochSecond(2, 1_000_000_000); // 2 秒之后再加上100萬納秒(1秒) Instant.ofEpochSecond(4, -1_000_000_000); // 4秒之前的100萬納秒(1秒)
- Instant.ofEpochSecond(秒/long, 納秒/long)
Duration/Period
Duration
- 作用:Duration類主要用于以秒和納秒衡量時(shí)間的長(zhǎng)短
- 注意:不要用機(jī)器時(shí)間相關(guān)API來計(jì)算Duration,你看不懂
而且,不要試圖在這兩類對(duì)象之間創(chuàng)建duration,會(huì)觸發(fā)一個(gè)DateTimeException異常。而且,不要放一個(gè)LocalDate對(duì)象作為參數(shù),不合適。LocalTime time1 = LocalTime.of(21, 50, 10); LocalTime time2 = LocalTime.of(22, 50, 10); LocalDateTime dateTime1 = LocalDateTime.of(2018, 11, 17, 21, 50, 10); LocalDateTime dateTime2 = LocalDateTime.of(2018, 11, 17, 23, 50, 10); Instant instant1 = Instant.ofEpochSecond(1000 * 60 * 2); Instant instant2 = Instant.ofEpochSecond(1000 * 60 * 3); // 可用工廠方法定義 Duration threeMinutes = Duration.ofMinutes(3); Duration fourMinutes = Duration.of(4, ChronoUnit.MINUTES); Duration d1 = Duration.between(time1, time2); Duration d2 = Duration.between(dateTime1, dateTime2); Duration d3 = Duration.between(instant1, instant2); // PT1H 相差1小時(shí) System.out.println("d1:" + d1); // PT2H 相差2小時(shí) System.out.println("d2:" + d2); // PT16H40M 相差16小時(shí)40分鐘 System.out.println("d3:" + d3);
Period
- 作用:以年、月或者日的方式對(duì)多個(gè)時(shí)間單位建模
- 用法:
// 可用工廠方法定義 Period tenDay = Period.ofDays(10); Period threeWeeks = Period.ofWeeks(3); Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1); Period period = Period.between(LocalDate.of(2018, 11, 7), LocalDate.of(2018, 11, 17)); System.out.println("Period between:" + period); // P10D 相差10天
修改、構(gòu)造時(shí)間
簡(jiǎn)單來說,API給我們劃分了讀取和修改兩類方法:
- 讀:
get - 修改:
with
下面這些方法都會(huì)生成一個(gè)新的時(shí)間對(duì)象,不會(huì)修改源對(duì)象:
// 2018-11-17
LocalDate date1 = LocalDate.of(2018, 11, 17);
// 2019-11-17
LocalDate date2 = date1.withYear(2019);
// 2019-11-25
LocalDate date3 = date2.withDayOfMonth(25);
// 2019-09-25
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);
特別:用TemporalAdjuster實(shí)現(xiàn)復(fù)雜操作
利用重寫各種withXX方法,并自定義TemporalAdjuster參數(shù),就能實(shí)現(xiàn)復(fù)雜的時(shí)間操作:
// 2018-11-17
LocalDate date1 = LocalDate.of(2018, 11, 17);
// 2018-11-19
LocalDate date2 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
// 2018-11-30
LocalDate date3 = date2.with(TemporalAdjusters.lastDayOfMonth());
我們看看這個(gè)用TemporalAdjuster接口,其實(shí)要自定義很簡(jiǎn)單,因?yàn)樗挥幸粋€(gè)接口:
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
Format
- 相關(guān)類:
java.time.format.DateTimeFormatter-
java.time.format.DateTimeFormatterBuilder:用于實(shí)現(xiàn)更加復(fù)雜的格式化
- 特點(diǎn):
- 所有的
DateTimeFormatter實(shí)例都是線程安全的。(所以,你能夠以單例模式創(chuàng)建格式器實(shí)例,就像DateTimeFormatter所定義的那些常量,并能在多個(gè)線程間共享這些實(shí)例。)
- 所有的
// Date 轉(zhuǎn) String
LocalDate date1 = LocalDate.of(2018, 11, 17);
String s1 = date1.format(DateTimeFormatter.BASIC_ISO_DATE); // 20181117
String s2 = date1.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2018-11-17
// String 轉(zhuǎn) Date
LocalDate date2 = LocalDate.parse("20181117", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date3 = LocalDate.parse("2018-11-17", DateTimeFormatter.ISO_LOCAL_DATE);
DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
LocalDate date5 = LocalDate.of(2018, 11, 16);
// 16. novembre 2018
String formattedDate2 = date5.format(italianFormatter);
// 2018-11-16
LocalDate date6 = LocalDate.parse(formattedDate2, italianFormatter);
用DateTimeFormatterBuilder實(shí)現(xiàn)細(xì)粒度格式化控制:
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);
LocalDate now = LocalDate.now();
// 17. novembre 2018
String s1 = now.format(italianFormatter);
處理不同的時(shí)區(qū)和歷法
- 相關(guān)類:
java.time.ZoneIdjava.time.ZoneOffset
- 特點(diǎn):
- 新的
java.time.ZoneId類是老版java.util.TimeZone的替代品。 - 更容易處理日光時(shí)(Daylight Saving Time,DST)這種問題。
- 新的
// 地區(qū)ID都為“{區(qū)域}/{城市}”的格式
ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");
LocalDate date = LocalDate.of(2018, 11, 17);
ZonedDateTime zdt1 = date.atStartOfDay(shanghaiZone);
LocalDateTime dateTime = LocalDateTime.of(2018, 11, 27, 18, 13, 15);
ZonedDateTime zdt2 = dateTime.atZone(shanghaiZone);
Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(shanghaiZone);
// LocalDateTime 轉(zhuǎn) Instant
LocalDateTime dateTime2 = LocalDateTime.of(2018, 11, 17, 18, 45);
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
Instant instantFromDateTime = dateTime2.toInstant(newYorkOffset);
// 通過反向的方式得到LocalDateTime對(duì)象
Instant instant2 = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant2, shanghaiZone);
// OffsetDateTime,它使用ISO-8601的歷法系統(tǒng),以相對(duì)于UTC/格林尼治時(shí)間的偏差方式表示日期時(shí)間。
LocalDateTime dateTime3 = LocalDateTime.of(2018, 11, 17, 18, 45);
OffsetDateTime offsetDateTime = OffsetDateTime.of(dateTime3, newYorkOffset);
