做者:小傅哥
博客:https://bugstack.cn - 原創系列優質專題文章
html
沉澱、分享、成長,讓本身和他人都能有所收穫!
一直想把jdk1.8的新特性整理下,剛好看到老外的git(文後有連接),在這個結構上繼續完善了說明和功能,作了41個單元測試案例,方便新人學習。如下內容很乾,對於一個萌新小白來講,學習jdk1.8的新特性,基本看一遍就知道個7788了,在熟讀兩遍最後跟着寫一遍,那麼在實際項目中就能夠運用了。不過!新特性,雖然很好。但若是想用,那麼本身必定要看看相對應的源碼並多練習,不然真的容易給本身搞暈,又很難閱讀。java
在jdk1.8以前,由於接口裏只能作方法定義不能有方法的實現,所以咱們一般會在抽象類裏面實現默認的方法{通常這個默認的方法是抽象後公用的方法,不須要每個繼承者都去實現,只需調用便可}。就像下面這樣;git
在定義的時候;
public abstract class AFormula { abstract double calculate(int a); // 平方 double sqrt(int a) { return Math.sqrt(a); } }
在使用的時候;
@Test public void test_00() { AFormula aFormula = new AFormula() { @Override double calculate(int a) { return a * a; } }; System.out.println(aFormula.calculate(2)); //求平方:4 System.out.println(aFormula.sqrt(2)); //求開方:1.4142135623730951 }
在jdk1.8裏面,不只能夠定義接口,還能夠在接口中提供默認的實現。這一個小小的改變卻讓整個抽象設計都隨着改變了!github
在定義的時候;{default 關鍵字必須}
public interface IFormula { double calculate(int a); // 平方 default double sqrt(int a) { return Math.sqrt(a); } }
在使用的時候(一);
@Test public void test_01() { IFormula formula = new IFormula() { @Override public double calculate(int a) { return a * a; } }; System.out.println(formula.calculate(2)); System.out.println(formula.sqrt(2)); }
在使用的時候(二);若是隻是一里面方式這麼使用,那麼就沒多大意思了。我一直說過;好的代碼都很騷!
@Test public void test_02() { // 入參a 和 實現 IFormula formula = a -> a * a; System.out.println(formula.calculate(2)); System.out.println(formula.sqrt(2)); }
由於有接口中能夠增長默認的方法實現,那麼Java確定是由於要簡化開發纔出現的這麼個設計。因此你會從各個咱們之前的List、Set等等全部接口中看到默認的方法實現。express
從一段熟悉的排序列子入手編程
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return b.compareTo(a); } });
Collections 工具類提供了靜態方法 sort 方法,入參是一個 List 集合,和一個 Comparator 比較器,以便對給定的 List 集合進行排序。上面的示例代碼建立了一個匿名內部類做爲入參,這種相似的操做在咱們平常的工做中隨處可見。c#
Java 8 中再也不推薦這種寫法,而是推薦使用 Lambda 表達:segmentfault
Collections.sort(names, (String a, String b) -> { return b.compareTo(a); });
上面的這段一樣功能的代碼塊,簡短乾淨了許多。就像婆媳同樣可能剛開始看不習慣,可是接觸接觸就喜歡了。由於,它還能夠更加簡短優秀;設計模式
Collections.sort(names, (String a, String b) -> b.compareTo(a));
爲了追求極致,咱們還可讓它再短點:{固然過你的實現不是一行代碼,那麼不能這麼幹}api
names.sort((a, b) -> b.compareTo(a));
java.util.List 集合如今已經添加了 sort 方法。並且 Java 編譯器可以根據類型推斷機制判斷出參數類型,這樣,你連入參的類型均可以省略啦,怎麼樣,是否是感受很騷氣呢!
java.util.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); } }
好了!你覺得這就結束了嗎,不!它還能夠更短!(得益於Comparator接口中還提供了stack默認方法,也就是說接口中不是隻可有default默認實現,還能夠有靜態方法)
names.sort(Comparator.reverseOrder());
How does lambda expressions fit into Java's type system? Each lambda corresponds to a given type, specified by an interface. A so called functional interface must contain exactly one abstract method declaration. Each lambda expression of that type will be matched to this abstract method. Since default methods are not abstract you're free to add default methods to your functional interface.
經過上面的例子咱們能夠看到經過Lambda能夠開發出一樣功能的邏輯可是代碼卻很簡單,那麼Jvm是如何進行類型推斷,而且找到對應的方法呢?
經過官文介紹以及咱們使用發現,並非每一個接口均可以縮寫成Lambda表達式的開發方式。實際上是隻有那些函數式接口(Functional Interface)才能縮寫成 Lambda 表示式。
所謂函數式接口(Functional Interface)就是隻包含一個抽象方法的聲明。針對該接口類型的全部 Lambda 表達式都會與這個抽象方法匹配。{另外,只是在接口上添加default並不算抽象方法}
總結:爲了保證一個接口明確的被定義爲一個函數式接口(Functional Interface),咱們須要爲該接口添加註解:@FunctionalInterface。這樣,一旦你添加了第二個抽象方法,編譯器會馬上拋出錯誤提示。{不填寫,可是隻寫一個default也能夠}
定義含有註解@FunctionalInterface的接口
@FunctionalInterface public interface IConverter<F, T> { T convert(F from); }
IConverter<String, Integer> converter01 = new IConverter<String, Integer>() { @Override public Integer convert(String from) { return Integer.valueOf(from); }
IConverter<String, Integer> converter02 = (from) -> { return Integer.valueOf(from); };
IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
IConverter<Integer, String> converter04 = String::valueOf;
在上面咱們先加了印象片斷 XX::xx,它也是Java8的新特性便捷式引用,這四個點可能你在其餘語言裏也見過。
IConverter<Integer, String> converter04 = String::valueOf; String converted04 = converter04.convert(11); System.out.println(converted04);
這四個點::的關鍵字,不僅是能夠引用方法和構造函數,還能夠引用普通方法。
public class Something{ public String startsWith(String s) { return String.valueOf(s.charAt(0)); } }
IConverter<String, String> converter01 = s -> String.valueOf(s.charAt(0)); //[參照物]直接把邏輯放到這調用 IConverter<String, String> converter02 = something::startsWith; //引用的方法體裏面邏輯能夠更多,不然只是一句代碼並不能適合全部的狀況 System.out.println(converter01.convert("Java")); System.out.println(converter02.convert("Java"));
接下來咱們在使用這四個點,來看下如何引用類的構造器。首先咱們建立一個這樣的類;
public class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
而後我還須要頂一個工廠類,用於生成Person對象;
@FunctionalInterface public interface IPersonFactory<P extends Person> { P create(String firstName, String lastName); }
如今就到了用四餅::的時候了;
IPersonFactory<Person> personFactory = Person::new; //[參照物]:(firstName, lastName) -> new Person(firstName, lastName); Person person = personFactory.create("Peter", "Parker");
提醒;工廠函數中依然只能有一個函數,不然會報錯
四餅::,可讓咱們直接引用到Person類的構造函數,而後 Java 編譯器可以根據類的簽名選中正確的構造器去實現 PersonFactory.create 方法。
Accessing outer scope variables from lambda expressions is very similar to anonymous objects. You can access final variables from the local outer scope as well as instance fields and static variables.
Lambda表達式訪問外部的變量(局部變量,成員變量,靜態變量,接口的默認方法),它與匿名內部類訪問外部變量很是類似。
咱們能夠從lambda表達式的外部範圍讀取最終局部變量num;
int num = 1; IConverter<Integer, String> stringConverter = from -> String.valueOf(from + num); String convert = stringConverter.convert(2); System.out.println(convert); // 3
可是這個num是不可變值,這樣改變值會報錯;
int num = 1; IConverter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); num = 3;
Variable used in lambda expression should be final or effectively final
另外在lambda表達式內部修改也是不容許的;
int num = 1; IConverter<Integer, String> converter = (from) -> { String value = String.valueOf(from + num); num = 3; return value; };
Variable used in lambda expression should be final or effectively final
在 Lambda 表達式中訪問局部變量。與局部變量相比,在 Lambda 表達式中對成員變量和靜態變量擁有讀寫權限:
public class Lambda4 { // 靜態變量 static int outerStaticNum; // 成員變量 int outerNum; void testScopes() { IConverter<Integer, String> stringConverter1 = (from) -> { // 對成員變量賦值 outerNum = 23; return String.valueOf(from); }; IConverter<Integer, String> stringConverter2 = (from) -> { // 對靜態變量賦值 outerStaticNum = 72; return String.valueOf(from); }; } }
還記得第一節的IFormula示例嗎?
public interface IFormula { double calculate(int a); // 平方 default double sqrt(int a) { return Math.sqrt(a); } }
當時,咱們在接口中定義了一個帶有默認實現的 sqrt 求平方根方法,在匿名內部類中咱們能夠很方便的訪問此方法:
IFormula formula = new IFormula() { @Override public double calculate(int a) { return a * a; } };
可是不能經過lambda表達式訪問默認方法,這樣的代碼無法經過編譯;
IFormula formula = (a) -> sqrt(a * a);
帶有默認實現的接口方法,是不能在 lambda 表達式中訪問的,上面這段代碼將沒法被編譯經過。
JDK 1.8 API 包含了不少內置的函數式接口。其中就包括咱們在老版本中常常見到的 Comparator 和 Runnable,Java 8 爲他們都添加了 @FunctionalInterface 註解,以用來支持 Lambda 表達式。
例如咱們舊版本的Jdk中經常使用的 Comparator 和 Runnable 外,還有一些新的函數式接口,能夠經過函數註解實現Lamdba支持,它們不少都借鑑於知名的 Google Guava 庫。
即便你已經熟悉這個類庫,也應該密切關注那些接口是如何經過一些有用的方法擴展來擴展的:
Predicate 是一個能夠指定入參類型,並返回 boolean 值的函數式接口。它內部提供了一些帶有默認實現的方法,能夠 被用來組合一個複雜的邏輯判斷(and, or, negate):
@Test public void test11() { Predicate<String> predicate = (s) -> s.length() > 0; boolean foo0 = predicate.test("foo"); // true boolean foo1 = predicate.negate().test("foo"); // negate否認至關於!true Predicate<Boolean> nonNull = Objects::nonNull; Predicate<Boolean> isNull = Objects::isNull; Predicate<String> isEmpty = String::isEmpty; Predicate<String> isNotEmpty = isEmpty.negate(); }
Function 函數式接口的做用是,咱們能夠爲其提供一個原料,他給生產一個最終的產品。經過它提供的默認方法,組合,鏈行處理(compose, andThen):
@Test public void test12() { Function<String, Integer> toInteger = Integer::valueOf; //轉Integer Function<String, String> backToString = toInteger.andThen(String::valueOf); //轉String Function<String, String> afterToStartsWith = backToString.andThen(new Something()::startsWith); //截取第一位 String apply = afterToStartsWith.apply("123");// "123" System.out.println(apply); }
Supplier 與 Function 不一樣,它不接受入參,直接爲咱們生產一個指定的結果,有點像生產者模式:
@Test public void test13() { Supplier<Person> personSupplier0 = Person::new; personSupplier0.get(); // new Person Supplier<String> personSupplier1 = Something::test01; //這個test方法是靜態的,且無入參 personSupplier1.get(); // hi Supplier<String> personSupplier2 = new Something()::test02; }
對於 Consumer,咱們須要提供入參,用來被消費,以下面這段示例代碼:
@Test public void test14() { // 參照物,方便知道下面的Lamdba表達式寫法 Consumer<Person> greeter01 = new Consumer<Person>() { @Override public void accept(Person p) { System.out.println("Hello, " + p.firstName); } }; Consumer<Person> greeter02 = (p) -> System.out.println("Hello, " + p.firstName); greeter02.accept(new Person("Luke", "Skywalker")); //Hello, Luke Consumer<Person> greeter03 = new MyConsumer<Person>()::accept; // 也能夠經過定義類和方法的方式去調用,這樣纔是實際開發的姿式 greeter03.accept(new Person("Luke", "Skywalker")); //Hello, Luke }
Comparator 在 Java 8 以前是使用比較廣泛的。Java 8 中除了將其升級成了函數式接口,還爲它拓展了一些默認方法:
@Test public void test15(){ Comparator<Person> comparator01 = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Comparator<Person> comparator02 = Comparator.comparing(p -> p.firstName); //等同於上面的方式 Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator01.compare(p1, p2); // > 0 comparator02.reversed().compare(p1, p2); // < 0 }
首先,Optional 它不是一個函數式接口,設計它的目的是爲了防止空指針異常(NullPointerException),要知道在 Java 編程中,空指針異常但是臭名昭著的。
讓咱們來快速瞭解一下 Optional 要如何使用!你能夠將 Optional 看作是包裝對象(多是 null, 也有可能非 null)的容器。當你定義了
一個方法,這個方法返回的對象多是空,也有可能非空的時候,你就能夠考慮用 Optional 來包裝它,這也是在 Java 8 被推薦使用的作法。
@Test public void test16(){ Optional<String> optional = Optional.of("bam"); optional.isPresent(); // true optional.get(); // "bam" optional.orElse("fallback"); // "bam" optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b" Optional<Person> optionalPerson = Optional.of(new Person()); optionalPerson.ifPresent(s -> System.out.println(s.firstName)); }
什麼是 Stream 流?
簡單來講,咱們可使用 java.util.Stream 對一個包含一個或多個元素的集合作各類操做。這些操做多是 中間操做 亦或是 終端操做。
終端操做會返回一個結果,而中間操做會返回一個 Stream 流。
須要注意的是,你只能對實現了 java.util.Collection 接口的類作流的操做。
Stream 流支持同步執行,也支持併發執行。
注意:Map不支持Stream流,可是他的key和value是支持的!
讓咱們先看看Stream流是如何工做的。首先,咱們以字符串列表的形式建立一個示例;
List<String> stringCollection = new ArrayList<>(); stringCollection.add("ddd2"); stringCollection.add("aaa2"); stringCollection.add("bbb1"); stringCollection.add("aaa1"); stringCollection.add("bbb3"); stringCollection.add("ccc"); stringCollection.add("bbb2"); stringCollection.add("ddd1");
Filter 的入參是一個 Predicate, 上面已經說到,Predicate 是一個斷言的中間操做,它可以幫咱們篩選出咱們須要的集合元素。它的返參一樣 是一個 Stream 流,咱們能夠經過 foreach 終端操做,來打印被篩選的元素:
@Test public void test17(){ stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); }
Sorted 一樣是一箇中間操做,它的返參是一個 Stream 流。另外,咱們能夠傳入一個 Comparator 用來自定義排序,若是不傳,則使用默認的排序規則。
@Test public void test18() { stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); }
注意;這個sorted 只是作了一個排序的視圖進行輸出,實際沒有將List內的數據進行排序
System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
中間操做映射經過給定的函數將每一個元素轉換爲另外一個對象。例以下面的示例,經過 map 咱們將每個 string 轉成大寫:
@Test public void test19(){ stringCollection .stream() .map(String::toUpperCase) .sorted(Comparator.reverseOrder()) //等同於(a, b) -> b.compareTo(a) .forEach(System.out::println); }
這個能夠用作DTO數據對象轉換,領域驅動設計開發中將DTO轉爲DO向後臺傳輸。
顧名思義,match 用來作匹配操做,它的返回值是一個 boolean 類型。經過 match, 咱們能夠方便的驗證一個 list 中是否存在某個類型的元素。
@Test public void test20(){ // anyMatch:驗證 list 中 string 是否有以 a 開頭的, 匹配到第一個,即返回 true boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true // allMatch:驗證 list 中 string 是否都是以 a 開頭的 boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false // noneMatch:驗證 list 中 string 是否都不是以 z 開頭的 boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true }
count 是一個終端操做,它可以統計 stream 流中的元素總數,返回值是 long 類型。
@Test public void test21() { // count:先對 list 中字符串開頭爲 b 進行過濾,讓後統計數量 long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3 }
Reduce 中文翻譯爲:減小、縮小。經過入參的 Function,咱們可以將 list 歸約成一個值。它的返回類型是 Optional 類型。
@Test public void test22() { Optional<String> reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2 }
如上所述,流能夠是順序的,也能夠是並行的。順序流上的操做在單個線程上執行,而並行流上的操做在多個線程上併發執行。
下面的示例演示了使用並行流來提升性能是多麼的容易。親測提高了1倍性能!
首先,咱們建立一個較大的List:
int max = 1000000; List<String> values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); }
@Test public void test23() { int max = 1000000; List<String> values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); } // 納秒 long t0 = System.nanoTime(); long count = values.stream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); // 納秒轉微秒 long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("順序流排序耗時: %d ms", millis)); //順序流排序耗時: 712 ms }
@Test public void test24(){ int max = 1000000; List<String> values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); } long t0 = System.nanoTime(); long count = values.parallelStream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("parallel sort took: %d ms", millis)); //parallel sort took: 385 ms }
如您所見,這兩個代碼片斷幾乎相同,但並行排序大約快50%。您只需將stream()更改成parallelStream()。
如前所講,Map是不支持 Stream 流的,由於 Map 接口並無像 Collection 接口那樣,定義了 stream() 方法。可是,咱們能夠對其 key, values, entry 使用 流操做,如 map.keySet().stream(), map.values().stream() 和 map.entrySet().stream().
另外, JDK 8 中對 map 提供了一些其餘新特性:
@Test public void test25() { Map<Integer, String> map = new HashMap<>(); for (int i = 0; i < 10; i++) { // 與老版不一樣的是,putIfAbent() 方法在 put 以前, 不用在寫if null continue了 // 會判斷 key 是否已經存在,存在則直接返回 value, 不然 put, 再返回 value map.putIfAbsent(i, "val" + i); } // forEach 能夠很方便地對 map 進行遍歷操做 map.forEach((key, value) -> System.out.println(value)); }
以後咱們作一個Map對象的轉換輸出;(定義兩個類BeanA、BeanB)
@Test public void test26() { Map<Integer, BeanA> map = new HashMap<>(); for (int i = 0; i < 10; i++) { // 與老版不一樣的是,putIfAbent() 方法在 put 以前, 不用在寫if null continue了 // 會判斷 key 是否已經存在,存在則直接返回 value, 不然 put, 再返回 value map.putIfAbsent(i, new BeanA(i, "明明" + i, i + 20, "89021839021830912809" + i)); } Stream<BeanB> beanBStream00 = map.values().stream().map(new Function<BeanA, BeanB>() { @Override public BeanB apply(BeanA beanA) { return new BeanB(beanA.getName(), beanA.getAge()); } }); Stream<BeanB> beanBStream01 = map.values().stream().map(beanA -> new BeanB(beanA.getName(), beanA.getAge())); beanBStream01.forEach(System.out::println); }
除了上面的 putIfAbsent() 和 forEach() 外,咱們還能夠很方便地對某個 key 的值作相關操做:
@Test public void test27() { // 以下:對 key 爲 3 的值,內部會先判斷值是否存在,存在,則作 value + key 的拼接操做 map.computeIfPresent(3, (num, val) -> val + num); map.get(3); // val33 // 先判斷 key 爲 9 的元素是否存在,存在,則作刪除操做 map.computeIfPresent(9, (num, val) -> null); map.containsKey(9); // false // computeIfAbsent(), 當 key 不存在時,纔會作相關處理 // 以下:先判斷 key 爲 23 的元素是否存在,不存在,則添加 map.computeIfAbsent(23, num -> "val" + num); map.containsKey(23); // true // 先判斷 key 爲 3 的元素是否存在,存在,則不作任何處理 map.computeIfAbsent(3, num -> "bam"); map.get(3); // val33 }
關於刪除操做,JDK 8 中提供了可以新的 remove() API:
@Test public void test28() { map.remove(3, "val3"); map.get(3); // val33 map.remove(3, "val33"); map.get(3); // null }
如上代碼,只有當給定的 key 和 value 徹底匹配時,纔會執行刪除操做。
關於添加方法,JDK 8 中提供了帶有默認值的 getOrDefault() 方法:
@Test public void test29() { // 若 key 42 不存在,則返回 not found map.getOrDefault(42, "not found"); // not found }
對於 value 的合併操做也變得更加簡單:
@Test public void test30() { // merge 方法,會先判斷進行合併的 key 是否存在,不存在,則會添加元素 map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); map.get(9); // val9 // 若 key 的元素存在,則對 value 執行拼接操做 map.merge(9, "concat", (value, newValue) -> value.concat(newValue)); map.get(9); // val9concat }
Java 8 中在包 java.time 下添加了新的日期 API. 它和 Joda-Time 庫類似,但又不徹底相同。接下來,我會經過一些示例代碼介紹一下新 API 中 最關鍵的特性:
Clock 提供對當前日期和時間的訪問。咱們能夠利用它來替代 System.currentTimeMillis() 方法。另外,經過 clock.instant() 可以獲取一個 instant 實例,
此實例可以方便地轉換成老版本中的 java.util.Date 對象。
@Test public void test31(){ Clock clock = Clock.systemDefaultZone(); long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // 老版本 java.util.Date }
ZoneId 表明時區類。經過靜態工廠方法方便地獲取它,入參咱們能夠傳入某個時區編碼。另外,時區類還定義了一個偏移量,用來在當前時刻或某時間 與目標時區時間之間進行轉換。
@Test public void test32() { System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules()); System.out.println(zone2.getRules()); //[Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/Gada/Atlantic, Atlantic/St_Helena, Australia/Tasmania, Libya, Europe/Guernsey, America/Grand_Turk, US/Pacific-New, Asia/Samarkand, America/Argentina/Cordoba, Asia/Phnom_Penh, Africa/Kigali, Asia/Almaty, US/Alaska, Asi... // ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00] }
LocalTime 表示一個沒有指定時區的時間類,例如,10 p.m.或者 17:30:15,下面示例代碼中,將會使用上面建立的 時區對象建立兩個 LocalTime。而後咱們會比較兩個時間,並計算它們之間的小時和分鐘的不一樣。
@Test public void test33(){ ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2); System.out.println(now1.isBefore(now2)); // false long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2); System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239 }
LocalTime 提供多個靜態工廠方法,目的是爲了簡化對時間對象實例的建立和操做,包括對時間字符串進行解析的操做等。
@Test public void test34(){ LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59 DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN); LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); System.out.println(leetTime); // 13:37 }
LocalDate 是一個日期對象,例如:2014-03-11。它和 LocalTime 同樣是個 final 類型對象。下面的例子演示瞭如何經過加減日,月,年等來計算一個新的日期。
@Test public void test35(){ LocalDate today = LocalDate.now(); // 今天加一天 LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); // 明天減兩天 LocalDate yesterday = tomorrow.minusDays(2); // 2014 年七月的第四天 LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); System.out.println(dayOfWeek); // 星期五 }
也能夠直接解析日期字符串,生成 LocalDate 實例。(和 LocalTime 操做同樣簡單)
@Test public void test36(){ DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN); LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); System.out.println(xmas); // 2014-12-24 }
LocalDateTime 是一個日期-時間對象。你也能夠將其當作是 LocalDate 和 LocalTime 的結合體。操做上,也大體相同。
@Test public void test37(){ LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // 星期三 Month month = sylvester.getMonth(); System.out.println(month); // 十二月 // 獲取改時間是該天中的第幾分鐘 long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439 }
若是再加上的時區信息,LocalDateTime 還可以被轉換成 Instance 實例。Instance 可以被轉換成老版本中 java.util.Date 對象。
@Test public void test38(){ LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); Instant instant = sylvester .atZone(ZoneId.systemDefault()) .toInstant(); Date legacyDate = Date.from(instant); System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014 }
格式化 LocalDateTime 對象就和格式化 LocalDate 或者 LocalTime 同樣。除了使用預約義的格式之外,也能夠自定義格式化輸出。
@Test public void test39(){ DateTimeFormatter formatter = DateTimeFormatter .ofPattern("MMM dd, yyyy - HH:mm"); LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter); String string = formatter.format(parsed); System.out.println(string); // Nov 03, 2014 - 07:13 }
Unlike java.text.NumberFormat the new DateTimeFormatter is immutable and thread-safe.
For details on the pattern syntax read here.
Java8中的註釋是可重複的。讓咱們直接深刻到一個例子中來解決這個問題。{在SpringBoot的啓動類中就能夠看到這中類型的註解}
首先,咱們定義一個包裝器註釋,它包含一個實際註釋數組:
@Repeatable(Hints.class) public @interface Hint { String value(); } public @interface Hints { Hint[] value(); }
Java 8經過聲明註釋@Repeatable,使咱們可以使用同一類型的多個註釋。
第一種形態:使用註解容器(老方法)
@Test public void test40() { @Hints({@Hint("hint1"), @Hint("hint2")}) class Person { } }
第二種形態:使用可重複註解(新方法)
@Test public void test41() { @Hint("hint1") @Hint("hint2") class Person { } }
java編譯器使用變量2隱式地在引擎蓋下設置@Hints註釋。這對於經過反射讀取註釋信息很重要。
@Test public void test41() { @Hint("hint1") @Hint("hint2") class Person { } Hint hint = Person.class.getAnnotation(Hint.class); System.out.println(hint); // null Hints hints1 = Person.class.getAnnotation(Hints.class); System.out.println(hints1.value().length); // 2 Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class System.out.println(hints2.length); // 2 }
儘管咱們絕對不會在 Person 類上聲明 @Hints 註解,可是它的信息仍然是能夠經過 getAnnotation(Hints.class) 來讀取的。
而且,getAnnotationsByType 方法會更方便,由於它賦予了全部 @Hints 註解標註的方法直接的訪問權限。
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface MyAnnotation {}