新特性總覽
- lambda表達式
- Stream操作數(shù)組
- Optional取代null
- 簡潔的并發(fā)編程
- LocalDateTime新的時間API
Lambda表達式
概念:Lambda表達式是一個匿名函數(shù)搁凸,Lambda表達式基于數(shù)學中的λ演算得名答渔,直接對應其中的Lambda抽象(lambda abstraction),是一個匿名函數(shù)系任,既沒有函數(shù)名的函數(shù)。Lambda表達式可以表示閉包(注意和數(shù)學傳統(tǒng)意義的不同)。你也可以理解為,簡潔的表示可傳遞的匿名函數(shù)的一種方式:它沒有名稱盈滴,但它有參數(shù)列表、函數(shù)主體轿钠、返回類型,可能還有一個可以拋出異常的列表病苗。
-
作用:既然是匿名函數(shù)疗垛,那就類比于匿名內(nèi)部類的用法咯,哪些地方硫朦,會用到一些代碼量大但實際邏輯不算復雜的方法調用贷腕,就可以用到它。
什么是函數(shù)式接口咬展?
答:僅僅只有一個抽象方法的接口泽裳。
-
語法:
() -> 表達式
() -> {語句;}
() -> 對象
-
(Class)() -> {語句;}
【指定對象類型】public static void doSomething(Runnable r) { r.run(); } public static void doSomething(Task a) { a.execute(); } .... doSomething(() -> System.out.println("DDDD")); // 為了避免隱晦的方法調用,嘗試顯式地類型轉換 doSomething((Runnable)() -> System.out.println("DDDD"));
-
使用場景(很多特殊場景都包含在內(nèi))
總的來說破婆,只有在接受函數(shù)式接口的地方才可以使用Lambda表達式涮总。
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
-
構造函數(shù)引用
- 無參構造器
Supplier<Apple> c1 = Apple::new; Apple apple = c1.get(); // 等價于 Supplier<Apple> c2 = () -> new Apple();
- 一參構造器
Function<Integer, Apple> c1 = Apple::new; Apple apple = c1.apply(123);
- 兩參構造器
BiFunction<Integer, String, Apple> c1 = Apple::new; Apple apple = c2.apply(120, "red");
- 無參構造器
-
簡化的數(shù)組排序
apples.sort(comparing(Apple::getWeight)); // 其中: // ArrayList.sort() since:1.2 // Comparator.comparing(Function<Apple, Integer>) since: 1.8
-
更復雜的數(shù)組排序
- 倒序
apples.sort(Comparator.comparing(Apple::getWeight).reversed());
- 多條件排序
apples.sort(Comparator.comparing(Apple::getWeight).reversed() .thenComparing(Apple::getCountry));
- 倒序
-
Predicate的復合
以下三個基本謂詞,可以配合已有的謂詞(Predicate)祷舀,來制造出更加復雜的謂詞瀑梗。
- 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的復合
- andThen
f.andThen(g)
相當于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)
相當于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ù)集合亿鲜。
- 特點:
- 流水線:類似“鏈式調用”,一步接一步冤吨。
- 內(nèi)部迭代:與使用迭代器顯式迭代的集合不同蒿柳,流的迭代操作是在背后進行的。
- 作用:
- 減少for循環(huán)
- 減少數(shù)組操作中可能聲明的垃圾變量的數(shù)量
- 直觀锅很、提高可讀性
- 流和集合的區(qū)別:
- 類似于看視頻其馏,無論點到視頻的哪一段,它都能很快加載出來爆安,這就是流叛复。而集合相當于我要把整部電影down下來,才能點哪看哪。
- 集合是內(nèi)存中的數(shù)據(jù)結構褐奥,它包含數(shù)據(jù)結構中目前所有的值咖耘,集合中每個元素都需要事先計算好,才被放入集合撬码。
- 流是在概念上固定的數(shù)據(jù)結構儿倒,其元素時按需計算的(懶加載)。需要多少就給多少呜笑。換一個角度夫否,流像是一個延遲創(chuàng)建的集合:只有在消費者要求的時候才會生成值。
- 看一個例子
如果只用Lambda表達式叫胁,那操作數(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就簡單很多了微谓。List<Apple> redHeavyApples = apples.stream() .filter(apple -> "red".equals(apple.color)) .filter(apple -> apple.weight > 120) .collect(Collectors.toList());
- 相關包、類输钩、方法
- 包:
java.util.stream
- 接口:
BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable
Stream<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>):LongStream
mapToDouble(ToDoubleFunction<? super T>):DoubleStream
mapToLong
flatMap(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>):void
forEachOrdered()
toArray():Object[]
toArray(IntFunction<A[]>):Object[]
reduce(T, BinaryOperator<T>):T
reduce(BinaryOperator<T>):Optional<T>
reduce(U, BiFunction<U, ? super T, U>, BinaryOperator<U>):U
collect(Supplier<R>, BiConsumer<R, ? super T>, BiConsumer<R,R>):R
collect(Collector<? super T,A,R>):R
min(Comparator<? super T>):Optional<T>
max
count():long
anyMatch(Predicate<? super T>):boolean
allMatch
noneMatch
findFirst():Optional<T>
findAny
builder():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()
- 流只能被消費一次
List<String> names = Arrays.asList("Java8", "Lambdas", "In", "Action"); Stream<String> s = names.stream(); s.forEach(System.out::println); // 再繼續(xù)執(zhí)行一次买乃,則會拋出異常 s.forEach(System.out::println);
- 用
flatMap()
實現(xiàn)流的扁平化
沒有打平剪验,出來的是兩個String[]哼绑,我需要兩個嵌套for 循環(huán)來打印內(nèi)容。而如果用flatMap:String[] words = {"Hello", "World"}; Stream<String> streamOfWords = Arrays.stream(words); // 沒有打平姻氨,是兩個 String[],我需要兩個嵌套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); // 打平碉咆,一個for循環(huán)就搞定 List<String> chars = streamOfWords .map(w -> w.split("")) .flatMap(Arrays::stream) .collect(toList()); for (String item: chars) { System.out.print(item + "-"); }
打平之后抖韩,直接操作一個數(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轉IntStream/LongStream/DoubleStream)
作用:直接特化為原始類型:int疫铜、long茂浮、double,避免暗含的裝箱成本壳咕。以及席揽,有了一些額外的計算方法。
- 映射到數(shù)值流:
mapToInt/mapToLong/mapToDouble
- 轉回對象流:
boxed
- 映射到數(shù)值流:
- 特化流的一下額外方法
// 獲得 1到100 的所有偶數(shù) IntStream.rangeClosed(1, 100).filter(num -> num%2 == 0).forEach(System.out::println);
- 構建流
- 方式一:由值創(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)都返回一個流谓厘。Files
since JDK1.7一個很有用的方法是
Files.lines
幌羞,它會返回一個由指定文件中的各行構成的字符串流。
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)生的流會用給定的函數(shù)按需創(chuàng)建值,因此可以無窮無盡地計算下去聂宾。以下兩個操作竟稳,可以創(chuàng)建“無限流”属桦,一般配合limit 使用 Stream.iterate(<初始值>, <值的變化函數(shù)>) Stream.generate(Supplier<? extends Object> s)
// 迭代:每次返回前一個元素加2的值 Stream.iterate(0, n -> n + 2) .limit(10) .forEach(System.out::println);
看下面這個例子果善,對比Lambda表達式(匿名函數(shù))和匿名內(nèi)部類:// 生成:接收一個Supplier類型的函數(shù)(有出無入的函數(shù)) Stream.generate(Math::random) .limit(5) .forEach(System.out::println);
如果這個// lambda IntStream twos = IntStream.generate(() -> 2); // 匿名內(nèi)部類 IntStream twos = IntStream.generate(new IntSupplier() { @Override public int getAsInt() { return 2; } });
IntSupplier
中不存在成員變量,那么系谐,兩者等價巾陕。總的來說纪他,匿名內(nèi)部類更加靈活鄙煤,而且其output值不一定唯一不變,較為靈活。
- 方式一:由值創(chuàng)建流
- 收集器的用法
- 接口:
java.util.stream.Collector
- 作用:對
Stream
的處理結果做收集。 - 例子:
-
分組
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));
-
多級分組
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())));
-
計數(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)));
看似沒有什么特點,但是其實和grouping類似句喜,有一個
downStream:Collector
的一個第二參數(shù)预愤,這就厲害了,擴展性很強咳胃。// 先劃分了素食和非素食植康,然后,每一類里面销睁,去熱量最高的一個冻记。 Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = menu.stream().collect( partitioningBy(Dish::isVegetarian, collectingAndThen( maxBy(comparingInt(Dish::getCalories)), Optional::get )));
-
- 接口:
- 自定義流(這個就再說咯)
- 并行流
相對于
stream()
来惧,用parallelStream()
就能把集合轉換為并行流冗栗。- 概念:并行流就是一個把內(nèi)容分成多個數(shù)據(jù)塊,并用不同線程分別處理每個數(shù)據(jù)塊的流供搀。
- 方法:
切換為并行流:Stream.parallel() 切換為順序流:Stream.sequential() // 注意隅居,誰最后調用,流就apply誰葛虐。
- 用并行流之前胎源,要測試性能(如果遇到iterator裝包解包等的情況,實際上并行鎖會更加慢屿脐,能用特化流就盡量用特化流)
- Spliterator 定義了并行流如何拆分它要遍歷的數(shù)據(jù)
Optional
- class:
java.util.Optional
- 作用:解決和避免NPE異常
- Optional對象的方法
-
get()
:不推薦使用涕蚤。如果變量存在宪卿,它直接返回封裝的變量值,否則就拋出一個NPE或者NoSuchElementException異常赞季。Optional方法 調用get()后拋出異常 Optional.of(null) java.lang.NullPointerException Optional.ofNullable(null) java.util.NoSuchElementException orElse(T other)
-
orElseGet(Supplier<? extends T> other)
:orElse
的延遲調用版愧捕,Supplier
方法只有在Optional對象不含值時才執(zhí)行。- 適用場景:
- 創(chuàng)建默認值是耗時的工作申钩。
- 或者需要十分確定某個方法僅在Optional為空時才調用次绘。
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);
- 適用場景:
-
orElseThrow(Supplier<? extends X> exceptionSupplier)
:定制拋出的異常邮偎。 -
ifPresent(Consumer<? super T>)
:當變量值存在時執(zhí)行一個作為參數(shù)傳入的方法,否則不做任何操作泻云。Person p1 = new Person(); Optional<Person> optP1 = Optional.ofNullable(p1); optP1.ifPresent(person -> System.out.println("Haha"));
-
filter
Person 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
- 當然层释,還有一些方法與Stream接口相似廉白,如
map
和flatMap
-
- 例子:
- 對象嵌套取值
- old
@Test public void test_optional_1() { Person person = new Person(); // 當然從重構角度來看楣嘁,這里是不對的瓢省,我們知道太多這個類內(nèi)部的東西,是需要重構的 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(); } }
- 兩個Optional對象的組合
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(); } }
- 對象嵌套取值
- 原理:
- 變量存在時,Optional類只是對類簡單封裝睦尽。
- 變量不存在時,缺失的值會被建模成一個“空”的Optional對象,由方法
Optional.empty()
返回朴则。 -
Optional.empty()
是一個靜態(tài)工廠方法,
函數(shù)式編程(Stream+Lambda)
函數(shù)式編程 VS 命令式編程
- 命令式編程關注怎么做,而函數(shù)式編程關注做什么
- 函數(shù)式編程 可讀性強,但運行速度不見得更快席镀。
例子
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. 接口的實現(xiàn)/匿名內(nèi)部類轉Lambda
/// 接口實現(xiàn)
Object target = new Runnable() {
@Override
public void run() {
System.out.println("新建一個線程");
}
};
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("新建一個線程2");
Runnable target3 = () -> System.out.println("新建一個線程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)建自定義接口的實例對象
必備條件:
- 該接口中只能有一個抽象方法
- 在接口上加上@FunctionalInterface注解(可選:為了編譯器的校驗屎篱,有這個注解的接口,當存在多個抽象方法時秦士,是會編譯報錯的提针。)
JDK8 中,接口中可以定義靜態(tài)方法和默認方法。
@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號";
String locationID = "";
Function<String, String> locationIDBuilder = locId -> locId + cityName; // Step 1
locationID = locationIDBuilder
.andThen(locId -> locId + "错妖,區(qū)號:" + 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對象,不返回值 |
Predicate | Predicate< T > | 接收T對象并返回boolean |
Function | Function< T, R > | 接收T對象,返回R對象 |
Supplier | Supplier< T > | 提供T對象(例如工廠)全跨,不接收值 |
UnaryOperator | UnaryOperator | 接收T對象渺杉,返回T對象 |
BinaryOperator | BinaryOperator | 接收兩個T對象,返回T對象 |
Lambda 與 設計模式
策略模式
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一些策略來當參數(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]+"));
模板模式(抽象類的應用)
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));
}
}
好的居触,看到了有一個觀察者接口轮洋,并且只有一個方法inform
开财,那么,我們是不是就可以少聲明這幾個實現(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!");
責任鏈模式
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);
}
一看到,又是抽象類加不同的實現(xiàn)帜羊,那就想到是不是可以用匿名函數(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 {
}
簡單情況下筷畦,可以用Supplier
去實現(xiàn)引用方法式的構造器調用吼砂,并且減少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);
}
}
當然,如果工廠方法 createProduct 需要接收多個傳遞給產(chǎn)品構造方法的參數(shù)蓉坎,這種方式的擴展性不是很好。
默認方法
Jdk8開始支持的東西
Jdk8中的接口支持在聲明方法的同時提供實現(xiàn)拓瞪。
- Jdk8支持接口中有靜態(tài)方法弓柱。
interface IBoy { static String getCompany() { return "OOL"; } }
- Jdk8支持接口中有默認方法航罗≈嘌【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); }
作用
- 為接口定義默認的方法實現(xiàn):默認方法提供了接口的這個方法的默認實現(xiàn)缭嫡,那么,在使用Lambda匿名地構造接口實現(xiàn)時眷茁,就不需要顯示重寫接口的這個方法,默認方法自動就會繼承過來登刺。
-
新增的接口晤锥,只要加上
default
修飾符,就可以不被顯式繼承蛉迹,因此,別的地方的代碼是不用改動的!攘宙!看個例子:jdk8以前的
Iterator
接口线召,實際上用戶是不在乎remove()
方法的祟蚀,而且當時是沒有forEachRemaining()
方法的,那么,當時的做法丙挽,就是每次實現(xiàn)這個接口的時候肺孵,都需要顯式繼承并重寫remove()
方法,很煩颜阐。到了jdk8平窘, 有了
default
修飾符,那么凳怨,我們不想顯式重寫的remove()
方法就可以不用重寫了均蜜,然后匪蟀,jdk8新增了forEachRemaining()
方法穗泵,也不需要其他實現(xiàn)類再去修改code了,因為它壓根不需要你顯式重寫。
解決繼承鏈上的沖突的規(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方法,那么董栽,以上代碼編譯不通過歹撒。
我們需要重寫這個方法:
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!");
}
}
組合式異步編程
- 術語: CompletableFuture
- 背景:
- Jdk 7 中引入了“并行/合并框架”
注意:并行和并發(fā)的區(qū)別:
- 并行(parallellism):
- 并行指的是同一個時刻闭专,多個任務確實真的在同時運行雀扶。多個任務不搶對方資源迫吐。
- 例子:兩個人,各自一邊吃水果诞吱,吃完就吃pizza命黔〗骼【有多個CPU內(nèi)核時,每個內(nèi)核各自不占對方的資源,各做各的事,是可以達到真正意義上的“同時”的∑蟛洌】
- 并發(fā)(concurrency):
- 并發(fā)是指在一段時間內(nèi)宏觀上多個程序同時運行它呀。多個任務互相搶資源。
- 例子:一個人叉跛,同時做多件事情赫冬∨ň担【單核計算器中,是不可能“同時”做兩件事的劲厌,只是時間片在進程間的切換很快膛薛,我們感覺不到而已〔贡牵】
- 并行(parallellism):
- Jdk 8 中引入了并行流
- Jdk 8 中改進了
Future
接口哄啄,并且,新增了CompletableFuture
接口风范。 - 如果我們現(xiàn)在有一個大的耗時任務要處理咨跌,我們可以將其拆分為多個小任務,讓其并行處理硼婿,最終再將處理的結果統(tǒng)計合并起來锌半, 那么,我們可以結合 并行/合并框架 + 并行流 來快速實現(xiàn)寇漫。
- Jdk 7 中引入了“并行/合并框架”
RecursiveTask(JDK 1.7)
例子:實現(xiàn)一個100000個自然數(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() {
// 如果不能進行更小粒度的任務分配
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;
}
// 小任務具體是做什么
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);
}
- 總結: 分支/合并框架讓你得以用遞歸方式將可以并行的任務拆分成更小的任務,在不同的線程上執(zhí)行猪腕,然后將各個子任務的結果合并起來生成整體結果冗澈。
Spliterator(可分迭代器 JDK 1.8)
(一)背景
- jdk 1.8 加入,和Iterator一樣陋葡,也用于遍歷數(shù)據(jù)源元素亚亲,但它是為了并行執(zhí)行而設計的。
- jdk8已經(jīng)為集合框架中的所有數(shù)據(jù)結構提供了一個默認的Spliterator實現(xiàn)。
- 目的:
- 為了優(yōu)化在并行流做任務處理時的數(shù)據(jù)源拆分遍歷時捌归,使用Iterator的裝包和解包的性能開銷肛响。
- 配合并行流更快地遍歷和處理元素。
- 內(nèi)部方法:
public interface Spliterator<T> { // 如果還有元素要遍歷惜索, 返回true boolean tryAdvance(Consumer<? super T> action); // 把一些元素拆分給第二個Spliterator,不斷對 Spliterator 調用 trySplit直到它返回 null 特笋,表明它處理的數(shù)據(jù)結構不能再分割 Spliterator<T> trySplit(); // 估計剩下多少元素要遍歷 long estimateSize(); // 用于影響開分過程的配置參數(shù) int characteristics(); }
- 延遲綁定的Spliterator:Spliterator可以在第一次遍歷、第一次拆分或第一次查詢估計大小時綁定元素的數(shù)據(jù)源巾兆,而不是在創(chuàng)建時就綁定猎物。這種情況下,它稱為延遲綁定(late-binding)的 Spliterator 角塑。
(二)例子
例子:一個自定義的并行迭代器蔫磨,用于處理單詞數(shù)量統(tǒng)計
// 首先,我們有一個英文句子圃伶,我們要統(tǒng)計它的單詞數(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 ";
- 方法一:通常手段堤如,寫一個方法,直接實現(xiàn)統(tǒng)計邏輯
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ù)聲明式來簡化代碼窒朋,但是要實現(xiàn)輔助對象了
實現(xiàn)了輔助對象后侥猩,實現(xiàn)一個傳stream處理的方法榔至,去調用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; } } // 調用此方法時搀罢,會把兩個子counter的部分結果進行匯總。 // 其實就是 把內(nèi)部計數(shù)器相加 public WordCounter combine(WordCounter wordCounter) { return new WordCounter(counter + wordCounter.counter, wordCounter.lastSpace); } public int getCounter() { return counter; } }
用以上這種方式,就只能實現(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的拆分/合并模式去實現(xiàn)并行迭代處理過程:
有了自定義的可分迭代器湾碎,我們就可以用并行的處理方式了:public class WordCounterSpliterator implements Spliterator<Character> { private final String string; private int currentChar = 0; public WordCounterSpliterator(String string) { this.string = string; } /** * 把String中當前位置的char 傳給 Consumer洛退,并讓其位置+1瓣俯, * 作為參數(shù)傳遞的Consumer是一個java內(nèi)部類杰标,在遍歷流時將要處理的char傳給一系列要對其執(zhí)行的函數(shù)。 * 這里只有一個歸約函數(shù)彩匕,即 WordCounter 類的 accumulate方法腔剂。 * 如果新的指針位置小于 String 的總長,且還有要遍歷的 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
- 簡單來說,就是寫法更靈活奠货、code可讀性更好的Future介褥。
- 是除了并行流之外的另一種并行方式,只是使用場景不同。
(一)一個商店商品報價的例子
普通的方法去寫一個同步的計算報價的方法:
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
來讓計算變成異步柔滔。
- 首先溢陪,用一個線程去執(zhí)行對應邏輯,并且返回一個
CompletableFuture
實例: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; } ............ }
- 好睛廊,然后我們在執(zhí)行此方法時形真,就實現(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)咆霜,其實,和用Future
去handle結果返回嘶朱,好像差不多裕便。
- 我們可以用
Future.get(timeout, TimeUnit)
來防止一直拿不到值而等待的情況。 - 可以用
Future.isDone()
來判斷當前任務是否跑完见咒,然后做不同的handle邏輯偿衰。
@Test
public void test_1() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newCachedThreadPool();
Future<Double> future = executor.submit(this::doSthAsync);
System.out.println("好,任務起來了改览,我去干別的先了");
for (int i = 0; i < 2; i++) {
System.out.println("主線程正在干活下翎。。");
Thread.sleep(500L);
}
try {
System.out.println("異步任務返回了: " + future.get(2, TimeUnit.SECONDS));
} catch (TimeoutException e) {
System.out.println("異步任務出了異常1Φ薄J邮隆!");
e.printStackTrace();
}
}
這里庆揩,就要說一下Future
的局限性了俐东。
-
我們很難表述
Future
結果之間的依賴性。比如這樣一個案例:“當長時間計算任務完成時订晌,請將該計算的結果通知到另一個長時間運行的計算任務虏辫,這兩個計算任務都完成后,將計算的結果與另一個查詢操作結果合并”锈拨。 -
以下場景砌庄,
Future
都難以表述:- 將兩個異步計算合并為一個——這兩個異步計算之間相互獨立,同時第二個又依賴于第一個的結果奕枢。
- 等待Future集合中的所有任務都完成娄昆。
- 僅等待Future集合中最快結束的任務完成(有可能因為它們試圖通過不同的方式計算同一個值),并返回它的結果缝彬。
- 通過編程方式完成一個Future任務的執(zhí)行(即以手工設定異步操作結果的方式)萌焰。
- 應對Future的完成事件(即當Future的完成事件發(fā)生時會收到通知,并能使用Future計算的結果進行下一步的操作谷浅,不只是簡單地阻塞等待操作的結果)扒俯。
(三)CompetableFuture與Future間的關系
CompetableFuture
之于Future
族购,相當于Stream
之于Collection
。
(四)CompetableFuture的一些用法
-
CompetableFuture的一些靜態(tài)方法陵珍,直接簡化創(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方法接受一個生產(chǎn)者(Supplier)作為參數(shù)寝杖,返回一個CompletableFuture對象,該對象完成異步執(zhí)行后會讀取調用生產(chǎn)者方法的返回值互纯。生產(chǎn)者方法會交由ForkJoinPool池中的某個執(zhí)行線程(Executor)運行瑟幕,但是你也可以使用supplyAsync方法的重載版本,傳遞第二個參數(shù)指定不同的執(zhí)行線程執(zhí)行生產(chǎn)者方法留潦。
-
再來一個例子只盹,之前是一家Shop做異步處理,這還不能發(fā)揮此接口的最大效用兔院,所以殖卑,這次,來一打Shops坊萝,比較最佳售價孵稽。
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 價格 %.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 價格 %.2f", shop.getName(), shop.getPrice(product)))) .collect(toList()); return completableFutures .stream() .map(CompletableFuture::join) .collect(toList()); }
默認
CompletableFuture.supplyAsyn()
內(nèi)部使用的線程池和ParallelStream
使用的是同一個線程池菩鲜,是默認的固定線程數(shù)量的線程池,這個線程數(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睦柴?
情況 | 推薦 | 原因 |
---|---|---|
如果你進行的是計算密集型的操作诽凌,并且沒有I/O | Stream | 實現(xiàn)簡單 |
如果你并行的工作單元還涉及等待I/O的操作(包括網(wǎng)絡連接等待) | CompletableFuture | 高靈活性 |
新的日期和時間API
背景
- Java 1.0
- 特點:只有java.util.Date類
- 缺點:這個類無法表示日期,只能以毫秒的精度表示時間爱只。而且皿淋,易用性差,如:
Date date = new Date(114, 2, 18);
居然表示2014年3月18日恬试。
- Java 1.1
- 更新:Date類中的很多方法被廢棄了,取而代之的是java.util.Calendar類
- 缺點:Calendar類同樣很難用疯暑,比如:
- 月份依舊是從0開始計算(不過训柴,至少Calendar類拿掉了由1900年開始計算年份這一設計)
- DateFormat方法也有它自己的問題,它不是線程安全的妇拯。
LocalDate和LocalTime
相關類:
java.time.LocalDate
java.time.LocalTime
java.time.LocalDateTime
三個類都實現(xiàn)了各種基本計算方法幻馁、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
機器的日期和時間格式
從計算機的角度來看膘滨,建模時間最自然的格式是表示一個持續(xù)時間段上某個點的單一大整型數(shù)。
-
相關類:
java.time.Instant
建模方式:以Unix元年時間(傳統(tǒng)的設定為UTC時區(qū)1970年1月1日午夜時分)開始所經(jīng)歷的秒數(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類主要用于以秒和納秒衡量時間的長短
- 注意:不要用機器時間相關API來計算Duration,你看不懂
而且德撬,不要試圖在這兩類對象之間創(chuàng)建duration铲咨,會觸發(fā)一個DateTimeException異常。而且蜓洪,不要放一個LocalDate對象作為參數(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小時 System.out.println("d1:" + d1); // PT2H 相差2小時 System.out.println("d2:" + d2); // PT16H40M 相差16小時40分鐘 System.out.println("d3:" + d3);
Period
- 作用:以年隆檀、月或者日的方式對多個時間單位建模
- 用法:
// 可用工廠方法定義 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天
修改摇天、構造時間
簡單來說,API給我們劃分了讀取和修改兩類方法:
- 讀:
get
- 修改:
with
下面這些方法都會生成一個新的時間對象恐仑,不會修改源對象:
// 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實現(xiàn)復雜操作
利用重寫各種withXX
方法闸翅,并自定義TemporalAdjuster
參數(shù),就能實現(xiàn)復雜的時間操作:
// 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());
我們看看這個用TemporalAdjuster
接口菊霜,其實要自定義很簡單坚冀,因為它只有一個接口:
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
Format
- 相關類:
java.time.format.DateTimeFormatter
-
java.time.format.DateTimeFormatterBuilder
:用于實現(xiàn)更加復雜的格式化
- 特點:
- 所有的
DateTimeFormatter
實例都是線程安全的。(所以鉴逞,你能夠以單例模式創(chuàng)建格式器實例记某,就像DateTimeFormatter所定義的那些常量,并能在多個線程間共享這些實例构捡。)
- 所有的
// Date 轉 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 轉 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
實現(xiàn)細粒度格式化控制:
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);
處理不同的時區(qū)和歷法
- 相關類:
java.time.ZoneId
java.time.ZoneOffset
- 特點:
- 新的
java.time.ZoneId
類是老版java.util.TimeZone
的替代品液南。 - 更容易處理日光時(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 轉 Instant
LocalDateTime dateTime2 = LocalDateTime.of(2018, 11, 17, 18, 45);
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
Instant instantFromDateTime = dateTime2.toInstant(newYorkOffset);
// 通過反向的方式得到LocalDateTime對象
Instant instant2 = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant2, shanghaiZone);
// OffsetDateTime滑凉,它使用ISO-8601的歷法系統(tǒng),以相對于UTC/格林尼治時間的偏差方式表示日期時間喘帚。
LocalDateTime dateTime3 = LocalDateTime.of(2018, 11, 17, 18, 45);
OffsetDateTime offsetDateTime = OffsetDateTime.of(dateTime3, newYorkOffset);