
- 「MoreThanJava」 宣揚(yáng)的是 「學(xué)習(xí),不止 CODE」,本系列 Java 基礎(chǔ)教程是自己在結(jié)合各方面的知識(shí)之后,對(duì) Java 基礎(chǔ)的一個(gè)總回顧,旨在 「幫助新朋友快速高質(zhì)量的學(xué)習(xí)」。
- 當(dāng)然 不論新老朋友 我相信您都可以 從中獲益。如果覺得 「不錯(cuò)」 的朋友,歡迎 「關(guān)注 + 留言 + 分享」,文末有完整的獲取鏈接,您的支持是我前進(jìn)的最大的動(dòng)力!
特性總覽
以下是 Java 8 中的引入的部分新特性。關(guān)于 Java 8 新特性更詳細(xì)的介紹可參考這里。
- 接口默認(rèn)方法和靜態(tài)方法
- Lambda 表達(dá)式
- 函數(shù)式接口
- 方法引用
- Stream
- Optional
- Date/Time API
- 重復(fù)注解
- 擴(kuò)展注解的支持
- Base64
- JavaFX
- 其它
- JDBC 4.2 規(guī)范
- 更好的類型推測(cè)機(jī)制
- HashMap 性能提升
- IO/NIO 的改進(jìn)
- JavaScript 引擎 Nashorn
- 并發(fā)(Concurrency)
- 類依賴分析器 jdeps
- JVM 的 PermGen 空間被移除
一. 接口默認(rèn)方法和靜態(tài)方法
接口默認(rèn)方法
在 Java 8 中,允許為接口方法提供一個(gè)默認(rèn)的實(shí)現(xiàn)。必須用 default 修飾符標(biāo)記這樣一個(gè)方法,例如 JDK 中的 Iterator 接口:
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() { throw new UnsupportedOperationExceition("remove"); }
}
這將非常有用!如果你要實(shí)現(xiàn)一個(gè)迭代器,就需要提供 hasNext() 和 next() 方法。這些方法沒有默認(rèn)實(shí)現(xiàn)——它們依賴于你要遍歷訪問的數(shù)據(jù)結(jié)構(gòu)。不過,如果你的迭代器是 只讀 的,那么就不用操心實(shí)現(xiàn) remove() 方法。
默認(rèn)方法也可以調(diào)用其他方法,例如,我們可以改造 Collection 接口,定義一個(gè)方便的 isEmpty() 方法:
public interface Collection {
int size(); // an abstract method
default boolean isEmpty() { return size() == 0; }
}
這樣,實(shí)現(xiàn) Collection 的程序員就不用再操心實(shí)現(xiàn) isEmpty() 方法了。
在 JVM 中,默認(rèn)方法的實(shí)現(xiàn)是非常高效的,并且通過字節(jié)碼指令為方法調(diào)用提供了支持。默認(rèn)方法允許繼續(xù)使用現(xiàn)有的 Java 接口,而同時(shí)能夠保障正常的編譯過程。這方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream(),parallelStream(),forEach(),removeIf()等。盡管默認(rèn)方法非常強(qiáng)大,但是在使用默認(rèn)方法時(shí)我們需要小心注意一個(gè)地方:在聲明一個(gè)默認(rèn)方法前,請(qǐng)仔細(xì)思考是不是真的有必要使用默認(rèn)方法。
解決默認(rèn)方法沖突
如果先在一個(gè)接口中將一個(gè)方法定義為默認(rèn)方法,然后又在類或另一個(gè)接口中定義同樣的方法,會(huì)發(fā)生什么?
// 測(cè)試接口 1
public interface TestInterface1 {
default void sameMethod() { System.out.println("Invoke TestInterface1 method!"); }
}
// 測(cè)試接口 2
public interface TestInterface2 {
default void sameMethod() { System.out.println("Invoke TestInterface2 method!"); }
}
// 繼承兩個(gè)接口的測(cè)試類
public class TestObject implements TestInterface1, TestInterface2 {
@Override
public void sameMethod() {
// 這里也可以選擇兩個(gè)接口中的一個(gè)默認(rèn)實(shí)現(xiàn)
// 如: TestInterface1.super.sameMethod();
System.out.println("Invoke Object method!");
}
}
// 測(cè)試類
public class Tester {
public static void main(String[] args) {
TestObject testObject = new TestObject();
testObject.sameMethod();
}
}
測(cè)試輸出:
Invoke Object method!
?? 對(duì)于 Scale 或者 C++ 這些語言來說,解決這種具有 二義性 的情況規(guī)則會(huì)很復(fù)雜,Java 的規(guī)則則簡(jiǎn)單得多:
- 類優(yōu)先。如果本類中提供了一個(gè)具體方法符合簽名,則同名且具有相同參數(shù)列表的接口中的默認(rèn)方法會(huì)被忽略;
- 接口沖突。如果一個(gè)接口提供了一個(gè)默認(rèn)方法,另一個(gè)接口提供了一個(gè)同名且參數(shù)列表相同的方法 (順序和類型都相同) ,則必須覆蓋這個(gè)方法來解決沖突 (就是??代碼的情況,不覆蓋編譯器不會(huì)編譯..);
Java 設(shè)計(jì)者更強(qiáng)調(diào)一致性,讓程序員自己來解決這樣的二義性似乎也顯得很合理。如果至少有一個(gè)接口提供了一個(gè)實(shí)現(xiàn),編譯器就會(huì)報(bào)告錯(cuò)誤,程序員就必須解決這個(gè)二義性。(如果兩個(gè)接口都沒有為共享方法提供默認(rèn)實(shí)現(xiàn),則不存在沖突,要么實(shí)現(xiàn),要么不實(shí)現(xiàn)..)
?? 我們只討論了兩個(gè)接口的命名沖突?,F(xiàn)在來考慮另一種情況,一個(gè)類繼承自一個(gè)類,同時(shí)實(shí)現(xiàn)了一個(gè)接口,從父類繼承的方法和接口擁有同樣的方法簽名,又將怎么辦呢?
// 測(cè)試接口
public interface TestInterface {
default void sameMethod() { System.out.println("Invoke TestInterface Method!"); }
}
// 父類
public class Father {
void sameMethod() { System.out.println("Invoke Father Method!"); }
}
// 子類
public class Son extends Father implements TestInterface {
@Override
public void sameMethod() {
System.out.println("Invoke Son Method!");
}
}
// 測(cè)試類
public class Tester {
public static void main(String[] args) { new Son().sameMethod(); }
}
程序輸出:
COPYInvoke Son Method!
還記得我們說過的方法調(diào)用的過程嗎 (先找本類的方法找不到再?gòu)母割愓??加上這里提到的 “類優(yōu)先” 原則 (本類中有方法則直接調(diào)用),這很容易理解!
千萬不要讓一個(gè)默認(rèn)方法重新定義
Object類中的某個(gè)方法。例如,不能為toString()或equals()定義默認(rèn)方法,盡管對(duì)于 List 之類的接口這可能很有吸引力,但由于 類優(yōu)先原則,這樣的方法絕對(duì)無法超越Object.toString()或者Object.equals()。
接口靜態(tài)方法
在 Java 8 中,允許在接口中增加靜態(tài)方法 (允許不構(gòu)建對(duì)象而直接使用的具體方法)。理論上講,沒有任何理由認(rèn)為這是不合法的,只是這有違將接口作為抽象規(guī)范的初衷。
例子:
public interface StaticInterface {
static void method() {
System.out.println("這是Java8接口中的靜態(tài)方法!");
}
}
調(diào)用:
public class Main {
public static void main(String[] args) {
StaticInterface.method(); // 輸出 這是Java8接口中的靜態(tài)方法!
}
}
目前為止,通常的做法都是將靜態(tài)方法放在 伴隨類 (可以理解為操作繼承接口的實(shí)用工具類) 中。在標(biāo)準(zhǔn)庫中,你可以看到成對(duì)出現(xiàn)的接口和實(shí)用工具類,如 Collection/ Collections 或 Path/ Paths。
在 Java 11 中,Path 接口就提供了一個(gè)與之工具類 Paths.get() 等價(jià)的方法 (該方法用于將一個(gè) URI 或者字符串序列構(gòu)造成一個(gè)文件或目錄的路徑):
COPYpublic interface Path {
public static Path of(String first, String... more) { ... }
public static Path of(URI uri) { ... }
}
這樣一來,Paths 類就不再是必要的了。類似地,如果實(shí)現(xiàn)你自己的接口時(shí),沒有理由再額外提供一個(gè)帶有實(shí)用方法的工具類。
?? 另外,在 Java 9 中,接口中的方法可以是 private。private 方法可以是靜態(tài)方法或?qū)嵗椒āS捎谒接蟹椒ㄖ荒茉诮涌诒旧淼姆椒ㄖ惺褂?,所以它們的用法很有限,只能作為接口中其他方法的輔助方法。
二. Lambda 表達(dá)式
Lambda表達(dá)式 (也稱為閉包) 是整個(gè) Java 8 發(fā)行版中最受期待的在 Java 語言層面上的改變,Lambda 允許把函數(shù)作為一個(gè)方法的參數(shù),即 行為參數(shù)化,函數(shù)作為參數(shù)傳遞進(jìn)方法中。
什么是 Lambda 表達(dá)式
我們知道,對(duì)于一個(gè) Java 變量,我們可以賦給一個(gè) 「值」。

如果你想把 「一塊代碼」 賦給一個(gè) Java 變量,應(yīng)該怎么做呢?
比如,我想把右邊的代碼塊,賦值給一個(gè)叫做 blockOfCode 的 Java 變量:

在 Java 8 之前,這個(gè)是做不到的,但是 Java 8 問世之后,利用 Lambda 特性,就可以做到了。

當(dāng)然,這個(gè)并不是一個(gè)很簡(jiǎn)潔的寫法,所以為了讓這個(gè)賦值操作變得更加優(yōu)雅,我們可以移除一些沒有必要的聲明。

這樣,我們就成功的非常優(yōu)雅的把「一塊代碼」賦給了一個(gè)變量。而「這塊代碼」,或者說「這個(gè)被賦給一個(gè)變量的函數(shù)」,就是一個(gè) Lambda 表達(dá)式。
但是這里仍然有一個(gè)問題,就是變量 blockOfCode 的類型應(yīng)該是什么?
在 Java 8 里面,所有的 Lambda 的類型都是一個(gè)接口,而 Lambda 表達(dá)式本身,也就是「那段代碼」,需要是這個(gè)接口的實(shí)現(xiàn)。這是理解 Lambda 的一個(gè)關(guān)鍵所在,簡(jiǎn)而言之就是,Lambda 表達(dá)式本身就是一個(gè)接口的實(shí)現(xiàn)。直接這樣說可能還是有點(diǎn)讓人困擾,我們繼續(xù)看看例子。我們給上面的 blockOfCode 加上一個(gè)類型:

這種只有一個(gè)接口函數(shù)需要被實(shí)現(xiàn)的接口類型,我們叫它「函數(shù)式接口」。
為了避免后來的人在這個(gè)接口中增加接口函數(shù)導(dǎo)致其有多個(gè)接口函數(shù)需要被實(shí)現(xiàn),變成「非函數(shù)接口」,我們可以在這個(gè)上面加上一個(gè)聲明 @FunctionalInterface, 這樣別人就無法在里面添加新的接口函數(shù)了:

這樣,我們就得到了一個(gè)完整的 Lambda 表達(dá)式聲明:

Lambda 表達(dá)式的作用
Lambda 最直觀的作用就是使代碼變得整潔.。
我們可以對(duì)比一下 Lambda 表達(dá)式和傳統(tǒng)的 Java 對(duì)同一個(gè)接口的實(shí)現(xiàn):

這兩種寫法本質(zhì)上是等價(jià)的。但是顯然,Java 8 中的寫法更加優(yōu)雅簡(jiǎn)潔。并且,由于 Lambda 可以直接賦值給一個(gè)變量,我們就可以直接把 Lambda 作為參數(shù)傳給函數(shù), 而傳統(tǒng)的 Java 必須有明確的接口實(shí)現(xiàn)的定義,初始化才行。
有些情況下,這個(gè)接口實(shí)現(xiàn)只需要用到一次。傳統(tǒng)的 Java 7 必須要求你定義一個(gè)“污染環(huán)境”的接口實(shí)現(xiàn) MyInterfaceImpl,而相較之下 Java 8 的 Lambda, 就顯得干凈很多。
三. 函數(shù)式接口
上面我們說到,只有一個(gè)接口函數(shù)需要被實(shí)現(xiàn)的接口類型,我們叫它「函數(shù)式接口」。Lambda 表達(dá)式配合函數(shù)式接口能讓我們代碼變得干凈許多。
Java 8 API 包含了很多內(nèi)建的函數(shù)式接口,在老 Java 中常用到的比如Comparator或者Runnable接口,這些接口都增加了@FunctionalInterface注解以便能用在Lambda上。
Java 8 API 同樣還提供了很多全新的函數(shù)式接口來讓工作更加方便,有一些接口是來自 Google Guava 庫里的,即便你對(duì)這些很熟悉了,還是有必要看看這些是如何擴(kuò)展到 Lambda 上使用的。
1 - Comparator(比較器接口)
Comparator是老Java中的經(jīng)典接口, Java 8 在此之上添加了多種默認(rèn)方法。源代碼及使用示例如下:
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
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
2 - Consumer(消費(fèi)型接口)
Consumer 接口表示執(zhí)行在單個(gè)參數(shù)上的操作。源代碼及使用示例如下:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
更多的Consumer接口
-
BiConsumer:void accept(T t, U u);: 接受兩個(gè)參數(shù)的二元函數(shù) -
DoubleConsumer:void accept(double value);: 接受一個(gè)double參數(shù)的一元函數(shù) -
IntConsumer:void accept(int value);: 接受一個(gè)int參數(shù)的一元函數(shù) -
LongConsumer:void accept(long value);: 接受一個(gè)long參數(shù)的一元函數(shù) -
ObjDoubleConsumer:void accept(T t, double value);: 接受一個(gè)泛型參數(shù)一個(gè)double參數(shù)的二元函數(shù) -
ObjIntConsumer:void accept(T t, int value);: 接受一個(gè)泛型參數(shù)一個(gè)int參數(shù)的二元函數(shù) -
ObjLongConsumer:void accept(T t, long value);: 接受一個(gè)泛型參數(shù)一個(gè)long參數(shù)的二元函數(shù)
3 - Supplier(供應(yīng)型接口)
Supplier 接口是不需要參數(shù)并返回一個(gè)任意范型的值。其簡(jiǎn)潔的聲明,會(huì)讓人以為不是函數(shù)。這個(gè)抽象方法的聲明,同 Consumer 相反,是一個(gè)只聲明了返回值,不需要參數(shù)的函數(shù)。也就是說 Supplier 其實(shí)表達(dá)的不是從一個(gè)參數(shù)空間到結(jié)果空間的映射能力,而是表達(dá)一種生成能力,因?yàn)槲覀兂R姷膱?chǎng)景中不止是要consume(Consumer)或者是簡(jiǎn)單的map(Function),還包括了 new 這個(gè)動(dòng)作。而 Supplier 就表達(dá)了這種能力。源代碼及使用示例如下:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
更多Supplier接口
-
BooleanSupplier:boolean getAsBoolean();: 返回boolean的無參函數(shù) -
DoubleSupplier:double getAsDouble();: 返回double的無參函數(shù) -
IntSupplier:int getAsInt();: 返回int的無參函數(shù) -
LongSupplier:long getAsLong();: 返回long的無參函數(shù)
4 - Predicate(斷言型接口)
Predicate 接口只有一個(gè)參數(shù),返回 boolean 類型。該接口包含多種默認(rèn)方法來將 Predicate 組合成其他復(fù)雜的邏輯(比如:與,或,非)。Stream 的 filter 方法就是接受 Predicate 作為入?yún)⒌?。這個(gè)具體在后面使用 Stream 的時(shí)候再分析深入。源代碼及使用示例如下:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
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();
更多的Predicate接口
-
BiPredicate:boolean test(T t, U u);: 接受兩個(gè)參數(shù)的二元斷言函數(shù) -
DoublePredicate:boolean test(double value);: 入?yún)閐ouble的斷言函數(shù) -
IntPredicate:boolean test(int value);: 入?yún)閕nt的斷言函數(shù) -
LongPredicate:boolean test(long value);: 入?yún)閘ong的斷言函數(shù)
5 - Function(功能型接口)
Function 接口有一個(gè)參數(shù)并且返回一個(gè)結(jié)果,并附帶了一些可以和其他函數(shù)組合的默認(rèn)方法(compose, andThen)。源代碼及使用示例如下:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
更多的Function接口
-
BiFunction :R apply(T t, U u);: 接受兩個(gè)參數(shù),返回一個(gè)值,代表一個(gè)二元函數(shù); -
DoubleFunction :R apply(double value);: 只處理double類型的一元函數(shù); -
IntFunction :R apply(int value);: 只處理int參數(shù)的一元函數(shù); -
LongFunction :R apply(long value);: 只處理long參數(shù)的一元函數(shù); -
ToDoubleFunction:double applyAsDouble(T value);: 返回double的一元函數(shù); -
ToDoubleBiFunction:double applyAsDouble(T t, U u);: 返回double的二元函數(shù); -
ToIntFunction:int applyAsInt(T value);: 返回int的一元函數(shù); -
ToIntBiFunction:int applyAsInt(T t, U u);: 返回int的二元函數(shù); -
ToLongFunction:long applyAsLong(T value);: 返回long的一元函數(shù); -
ToLongBiFunction:long applyAsLong(T t, U u);: 返回long的二元函數(shù); -
DoubleToIntFunction:int applyAsInt(double value);: 接受double返回int的一元函數(shù); -
DoubleToLongFunction:long applyAsLong(double value);: 接受double返回long的一元函數(shù); -
IntToDoubleFunction:double applyAsDouble(int value);: 接受int返回double的一元函數(shù); -
IntToLongFunction:long applyAsLong(int value);: 接受int返回long的一元函數(shù); -
LongToDoubleFunction:double applyAsDouble(long value);: 接受long返回double的一元函數(shù); -
LongToIntFunction:int applyAsInt(long value);: 接受long返回int的一元函數(shù);
6 - Operator
Operator 其實(shí)就是 Function,函數(shù)有時(shí)候也叫作算子。算子在Java8中接口描述更像是函數(shù)的補(bǔ)充,和上面的很多類型映射型函數(shù)類似。算子 Operator 包括:UnaryOperator 和 BinaryOperator。分別對(duì)應(yīng)單(一)元算子和二元算子。
算子的接口聲明如下:
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
Operator只需聲明一個(gè)泛型參數(shù) T 即可。對(duì)應(yīng)的使用示例如下:
UnaryOperator<Integer> increment = x -> x + 1;
System.out.println("遞增:" + increment.apply(2)); // 輸出 遞增:3
BinaryOperator<Integer> add = (x, y) -> x + y;
System.out.println("相加:" + add.apply(2, 3)); // 輸出 相加:5
BinaryOperator<Integer> min = BinaryOperator.minBy((o1, o2) -> o1 - o2);
System.out.println("最小值:" + min.apply(2, 3)); // 輸出 最小值:2
更多的Operator接口
-
LongUnaryOperator:long applyAsLong(long operand);: 對(duì)long類型做操作的一元算子 -
IntUnaryOperator:int applyAsInt(int operand);: 對(duì)int類型做操作的一元算子 -
DoubleUnaryOperator:double applyAsDouble(double operand);: 對(duì)double類型做操作的一元算子 -
DoubleBinaryOperator:double applyAsDouble(double left, double right);: 對(duì)double類型做操作的二元算子 -
IntBinaryOperator:int applyAsInt(int left, int right);: 對(duì)int類型做操作的二元算子 -
LongBinaryOperator:long applyAsLong(long left, long right);: 對(duì)long類型做操作的二元算子
7 - 其他函數(shù)式接口
java.lang.Runnablejava.util.concurrent.Callablejava.security.PrivilegedActionjava.io.FileFilterjava.nio.file.PathMatcherjava.lang.reflect.InvocationHandlerjava.beans.PropertyChangeListenerjava.awt.event.ActionListenerjavax.swing.event.ChangeListener
四. 方法引用
1 - 概述
在學(xué)習(xí)了 Lambda 表達(dá)式之后,我們通常使用 Lambda 表達(dá)式來創(chuàng)建匿名方法。然而,有時(shí)候我們僅僅是調(diào)用了一個(gè)已存在的方法。如下:
Arrays.sort(strArray, (s1, s2) -> s1.compareToIgnoreCase(s2));
在 Java 8 中,我們可以直接通過方法引用來簡(jiǎn)寫 Lambda 表達(dá)式中已經(jīng)存在的方法。
Arrays.sort(strArray, String::compareToIgnoreCase);
這種特性就叫做方法引用(Method Reference)。
方法引用是用來直接訪問類或者實(shí)例的已經(jīng)存在的方法或者構(gòu)造方法。方法引用提供了一種引用而不執(zhí)行方法的方式,它需要由兼容的函數(shù)式接口構(gòu)成的目標(biāo)類型上下文。計(jì)算時(shí),方法引用會(huì)創(chuàng)建函數(shù)式接口的一個(gè)實(shí)例。當(dāng) Lambda 表達(dá)式中只是執(zhí)行一個(gè)方法調(diào)用時(shí),不用 Lambda 表達(dá)式,直接通過方法引用的形式可讀性更高一些。方法引用是一種更簡(jiǎn)潔易懂的 Lambda 表達(dá)式。
注意: 方法引用是一個(gè) Lambda 表達(dá)式,其中方法引用的操作符是雙冒號(hào)
::。
2 - 分類
方法引用的標(biāo)準(zhǔn)形式是:類名::方法名。(注意:只需要寫方法名,不需要寫括號(hào))
有以下四種形式的方法引用:
- 引用靜態(tài)方法: ContainingClass::staticMethodName
- 引用某個(gè)對(duì)象的實(shí)例方法: containingObject::instanceMethodName
- 引用某個(gè)類型的任意對(duì)象的實(shí)例方法:ContainingType::methodName
- 引用構(gòu)造方法: ClassName::new
3 - 示例
使用示例如下:
public class Person {
String name;
LocalDate birthday;
public Person(String name, LocalDate birthday) {
this.name = name;
this.birthday = birthday;
}
public LocalDate getBirthday() {
return birthday;
}
public static int compareByAge(Person a, Person b) {
return a.birthday.compareTo(b.birthday);
}
@Override
public String toString() {
return this.name;
}
}
測(cè)試類:
public class MethodReferenceTest {
@Test
public static void main() {
Person[] pArr = new Person[] {
new Person("003", LocalDate.of(2016,9,1)),
new Person("001", LocalDate.of(2016,2,1)),
new Person("002", LocalDate.of(2016,3,1)),
new Person("004", LocalDate.of(2016,12,1))
};
// 使用匿名類
Arrays.sort(pArr, new Comparator<Person>() {
@Override
public int compare(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
});
//使用lambda表達(dá)式
Arrays.sort(pArr, (Person a, Person b) -> {
return a.getBirthday().compareTo(b.getBirthday());
});
//使用方法引用,引用的是類的靜態(tài)方法
Arrays.sort(pArr, Person::compareByAge);
}
}
五. Stream 流操作
流是 Java8 中 API 的新成員,它允許你以 聲明式 的方式處理數(shù)據(jù)集合(通過查詢語句來表達(dá),而不是臨時(shí)編寫一個(gè)實(shí)現(xiàn))。這有點(diǎn)兒像是我們操作數(shù)據(jù)庫一樣,例如我想要查詢出熱量較低的菜品名字我就可以像下面這樣:
COPYSELECT name FROM dishes WHERE calorie < 400;
您看,我們并沒有對(duì)菜品的什么屬性進(jìn)行篩選(比如像之前使用迭代器一樣每個(gè)做判斷),我們只是表達(dá)了我們想要什么。那么為什么到了 Java 的集合中,這樣做就不行了呢?
另外一點(diǎn),如果我們想要處理大量的數(shù)據(jù)又該怎么辦?是否是考慮使用多線程進(jìn)行并發(fā)處理呢?如果是,那么可能編寫的關(guān)于并發(fā)的代碼比使用迭代器本身更加的復(fù)雜,而且調(diào)試起來也會(huì)變得麻煩。
基于以上的幾點(diǎn)考慮,Java 設(shè)計(jì)者在 Java 8 版本中 (真正把函數(shù)式編程風(fēng)格引入到 Java 中),引入了流的概念,來幫助您節(jié)約時(shí)間!并且有了 Lambda 的參與,流操作的使用將更加順暢!
1 - 流操作特點(diǎn)
特點(diǎn)一:內(nèi)部迭代
就現(xiàn)在來說,您可以把它簡(jiǎn)單的當(dāng)成一種高級(jí)的迭代器(Iterator),或者是高級(jí)的 for 循環(huán),區(qū)別在于,前面兩者都是屬于外部迭代,而流采用內(nèi)部迭代。

上圖簡(jiǎn)要說明了內(nèi)部迭代與外部迭代的差異,我們?cè)倥e一個(gè)生活中實(shí)際的例子(引自《Java 8 實(shí)戰(zhàn)》),比如您想讓您兩歲的孩子索菲亞把她的玩具都收到盒子里面去,你們之間可能會(huì)產(chǎn)生如下的對(duì)話:
- 你:“索菲亞,我們把玩具收起來吧,地上還有玩具嗎?”
- 索菲亞:“有,球?!?/li>
- 你:“好,把球放進(jìn)盒子里面吧,還有嗎?”
- 索菲亞:“有,那是我的娃娃?!?/li>
- 你:“好,把娃娃也放進(jìn)去吧,還有嗎?”
- 索菲亞:“有,有我的書。”
- 你:“好,把書也放進(jìn)去,還有嗎?”
- 索菲亞:“沒有了。”
- 你:“好,我們收好啦?!?/li>
這正是你每天都要對(duì) Java 集合做的事情。你外部迭代了一個(gè)集合,顯式地取出每個(gè)項(xiàng)目再加以處理,但是如果你只是跟索菲亞說:“把地上所有玩具都放進(jìn)盒子里”,那么索菲亞就可以選擇一手拿娃娃一手拿球,或是選擇先拿離盒子最近的那個(gè)東西,再拿其他的東西。
采用內(nèi)部迭代,項(xiàng)目可以透明地并行處理,或者用優(yōu)化的順序進(jìn)行處理,要是使用 Java 過去的外部迭代方法,這些優(yōu)化都是很困難的。
這或許有點(diǎn)雞蛋里挑骨頭,但這差不多就是 Java 8 引入流的原因了——Streams 庫的內(nèi)部迭代可以自動(dòng)選擇一種是和你硬件的數(shù)據(jù)表示和并行實(shí)現(xiàn)。
特點(diǎn)二:只能遍歷一次
請(qǐng)注意,和迭代器一樣,流只能遍歷一次。當(dāng)流遍歷完之后,我們就說這個(gè)流已經(jīng)被消費(fèi)掉了,你可以從原始數(shù)據(jù)那里重新獲得一條新的流,但是卻不允許消費(fèi)已消費(fèi)掉的流。例如下面代碼就會(huì)拋出一個(gè)異常,說流已被消費(fèi)掉了:
List<String> title = Arrays.asList("Wmyskxz", "Is", "Learning", "Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);
// 運(yùn)行上面程序會(huì)報(bào)以下錯(cuò)誤
/*
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at Test1.main(Tester.java:17)
*/
特點(diǎn)三:方便的并行處理
Java 8 中不僅提供了方便的一些流操作(比如過濾、排序之類的),更重要的是對(duì)于并行處理有很好的支持,只需要加上 .parallel() 就行了!例如我們使用下面程序來說明一下多線程流操作的方便和快捷,并且與單線程做了一下對(duì)比:
COPYpublic class StreamParallelDemo {
/** 總數(shù) */
private static int total = 100_000_000;
public static void main(String[] args) {
System.out.println(String.format("本計(jì)算機(jī)的核數(shù):%d", Runtime.getRuntime().availableProcessors()));
// 產(chǎn)生1000w個(gè)隨機(jī)數(shù)(1 ~ 100),組成列表
Random random = new Random();
List<Integer> list = new ArrayList<>(total);
for (int i = 0; i < total; i++) {
list.add(random.nextInt(100));
}
long prevTime = getCurrentTime();
list.stream().reduce((a, b) -> a + b).ifPresent(System.out::println);
System.out.println(String.format("單線程計(jì)算耗時(shí):%d", getCurrentTime() - prevTime));
prevTime = getCurrentTime();
// 只需要加上 .parallel() 就行了
list.stream().parallel().reduce((a, b) -> a + b).ifPresent(System.out::println);
System.out.println(String.format("多線程計(jì)算耗時(shí):%d", getCurrentTime() - prevTime));
}
private static long getCurrentTime() {
return System.currentTimeMillis();
}
}
以上程序分別使用了單線程流和多線程流計(jì)算了一千萬個(gè)隨機(jī)數(shù)的和,輸出如下:
本計(jì)算機(jī)的核數(shù):8
655028378
單線程計(jì)算耗時(shí):4159
655028378
多線程計(jì)算耗時(shí):540
并行流的內(nèi)部使用了默認(rèn)的 ForkJoinPool 分支/合并框架,它的默認(rèn)線程數(shù)量就是你的處理器數(shù)量,這個(gè)值是由 Runtime.getRuntime().availableProcessors() 得到的(當(dāng)然我們也可以全局設(shè)置這個(gè)值)。我們也不再去過度的操心加鎖線程安全等一系列問題。
2 - 一些重要方法說明
-
stream: 返回?cái)?shù)據(jù)流,集合作為其源 -
parallelStream: 返回并行數(shù)據(jù)流, 集合作為其源 -
filter: 方法用于過濾出滿足條件的元素 -
map: 方法用于映射每個(gè)元素對(duì)應(yīng)的結(jié)果 -
forEach: 方法遍歷該流中的每個(gè)元素 -
limit: 方法用于減少流的大小 -
sorted: 方法用來對(duì)流中的元素進(jìn)行排序 -
anyMatch: 是否存在任意一個(gè)元素滿足條件(返回布爾值) -
allMatch: 是否所有元素都滿足條件(返回布爾值) -
noneMatch: 是否所有元素都不滿足條件(返回布爾值) -
collect: 方法是終端操作,這是通常出現(xiàn)在管道傳輸操作結(jié)束標(biāo)記流的結(jié)束
3 - 一些使用示例
Filter 過濾
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
Sort 排序
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
Map 映射
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
Match 匹配
boolean anyStartsWithA = stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA = stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ = stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
Count 計(jì)數(shù)
long startsWithB = stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
Reduce 歸約
這是一個(gè)最終操作,允許通過指定的函數(shù)來將 stream 中的多個(gè)元素規(guī)約為一個(gè)元素,規(guī)越后的結(jié)果是通過 Optional 接口表示的。代碼如下:
Optional<String> reduced = stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
想了解更多請(qǐng)參考:https://www.wmyskxz.com/2019/08/03/java8-liu-cao-zuo-ji-ben-shi-yong-xing-neng-ce-shi/
六. Optional
到目前為止,臭名昭著的空指針異常是導(dǎo)致 Java 應(yīng)用程序失敗的最常見原因。以前,為了解決空指針異常,Google公司著名的 Guava 項(xiàng)目引入了 Optional 類,Guava 通過使用檢查空值的方式來防止代碼污染,它鼓勵(lì)程序員寫更干凈的代碼。受到 Google Guava 的啟發(fā),Optional類已經(jīng)成為 Java 8 類庫的一部分。
Optional 實(shí)際上是個(gè)容器:它可以保存類型 T 的值,或者僅僅保存 null。Optional 提供很多有用的方法,這樣我們就不用顯式進(jìn)行空值檢測(cè)。
我們下面用兩個(gè)小例子來演示如何使用 Optional 類:一個(gè)允許為空值,一個(gè)不允許為空值。
Optional<String> fullName = Optional.ofNullable(null);
System.out.println("Full Name is set? " + fullName.isPresent());
System.out.println("Full Name: " + fullName.orElseGet(() -> "[none]"));
System.out.println(fullName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!"));
如果 Optional 類的實(shí)例為非空值的話,isPresent() 返回 true,否從返回 false。為了防止 Optional 為空值,orElseGet() 方法通過回調(diào)函數(shù)來產(chǎn)生一個(gè)默認(rèn)值。map() 函數(shù)對(duì)當(dāng)前 Optional 的值進(jìn)行轉(zhuǎn)化,然后返回一個(gè)新的 Optional 實(shí)例。orElse() 方法和 orElseGet() 方法類似,但是 orElse 接受一個(gè)默認(rèn)值而不是一個(gè)回調(diào)函數(shù)。下面是這個(gè)程序的輸出:
Full Name is set? false
Full Name: [none]
Hey Stranger!
讓我們來看看另一個(gè)例子:
Optional<String> firstName = Optional.of("Tom");
System.out.println("First Name is set? " + firstName.isPresent());
System.out.println("First Name: " + firstName.orElseGet(() -> "[none]"));
System.out.println(firstName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!"));
System.out.println();
下面是程序的輸出:
First Name is set? true
First Name: Tom
Hey Tom!
Lambda 配合 Optinal 優(yōu)雅解決 null
這里假設(shè)我們有一個(gè) person object,以及一個(gè) person object 的 Optional wrapper:

Optional<T> 如果不結(jié)合 Lambda 使用的話,并不能使原來繁瑣的 null check 變的簡(jiǎn)單。

只有當(dāng) Optional<T> 結(jié)合 Lambda 一起使用的時(shí)候,才能發(fā)揮出其真正的威力!
我們現(xiàn)在就來對(duì)比一下下面四種常見的 null 處理中,Java 8 的 Lambda + Optional<T> 和傳統(tǒng) Java 兩者之間對(duì)于 null 的處理差異。
情況一:存在則繼續(xù)

情況二:存在則返回,無則返回不存在

情況三:存在則返回,無則由函數(shù)產(chǎn)生

情況四:奪命連環(huán) null 檢查

由上述四種情況可以清楚地看到,Optional<T> + Lambda 可以讓我們少寫很多 ifElse 塊。尤其是對(duì)于情況四那種奪命連環(huán) null 檢查,傳統(tǒng) Java 的寫法顯得冗長(zhǎng)難懂,而新的 Optional<T> +Lambda 則清新脫俗,清楚簡(jiǎn)潔。
七. Data/Time API
Java 8 在包 java.time下包含了一組全新的時(shí)間日期API。新的日期API和開源的 Joda-Time庫差不多,但又不完全一樣,下面的例子展示了這組新API里最重要的一些部分:
1 - Clock 時(shí)鐘
Clock類提供了訪問當(dāng)前日期和時(shí)間的方法,Clock 是時(shí)區(qū)敏感的,可以用來取代 System.currentTimeMillis() 來獲取當(dāng)前的微秒數(shù)。某一個(gè)特定的時(shí)間點(diǎn)也可以使用 Instant 類來表示,Instant 類也可以用來創(chuàng)建老的 java.util.Date 對(duì)象。代碼如下:
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // legacy java.util.Date
2 - Timezones 時(shí)區(qū)
在新 AP I中時(shí)區(qū)使用 ZoneId 來表示。時(shí)區(qū)可以很方便的使用靜態(tài)方法 of 來獲取到。時(shí)區(qū)定義了到 UTS 時(shí)間的時(shí)間差,在 Instant 時(shí)間點(diǎn)對(duì)象到本地日期對(duì)象之間轉(zhuǎn)換的時(shí)候是極其重要的。代碼如下:
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());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
3 - LocalTime 本地時(shí)間
LocalTime定義了一個(gè)沒有時(shí)區(qū)信息的時(shí)間,例如 晚上 10 點(diǎn),或者 17:30:15。下面的例子使用前面代碼創(chuàng)建的時(shí)區(qū)創(chuàng)建了兩個(gè)本地時(shí)間。之后比較時(shí)間并以小時(shí)和分鐘為單位計(jì)算兩個(gè)時(shí)間的時(shí)間差。代碼如下:
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提供了多種工廠方法來簡(jiǎn)化對(duì)象的創(chuàng)建,包括解析時(shí)間字符串。代碼如下:
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
4 - LocalData 本地日期
LocalDate 表示了一個(gè)確切的日期,比如 2014-03-11。該對(duì)象值是不可變的,用起來和 LocalTime 基本一致。下面的例子展示了如何給 Date 對(duì)象加減天/月/年。另外要注意的是這些對(duì)象是不可變的,操作返回的總是一個(gè)新實(shí)例。代碼如下:
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // FRIDAY
從字符串解析一個(gè) LocalDate 類型和解析 LocalTime 一樣簡(jiǎn)單。代碼如下:
DateTimeFormatter germanFormatter = DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
5 - LocalDateTime 本地日期時(shí)間
LocalDateTime同時(shí)表示了時(shí)間和日期,相當(dāng)于前兩節(jié)內(nèi)容合并到一個(gè)對(duì)象上了。LocalDateTime和LocalTime還有LocalDate一樣,都是不可變的。LocalDateTime提供了一些能訪問具體字段的方法。代碼如下:
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
只要附加上時(shí)區(qū)信息,就可以將其轉(zhuǎn)換為一個(gè)時(shí)間點(diǎn)Instant對(duì)象,Instant時(shí)間點(diǎn)對(duì)象可以很容易的轉(zhuǎn)換為老式的java.util.Date。代碼如下:
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和格式化時(shí)間和日期一樣的,除了使用預(yù)定義好的格式外,我們也可以自己定義格式。代碼如下:
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是不可變的,所以它是線程安全的。
八. 重復(fù)注解
自從 Java 5 引入了注解機(jī)制,這一特性就變得非常流行并且廣為使用。然而,使用注解的一個(gè)限制是相同的注解在同一位置只能聲明一次,不能聲明多次。Java 8 打破了這條規(guī)則,引入了重復(fù)注解機(jī)制,這樣相同的注解可以在同一地方聲明多次。
重復(fù)注解機(jī)制本身必須用 @Repeatable 注解。事實(shí)上,這并不是語言層面上的改變,更多的是編譯器的技巧,底層的原理保持不變。讓我們看一個(gè)快速入門的例子:
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class RepeatingAnnotations {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Filters {
Filter[] value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Filters.class)
public @interface Filter {
String value();
};
@Filter("filter1")
@Filter("filter2")
public interface Filterable {
}
public static void main(String[] args) {
for(Filter filter: Filterable.class.getAnnotationsByType(Filter.class)) {
System.out.println(filter.value());
}
}
}
正如我們看到的,這里有個(gè)使用 @Repeatable(Filters.class) 注解的注解類 Filter,Filters僅僅是 Filter 注解的數(shù)組,但Java編譯器并不想讓程序員意識(shí)到 Filters 的存在。這樣,接口 Filterable 就擁有了兩次 Filter(并沒有提到Filter)注解。
同時(shí),反射相關(guān)的API提供了新的函數(shù)getAnnotationsByType()來返回重復(fù)注解的類型(請(qǐng)注意Filterable.class.getAnnotation(Filters.class)`經(jīng)編譯器處理后將會(huì)返回Filters的實(shí)例)。
九. 擴(kuò)展注解的支持
Java 8 擴(kuò)展了注解的上下文。現(xiàn)在幾乎可以為任何東西添加注解:局部變量、泛型類、父類與接口的實(shí)現(xiàn),就連方法的異常也能添加注解。下面演示幾個(gè)例子:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
public class Annotations {
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
public @interface NonEmpty {
}
public static class Holder<@NonEmpty T> extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
}
}
@SuppressWarnings("unused")
public static void main(String[] args) {
final Holder<String> holder = new @NonEmpty Holder<String>();
@NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
}
}
十. Base64
在 Java 8 中,Base64 編碼已經(jīng)成為 Java 類庫的標(biāo)準(zhǔn)。它的使用十分簡(jiǎn)單,下面讓我們看一個(gè)例子:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Base64s {
public static void main(String[] args) {
final String text = "Base64 finally in Java 8!";
final String encoded = Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
System.out.println(encoded);
final String decoded = new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8);
System.out.println(decoded);
}
}
程序在控制臺(tái)上輸出了編碼后的字符與解碼后的字符:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
Base64 類同時(shí)還提供了對(duì) URL、MIME 友好的編碼器與解碼器(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。
十一. JavaFX
JavaFX是一個(gè)強(qiáng)大的圖形和多媒體處理工具包集合,它允許開發(fā)者來設(shè)計(jì)、創(chuàng)建、測(cè)試、調(diào)試和部署富客戶端程序,并且和Java一樣跨平臺(tái)。從Java8開始,JavaFx已經(jīng)內(nèi)置到了JDK中。關(guān)于JavaFx更詳細(xì)的文檔可參考JavaFX中文文檔。
十二. 其它
1. JDBC4.2規(guī)范
JDBC4.2主要有以下幾點(diǎn)改動(dòng):
- 增加了對(duì)
REF Cursor的支持 - 修改返回值大小范圍(update count)
- 增加了
java.sql.DriverAction接口 - 增加了
java.sql.SQLType接口 - 增加了
java.sql.JDBCtype枚舉 - 對(duì)
java.time包時(shí)間類型的支持
2. 更好的類型推測(cè)機(jī)制
Java 8 在類型推測(cè)方面有了很大的提高。在很多情況下,編譯器可以推測(cè)出確定的參數(shù)類型,這樣就能使代碼更整潔。讓我們看一個(gè)例子:
public class Value<T> {
public static<T> T defaultValue() {
return null;
}
public T getOrDefault(T value, T defaultValue) {
return (value != null) ? value : defaultValue;
}
}
這里是Value<String>類型的用法。
public class TypeInference {
public static void main(String[] args) {
final Value<String> value = new Value<>();
value.getOrDefault("22", Value.defaultValue());
}
}
Value.defaultValue()的參數(shù)類型可以被推測(cè)出,所以就不必明確給出。在Java 7中,相同的例子將不會(huì)通過編譯,正確的書寫方式是Value.<String>defaultValue()。
3. HashMap性能提升
Java 8 中,HashMap 內(nèi)部實(shí)現(xiàn)又引入了紅黑樹,使得 HashMap 的總體性能相較于 Java 7 有比較明顯的提升。以下是對(duì) Hash 均勻和不均勻的情況下的性能對(duì)比
Hash較均勻的情況

Hash極不均勻的情況

想要了解更多 HashMap 的童鞋戳這里吧:傳送門
4. IO/NIO 的改進(jìn)
Java 8 對(duì)IO/NIO也做了一些改進(jìn)。主要包括:改進(jìn)了java.nio.charset.Charset的實(shí)現(xiàn),使編碼和解碼的效率得以提升,也精簡(jiǎn)了jre/lib/charsets.jar包;優(yōu)化了String(byte[], *)構(gòu)造方法和String.getBytes()方法的性能;還增加了一些新的IO/NIO方法,使用這些方法可以從文件或者輸入流中獲取流(java.util.stream.Stream),通過對(duì)流的操作,可以簡(jiǎn)化文本行處理、目錄遍歷和文件查找。
新增的 API 如下:
-
BufferedReader.line(): 返回文本行的流Stream<String> -
File.lines(Path, Charset): 返回文本行的流Stream<String> -
File.list(Path): 遍歷當(dāng)前目錄下的文件和目錄 -
File.walk(Path, int, FileVisitOption): 遍歷某一個(gè)目錄下的所有文件和指定深度的子目錄 -
File.find(Path, int, BiPredicate, FileVisitOption...): 查找相應(yīng)的文件
下面就是用流式操作列出當(dāng)前目錄下的所有文件和目錄:
Files.list(new File(".").toPath()).forEach(System.out::println);
5. JavaScript 引擎 Nashorn
Java 8 提供了一個(gè)新的Nashorn javascript引擎,它允許我們?cè)?JVM 上運(yùn)行特定的 javascript 應(yīng)用。Nashorn javascript 引擎只是javax.script.ScriptEngine另一個(gè)實(shí)現(xiàn),而且規(guī)則也一樣,允許Java和JavaScript互相操作。這里有個(gè)小例子:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
System.out.println(engine.getClass().getName());
System.out.println("Result:" + engine.eval("function f(){return 1;}; f() + 1;"));
輸出如下:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2
6. 并發(fā)(Concurrency)
在新增Stream機(jī)制與Lambda的基礎(chǔ)之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法來支持聚集操作。同時(shí)也在java.util.concurrent.ForkJoinPool類中加入了一些新方法來支持共有資源池(common pool)(請(qǐng)查看我們關(guān)于Java 并發(fā)的免費(fèi)課程)。
新增的java.util.concurrent.locks.StampedLock類提供一直基于容量的鎖,這種鎖有三個(gè)模型來控制讀寫操作(它被認(rèn)為是不太有名的java.util.concurrent.locks.ReadWriteLock類的替代者)。
在java.util.concurrent.atomic包中還增加了下面這些類:
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
7. 類依賴分析器jdeps
Jdeps是一個(gè)功能強(qiáng)大的命令行工具,它可以幫我們顯示出包層級(jí)或者類層級(jí)java類文件的依賴關(guān)系。它接受class文件、目錄、jar文件作為輸入,默認(rèn)情況下,jdeps會(huì)輸出到控制臺(tái)。
作為例子,讓我們看看現(xiàn)在很流行的 Spring 框架的庫的依賴關(guān)系報(bào)告。為了讓報(bào)告短一些,我們只分析一個(gè) jar: org.springframework.core-3.0.5.RELEASE.jar.
jdeps org.springframework.core-3.0.5.RELEASE.jar這個(gè)命令輸出內(nèi)容很多,我們只看其中的一部分,這些依賴關(guān)系根絕包來分組,如果依賴關(guān)系在classpath里找不到,就會(huì)顯示 not found。
C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
-> java.io
-> java.lang
-> java.lang.annotation
-> java.lang.ref
-> java.lang.reflect
-> java.util
-> java.util.concurrent
-> org.apache.commons.logging not found
-> org.springframework.asm not found
-> org.springframework.asm.commons not found
org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
-> java.lang
-> java.lang.annotation
-> java.lang.reflect
-> java.util
8. JVM 的 PermGen 空間被移除
PermGen空間被移除了,取而代之的是Metaspace(JEP 122)。JVM 選項(xiàng)-XX:PermSize與-XX:MaxPermSize分別被-XX:MetaSpaceSize與-XX:MaxMetaspaceSize所代替。
區(qū)別:
- 元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存
- 默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制
- 也可以通過-XX:MetaspaceSize指定元空間大小
參考資料
- 「MoreThanJava」Day 7:接口詳解 - https://www.wmyskxz.com/2020/08/13/morethanjava-day-7-jie-kou-xiang-jie/
- 【知乎問題】Lambda 表達(dá)式 有何用處?如何使用? | @Mingqi - https://www.zhihu.com/question/20125256
- Java8新特性及使用(一) | 閃爍之狐 - http://blinkfox.com/2018/11/13/hou-duan/java/java8-xin-te-xing-ji-shi-yong-yi/#toc-heading-21
- Java8新特性及使用(二) | 閃爍之狐 - http://blinkfox.com/2018/11/14/hou-duan/java/java8-xin-te-xing-ji-shi-yong-er/
文章推薦
- 這都JDK15了,JDK7還不了解? - https://www.wmyskxz.com/2020/08/18/java7-ban-ben-te-xing-xiang-jie/
- 你記筆記嗎?關(guān)于最近知識(shí)管理工具革新潮心臟有話要說 - https://www.wmyskxz.com/2020/08/16/ni-ji-bi-ji-ma-guan-yu-zui-jin-zhi-shi-guan-li-gong-ju-ge-xin-chao-xin-zang-you-hua-yao-shuo/
- 黑莓OS手冊(cè)是如何詳細(xì)闡述底層的進(jìn)程和線程模型的? - https://www.wmyskxz.com/2020/07/31/hao-wen-tui-jian-hei-mei-os-shou-ce-shi-ru-he-xiang-xi-chan-shu-di-ceng-de-jin-cheng-he-xian-cheng-mo-xing-de/
- 「MoreThanJava」系列文集 - https://www.wmyskxz.com/categories/MoreThanJava/
- 本文已收錄至我的 Github 程序員成長(zhǎng)系列 【More Than Java】,學(xué)習(xí),不止 Code,歡迎 star:https://github.com/wmyskxz/MoreThanJava
- 個(gè)人公眾號(hào) :wmyskxz,個(gè)人獨(dú)立域名博客:wmyskxz.com,堅(jiān)持原創(chuàng)輸出,下方掃碼關(guān)注,2020,與您共同成長(zhǎng)!

非常感謝各位人才能 看到這里,如果覺得本篇文章寫得不錯(cuò),覺得 「我沒有三顆心臟」有點(diǎn)東西 的話,求點(diǎn)贊,求關(guān)注,求分享,求留言!
創(chuàng)作不易,各位的支持和認(rèn)可,就是我創(chuàng)作的最大動(dòng)力,我們下篇文章見!