# Java 8 Tutorial
歡迎閱讀我對Java 8的介紹。本教程將逐步指導您完成所有新語言功能。 在簡短的代碼示例的基礎上,您將學習如何使用默認接口方法,lambda表達式,方法引用和可重復注釋。 在本文的最后,您將熟悉最新的 API 更改,如流,函數式接口(Functional Interfaces),Map 類的擴展和新的 Date API。 沒有大段枯燥的文字,只有一堆注釋的代碼片段。
### 接口的默認方法(Default Methods for Interfaces)
Java 8使我們能夠通過使用`default`關鍵字向接口添加非抽象方法實現。 此功能也稱為[虛擬擴展方法](http://stackoverflow.com/a/24102730)。
第一個例子:
~~~java
interface Formula{
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
~~~
Formula 接口中除了抽象方法計算接口公式還定義了默認方法`sqrt`。 實現該接口的類只需要實現抽象方法`calculate`。 默認方法`sqrt`可以直接使用。當然你也可以直接通過接口創建對象,然后實現接口中的默認方法就可以了,我們通過代碼演示一下這種方式。
~~~java
public class Main {
public static void main(String[] args) {
// TODO 通過匿名內部類方式訪問接口
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
System.out.println(formula.calculate(100)); // 100.0
System.out.println(formula.sqrt(16)); // 4.0
}
}
~~~
formula 是作為匿名對象實現的。該代碼非常容易理解,6行代碼實現了計算`sqrt(a * 100)`。在下一節中,我們將會看到在 Java 8 中實現單個方法對象有一種更好更方便的方法。
**譯者注:**不管是抽象類還是接口,都可以通過匿名內部類的方式訪問。不能通過抽象類或者接口直接創建對象。對于上面通過匿名內部類方式訪問接口,我們可以這樣理解:一個內部類實現了接口里的抽象方法并且返回一個內部類對象,之后我們讓接口的引用來指向這個對象。
### Lambda表達式(Lambda expressions)
首先看看在老版本的Java中是如何排列字符串的:
~~~java
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 對象以及一個比較器來按指定順序排列。通常做法都是創建一個匿名的比較器對象然后將其傳遞給`sort`方法。
在Java 8 中你就沒必要使用這種傳統的匿名對象的方式了,Java 8提供了更簡潔的語法,lambda表達式:
~~~java
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
~~~
可以看出,代碼變得更段且更具有可讀性,但是實際上還可以寫得更短:
~~~java
Collections.sort(names, (String a, String b) -> b.compareTo(a));
~~~
對于函數體只有一行代碼的,你可以去掉大括號{}以及return關鍵字,但是你還可以寫得更短點:
~~~java
names.sort((a, b) -> b.compareTo(a));
~~~
List 類本身就有一個`sort`方法。并且Java編譯器可以自動推導出參數類型,所以你可以不用再寫一次類型。接下來我們看看lambda表達式還有什么其他用法。
### 函數式接口(Functional Interfaces)
**譯者注:**原文對這部分解釋不太清楚,故做了修改!
Java 語言設計者們投入了大量精力來思考如何使現有的函數友好地支持Lambda。最終采取的方法是:增加函數式接口的概念。**“函數式接口”是指僅僅只包含一個抽象方法,但是可以有多個非抽象方法(也就是上面提到的默認方法)的接口。**像這樣的接口,可以被隱式轉換為lambda表達式。`java.lang.Runnable`與`java.util.concurrent.Callable`是函數式接口最典型的兩個例子。Java 8增加了一種特殊的注解`@FunctionalInterface`,但是這個注解通常不是必須的(某些情況建議使用),只要接口只包含一個抽象方法,虛擬機會自動判斷該接口為函數式接口。一般建議在接口上使用`@FunctionalInterface`注解進行聲明,這樣的話,編譯器如果發現你標注了這個注解的接口有多于一個抽象方法的時候會報錯的,如下圖所示
[](https://camo.githubusercontent.com/1fa0456a959db0100192af7a9699b32329a6b33d/68747470733a2f2f6d792d626c6f672d746f2d7573652e6f73732d636e2d6265696a696e672e616c6979756e63732e636f6d2f323031392d322f4046756e6374696f6e616c496e746572666163652e706e67)
示例:
~~~java
@FunctionalInterface
public interface Converter<F, T> {
T convert(F from);
}
~~~
~~~java
// TODO 將數字字符串轉換為整數類型
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted.getClass()); //class java.lang.Integer
~~~
**譯者注:**大部分函數式接口都不用我們自己寫,Java8都給我們實現好了,這些接口都在java.util.function包里。
### 方法和構造函數引用(Method and Constructor References)
前一節中的代碼還可以通過靜態方法引用來表示:
~~~java
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted.getClass()); //class java.lang.Integer
~~~
Java 8允許您通過`::`關鍵字傳遞方法或構造函數的引用。 上面的示例顯示了如何引用靜態方法。 但我們也可以引用對象方法:
~~~java
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
~~~
~~~java
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"
~~~
接下來看看構造函數是如何使用`::`關鍵字來引用的,首先我們定義一個包含多個構造函數的簡單類:
~~~java
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
~~~
接下來我們指定一個用來創建Person對象的對象工廠接口:
~~~java
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
~~~
這里我們使用構造函數引用來將他們關聯起來,而不是手動實現一個完整的工廠:
~~~java
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
~~~
我們只需要使用`Person::new`來獲取Person類構造函數的引用,Java編譯器會自動根據`PersonFactory.create`方法的參數類型來選擇合適的構造函數。
### Lamda 表達式作用域(Lambda Scopes)
#### 訪問局部變量
我們可以直接在 lambda 表達式中訪問外部的局部變量:
~~~java
final int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
~~~
但是和匿名對象不同的是,這里的變量num可以不用聲明為final,該代碼同樣正確:
~~~java
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
~~~
不過這里的 num 必須不可被后面的代碼修改(即隱性的具有final的語義),例如下面的就無法編譯:
~~~java
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num = 3;//在lambda表達式中試圖修改num同樣是不允許的。
~~~
#### 訪問字段和靜態變量
與局部變量相比,我們對lambda表達式中的實例字段和靜態變量都有讀寫訪問權限。 該行為和匿名對象是一致的。
~~~java
class Lambda4 {
static int outerStaticNum;
int outerNum;
void testScopes() {
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
}
~~~
#### 訪問默認接口方法
還記得第一節中的 formula 示例嗎?`Formula`接口定義了一個默認方法`sqrt`,可以從包含匿名對象的每個 formula 實例訪問該方法。 這不適用于lambda表達式。
無法從 lambda 表達式中訪問默認方法,故以下代碼無法編譯:
~~~java
Formula formula = (a) -> sqrt(a * 100);
~~~
### 內置函數式接口(Built-in Functional Interfaces)
JDK 1.8 API包含許多內置函數式接口。 其中一些借口在老版本的 Java 中是比較常見的比如:`Comparator`或`Runnable`,這些接口都增加了`@FunctionalInterface`注解以便能用在 lambda 表達式上。
但是 Java 8 API 同樣還提供了很多全新的函數式接口來讓你的編程工作更加方便,有一些接口是來自[Google Guava](https://code.google.com/p/guava-libraries/)庫里的,即便你對這些很熟悉了,還是有必要看看這些是如何擴展到lambda上使用的。
#### Predicates
Predicate 接口是只有一個參數的返回布爾類型值的**斷言型**接口。該接口包含多種默認方法來將 Predicate 組合成其他復雜的邏輯(比如:與,或,非):
**譯者注:**Predicate 接口源碼如下
~~~java
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
// 該方法是接受一個傳入類型,返回一個布爾值.此方法應用于判斷.
boolean test(T t);
//and方法與關系型運算符"&&"相似,兩邊都成立才返回true
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
// 與關系運算符"!"相似,對判斷進行取反
default Predicate<T> negate() {
return (t) -> !test(t);
}
//or方法與關系型運算符"||"相似,兩邊只要有一個成立就返回true
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
// 該方法接收一個Object對象,返回一個Predicate類型.此方法用于判斷第一個test的方法與第二個test方法相同(equal).
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
~~~
示例:
~~~java
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
~~~
#### Functions
Function 接口接受一個參數并生成結果。默認方法可用于將多個函數鏈接在一起(compose, andThen):
**譯者注:**Function 接口源碼如下
~~~java
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> {
//將Function對象應用到輸入的參數上,然后返回計算結果。
R apply(T t);
//將兩個Function整合,并返回一個能夠執行兩個Function對象功能的Function對象。
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
//
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
~~~
~~~java
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
~~~
#### Suppliers
Supplier 接口產生給定泛型類型的結果。 與 Function 接口不同,Supplier 接口不接受參數。
~~~java
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
~~~
#### Consumers
Consumer 接口表示要對單個輸入參數執行的操作。
~~~java
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
~~~
#### Comparators
Comparator 是老Java中的經典接口, Java 8在此之上添加了多種默認方法:
~~~java
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
~~~
## Optionals
Optionals不是函數式接口,而是用于防止 NullPointerException 的漂亮工具。這是下一節的一個重要概念,讓我們快速了解一下Optionals的工作原理。
Optional 是一個簡單的容器,其值可能是null或者不是null。在Java 8之前一般某個函數應該返回非空對象但是有時卻什么也沒有返回,而在Java 8中,你應該返回 Optional 而不是 null。
譯者注:示例中每個方法的作用已經添加。
~~~java
//of():為非null的值創建一個Optional
Optional<String> optional = Optional.of("bam");
// isPresent(): 如果值存在返回true,否則返回false
optional.isPresent(); // true
//get():如果Optional有值則將其返回,否則拋出NoSuchElementException
optional.get(); // "bam"
//orElse():如果有值則將其返回,否則返回指定的其它值
optional.orElse("fallback"); // "bam"
//ifPresent():如果Optional實例有值則為其調用consumer,否則不做處理
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
~~~
推薦閱讀:[\[Java8\]如何正確使用Optional](https://blog.kaaass.net/archives/764)
## Streams(流)
`java.util.Stream`表示能應用在一組元素上一次執行的操作序列。Stream 操作分為中間操作或者最終操作兩種,最終操作返回一特定類型的計算結果,而中間操作返回Stream本身,這樣你就可以將多個操作依次串起來。Stream 的創建需要指定一個數據源,比如`java.util.Collection`的子類,List 或者 Set, Map 不支持。Stream 的操作可以串行執行或者并行執行。
首先看看Stream是怎么用,首先創建實例代碼的用到的數據List:
~~~java
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");
~~~
Java 8擴展了集合類,可以通過 Collection.stream() 或者 Collection.parallelStream() 來創建一個Stream。下面幾節將詳細解釋常用的Stream操作:
### Filter(過濾)
過濾通過一個predicate接口來過濾并只保留符合條件的元素,該操作屬于**中間操作**,所以我們可以在過濾后的結果來應用其他Stream操作(比如forEach)。forEach需要一個函數來對過濾后的元素依次執行。forEach是一個最終操作,所以我們不能在forEach之后來執行其他Stream操作。
~~~java
// 測試 Filter(過濾)
stringList
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);//aaa2 aaa1
~~~
forEach 是為 Lambda 而設計的,保持了最緊湊的風格。而且 Lambda 表達式本身是可以重用的,非常方便。
### Sorted(排序)
排序是一個**中間操作**,返回的是排序好后的 Stream。**如果你不指定一個自定義的 Comparator 則會使用默認排序。**
~~~java
// 測試 Sort (排序)
stringList
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);// aaa1 aaa2
~~~
需要注意的是,排序只創建了一個排列好后的Stream,而不會影響原有的數據源,排序之后原數據stringCollection是不會被修改的:
~~~java
System.out.println(stringList);// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
~~~
### Map(映射)
中間操作 map 會將元素根據指定的 Function 接口來依次將元素轉成另外的對象。
下面的示例展示了將字符串轉換為大寫字符串。你也可以通過map來講對象轉換成其他類型,map返回的Stream類型是根據你map傳遞進去的函數的返回值決定的。
~~~java
// 測試 Map 操作
stringList
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
~~~
### Match(匹配)
Stream提供了多種匹配操作,允許檢測指定的Predicate是否匹配整個Stream。所有的匹配操作都是**最終操作**,并返回一個 boolean 類型的值。
~~~java
// 測試 Match (匹配)操作
boolean anyStartsWithA =
stringList
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringList
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringList
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
~~~
### Count(計數)
計數是一個**最終操作**,返回Stream中元素的個數,**返回值類型是 long**。
~~~java
//測試 Count (計數)操作
long startsWithB =
stringList
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
~~~
### Reduce(規約)
這是一個**最終操作**,允許通過指定的函數來講stream中的多個元素規約為一個元素,規約后的結果是通過Optional 接口表示的:
~~~java
//測試 Reduce (規約)操作
Optional<String> reduced =
stringList
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);//aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2
~~~
**譯者注:**這個方法的主要作用是把 Stream 元素組合起來。它提供一個起始值(種子),然后依照運算規則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字符串拼接、數值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相當于`Integer sum = integers.reduce(0, (a, b) -> a+b);`也有沒有起始值的情況,這時會把 Stream 的前面兩個元素組合起來,返回的是 Optional。
~~~java
// 字符串連接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 無起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 過濾,字符串連接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
filter(x -> x.compareTo("Z") > 0).
reduce("", String::concat);
~~~
上面代碼例如第一個示例的 reduce(),第一個參數(空白字符)即為起始值,第二個參數(String::concat)為 BinaryOperator。這類有起始值的 reduce() 都返回具體的對象。而對于第四個示例沒有起始值的 reduce(),由于可能沒有足夠的元素,返回的是 Optional,請留意這個區別。更多內容查看:[IBM:Java 8 中的 Streams API 詳解](https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/index.html)
## Parallel Streams(并行流)
前面提到過Stream有串行和并行兩種,串行Stream上的操作是在一個線程中依次完成,而并行Stream則是在多個線程上同時執行。
下面的例子展示了是如何通過并行Stream來提升性能:
首先我們創建一個沒有重復元素的大表:
~~~java
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
~~~
我們分別用串行和并行兩種方式對其進行排序,最后看看所用時間的對比。
### Sequential Sort(串行排序)
~~~java
//串行排序
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("sequential sort took: %d ms", millis));
~~~
~~~
1000000
sequential sort took: 709 ms//串行排序所用的時間
~~~
### Parallel Sort(并行排序)
~~~java
//并行排序
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));
~~~
~~~java
1000000
parallel sort took: 475 ms//串行排序所用的時間
~~~
上面兩個代碼幾乎是一樣的,但是并行版的快了 50% 左右,唯一需要做的改動就是將`stream()`改為`parallelStream()`。
## Maps
前面提到過,Map 類型不支持 streams,不過Map提供了一些新的有用的方法來處理一些日常任務。Map接口本身沒有可用的`stream()`方法,但是你可以在鍵,值上創建專門的流或者通過`map.keySet().stream()`,`map.values().stream()`和`map.entrySet().stream()`。
此外,Maps 支持各種新的和有用的方法來執行常見任務。
~~~java
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, "val" + i);
}
map.forEach((id, val) -> System.out.println(val));//val0 val1 val2 val3 val4 val5 val6 val7 val8 val9
~~~
`putIfAbsent`阻止我們在null檢查時寫入額外的代碼;`forEach`接受一個 consumer 來對 map 中的每個元素操作。
此示例顯示如何使用函數在 map 上計算代碼:
~~~java
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
~~~
接下來展示如何在Map里刪除一個鍵值全都匹配的項:
~~~java
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null
~~~
另外一個有用的方法:
~~~java
map.getOrDefault(42, "not found"); // not found
~~~
對Map的元素做合并也變得很容易了:
~~~java
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
~~~
Merge 做的事情是如果鍵名不存在則插入,否則則對原鍵對應的值做合并操作并重新插入到map中。
## Date API(日期相關API)
Java 8在`java.time`包下包含一個全新的日期和時間API。新的Date API與Joda-Time庫相似,但它們不一樣。以下示例涵蓋了此新 API 的最重要部分。譯者對這部分內容參考相關書籍做了大部分修改。
**譯者注(總結):**
* Clock 類提供了訪問當前日期和時間的方法,Clock 是時區敏感的,可以用來取代`System.currentTimeMillis()`來獲取當前的微秒數。某一個特定的時間點也可以使用`Instant`類來表示,`Instant`類也可以用來創建舊版本的`java.util.Date`對象。
* 在新API中時區使用 ZoneId 來表示。時區可以很方便的使用靜態方法of來獲取到。 抽象類`ZoneId`(在`java.time`包中)表示一個區域標識符。 它有一個名為`getAvailableZoneIds`的靜態方法,它返回所有區域標識符。
* jdk1.8中新增了 LocalDate 與 LocalDateTime等類來解決日期處理方法,同時引入了一個新的類DateTimeFormatter 來解決日期格式化問題。可以使用Instant代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。
### Clock
Clock 類提供了訪問當前日期和時間的方法,Clock 是時區敏感的,可以用來取代`System.currentTimeMillis()`來獲取當前的微秒數。某一個特定的時間點也可以使用`Instant`類來表示,`Instant`類也可以用來創建舊版本的`java.util.Date`對象。
~~~java
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
System.out.println(millis);//1552379579043
Instant instant = clock.instant();
System.out.println(instant);
Date legacyDate = Date.from(instant); //2019-03-12T08:46:42.588Z
System.out.println(legacyDate);//Tue Mar 12 16:32:59 CST 2019
~~~
### Timezones(時區)
在新API中時區使用 ZoneId 來表示。時區可以很方便的使用靜態方法of來獲取到。 抽象類`ZoneId`(在`java.time`包中)表示一個區域標識符。 它有一個名為`getAvailableZoneIds`的靜態方法,它返回所有區域標識符。
~~~java
//輸出所有區域標識符
System.out.println(ZoneId.getAvailableZoneIds());
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());// ZoneRules[currentStandardOffset=+01:00]
System.out.println(zone2.getRules());// ZoneRules[currentStandardOffset=-03:00]
~~~
### LocalTime(本地時間)
LocalTime 定義了一個沒有時區信息的時間,例如 晚上10點或者 17:30:15。下面的例子使用前面代碼創建的時區創建了兩個本地時間。之后比較時間并以小時和分鐘為單位計算兩個時間的時間差:
~~~java
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 提供了多種工廠方法來簡化對象的創建,包括解析時間字符串.
~~~java
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(本地日期)
LocalDate 表示了一個確切的日期,比如 2014-03-11。該對象值是不可變的,用起來和LocalTime基本一致。下面的例子展示了如何給Date對象加減天/月/年。另外要注意的是這些對象是不可變的,操作返回的總是一個新實例。
~~~java
LocalDate today = LocalDate.now();//獲取現在的日期
System.out.println("今天的日期: "+today);//2019-03-12
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
System.out.println("明天的日期: "+tomorrow);//2019-03-13
LocalDate yesterday = tomorrow.minusDays(2);
System.out.println("昨天的日期: "+yesterday);//2019-03-11
LocalDate independenceDay = LocalDate.of(2019, Month.MARCH, 12);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println("今天是周幾:"+dayOfWeek);//TUESDAY
~~~
從字符串解析一個 LocalDate 類型和解析 LocalTime 一樣簡單,下面是使用`DateTimeFormatter`解析字符串的例子:
~~~java
String str1 = "2014==04==12 01時06分09秒";
// 根據需要解析的日期、時間字符串定義解析所用的格式器
DateTimeFormatter fomatter1 = DateTimeFormatter
.ofPattern("yyyy==MM==dd HH時mm分ss秒");
LocalDateTime dt1 = LocalDateTime.parse(str1, fomatter1);
System.out.println(dt1); // 輸出 2014-04-12T01:06:09
String str2 = "2014$$$四月$$$13 20小時";
DateTimeFormatter fomatter2 = DateTimeFormatter
.ofPattern("yyy$$$MMM$$$dd HH小時");
LocalDateTime dt2 = LocalDateTime.parse(str2, fomatter2);
System.out.println(dt2); // 輸出 2014-04-13T20:00
~~~
再來看一個使用`DateTimeFormatter`格式化日期的示例
~~~java
LocalDateTime rightNow=LocalDateTime.now();
String date=DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(rightNow);
System.out.println(date);//2019-03-12T16:26:48.29
DateTimeFormatter formatter=DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
System.out.println(formatter.format(rightNow));//2019-03-12 16:26:48
~~~
### LocalDateTime(本地日期時間)
LocalDateTime 同時表示了時間和日期,相當于前兩節內容合并到一個對象上了。LocalDateTime 和 LocalTime還有 LocalDate 一樣,都是不可變的。LocalDateTime 提供了一些能訪問具體字段的方法。
~~~java
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month); // DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
~~~
只要附加上時區信息,就可以將其轉換為一個時間點Instant對象,Instant時間點對象可以很容易的轉換為老式的`java.util.Date`。
~~~java
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和格式化時間和日期一樣的,除了使用預定義好的格式外,我們也可以自己定義格式:
~~~java
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
~~~
和java.text.NumberFormat不一樣的是新版的DateTimeFormatter是不可變的,所以它是線程安全的。 關于時間日期格式的詳細信息在[這里](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html)。
## Annotations(注解)
在Java 8中支持多重注解了,先看個例子來理解一下是什么意思。 首先定義一個包裝類Hints注解用來放置一組具體的Hint注解:
~~~java
@interface Hints {
Hint[] value();
}
@Repeatable(Hints.class)
@interface Hint {
String value();
}
~~~
Java 8允許我們把同一個類型的注解使用多次,只需要給該注解標注一下`@Repeatable`即可。
例 1: 使用包裝類當容器來存多個注解(老方法)
~~~java
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}
~~~
例 2:使用多重注解(新方法)
~~~java
@Hint("hint1")
@Hint("hint2")
class Person {}
~~~
第二個例子里java編譯器會隱性的幫你定義好@Hints注解,了解這一點有助于你用反射來獲取這些信息:
~~~java
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)`來獲取`@Hints`注解,更加方便的方法是使用`getAnnotationsByType`可以直接獲取到所有的`@Hint`注解。 另外Java 8的注解還增加到兩種新的target上了:
~~~java
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
~~~
## Whete to go from here?
關于Java 8的新特性就寫到這了,肯定還有更多的特性等待發掘。JDK 1.8里還有很多很有用的東西,比如`Arrays.parallelSort`,`StampedLock`和`CompletableFuture`等等。
- 一.JVM
- 1.1 java代碼是怎么運行的
- 1.2 JVM的內存區域
- 1.3 JVM運行時內存
- 1.4 JVM內存分配策略
- 1.5 JVM類加載機制與對象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面試相關文章
- 2.1 可能是把Java內存區域講得最清楚的一篇文章
- 2.0 GC調優參數
- 2.1GC排查系列
- 2.2 內存泄漏和內存溢出
- 2.2.3 深入理解JVM-hotspot虛擬機對象探秘
- 1.10 并發的可達性分析相關問題
- 二.Java集合架構
- 1.ArrayList深入源碼分析
- 2.Vector深入源碼分析
- 3.LinkedList深入源碼分析
- 4.HashMap深入源碼分析
- 5.ConcurrentHashMap深入源碼分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的設計模式
- 8.集合架構之面試指南
- 9.TreeSet和TreeMap
- 三.Java基礎
- 1.基礎概念
- 1.1 Java程序初始化的順序是怎么樣的
- 1.2 Java和C++的區別
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字節與字符的區別以及訪問修飾符
- 1.7 深拷貝與淺拷貝
- 1.8 字符串常量池
- 2.面向對象
- 3.關鍵字
- 4.基本數據類型與運算
- 5.字符串與數組
- 6.異常處理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 數據流(Stream)
- 8.3 Java 8 并發教程:線程和執行器
- 8.4 Java 8 并發教程:同步和鎖
- 8.5 Java 8 并發教程:原子變量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、數值、算術和文件
- 8.7 在 Java 8 中避免 Null 檢查
- 8.8 使用 Intellij IDEA 解決 Java 8 的數據流問題
- 四.Java 并發編程
- 1.線程的實現/創建
- 2.線程生命周期/狀態轉換
- 3.線程池
- 4.線程中的協作、中斷
- 5.Java鎖
- 5.1 樂觀鎖、悲觀鎖和自旋鎖
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平鎖和非公平鎖
- 5.3.1 說說ReentrantLock的實現原理,以及ReentrantLock的核心源碼是如何實現的?
- 5.5 鎖優化和升級
- 6.多線程的上下文切換
- 7.死鎖的產生和解決
- 8.J.U.C(java.util.concurrent)
- 0.簡化版(快速復習用)
- 9.鎖優化
- 10.Java 內存模型(JMM)
- 11.ThreadLocal詳解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的實現原理
- 1.DelayQueue的實現原理
- 14.Thread.join()實現原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的實際使用場景
- 五.Java I/O NIO
- 1.I/O模型簡述
- 2.Java NIO之緩沖區
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之選擇器
- 6.基于 Java NIO 實現簡單的 HTTP 服務器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面試題
- 六.Java設計模式
- 1.單例模式
- 2.策略模式
- 3.模板方法
- 4.適配器模式
- 5.簡單工廠
- 6.門面模式
- 7.代理模式
- 七.數據結構和算法
- 1.什么是紅黑樹
- 2.二叉樹
- 2.1 二叉樹的前序、中序、后序遍歷
- 3.排序算法匯總
- 4.java實現鏈表及鏈表的重用操作
- 4.1算法題-鏈表反轉
- 5.圖的概述
- 6.常見的幾道字符串算法題
- 7.幾道常見的鏈表算法題
- 8.leetcode常見算法題1
- 9.LRU緩存策略
- 10.二進制及位運算
- 10.1.二進制和十進制轉換
- 10.2.位運算
- 11.常見鏈表算法題
- 12.算法好文推薦
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事務管理
- 4.SpringMVC 運行流程和手動實現
- 0.Spring 核心技術
- 5.spring如何解決循環依賴問題
- 6.springboot自動裝配原理
- 7.Spring中的循環依賴解決機制中,為什么要三級緩存,用二級緩存不夠嗎
- 8.beanFactory和factoryBean有什么區別
- 九.數據庫
- 1.mybatis
- 1.1 MyBatis-# 與 $ 區別以及 sql 預編譯
- Mybatis系列1-Configuration
- Mybatis系列2-SQL執行過程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-參數設置揭秘(ParameterHandler)
- Mybatis系列8-緩存機制
- 2.淺談聚簇索引和非聚簇索引的區別
- 3.mysql 證明為什么用limit時,offset很大會影響性能
- 4.MySQL中的索引
- 5.數據庫索引2
- 6.面試題收集
- 7.MySQL行鎖、表鎖、間隙鎖詳解
- 8.數據庫MVCC詳解
- 9.一條SQL查詢語句是如何執行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能優化神器 Explain 使用分析
- 12.mysql中,一條update語句執行的過程是怎么樣的?期間用到了mysql的哪些log,分別有什么作用
- 十.Redis
- 0.快速復習回顧Redis
- 1.通俗易懂的Redis數據結構基礎教程
- 2.分布式鎖(一)
- 3.分布式鎖(二)
- 4.延時隊列
- 5.位圖Bitmaps
- 6.Bitmaps(位圖)的使用
- 7.Scan
- 8.redis緩存雪崩、緩存擊穿、緩存穿透
- 9.Redis為什么是單線程、及高并發快的3大原因詳解
- 10.布隆過濾器你值得擁有的開發利器
- 11.Redis哨兵、復制、集群的設計原理與區別
- 12.redis的IO多路復用
- 13.相關redis面試題
- 14.redis集群
- 十一.中間件
- 1.RabbitMQ
- 1.1 RabbitMQ實戰,hello world
- 1.2 RabbitMQ 實戰,工作隊列
- 1.3 RabbitMQ 實戰, 發布訂閱
- 1.4 RabbitMQ 實戰,路由
- 1.5 RabbitMQ 實戰,主題
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 實戰 – 整合 RabbitMQ 發送郵件
- 1.8 RabbitMQ 的消息持久化與 Spring AMQP 的實現剖析
- 1.9 RabbitMQ必備核心知識
- 2.RocketMQ 的幾個簡單問題與答案
- 2.Kafka
- 2.1 kafka 基礎概念和術語
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志機制
- 2.4 kafka是pull還是push的方式傳遞消息的?
- 2.5 Kafka的數據處理流程
- 2.6 Kafka的腦裂預防和處理機制
- 2.7 Kafka中partition副本的Leader選舉機制
- 2.8 如果Leader掛了的時候,follower沒來得及同步,是否會出現數據不一致
- 2.9 kafka的partition副本是否會出現腦裂情況
- 十二.Zookeeper
- 0.什么是Zookeeper(漫畫)
- 1.使用docker安裝Zookeeper偽集群
- 3.ZooKeeper-Plus
- 4.zk實現分布式鎖
- 5.ZooKeeper之Watcher機制
- 6.Zookeeper之選舉及數據一致性
- 十三.計算機網絡
- 1.進制轉換:二進制、八進制、十六進制、十進制之間的轉換
- 2.位運算
- 3.計算機網絡面試題匯總1
- 十四.Docker
- 100.面試題收集合集
- 1.美團面試常見問題總結
- 2.b站部分面試題
- 3.比心面試題
- 4.騰訊面試題
- 5.哈羅部分面試
- 6.筆記
- 十五.Storm
- 1.Storm和流處理簡介
- 2.Storm 核心概念詳解
- 3.Storm 單機版本環境搭建
- 4.Storm 集群環境搭建
- 5.Storm 編程模型詳解
- 6.Storm 項目三種打包方式對比分析
- 7.Storm 集成 Redis 詳解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初識ElasticSearch
- 2.文檔基本CRUD、集群健康檢查
- 3.shard&replica
- 4.document核心元數據解析及ES的并發控制
- 5.document的批量操作及數據路由原理
- 6.倒排索引
- 十七.分布式相關
- 1.分布式事務解決方案一網打盡
- 2.關于xxx怎么保證高可用的問題
- 3.一致性hash原理與實現
- 4.微服務注冊中心 Nacos 比 Eureka的優勢
- 5.Raft 協議算法
- 6.為什么微服務架構中需要網關
- 0.CAP與BASE理論
- 十八.Dubbo
- 1.快速掌握Dubbo常規應用
- 2.Dubbo應用進階
- 3.Dubbo調用模塊詳解
- 4.Dubbo調用模塊源碼分析
- 6.Dubbo協議模塊