久久亚洲精品国产精品_羞羞漫画在线版免费阅读网页漫画_国产精品久久久久久久久久久久_午夜dj免费观看在线视频_希崎杰西卡番号

jdk1.7(jdk17特性)

前沿拓展:

jdk1.7

濤 你配置了path環(huán)境變量沒有配置正確
我的path變量里配的是C:\Program Files\Java\jdk1.7.0\bin; 我的java裝在C盤


作者:小傅哥 博客:https://bugstack.cn

沉淀、分享、成長,讓自己和他人都能有所收獲!

前言

一直想把jdk1.8的新特性整理下,恰好看到老外的git(文后有鏈接),在這個(gè)結(jié)構(gòu)上繼續(xù)完善了說明和功能,做了41個(gè)單元測試案例,方便新人學(xué)習(xí)。以下內(nèi)容很干,對于一個(gè)萌新小白來說,學(xué)習(xí)jdk1.8的新特性,基本看一遍就知道個(gè)7788了,在熟讀兩遍最后跟著寫一遍,那么在實(shí)際項(xiàng)目中就可以運(yùn)用了。不過!新特性,雖然很好。但如果想用,那么自己一定要看看相對應(yīng)的源碼并多練習(xí),否則真的容易給自己搞暈,又很難閱讀。

零、回顧一個(gè)抽象類

在jdk1.8之前,因?yàn)榻涌诶镏荒茏龇椒ǘx不能有方法的實(shí)現(xiàn),因此我們通常會在抽象類里面實(shí)現(xiàn)默認(rèn)的方法{一般這個(gè)默認(rèn)的方法是抽象后公用的方法,不需要每一個(gè)繼承者都去實(shí)現(xiàn),只需調(diào)用即可}。就像下面這樣;

在定義的時(shí)候;

public abstract class AFormula {

abstract double calculate(int a);

// 平方
double sqrt(int a) {
return Math.sqrt(a);
}

}

在使用的時(shí)候;

@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
}
一、在接口中提供默認(rèn)的方法實(shí)現(xiàn)(有點(diǎn)像抽象類)

在jdk1.8里面,不僅可以定義接口,還可以在接口中提供默認(rèn)的實(shí)現(xiàn)。這一個(gè)小小的改變卻讓整個(gè)抽象設(shè)計(jì)都隨著改變了!

在定義的時(shí)候;{default 關(guān)鍵字必須}

public interface IFormula {

double calculate(int a);

// 平方
default double sqrt(int a) {
return Math.sqrt(a);
}

}

在使用的時(shí)候(一);

@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));
}

在使用的時(shí)候(二);如果只是一里面方式這么使用,那么就沒多大意思了。我一直說過;好的代碼都很**!

a; a是一個(gè)入?yún)⒚Q,可以其他任何名字->a*a; 箭頭指向是具體的實(shí)現(xiàn)但是,這樣其實(shí)不太適合加日志了@Test
public void test_02() {
// 入?yún) 和 實(shí)現(xiàn)
IFormula formula = a -> a * a;
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}
二、Lambda 表達(dá)式

因?yàn)橛薪涌谥锌梢栽黾幽J(rèn)的方法實(shí)現(xiàn),那么Java肯定是因?yàn)橐喕_發(fā)才出現(xiàn)的這么個(gè)設(shè)計(jì)。所以你會從各個(gè)我們以前的List、Set等等所有接口中看到默認(rèn)的方法實(shí)現(xiàn)。

從一段熟悉的排序列子入手

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 工具類提供了靜態(tài)方法 sort 方法,入?yún)⑹且粋€(gè) List **,和一個(gè) Comparator 比較器,以便對給定的 List **進(jìn)行排序。上面的示例代碼創(chuàng)建了一個(gè)匿名內(nèi)部類作為入?yún)ⅲ@種類似的**作在我們?nèi)粘5墓ぷ髦须S處可見。

Java 8 中不再推薦這種寫法,而是推薦使用 Lambda 表達(dá):

Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});

上面的這段同樣功能的代碼塊,簡短干凈了許多。就像婆媳一樣可能剛開始看不習(xí)慣,但是接觸接觸就喜歡了。因?yàn)椋€可以更加簡短優(yōu)秀;

Collections.sort(names, (String a, String b) -> b.compareTo(a));

為了追求極致,我們還可以讓它再短點(diǎn):{當(dāng)然過你的實(shí)現(xiàn)不是一行代碼,那么不能這么干}

names.sort((a, b) -> b.compareTo(a));

java.util.List **現(xiàn)在已經(jīng)添加了 sort 方法。而且 Java 編譯器能夠根據(jù)類型推斷機(jī)制判斷出參數(shù)類型,這樣,你連入?yún)⒌念愋投伎梢允÷岳玻趺礃樱遣皇歉杏X很**氣呢!

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);
}
}

好了!你以為這就結(jié)束了嗎,不!它還可以更短!(得益于Comparator接口中還提供了stack默認(rèn)方法,也就是說接口中不是只可有default默認(rèn)實(shí)現(xiàn),還可以有靜態(tài)方法)

names.sort(Comparator.reverseOrder());
三、函數(shù)式接口 Functional Interfaces

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可以開發(fā)出同樣功能的邏輯但是代碼卻很簡單,那么Jvm是如何進(jìn)行類型推斷,并且找到對應(yīng)的方法呢?

通過官文介紹以及我們使用發(fā)現(xiàn),并不是每個(gè)接口都可以縮寫成Lambda表達(dá)式的開發(fā)方式。其實(shí)是只有那些函數(shù)式接口(Functional Interface)才能縮寫成 Lambda 表示式。

所謂函數(shù)式接口(Functional Interface)就是只包含一個(gè)抽象方法的聲明。針對該接口類型的所有 Lambda 表達(dá)式都會與這個(gè)抽象方法匹配。{另外,只是在接口上添加default并不算抽象方法}

小編綜合來說:為了保證一個(gè)接口明確的被定義為一個(gè)函數(shù)式接口(Functional Interface),我們需要為該接口添加注解:@FunctionalInterface。這樣,一旦你添加了第二個(gè)抽象方法,編譯器會立刻拋出錯誤提示。{不填寫,但是只寫一個(gè)default也可以}

定義含有注解@FunctionalInterface的接口

@FunctionalInterface
public interface IConverter<F, T> {

T convert(F from);

}
先來一段傳統(tǒng)方式 & 簡單易懂哈,因?yàn)榭戳?xí)慣了IConverter<String, Integer> converter01 = new IConverter<String, Integer>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
稍微簡化下,化個(gè)妝 & (form),只有一個(gè)參數(shù)括號可以不要IConverter<String, Integer> converter02 = (from) -> {
return Integer.valueOf(from);
};
繼續(xù)簡化,因?yàn)樗膶?shí)現(xiàn)只有一行代碼,可以更加簡短IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
還能短點(diǎn),其實(shí)這個(gè)另類屬于下一段的內(nèi)容了,先放這有個(gè)印象IConverter<Integer, String> converter04 = String::valueOf;
四、方法和構(gòu)造函數(shù)的便捷應(yīng)用

在上面我們先加了印象片段 XX::xx,它也是Java8的新特性便捷式引用,這四個(gè)點(diǎn)可能你在其他語言里也見過。

IConverter<Integer, String> converter04 = String::valueOf;
String converted04 = converter04.convert(11);
System.out.println(converted04);

這四個(gè)點(diǎn)::的關(guān)鍵字,不只是可以引用方法和構(gòu)造函數(shù),還可以引用普通方法。

public class Something{
public String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
IConverter<String, String> converter01 = s -> String.valueOf(s.charAt(0)); //[參照物]直接把邏輯放到這調(diào)用
IConverter<String, String> converter02 = something::startsWith; //引用的方法體里面邏輯可以更多,否則只是一句代碼并不能適合所有的情況
System.out.println(converter01.convert("Java"));
System.out.println(converter02.convert("Java"));

接下來我們在使用這四個(gè)點(diǎn),來看下如何引用類的構(gòu)造器。第一我們創(chuàng)建一個(gè)這樣的類;

public class Person {
String firstName;
String lastName;

Person() {}

Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}

第二我還需要頂一個(gè)工廠類,用于生成Person對象;

@FunctionalInterface
public interface IPersonFactory<P extends Person> {

P create(String firstName, String lastName);

}

現(xiàn)在就到了用四餅::的時(shí)候了;

IPersonFactory<Person> personFactory = Person::new; //[參照物]:(firstName, lastName) -> new Person(firstName, lastName);
Person person = personFactory.create("Peter", "Parker");

提醒;工廠函數(shù)中依然只能有一個(gè)函數(shù),否則會報(bào)錯

四餅::,可以讓我們直接引用到Person類的構(gòu)造函數(shù),第二 Java 編譯器能夠根據(jù)類的簽名選中正確的構(gòu)造器去實(shí)現(xiàn) PersonFactory.create 方法。

五、Lambda作用范圍

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表達(dá)式訪問外部的變量(局部變量,成員變量,靜態(tài)變量,接口的默認(rèn)方法),它與匿名內(nèi)部類訪問外部變量非常相似。

1. 訪問局部變量

我們可以從lambda表達(dá)式的外部范圍讀取最終局部變量num;

int num = 1;
IConverter<Integer, String> stringConverter = from -> String.valueOf(from + num);
String convert = stringConverter.convert(2);
System.out.println(convert); // 3

但是這個(gè)num是不可變值,這樣改變值會報(bào)錯;

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表達(dá)式內(nèi)部修改也是不允許的;

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

2. 訪問成員變量和靜態(tài)變量

在 Lambda 表達(dá)式中訪問局部變量。與局部變量相比,在 Lambda 表達(dá)式中對成員變量和靜態(tài)變量擁有讀寫權(quán)限:

public class Lambda4 {

// 靜態(tài)變量
static int outerStaticNum;
// 成員變量
int outerNum;

void testScopes() {
IConverter<Integer, String> stringConverter1 = (from) -> {
// 對成員變量賦值
outerNum = 23;
return String.valueOf(from);
};

IConverter<Integer, String> stringConverter2 = (from) -> {
// 對靜態(tài)變量賦值
outerStaticNum = 72;
return String.valueOf(from);
};
}

}
3. 訪問默認(rèn)接口方法

還記得第一節(jié)的IFormula示例嗎?

public interface IFormula {

double calculate(int a);

// 平方
default double sqrt(int a) {
return Math.sqrt(a);
}

}

當(dāng)時(shí),我們在接口中定義了一個(gè)帶有默認(rèn)實(shí)現(xiàn)的 sqrt 求平方根方法,在匿名內(nèi)部類中我們可以很方便的訪問此方法:

IFormula formula = new IFormula() {
@Override
public double calculate(int a) {
return a * a;
}
};

但是不能通過lambda表達(dá)式訪問默認(rèn)方法,這樣的代碼沒法通過編譯;

IFormula formula = (a) -> sqrt(a * a);

帶有默認(rèn)實(shí)現(xiàn)的接口方法,是不能在 lambda 表達(dá)式中訪問的,上面這段代碼將無法被編譯通過。

六、內(nèi)置的函數(shù)式接口

JDK 1.8 API 包含了很多內(nèi)置的函數(shù)式接口。其中就包括我們在老版本中經(jīng)常見到的 Comparator 和 Runnable,Java 8 為他們都添加了 @FunctionalInterface 注解,以用來支持 Lambda 表達(dá)式。

例如我們舊版本的Jdk中常用的 Comparator 和 Runnable 外,還有一些新的函數(shù)式接口,可以通過函數(shù)注解實(shí)現(xiàn)Lamdba支持,它們很多都借鑒于知名的 Google Guava 庫。

即使你已經(jīng)熟悉這個(gè)類庫,也應(yīng)該密切關(guān)注那些接口是如何通過一些有用的方法擴(kuò)展來擴(kuò)展的:

1. Predicate 斷言

Predicate 是一個(gè)可以指定入?yún)㈩愋停⒎祷?boolean 值的函數(shù)式接口。它內(nèi)部提供了一些帶有默認(rèn)實(shí)現(xiàn)的方法,可以 被用來組合一個(gè)復(fù)雜的邏輯判斷(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否定相當(dāng)于!true

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
}
2. Functions

Function 函數(shù)式接口的作用是,我們可以為其提供一個(gè)原料,他給生產(chǎn)一個(gè)最終的產(chǎn)品。通過它提供的默認(rèn)方法,組合,鏈行處理(compose, andThen):

@Test
public void test12() {
Function<String, Integer> toInteger = Integer::valueOf; //轉(zhuǎn)Integer
Function<String, String> backToString = toInteger.andThen(String::valueOf); //轉(zhuǎn)String
Function<String, String> afterToStartsWith = backToString.andThen(new Something()::startsWith); //截取第一位
String apply = afterToStartsWith.apply("123");// "123"
System.out.println(apply);
}
3. Suppliers

Supplier 與 Function 不同,它不接受入?yún)ⅲ苯訛槲覀兩a(chǎn)一個(gè)指定的結(jié)果,有點(diǎn)像生產(chǎn)者模式:

@Test
public void test13() {
Supplier<Person> personSupplier0 = Person::new;
personSupplier0.get(); // new Person
Supplier<String> personSupplier1 = Something::test01; //這個(gè)test方法是靜態(tài)的,且無入?yún)?br /> personSupplier1.get(); // hi

Supplier<String> personSupplier2 = new Something()::test02;
}
4. Consumers

對于 Consumer,我們需要提供入?yún)ⅲ脕肀幌M(fèi),如下面這段示例代碼:

@Test
public void test14() {
// 參照物,方便知道下面的Lamdba表達(dá)式寫法
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; // 也可以通過定義類和方法的方式去調(diào)用,這樣才是實(shí)際開發(fā)的姿勢
greeter03.accept(new Person("Luke", "Skywalker")); //Hello, Luke
}
5. Comparators

Comparator 在 Java 8 之前是使用比較普遍的。Java 8 中除了將其升級成了函數(shù)式接口,還為它拓展了一些默認(rèn)方法:

@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
}
七、Optionals

第一,Optional 它不是一個(gè)函數(shù)式接口,設(shè)計(jì)它的目的是為了防止空指針異常(NullPointerException),要知道在 Java 編程中,空指針異常可是臭名昭著的。

讓我們來快速了解一下 Optional 要如何使用!你可以將 Optional 看做是包裝對象(可能是 null, 也有可能非 null)的容器。當(dāng)你定義了

一個(gè)方法,這個(gè)方法返回的對象可能是空,也有可能非空的時(shí)候,你就可以考慮用 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 流

什么是 Stream 流?

簡單來說,我們可以使用 java.util.Stream 對一個(gè)包含一個(gè)或多個(gè)元素的**做各種**作。這些**作可能是 中間**作 亦或是 終端**作。 終端**作會返回一個(gè)結(jié)果,而中間**作會返回一個(gè) Stream 流。

需要注意的是,你只能對實(shí)現(xiàn)了 java.util.Collection 接口的類做流的**作。

Stream 流支持同步執(zhí)行,也支持并發(fā)執(zhí)行。

注意:Map不支持Stream流,但是他的key和value是支持的!

讓我們先看看Stream流是如何工作的。第一,我們以字符串列表的形式創(chuàng)建一個(gè)示例;

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");
1. Filter 過濾

Filter 的入?yún)⑹且粋€(gè) Predicate, 上面已經(jīng)說到,Predicate 是一個(gè)斷言的中間**作,它能夠幫我們篩選出我們需要的**元素。它的返參同樣 是一個(gè) Stream 流,我們可以通過 foreach 終端**作,來打印被篩選的元素:

@Test
public void test17(){
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
}
2. Sorted 排序

Sorted 同樣是一個(gè)中間**作,它的返參是一個(gè) Stream 流。另外,我們可以傳入一個(gè) Comparator 用來自定義排序,如果不傳,則使用默認(rèn)的排序規(guī)則。

@Test
public void test18() {
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
}

注意;這個(gè)sorted 只是做了一個(gè)排序的視圖進(jìn)行輸出,實(shí)際沒有將List內(nèi)的數(shù)據(jù)進(jìn)行排序

System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
3. Map 轉(zhuǎn)換

中間**作映射通過給定的函數(shù)將每個(gè)元素轉(zhuǎn)換為另一個(gè)對象。例如下面的示例,通過 map 我們將每一個(gè) string 轉(zhuǎn)成大寫:

@Test
public void test19(){
stringCollection
.stream()
.map(String::toUpperCase)
.sorted(Comparator.reverseOrder()) //等同于(a, b) -> b.compareTo(a)
.forEach(System.out::println);
}

這個(gè)可以用做DTO數(shù)據(jù)對象轉(zhuǎn)換,領(lǐng)域驅(qū)動設(shè)計(jì)開發(fā)中將DTO轉(zhuǎn)為DO向后臺傳輸。

4. Match 匹配

顧名思義,match 用來做匹配**作,它的返回值是一個(gè) boolean 類型。通過 match, 我們可以方便的驗(yàn)證一個(gè) list 中是否存在某個(gè)類型的元素。

@Test
public void test20(){
// anyMatch:驗(yàn)證 list 中 string 是否有以 a 開頭的, 匹配到第一個(gè),即返回 true
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
// allMatch:驗(yàn)證 list 中 string 是否都是以 a 開頭的
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
// noneMatch:驗(yàn)證 list 中 string 是否都不是以 z 開頭的
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
}
5. Count 計(jì)數(shù)

count 是一個(gè)終端**作,它能夠統(tǒng)計(jì) stream 流中的元素總數(shù),返回值是 long 類型。

@Test
public void test21() {
// count:先對 list 中字符串開頭為 b 進(jìn)行過濾,讓后統(tǒng)計(jì)數(shù)量
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
}
6. Reduce

Reduce 中文翻譯為:減少、縮小。通過入?yún)⒌?Function,我們能夠?qū)?list 歸約成一個(gè)值。它的返回類型是 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
}
九、Parallel-Streams 并行流

如上所述,流可以是順序的,也可以是并行的。順序流上的**作在單個(gè)線程上執(zhí)行,而并行流上的**作在多個(gè)線程上并發(fā)執(zhí)行。

下面的示例演示了使用并行流來提高性能是多么的容易。親測提升了1倍性能!

第一,我們創(chuàng)建一個(gè)較大的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());
}
1. Sequential Sort 順序流排序@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();
// 納秒轉(zhuǎn)微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 – t0);
System.out.println(String.format("順序流排序耗時(shí): %d ms", millis));
//順序流排序耗時(shí): 712 ms
}
2. Parallel Sort 并行流排序@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
}

如您所見,這兩個(gè)代碼片段幾乎相同,但并行排序大約快50%。您只需將stream()更改為parallelStream()。

十、Map **

如前所講,Map是不支持 Stream 流的,因?yàn)?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 是否已經(jīng)存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, "val" + i);
}
// forEach 可以很方便地對 map 進(jìn)行遍歷**作
map.forEach((key, value) -> System.out.println(value));
}

之后我們做一個(gè)Map對象的轉(zhuǎn)換輸出;(定義兩個(gè)類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 是否已經(jīng)存在,存在則直接返回 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() 外,我們還可以很方便地對某個(gè) key 的值做相關(guān)**作:

@Test
public void test27() {
// 如下:對 key 為 3 的值,內(nèi)部會先判斷值是否存在,存在,則做 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(), 當(dāng) key 不存在時(shí),才會做相關(guān)處理
// 如下:先判斷 key 為 23 的元素是否存在,不存在,則添加
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true

// 先判斷 key 為 3 的元素是否存在,存在,則不做任何處理
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
}

關(guān)于刪除**作,JDK 8 中提供了能夠新的 remove() API:

@Test
public void test28() {
map.remove(3, "val3");
map.get(3); // val33

map.remove(3, "val33");
map.get(3); // null
}

如上代碼,只有當(dāng)給定的 key 和 value 完全匹配時(shí),才會執(zhí)行刪除**作。

關(guān)于添加方法,JDK 8 中提供了帶有默認(rèn)值的 getOrDefault() 方法:

@Test
public void test29() {
// 若 key 42 不存在,則返回 not found
map.getOrDefault(42, "not found"); // not found
}

對于 value 的合并**作也變得更加簡單:

@Test
public void test30() {
// merge 方法,會先判斷進(jìn)行合并的 key 是否存在,不存在,則會添加元素
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
// 若 key 的元素存在,則對 value 執(zhí)行拼接**作
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
}
十一、日期 Date API

Java 8 中在包 java.time 下添加了新的日期 API. 它和 Joda-Time 庫相似,但又不完全相同。接下來,我會通過一些示例代碼介紹一下新 API 中 最關(guān)鍵的特性:

1. Clock

Clock 提供對當(dāng)前日期和時(shí)間的訪問。我們可以利用它來替代 System.currentTimeMillis() 方法。另外,通過 clock.instant() 能夠獲取一個(gè) instant 實(shí)例, 此實(shí)例能夠方便地轉(zhuǎn)換成老版本中的 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
}
2. Timezones 時(shí)區(qū)

ZoneId 代表時(shí)區(qū)類。通過靜態(tài)工廠方法方便地獲取它,入?yún)⑽覀兛梢詡魅肽硞€(gè)時(shí)區(qū)編碼。另外,時(shí)區(qū)類還定義了一個(gè)偏移量,用來在當(dāng)前時(shí)刻或某時(shí)間 與目標(biāo)時(shí)區(qū)時(shí)間之間進(jìn)行轉(zhuǎn)換。

@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/Ta**ania, 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]
}
3. LocalTime

LocalTime 表示一個(gè)沒有指定時(shí)區(qū)的時(shí)間類,例如,10 p.m.或者 17:30:15,下面示例代碼中,將會使用上面創(chuàng)建的 時(shí)區(qū)對象創(chuàng)建兩個(gè) LocalTime。第二我們會比較兩個(gè)時(shí)間,并計(jì)算它們之間的小時(shí)和分鐘的不同。

@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.i**efore(now2)); // false
long hour**etween = ChronoUnit.HOURS.between(now1, now2);
long minute**etween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hour**etween); // -3
System.out.println(minute**etween); // -239
}

LocalTime 提供多個(gè)靜態(tài)工廠方法,目的是為了簡化對時(shí)間對象實(shí)例的創(chuàng)建和**作,包括對時(shí)間字符串進(jìn)行解析的**作等。

@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
}
4. LocalDate

LocalDate 是一個(gè)日期對象,例如:2014-03-11。它和 LocalTime 一樣是個(gè) final 類型對象。下面的例子演示了如何通過加減日,月,年等來計(jì)算一個(gè)新的日期。

@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 實(shí)例。(和 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
}
5. LocalDateTime

LocalDateTime 是一個(gè)日期-時(shí)間對象。你也可以將其看成是 LocalDate 和 LocalTime 的結(jié)合體。**作上,也大致相同。

@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); // 十二月
// 獲取改時(shí)間是該天中的第幾分鐘
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
}

如果再加上的時(shí)區(qū)信息,LocalDateTime 還能夠被轉(zhuǎn)換成 Instance 實(shí)例。Instance 能夠被轉(zhuǎn)換成老版本中 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 一樣。除了使用預(yù)定義的格式以外,也可以自定義格式化輸出。

@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.

十二、Annotations 注解

Java8中的注釋是可重復(fù)的。讓我們直接深入到一個(gè)例子中來解決這個(gè)問題。{在SpringBoot的啟動類中就可以看到這中類型的注解}

第一,我們定義一個(gè)包裝器注釋,它包含一個(gè)實(shí)際注釋數(shù)組:

@Repeatable(Hints.class)
public @interface Hint {
String value();
}

public @interface Hints {
Hint[] value();
}

Java 8通過聲明注釋@Repeatable,使我們能夠使用同一類型的多個(gè)注釋。

第一種形態(tài):使用注解容器(老方法)

@Test
public void test40() {
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {
}
}

第二種形態(tài):使用可重復(fù)注解(新方法)

@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
}

java編譯器使用變量2隱式地在引擎蓋下設(shè)置@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.getAnnotation**yType(Hint.class
System.out.println(hints2.length); // 2
}

盡管我們絕對不會在 Person 類上聲明 @Hints 注解,但是它的信息仍然是可以通過 getAnnotation(Hints.class) 來讀取的。 并且,getAnnotation**yType 方**更方便,因?yàn)樗x予了所有 @Hints 注解標(biāo)注的方法直接的訪問權(quán)限。

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
綜上小編綜合來說jdk8的新特性包括了;Lambda、函數(shù)式接口、四餅調(diào)用::、內(nèi)置函數(shù)(斷言、Function、生產(chǎn)者、消費(fèi)者)、Stream流、Map**特性、日期、注解等合理的組合運(yùn)行新的特性可以減少很多的編碼量,同時(shí)讓代碼更加整潔在一些新的框架中SpringBoot里如果翻看源碼可以看到很多的新特性使用

拓展知識:

jdk1.7

1.56。由于springboot2.0以后不支持jdk1.7,所以只能自降springboot的版本由2.0降為1.56。當(dāng)選用jdk1.7構(gòu)建springboot項(xiàng)目時(shí)會提示,選用jdk1.8。

前沿拓展:

jdk1.7

濤 你配置了path環(huán)境變量沒有配置正確
我的path變量里配的是C:\Program Files\Java\jdk1.7.0\bin; 我的java裝在C盤


作者:小傅哥 博客:https://bugstack.cn

沉淀、分享、成長,讓自己和他人都能有所收獲!

前言

一直想把jdk1.8的新特性整理下,恰好看到老外的git(文后有鏈接),在這個(gè)結(jié)構(gòu)上繼續(xù)完善了說明和功能,做了41個(gè)單元測試案例,方便新人學(xué)習(xí)。以下內(nèi)容很干,對于一個(gè)萌新小白來說,學(xué)習(xí)jdk1.8的新特性,基本看一遍就知道個(gè)7788了,在熟讀兩遍最后跟著寫一遍,那么在實(shí)際項(xiàng)目中就可以運(yùn)用了。不過!新特性,雖然很好。但如果想用,那么自己一定要看看相對應(yīng)的源碼并多練習(xí),否則真的容易給自己搞暈,又很難閱讀。

零、回顧一個(gè)抽象類

在jdk1.8之前,因?yàn)榻涌诶镏荒茏龇椒ǘx不能有方法的實(shí)現(xiàn),因此我們通常會在抽象類里面實(shí)現(xiàn)默認(rèn)的方法{一般這個(gè)默認(rèn)的方法是抽象后公用的方法,不需要每一個(gè)繼承者都去實(shí)現(xiàn),只需調(diào)用即可}。就像下面這樣;

在定義的時(shí)候;

public abstract class AFormula {

abstract double calculate(int a);

// 平方
double sqrt(int a) {
return Math.sqrt(a);
}

}

在使用的時(shí)候;

@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
}
一、在接口中提供默認(rèn)的方法實(shí)現(xiàn)(有點(diǎn)像抽象類)

在jdk1.8里面,不僅可以定義接口,還可以在接口中提供默認(rèn)的實(shí)現(xiàn)。這一個(gè)小小的改變卻讓整個(gè)抽象設(shè)計(jì)都隨著改變了!

在定義的時(shí)候;{default 關(guān)鍵字必須}

public interface IFormula {

double calculate(int a);

// 平方
default double sqrt(int a) {
return Math.sqrt(a);
}

}

在使用的時(shí)候(一);

@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));
}

在使用的時(shí)候(二);如果只是一里面方式這么使用,那么就沒多大意思了。我一直說過;好的代碼都很**!

a; a是一個(gè)入?yún)⒚Q,可以其他任何名字->a*a; 箭頭指向是具體的實(shí)現(xiàn)但是,這樣其實(shí)不太適合加日志了@Test
public void test_02() {
// 入?yún) 和 實(shí)現(xiàn)
IFormula formula = a -> a * a;
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}
二、Lambda 表達(dá)式

因?yàn)橛薪涌谥锌梢栽黾幽J(rèn)的方法實(shí)現(xiàn),那么Java肯定是因?yàn)橐喕_發(fā)才出現(xiàn)的這么個(gè)設(shè)計(jì)。所以你會從各個(gè)我們以前的List、Set等等所有接口中看到默認(rèn)的方法實(shí)現(xiàn)。

從一段熟悉的排序列子入手

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 工具類提供了靜態(tài)方法 sort 方法,入?yún)⑹且粋€(gè) List **,和一個(gè) Comparator 比較器,以便對給定的 List **進(jìn)行排序。上面的示例代碼創(chuàng)建了一個(gè)匿名內(nèi)部類作為入?yún)ⅲ@種類似的**作在我們?nèi)粘5墓ぷ髦须S處可見。

Java 8 中不再推薦這種寫法,而是推薦使用 Lambda 表達(dá):

Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});

上面的這段同樣功能的代碼塊,簡短干凈了許多。就像婆媳一樣可能剛開始看不習(xí)慣,但是接觸接觸就喜歡了。因?yàn)椋€可以更加簡短優(yōu)秀;

Collections.sort(names, (String a, String b) -> b.compareTo(a));

為了追求極致,我們還可以讓它再短點(diǎn):{當(dāng)然過你的實(shí)現(xiàn)不是一行代碼,那么不能這么干}

names.sort((a, b) -> b.compareTo(a));

java.util.List **現(xiàn)在已經(jīng)添加了 sort 方法。而且 Java 編譯器能夠根據(jù)類型推斷機(jī)制判斷出參數(shù)類型,這樣,你連入?yún)⒌念愋投伎梢允÷岳玻趺礃樱遣皇歉杏X很**氣呢!

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);
}
}

好了!你以為這就結(jié)束了嗎,不!它還可以更短!(得益于Comparator接口中還提供了stack默認(rèn)方法,也就是說接口中不是只可有default默認(rèn)實(shí)現(xiàn),還可以有靜態(tài)方法)

names.sort(Comparator.reverseOrder());
三、函數(shù)式接口 Functional Interfaces

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可以開發(fā)出同樣功能的邏輯但是代碼卻很簡單,那么Jvm是如何進(jìn)行類型推斷,并且找到對應(yīng)的方法呢?

通過官文介紹以及我們使用發(fā)現(xiàn),并不是每個(gè)接口都可以縮寫成Lambda表達(dá)式的開發(fā)方式。其實(shí)是只有那些函數(shù)式接口(Functional Interface)才能縮寫成 Lambda 表示式。

所謂函數(shù)式接口(Functional Interface)就是只包含一個(gè)抽象方法的聲明。針對該接口類型的所有 Lambda 表達(dá)式都會與這個(gè)抽象方法匹配。{另外,只是在接口上添加default并不算抽象方法}

小編綜合來說:為了保證一個(gè)接口明確的被定義為一個(gè)函數(shù)式接口(Functional Interface),我們需要為該接口添加注解:@FunctionalInterface。這樣,一旦你添加了第二個(gè)抽象方法,編譯器會立刻拋出錯誤提示。{不填寫,但是只寫一個(gè)default也可以}

定義含有注解@FunctionalInterface的接口

@FunctionalInterface
public interface IConverter<F, T> {

T convert(F from);

}
先來一段傳統(tǒng)方式 & 簡單易懂哈,因?yàn)榭戳?xí)慣了IConverter<String, Integer> converter01 = new IConverter<String, Integer>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
稍微簡化下,化個(gè)妝 & (form),只有一個(gè)參數(shù)括號可以不要IConverter<String, Integer> converter02 = (from) -> {
return Integer.valueOf(from);
};
繼續(xù)簡化,因?yàn)樗膶?shí)現(xiàn)只有一行代碼,可以更加簡短IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
還能短點(diǎn),其實(shí)這個(gè)另類屬于下一段的內(nèi)容了,先放這有個(gè)印象IConverter<Integer, String> converter04 = String::valueOf;
四、方法和構(gòu)造函數(shù)的便捷應(yīng)用

在上面我們先加了印象片段 XX::xx,它也是Java8的新特性便捷式引用,這四個(gè)點(diǎn)可能你在其他語言里也見過。

IConverter<Integer, String> converter04 = String::valueOf;
String converted04 = converter04.convert(11);
System.out.println(converted04);

這四個(gè)點(diǎn)::的關(guān)鍵字,不只是可以引用方法和構(gòu)造函數(shù),還可以引用普通方法。

public class Something{
public String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
IConverter<String, String> converter01 = s -> String.valueOf(s.charAt(0)); //[參照物]直接把邏輯放到這調(diào)用
IConverter<String, String> converter02 = something::startsWith; //引用的方法體里面邏輯可以更多,否則只是一句代碼并不能適合所有的情況
System.out.println(converter01.convert("Java"));
System.out.println(converter02.convert("Java"));

接下來我們在使用這四個(gè)點(diǎn),來看下如何引用類的構(gòu)造器。第一我們創(chuàng)建一個(gè)這樣的類;

public class Person {
String firstName;
String lastName;

Person() {}

Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}

第二我還需要頂一個(gè)工廠類,用于生成Person對象;

@FunctionalInterface
public interface IPersonFactory<P extends Person> {

P create(String firstName, String lastName);

}

現(xiàn)在就到了用四餅::的時(shí)候了;

IPersonFactory<Person> personFactory = Person::new; //[參照物]:(firstName, lastName) -> new Person(firstName, lastName);
Person person = personFactory.create("Peter", "Parker");

提醒;工廠函數(shù)中依然只能有一個(gè)函數(shù),否則會報(bào)錯

四餅::,可以讓我們直接引用到Person類的構(gòu)造函數(shù),第二 Java 編譯器能夠根據(jù)類的簽名選中正確的構(gòu)造器去實(shí)現(xiàn) PersonFactory.create 方法。

五、Lambda作用范圍

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表達(dá)式訪問外部的變量(局部變量,成員變量,靜態(tài)變量,接口的默認(rèn)方法),它與匿名內(nèi)部類訪問外部變量非常相似。

1. 訪問局部變量

我們可以從lambda表達(dá)式的外部范圍讀取最終局部變量num;

int num = 1;
IConverter<Integer, String> stringConverter = from -> String.valueOf(from + num);
String convert = stringConverter.convert(2);
System.out.println(convert); // 3

但是這個(gè)num是不可變值,這樣改變值會報(bào)錯;

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表達(dá)式內(nèi)部修改也是不允許的;

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

2. 訪問成員變量和靜態(tài)變量

在 Lambda 表達(dá)式中訪問局部變量。與局部變量相比,在 Lambda 表達(dá)式中對成員變量和靜態(tài)變量擁有讀寫權(quán)限:

public class Lambda4 {

// 靜態(tài)變量
static int outerStaticNum;
// 成員變量
int outerNum;

void testScopes() {
IConverter<Integer, String> stringConverter1 = (from) -> {
// 對成員變量賦值
outerNum = 23;
return String.valueOf(from);
};

IConverter<Integer, String> stringConverter2 = (from) -> {
// 對靜態(tài)變量賦值
outerStaticNum = 72;
return String.valueOf(from);
};
}

}
3. 訪問默認(rèn)接口方法

還記得第一節(jié)的IFormula示例嗎?

public interface IFormula {

double calculate(int a);

// 平方
default double sqrt(int a) {
return Math.sqrt(a);
}

}

當(dāng)時(shí),我們在接口中定義了一個(gè)帶有默認(rèn)實(shí)現(xiàn)的 sqrt 求平方根方法,在匿名內(nèi)部類中我們可以很方便的訪問此方法:

IFormula formula = new IFormula() {
@Override
public double calculate(int a) {
return a * a;
}
};

但是不能通過lambda表達(dá)式訪問默認(rèn)方法,這樣的代碼沒法通過編譯;

IFormula formula = (a) -> sqrt(a * a);

帶有默認(rèn)實(shí)現(xiàn)的接口方法,是不能在 lambda 表達(dá)式中訪問的,上面這段代碼將無法被編譯通過。

六、內(nèi)置的函數(shù)式接口

JDK 1.8 API 包含了很多內(nèi)置的函數(shù)式接口。其中就包括我們在老版本中經(jīng)常見到的 Comparator 和 Runnable,Java 8 為他們都添加了 @FunctionalInterface 注解,以用來支持 Lambda 表達(dá)式。

例如我們舊版本的Jdk中常用的 Comparator 和 Runnable 外,還有一些新的函數(shù)式接口,可以通過函數(shù)注解實(shí)現(xiàn)Lamdba支持,它們很多都借鑒于知名的 Google Guava 庫。

即使你已經(jīng)熟悉這個(gè)類庫,也應(yīng)該密切關(guān)注那些接口是如何通過一些有用的方法擴(kuò)展來擴(kuò)展的:

1. Predicate 斷言

Predicate 是一個(gè)可以指定入?yún)㈩愋停⒎祷?boolean 值的函數(shù)式接口。它內(nèi)部提供了一些帶有默認(rèn)實(shí)現(xiàn)的方法,可以 被用來組合一個(gè)復(fù)雜的邏輯判斷(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否定相當(dāng)于!true

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
}
2. Functions

Function 函數(shù)式接口的作用是,我們可以為其提供一個(gè)原料,他給生產(chǎn)一個(gè)最終的產(chǎn)品。通過它提供的默認(rèn)方法,組合,鏈行處理(compose, andThen):

@Test
public void test12() {
Function<String, Integer> toInteger = Integer::valueOf; //轉(zhuǎn)Integer
Function<String, String> backToString = toInteger.andThen(String::valueOf); //轉(zhuǎn)String
Function<String, String> afterToStartsWith = backToString.andThen(new Something()::startsWith); //截取第一位
String apply = afterToStartsWith.apply("123");// "123"
System.out.println(apply);
}
3. Suppliers

Supplier 與 Function 不同,它不接受入?yún)ⅲ苯訛槲覀兩a(chǎn)一個(gè)指定的結(jié)果,有點(diǎn)像生產(chǎn)者模式:

@Test
public void test13() {
Supplier<Person> personSupplier0 = Person::new;
personSupplier0.get(); // new Person
Supplier<String> personSupplier1 = Something::test01; //這個(gè)test方法是靜態(tài)的,且無入?yún)?br /> personSupplier1.get(); // hi

Supplier<String> personSupplier2 = new Something()::test02;
}
4. Consumers

對于 Consumer,我們需要提供入?yún)ⅲ脕肀幌M(fèi),如下面這段示例代碼:

@Test
public void test14() {
// 參照物,方便知道下面的Lamdba表達(dá)式寫法
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; // 也可以通過定義類和方法的方式去調(diào)用,這樣才是實(shí)際開發(fā)的姿勢
greeter03.accept(new Person("Luke", "Skywalker")); //Hello, Luke
}
5. Comparators

Comparator 在 Java 8 之前是使用比較普遍的。Java 8 中除了將其升級成了函數(shù)式接口,還為它拓展了一些默認(rèn)方法:

@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
}
七、Optionals

第一,Optional 它不是一個(gè)函數(shù)式接口,設(shè)計(jì)它的目的是為了防止空指針異常(NullPointerException),要知道在 Java 編程中,空指針異常可是臭名昭著的。

讓我們來快速了解一下 Optional 要如何使用!你可以將 Optional 看做是包裝對象(可能是 null, 也有可能非 null)的容器。當(dāng)你定義了

一個(gè)方法,這個(gè)方法返回的對象可能是空,也有可能非空的時(shí)候,你就可以考慮用 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 流

什么是 Stream 流?

簡單來說,我們可以使用 java.util.Stream 對一個(gè)包含一個(gè)或多個(gè)元素的**做各種**作。這些**作可能是 中間**作 亦或是 終端**作。 終端**作會返回一個(gè)結(jié)果,而中間**作會返回一個(gè) Stream 流。

需要注意的是,你只能對實(shí)現(xiàn)了 java.util.Collection 接口的類做流的**作。

Stream 流支持同步執(zhí)行,也支持并發(fā)執(zhí)行。

注意:Map不支持Stream流,但是他的key和value是支持的!

讓我們先看看Stream流是如何工作的。第一,我們以字符串列表的形式創(chuàng)建一個(gè)示例;

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");
1. Filter 過濾

Filter 的入?yún)⑹且粋€(gè) Predicate, 上面已經(jīng)說到,Predicate 是一個(gè)斷言的中間**作,它能夠幫我們篩選出我們需要的**元素。它的返參同樣 是一個(gè) Stream 流,我們可以通過 foreach 終端**作,來打印被篩選的元素:

@Test
public void test17(){
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
}
2. Sorted 排序

Sorted 同樣是一個(gè)中間**作,它的返參是一個(gè) Stream 流。另外,我們可以傳入一個(gè) Comparator 用來自定義排序,如果不傳,則使用默認(rèn)的排序規(guī)則。

@Test
public void test18() {
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
}

注意;這個(gè)sorted 只是做了一個(gè)排序的視圖進(jìn)行輸出,實(shí)際沒有將List內(nèi)的數(shù)據(jù)進(jìn)行排序

System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
3. Map 轉(zhuǎn)換

中間**作映射通過給定的函數(shù)將每個(gè)元素轉(zhuǎn)換為另一個(gè)對象。例如下面的示例,通過 map 我們將每一個(gè) string 轉(zhuǎn)成大寫:

@Test
public void test19(){
stringCollection
.stream()
.map(String::toUpperCase)
.sorted(Comparator.reverseOrder()) //等同于(a, b) -> b.compareTo(a)
.forEach(System.out::println);
}

這個(gè)可以用做DTO數(shù)據(jù)對象轉(zhuǎn)換,領(lǐng)域驅(qū)動設(shè)計(jì)開發(fā)中將DTO轉(zhuǎn)為DO向后臺傳輸。

4. Match 匹配

顧名思義,match 用來做匹配**作,它的返回值是一個(gè) boolean 類型。通過 match, 我們可以方便的驗(yàn)證一個(gè) list 中是否存在某個(gè)類型的元素。

@Test
public void test20(){
// anyMatch:驗(yàn)證 list 中 string 是否有以 a 開頭的, 匹配到第一個(gè),即返回 true
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
// allMatch:驗(yàn)證 list 中 string 是否都是以 a 開頭的
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
// noneMatch:驗(yàn)證 list 中 string 是否都不是以 z 開頭的
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
}
5. Count 計(jì)數(shù)

count 是一個(gè)終端**作,它能夠統(tǒng)計(jì) stream 流中的元素總數(shù),返回值是 long 類型。

@Test
public void test21() {
// count:先對 list 中字符串開頭為 b 進(jìn)行過濾,讓后統(tǒng)計(jì)數(shù)量
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
}
6. Reduce

Reduce 中文翻譯為:減少、縮小。通過入?yún)⒌?Function,我們能夠?qū)?list 歸約成一個(gè)值。它的返回類型是 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
}
九、Parallel-Streams 并行流

如上所述,流可以是順序的,也可以是并行的。順序流上的**作在單個(gè)線程上執(zhí)行,而并行流上的**作在多個(gè)線程上并發(fā)執(zhí)行。

下面的示例演示了使用并行流來提高性能是多么的容易。親測提升了1倍性能!

第一,我們創(chuàng)建一個(gè)較大的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());
}
1. Sequential Sort 順序流排序@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();
// 納秒轉(zhuǎn)微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 – t0);
System.out.println(String.format("順序流排序耗時(shí): %d ms", millis));
//順序流排序耗時(shí): 712 ms
}
2. Parallel Sort 并行流排序@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
}

如您所見,這兩個(gè)代碼片段幾乎相同,但并行排序大約快50%。您只需將stream()更改為parallelStream()。

十、Map **

如前所講,Map是不支持 Stream 流的,因?yàn)?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 是否已經(jīng)存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, "val" + i);
}
// forEach 可以很方便地對 map 進(jìn)行遍歷**作
map.forEach((key, value) -> System.out.println(value));
}

之后我們做一個(gè)Map對象的轉(zhuǎn)換輸出;(定義兩個(gè)類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 是否已經(jīng)存在,存在則直接返回 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() 外,我們還可以很方便地對某個(gè) key 的值做相關(guān)**作:

@Test
public void test27() {
// 如下:對 key 為 3 的值,內(nèi)部會先判斷值是否存在,存在,則做 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(), 當(dāng) key 不存在時(shí),才會做相關(guān)處理
// 如下:先判斷 key 為 23 的元素是否存在,不存在,則添加
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true

// 先判斷 key 為 3 的元素是否存在,存在,則不做任何處理
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
}

關(guān)于刪除**作,JDK 8 中提供了能夠新的 remove() API:

@Test
public void test28() {
map.remove(3, "val3");
map.get(3); // val33

map.remove(3, "val33");
map.get(3); // null
}

如上代碼,只有當(dāng)給定的 key 和 value 完全匹配時(shí),才會執(zhí)行刪除**作。

關(guān)于添加方法,JDK 8 中提供了帶有默認(rèn)值的 getOrDefault() 方法:

@Test
public void test29() {
// 若 key 42 不存在,則返回 not found
map.getOrDefault(42, "not found"); // not found
}

對于 value 的合并**作也變得更加簡單:

@Test
public void test30() {
// merge 方法,會先判斷進(jìn)行合并的 key 是否存在,不存在,則會添加元素
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
// 若 key 的元素存在,則對 value 執(zhí)行拼接**作
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
}
十一、日期 Date API

Java 8 中在包 java.time 下添加了新的日期 API. 它和 Joda-Time 庫相似,但又不完全相同。接下來,我會通過一些示例代碼介紹一下新 API 中 最關(guān)鍵的特性:

1. Clock

Clock 提供對當(dāng)前日期和時(shí)間的訪問。我們可以利用它來替代 System.currentTimeMillis() 方法。另外,通過 clock.instant() 能夠獲取一個(gè) instant 實(shí)例, 此實(shí)例能夠方便地轉(zhuǎn)換成老版本中的 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
}
2. Timezones 時(shí)區(qū)

ZoneId 代表時(shí)區(qū)類。通過靜態(tài)工廠方法方便地獲取它,入?yún)⑽覀兛梢詡魅肽硞€(gè)時(shí)區(qū)編碼。另外,時(shí)區(qū)類還定義了一個(gè)偏移量,用來在當(dāng)前時(shí)刻或某時(shí)間 與目標(biāo)時(shí)區(qū)時(shí)間之間進(jìn)行轉(zhuǎn)換。

@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/Ta**ania, 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]
}
3. LocalTime

LocalTime 表示一個(gè)沒有指定時(shí)區(qū)的時(shí)間類,例如,10 p.m.或者 17:30:15,下面示例代碼中,將會使用上面創(chuàng)建的 時(shí)區(qū)對象創(chuàng)建兩個(gè) LocalTime。第二我們會比較兩個(gè)時(shí)間,并計(jì)算它們之間的小時(shí)和分鐘的不同。

@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.i**efore(now2)); // false
long hour**etween = ChronoUnit.HOURS.between(now1, now2);
long minute**etween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hour**etween); // -3
System.out.println(minute**etween); // -239
}

LocalTime 提供多個(gè)靜態(tài)工廠方法,目的是為了簡化對時(shí)間對象實(shí)例的創(chuàng)建和**作,包括對時(shí)間字符串進(jìn)行解析的**作等。

@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
}
4. LocalDate

LocalDate 是一個(gè)日期對象,例如:2014-03-11。它和 LocalTime 一樣是個(gè) final 類型對象。下面的例子演示了如何通過加減日,月,年等來計(jì)算一個(gè)新的日期。

@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 實(shí)例。(和 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
}
5. LocalDateTime

LocalDateTime 是一個(gè)日期-時(shí)間對象。你也可以將其看成是 LocalDate 和 LocalTime 的結(jié)合體。**作上,也大致相同。

@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); // 十二月
// 獲取改時(shí)間是該天中的第幾分鐘
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
}

如果再加上的時(shí)區(qū)信息,LocalDateTime 還能夠被轉(zhuǎn)換成 Instance 實(shí)例。Instance 能夠被轉(zhuǎn)換成老版本中 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 一樣。除了使用預(yù)定義的格式以外,也可以自定義格式化輸出。

@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.

十二、Annotations 注解

Java8中的注釋是可重復(fù)的。讓我們直接深入到一個(gè)例子中來解決這個(gè)問題。{在SpringBoot的啟動類中就可以看到這中類型的注解}

第一,我們定義一個(gè)包裝器注釋,它包含一個(gè)實(shí)際注釋數(shù)組:

@Repeatable(Hints.class)
public @interface Hint {
String value();
}

public @interface Hints {
Hint[] value();
}

Java 8通過聲明注釋@Repeatable,使我們能夠使用同一類型的多個(gè)注釋。

第一種形態(tài):使用注解容器(老方法)

@Test
public void test40() {
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {
}
}

第二種形態(tài):使用可重復(fù)注解(新方法)

@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
}

java編譯器使用變量2隱式地在引擎蓋下設(shè)置@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.getAnnotation**yType(Hint.class
System.out.println(hints2.length); // 2
}

盡管我們絕對不會在 Person 類上聲明 @Hints 注解,但是它的信息仍然是可以通過 getAnnotation(Hints.class) 來讀取的。 并且,getAnnotation**yType 方**更方便,因?yàn)樗x予了所有 @Hints 注解標(biāo)注的方法直接的訪問權(quán)限。

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
綜上小編綜合來說jdk8的新特性包括了;Lambda、函數(shù)式接口、四餅調(diào)用::、內(nèi)置函數(shù)(斷言、Function、生產(chǎn)者、消費(fèi)者)、Stream流、Map**特性、日期、注解等合理的組合運(yùn)行新的特性可以減少很多的編碼量,同時(shí)讓代碼更加整潔在一些新的框架中SpringBoot里如果翻看源碼可以看到很多的新特性使用

拓展知識:

jdk1.7

1.56。由于springboot2.0以后不支持jdk1.7,所以只能自降springboot的版本由2.0降為1.56。當(dāng)選用jdk1.7構(gòu)建springboot項(xiàng)目時(shí)會提示,選用jdk1.8。

前沿拓展:

jdk1.7

濤 你配置了path環(huán)境變量沒有配置正確
我的path變量里配的是C:\Program Files\Java\jdk1.7.0\bin; 我的java裝在C盤


作者:小傅哥 博客:https://bugstack.cn

沉淀、分享、成長,讓自己和他人都能有所收獲!

前言

一直想把jdk1.8的新特性整理下,恰好看到老外的git(文后有鏈接),在這個(gè)結(jié)構(gòu)上繼續(xù)完善了說明和功能,做了41個(gè)單元測試案例,方便新人學(xué)習(xí)。以下內(nèi)容很干,對于一個(gè)萌新小白來說,學(xué)習(xí)jdk1.8的新特性,基本看一遍就知道個(gè)7788了,在熟讀兩遍最后跟著寫一遍,那么在實(shí)際項(xiàng)目中就可以運(yùn)用了。不過!新特性,雖然很好。但如果想用,那么自己一定要看看相對應(yīng)的源碼并多練習(xí),否則真的容易給自己搞暈,又很難閱讀。

零、回顧一個(gè)抽象類

在jdk1.8之前,因?yàn)榻涌诶镏荒茏龇椒ǘx不能有方法的實(shí)現(xiàn),因此我們通常會在抽象類里面實(shí)現(xiàn)默認(rèn)的方法{一般這個(gè)默認(rèn)的方法是抽象后公用的方法,不需要每一個(gè)繼承者都去實(shí)現(xiàn),只需調(diào)用即可}。就像下面這樣;

在定義的時(shí)候;

public abstract class AFormula {

abstract double calculate(int a);

// 平方
double sqrt(int a) {
return Math.sqrt(a);
}

}

在使用的時(shí)候;

@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
}
一、在接口中提供默認(rèn)的方法實(shí)現(xiàn)(有點(diǎn)像抽象類)

在jdk1.8里面,不僅可以定義接口,還可以在接口中提供默認(rèn)的實(shí)現(xiàn)。這一個(gè)小小的改變卻讓整個(gè)抽象設(shè)計(jì)都隨著改變了!

在定義的時(shí)候;{default 關(guān)鍵字必須}

public interface IFormula {

double calculate(int a);

// 平方
default double sqrt(int a) {
return Math.sqrt(a);
}

}

在使用的時(shí)候(一);

@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));
}

在使用的時(shí)候(二);如果只是一里面方式這么使用,那么就沒多大意思了。我一直說過;好的代碼都很**!

a; a是一個(gè)入?yún)⒚Q,可以其他任何名字->a*a; 箭頭指向是具體的實(shí)現(xiàn)但是,這樣其實(shí)不太適合加日志了@Test
public void test_02() {
// 入?yún) 和 實(shí)現(xiàn)
IFormula formula = a -> a * a;
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}
二、Lambda 表達(dá)式

因?yàn)橛薪涌谥锌梢栽黾幽J(rèn)的方法實(shí)現(xiàn),那么Java肯定是因?yàn)橐喕_發(fā)才出現(xiàn)的這么個(gè)設(shè)計(jì)。所以你會從各個(gè)我們以前的List、Set等等所有接口中看到默認(rèn)的方法實(shí)現(xiàn)。

從一段熟悉的排序列子入手

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 工具類提供了靜態(tài)方法 sort 方法,入?yún)⑹且粋€(gè) List **,和一個(gè) Comparator 比較器,以便對給定的 List **進(jìn)行排序。上面的示例代碼創(chuàng)建了一個(gè)匿名內(nèi)部類作為入?yún)ⅲ@種類似的**作在我們?nèi)粘5墓ぷ髦须S處可見。

Java 8 中不再推薦這種寫法,而是推薦使用 Lambda 表達(dá):

Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});

上面的這段同樣功能的代碼塊,簡短干凈了許多。就像婆媳一樣可能剛開始看不習(xí)慣,但是接觸接觸就喜歡了。因?yàn)椋€可以更加簡短優(yōu)秀;

Collections.sort(names, (String a, String b) -> b.compareTo(a));

為了追求極致,我們還可以讓它再短點(diǎn):{當(dāng)然過你的實(shí)現(xiàn)不是一行代碼,那么不能這么干}

names.sort((a, b) -> b.compareTo(a));

java.util.List **現(xiàn)在已經(jīng)添加了 sort 方法。而且 Java 編譯器能夠根據(jù)類型推斷機(jī)制判斷出參數(shù)類型,這樣,你連入?yún)⒌念愋投伎梢允÷岳玻趺礃樱遣皇歉杏X很**氣呢!

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);
}
}

好了!你以為這就結(jié)束了嗎,不!它還可以更短!(得益于Comparator接口中還提供了stack默認(rèn)方法,也就是說接口中不是只可有default默認(rèn)實(shí)現(xiàn),還可以有靜態(tài)方法)

names.sort(Comparator.reverseOrder());
三、函數(shù)式接口 Functional Interfaces

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可以開發(fā)出同樣功能的邏輯但是代碼卻很簡單,那么Jvm是如何進(jìn)行類型推斷,并且找到對應(yīng)的方法呢?

通過官文介紹以及我們使用發(fā)現(xiàn),并不是每個(gè)接口都可以縮寫成Lambda表達(dá)式的開發(fā)方式。其實(shí)是只有那些函數(shù)式接口(Functional Interface)才能縮寫成 Lambda 表示式。

所謂函數(shù)式接口(Functional Interface)就是只包含一個(gè)抽象方法的聲明。針對該接口類型的所有 Lambda 表達(dá)式都會與這個(gè)抽象方法匹配。{另外,只是在接口上添加default并不算抽象方法}

小編綜合來說:為了保證一個(gè)接口明確的被定義為一個(gè)函數(shù)式接口(Functional Interface),我們需要為該接口添加注解:@FunctionalInterface。這樣,一旦你添加了第二個(gè)抽象方法,編譯器會立刻拋出錯誤提示。{不填寫,但是只寫一個(gè)default也可以}

定義含有注解@FunctionalInterface的接口

@FunctionalInterface
public interface IConverter<F, T> {

T convert(F from);

}
先來一段傳統(tǒng)方式 & 簡單易懂哈,因?yàn)榭戳?xí)慣了IConverter<String, Integer> converter01 = new IConverter<String, Integer>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
稍微簡化下,化個(gè)妝 & (form),只有一個(gè)參數(shù)括號可以不要IConverter<String, Integer> converter02 = (from) -> {
return Integer.valueOf(from);
};
繼續(xù)簡化,因?yàn)樗膶?shí)現(xiàn)只有一行代碼,可以更加簡短IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
還能短點(diǎn),其實(shí)這個(gè)另類屬于下一段的內(nèi)容了,先放這有個(gè)印象IConverter<Integer, String> converter04 = String::valueOf;
四、方法和構(gòu)造函數(shù)的便捷應(yīng)用

在上面我們先加了印象片段 XX::xx,它也是Java8的新特性便捷式引用,這四個(gè)點(diǎn)可能你在其他語言里也見過。

IConverter<Integer, String> converter04 = String::valueOf;
String converted04 = converter04.convert(11);
System.out.println(converted04);

這四個(gè)點(diǎn)::的關(guān)鍵字,不只是可以引用方法和構(gòu)造函數(shù),還可以引用普通方法。

public class Something{
public String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
IConverter<String, String> converter01 = s -> String.valueOf(s.charAt(0)); //[參照物]直接把邏輯放到這調(diào)用
IConverter<String, String> converter02 = something::startsWith; //引用的方法體里面邏輯可以更多,否則只是一句代碼并不能適合所有的情況
System.out.println(converter01.convert("Java"));
System.out.println(converter02.convert("Java"));

接下來我們在使用這四個(gè)點(diǎn),來看下如何引用類的構(gòu)造器。第一我們創(chuàng)建一個(gè)這樣的類;

public class Person {
String firstName;
String lastName;

Person() {}

Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}

第二我還需要頂一個(gè)工廠類,用于生成Person對象;

@FunctionalInterface
public interface IPersonFactory<P extends Person> {

P create(String firstName, String lastName);

}

現(xiàn)在就到了用四餅::的時(shí)候了;

IPersonFactory<Person> personFactory = Person::new; //[參照物]:(firstName, lastName) -> new Person(firstName, lastName);
Person person = personFactory.create("Peter", "Parker");

提醒;工廠函數(shù)中依然只能有一個(gè)函數(shù),否則會報(bào)錯

四餅::,可以讓我們直接引用到Person類的構(gòu)造函數(shù),第二 Java 編譯器能夠根據(jù)類的簽名選中正確的構(gòu)造器去實(shí)現(xiàn) PersonFactory.create 方法。

五、Lambda作用范圍

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表達(dá)式訪問外部的變量(局部變量,成員變量,靜態(tài)變量,接口的默認(rèn)方法),它與匿名內(nèi)部類訪問外部變量非常相似。

1. 訪問局部變量

我們可以從lambda表達(dá)式的外部范圍讀取最終局部變量num;

int num = 1;
IConverter<Integer, String> stringConverter = from -> String.valueOf(from + num);
String convert = stringConverter.convert(2);
System.out.println(convert); // 3

但是這個(gè)num是不可變值,這樣改變值會報(bào)錯;

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表達(dá)式內(nèi)部修改也是不允許的;

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

2. 訪問成員變量和靜態(tài)變量

在 Lambda 表達(dá)式中訪問局部變量。與局部變量相比,在 Lambda 表達(dá)式中對成員變量和靜態(tài)變量擁有讀寫權(quán)限:

public class Lambda4 {

// 靜態(tài)變量
static int outerStaticNum;
// 成員變量
int outerNum;

void testScopes() {
IConverter<Integer, String> stringConverter1 = (from) -> {
// 對成員變量賦值
outerNum = 23;
return String.valueOf(from);
};

IConverter<Integer, String> stringConverter2 = (from) -> {
// 對靜態(tài)變量賦值
outerStaticNum = 72;
return String.valueOf(from);
};
}

}
3. 訪問默認(rèn)接口方法

還記得第一節(jié)的IFormula示例嗎?

public interface IFormula {

double calculate(int a);

// 平方
default double sqrt(int a) {
return Math.sqrt(a);
}

}

當(dāng)時(shí),我們在接口中定義了一個(gè)帶有默認(rèn)實(shí)現(xiàn)的 sqrt 求平方根方法,在匿名內(nèi)部類中我們可以很方便的訪問此方法:

IFormula formula = new IFormula() {
@Override
public double calculate(int a) {
return a * a;
}
};

但是不能通過lambda表達(dá)式訪問默認(rèn)方法,這樣的代碼沒法通過編譯;

IFormula formula = (a) -> sqrt(a * a);

帶有默認(rèn)實(shí)現(xiàn)的接口方法,是不能在 lambda 表達(dá)式中訪問的,上面這段代碼將無法被編譯通過。

六、內(nèi)置的函數(shù)式接口

JDK 1.8 API 包含了很多內(nèi)置的函數(shù)式接口。其中就包括我們在老版本中經(jīng)常見到的 Comparator 和 Runnable,Java 8 為他們都添加了 @FunctionalInterface 注解,以用來支持 Lambda 表達(dá)式。

例如我們舊版本的Jdk中常用的 Comparator 和 Runnable 外,還有一些新的函數(shù)式接口,可以通過函數(shù)注解實(shí)現(xiàn)Lamdba支持,它們很多都借鑒于知名的 Google Guava 庫。

即使你已經(jīng)熟悉這個(gè)類庫,也應(yīng)該密切關(guān)注那些接口是如何通過一些有用的方法擴(kuò)展來擴(kuò)展的:

1. Predicate 斷言

Predicate 是一個(gè)可以指定入?yún)㈩愋停⒎祷?boolean 值的函數(shù)式接口。它內(nèi)部提供了一些帶有默認(rèn)實(shí)現(xiàn)的方法,可以 被用來組合一個(gè)復(fù)雜的邏輯判斷(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否定相當(dāng)于!true

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
}
2. Functions

Function 函數(shù)式接口的作用是,我們可以為其提供一個(gè)原料,他給生產(chǎn)一個(gè)最終的產(chǎn)品。通過它提供的默認(rèn)方法,組合,鏈行處理(compose, andThen):

@Test
public void test12() {
Function<String, Integer> toInteger = Integer::valueOf; //轉(zhuǎn)Integer
Function<String, String> backToString = toInteger.andThen(String::valueOf); //轉(zhuǎn)String
Function<String, String> afterToStartsWith = backToString.andThen(new Something()::startsWith); //截取第一位
String apply = afterToStartsWith.apply("123");// "123"
System.out.println(apply);
}
3. Suppliers

Supplier 與 Function 不同,它不接受入?yún)ⅲ苯訛槲覀兩a(chǎn)一個(gè)指定的結(jié)果,有點(diǎn)像生產(chǎn)者模式:

@Test
public void test13() {
Supplier<Person> personSupplier0 = Person::new;
personSupplier0.get(); // new Person
Supplier<String> personSupplier1 = Something::test01; //這個(gè)test方法是靜態(tài)的,且無入?yún)?br /> personSupplier1.get(); // hi

Supplier<String> personSupplier2 = new Something()::test02;
}
4. Consumers

對于 Consumer,我們需要提供入?yún)ⅲ脕肀幌M(fèi),如下面這段示例代碼:

@Test
public void test14() {
// 參照物,方便知道下面的Lamdba表達(dá)式寫法
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; // 也可以通過定義類和方法的方式去調(diào)用,這樣才是實(shí)際開發(fā)的姿勢
greeter03.accept(new Person("Luke", "Skywalker")); //Hello, Luke
}
5. Comparators

Comparator 在 Java 8 之前是使用比較普遍的。Java 8 中除了將其升級成了函數(shù)式接口,還為它拓展了一些默認(rèn)方法:

@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
}
七、Optionals

第一,Optional 它不是一個(gè)函數(shù)式接口,設(shè)計(jì)它的目的是為了防止空指針異常(NullPointerException),要知道在 Java 編程中,空指針異常可是臭名昭著的。

讓我們來快速了解一下 Optional 要如何使用!你可以將 Optional 看做是包裝對象(可能是 null, 也有可能非 null)的容器。當(dāng)你定義了

一個(gè)方法,這個(gè)方法返回的對象可能是空,也有可能非空的時(shí)候,你就可以考慮用 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 流

什么是 Stream 流?

簡單來說,我們可以使用 java.util.Stream 對一個(gè)包含一個(gè)或多個(gè)元素的**做各種**作。這些**作可能是 中間**作 亦或是 終端**作。 終端**作會返回一個(gè)結(jié)果,而中間**作會返回一個(gè) Stream 流。

需要注意的是,你只能對實(shí)現(xiàn)了 java.util.Collection 接口的類做流的**作。

Stream 流支持同步執(zhí)行,也支持并發(fā)執(zhí)行。

注意:Map不支持Stream流,但是他的key和value是支持的!

讓我們先看看Stream流是如何工作的。第一,我們以字符串列表的形式創(chuàng)建一個(gè)示例;

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");
1. Filter 過濾

Filter 的入?yún)⑹且粋€(gè) Predicate, 上面已經(jīng)說到,Predicate 是一個(gè)斷言的中間**作,它能夠幫我們篩選出我們需要的**元素。它的返參同樣 是一個(gè) Stream 流,我們可以通過 foreach 終端**作,來打印被篩選的元素:

@Test
public void test17(){
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
}
2. Sorted 排序

Sorted 同樣是一個(gè)中間**作,它的返參是一個(gè) Stream 流。另外,我們可以傳入一個(gè) Comparator 用來自定義排序,如果不傳,則使用默認(rèn)的排序規(guī)則。

@Test
public void test18() {
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
}

注意;這個(gè)sorted 只是做了一個(gè)排序的視圖進(jìn)行輸出,實(shí)際沒有將List內(nèi)的數(shù)據(jù)進(jìn)行排序

System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
3. Map 轉(zhuǎn)換

中間**作映射通過給定的函數(shù)將每個(gè)元素轉(zhuǎn)換為另一個(gè)對象。例如下面的示例,通過 map 我們將每一個(gè) string 轉(zhuǎn)成大寫:

@Test
public void test19(){
stringCollection
.stream()
.map(String::toUpperCase)
.sorted(Comparator.reverseOrder()) //等同于(a, b) -> b.compareTo(a)
.forEach(System.out::println);
}

這個(gè)可以用做DTO數(shù)據(jù)對象轉(zhuǎn)換,領(lǐng)域驅(qū)動設(shè)計(jì)開發(fā)中將DTO轉(zhuǎn)為DO向后臺傳輸。

4. Match 匹配

顧名思義,match 用來做匹配**作,它的返回值是一個(gè) boolean 類型。通過 match, 我們可以方便的驗(yàn)證一個(gè) list 中是否存在某個(gè)類型的元素。

@Test
public void test20(){
// anyMatch:驗(yàn)證 list 中 string 是否有以 a 開頭的, 匹配到第一個(gè),即返回 true
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
// allMatch:驗(yàn)證 list 中 string 是否都是以 a 開頭的
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
// noneMatch:驗(yàn)證 list 中 string 是否都不是以 z 開頭的
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
}
5. Count 計(jì)數(shù)

count 是一個(gè)終端**作,它能夠統(tǒng)計(jì) stream 流中的元素總數(shù),返回值是 long 類型。

@Test
public void test21() {
// count:先對 list 中字符串開頭為 b 進(jìn)行過濾,讓后統(tǒng)計(jì)數(shù)量
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
}
6. Reduce

Reduce 中文翻譯為:減少、縮小。通過入?yún)⒌?Function,我們能夠?qū)?list 歸約成一個(gè)值。它的返回類型是 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
}
九、Parallel-Streams 并行流

如上所述,流可以是順序的,也可以是并行的。順序流上的**作在單個(gè)線程上執(zhí)行,而并行流上的**作在多個(gè)線程上并發(fā)執(zhí)行。

下面的示例演示了使用并行流來提高性能是多么的容易。親測提升了1倍性能!

第一,我們創(chuàng)建一個(gè)較大的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());
}
1. Sequential Sort 順序流排序@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();
// 納秒轉(zhuǎn)微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 – t0);
System.out.println(String.format("順序流排序耗時(shí): %d ms", millis));
//順序流排序耗時(shí): 712 ms
}
2. Parallel Sort 并行流排序@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
}

如您所見,這兩個(gè)代碼片段幾乎相同,但并行排序大約快50%。您只需將stream()更改為parallelStream()。

十、Map **

如前所講,Map是不支持 Stream 流的,因?yàn)?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 是否已經(jīng)存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, "val" + i);
}
// forEach 可以很方便地對 map 進(jìn)行遍歷**作
map.forEach((key, value) -> System.out.println(value));
}

之后我們做一個(gè)Map對象的轉(zhuǎn)換輸出;(定義兩個(gè)類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 是否已經(jīng)存在,存在則直接返回 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() 外,我們還可以很方便地對某個(gè) key 的值做相關(guān)**作:

@Test
public void test27() {
// 如下:對 key 為 3 的值,內(nèi)部會先判斷值是否存在,存在,則做 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(), 當(dāng) key 不存在時(shí),才會做相關(guān)處理
// 如下:先判斷 key 為 23 的元素是否存在,不存在,則添加
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true

// 先判斷 key 為 3 的元素是否存在,存在,則不做任何處理
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
}

關(guān)于刪除**作,JDK 8 中提供了能夠新的 remove() API:

@Test
public void test28() {
map.remove(3, "val3");
map.get(3); // val33

map.remove(3, "val33");
map.get(3); // null
}

如上代碼,只有當(dāng)給定的 key 和 value 完全匹配時(shí),才會執(zhí)行刪除**作。

關(guān)于添加方法,JDK 8 中提供了帶有默認(rèn)值的 getOrDefault() 方法:

@Test
public void test29() {
// 若 key 42 不存在,則返回 not found
map.getOrDefault(42, "not found"); // not found
}

對于 value 的合并**作也變得更加簡單:

@Test
public void test30() {
// merge 方法,會先判斷進(jìn)行合并的 key 是否存在,不存在,則會添加元素
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
// 若 key 的元素存在,則對 value 執(zhí)行拼接**作
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
}
十一、日期 Date API

Java 8 中在包 java.time 下添加了新的日期 API. 它和 Joda-Time 庫相似,但又不完全相同。接下來,我會通過一些示例代碼介紹一下新 API 中 最關(guān)鍵的特性:

1. Clock

Clock 提供對當(dāng)前日期和時(shí)間的訪問。我們可以利用它來替代 System.currentTimeMillis() 方法。另外,通過 clock.instant() 能夠獲取一個(gè) instant 實(shí)例, 此實(shí)例能夠方便地轉(zhuǎn)換成老版本中的 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
}
2. Timezones 時(shí)區(qū)

ZoneId 代表時(shí)區(qū)類。通過靜態(tài)工廠方法方便地獲取它,入?yún)⑽覀兛梢詡魅肽硞€(gè)時(shí)區(qū)編碼。另外,時(shí)區(qū)類還定義了一個(gè)偏移量,用來在當(dāng)前時(shí)刻或某時(shí)間 與目標(biāo)時(shí)區(qū)時(shí)間之間進(jìn)行轉(zhuǎn)換。

@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/Ta**ania, 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]
}
3. LocalTime

LocalTime 表示一個(gè)沒有指定時(shí)區(qū)的時(shí)間類,例如,10 p.m.或者 17:30:15,下面示例代碼中,將會使用上面創(chuàng)建的 時(shí)區(qū)對象創(chuàng)建兩個(gè) LocalTime。第二我們會比較兩個(gè)時(shí)間,并計(jì)算它們之間的小時(shí)和分鐘的不同。

@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.i**efore(now2)); // false
long hour**etween = ChronoUnit.HOURS.between(now1, now2);
long minute**etween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hour**etween); // -3
System.out.println(minute**etween); // -239
}

LocalTime 提供多個(gè)靜態(tài)工廠方法,目的是為了簡化對時(shí)間對象實(shí)例的創(chuàng)建和**作,包括對時(shí)間字符串進(jìn)行解析的**作等。

@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
}
4. LocalDate

LocalDate 是一個(gè)日期對象,例如:2014-03-11。它和 LocalTime 一樣是個(gè) final 類型對象。下面的例子演示了如何通過加減日,月,年等來計(jì)算一個(gè)新的日期。

@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 實(shí)例。(和 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
}
5. LocalDateTime

LocalDateTime 是一個(gè)日期-時(shí)間對象。你也可以將其看成是 LocalDate 和 LocalTime 的結(jié)合體。**作上,也大致相同。

@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); // 十二月
// 獲取改時(shí)間是該天中的第幾分鐘
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
}

如果再加上的時(shí)區(qū)信息,LocalDateTime 還能夠被轉(zhuǎn)換成 Instance 實(shí)例。Instance 能夠被轉(zhuǎn)換成老版本中 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 一樣。除了使用預(yù)定義的格式以外,也可以自定義格式化輸出。

@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.

十二、Annotations 注解

Java8中的注釋是可重復(fù)的。讓我們直接深入到一個(gè)例子中來解決這個(gè)問題。{在SpringBoot的啟動類中就可以看到這中類型的注解}

第一,我們定義一個(gè)包裝器注釋,它包含一個(gè)實(shí)際注釋數(shù)組:

@Repeatable(Hints.class)
public @interface Hint {
String value();
}

public @interface Hints {
Hint[] value();
}

Java 8通過聲明注釋@Repeatable,使我們能夠使用同一類型的多個(gè)注釋。

第一種形態(tài):使用注解容器(老方法)

@Test
public void test40() {
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {
}
}

第二種形態(tài):使用可重復(fù)注解(新方法)

@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
}

java編譯器使用變量2隱式地在引擎蓋下設(shè)置@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.getAnnotation**yType(Hint.class
System.out.println(hints2.length); // 2
}

盡管我們絕對不會在 Person 類上聲明 @Hints 注解,但是它的信息仍然是可以通過 getAnnotation(Hints.class) 來讀取的。 并且,getAnnotation**yType 方**更方便,因?yàn)樗x予了所有 @Hints 注解標(biāo)注的方法直接的訪問權(quán)限。

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
綜上小編綜合來說jdk8的新特性包括了;Lambda、函數(shù)式接口、四餅調(diào)用::、內(nèi)置函數(shù)(斷言、Function、生產(chǎn)者、消費(fèi)者)、Stream流、Map**特性、日期、注解等合理的組合運(yùn)行新的特性可以減少很多的編碼量,同時(shí)讓代碼更加整潔在一些新的框架中SpringBoot里如果翻看源碼可以看到很多的新特性使用

拓展知識:

jdk1.7

1.56。由于springboot2.0以后不支持jdk1.7,所以只能自降springboot的版本由2.0降為1.56。當(dāng)選用jdk1.7構(gòu)建springboot項(xiàng)目時(shí)會提示,選用jdk1.8。

前沿拓展:

jdk1.7

濤 你配置了path環(huán)境變量沒有配置正確
我的path變量里配的是C:\Program Files\Java\jdk1.7.0\bin; 我的java裝在C盤


作者:小傅哥 博客:https://bugstack.cn

沉淀、分享、成長,讓自己和他人都能有所收獲!

前言

一直想把jdk1.8的新特性整理下,恰好看到老外的git(文后有鏈接),在這個(gè)結(jié)構(gòu)上繼續(xù)完善了說明和功能,做了41個(gè)單元測試案例,方便新人學(xué)習(xí)。以下內(nèi)容很干,對于一個(gè)萌新小白來說,學(xué)習(xí)jdk1.8的新特性,基本看一遍就知道個(gè)7788了,在熟讀兩遍最后跟著寫一遍,那么在實(shí)際項(xiàng)目中就可以運(yùn)用了。不過!新特性,雖然很好。但如果想用,那么自己一定要看看相對應(yīng)的源碼并多練習(xí),否則真的容易給自己搞暈,又很難閱讀。

零、回顧一個(gè)抽象類

在jdk1.8之前,因?yàn)榻涌诶镏荒茏龇椒ǘx不能有方法的實(shí)現(xiàn),因此我們通常會在抽象類里面實(shí)現(xiàn)默認(rèn)的方法{一般這個(gè)默認(rèn)的方法是抽象后公用的方法,不需要每一個(gè)繼承者都去實(shí)現(xiàn),只需調(diào)用即可}。就像下面這樣;

在定義的時(shí)候;

public abstract class AFormula {

abstract double calculate(int a);

// 平方
double sqrt(int a) {
return Math.sqrt(a);
}

}

在使用的時(shí)候;

@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
}
一、在接口中提供默認(rèn)的方法實(shí)現(xiàn)(有點(diǎn)像抽象類)

在jdk1.8里面,不僅可以定義接口,還可以在接口中提供默認(rèn)的實(shí)現(xiàn)。這一個(gè)小小的改變卻讓整個(gè)抽象設(shè)計(jì)都隨著改變了!

在定義的時(shí)候;{default 關(guān)鍵字必須}

public interface IFormula {

double calculate(int a);

// 平方
default double sqrt(int a) {
return Math.sqrt(a);
}

}

在使用的時(shí)候(一);

@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));
}

在使用的時(shí)候(二);如果只是一里面方式這么使用,那么就沒多大意思了。我一直說過;好的代碼都很**!

a; a是一個(gè)入?yún)⒚Q,可以其他任何名字->a*a; 箭頭指向是具體的實(shí)現(xiàn)但是,這樣其實(shí)不太適合加日志了@Test
public void test_02() {
// 入?yún) 和 實(shí)現(xiàn)
IFormula formula = a -> a * a;
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}
二、Lambda 表達(dá)式

因?yàn)橛薪涌谥锌梢栽黾幽J(rèn)的方法實(shí)現(xiàn),那么Java肯定是因?yàn)橐喕_發(fā)才出現(xiàn)的這么個(gè)設(shè)計(jì)。所以你會從各個(gè)我們以前的List、Set等等所有接口中看到默認(rèn)的方法實(shí)現(xiàn)。

從一段熟悉的排序列子入手

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 工具類提供了靜態(tài)方法 sort 方法,入?yún)⑹且粋€(gè) List **,和一個(gè) Comparator 比較器,以便對給定的 List **進(jìn)行排序。上面的示例代碼創(chuàng)建了一個(gè)匿名內(nèi)部類作為入?yún)ⅲ@種類似的**作在我們?nèi)粘5墓ぷ髦须S處可見。

Java 8 中不再推薦這種寫法,而是推薦使用 Lambda 表達(dá):

Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});

上面的這段同樣功能的代碼塊,簡短干凈了許多。就像婆媳一樣可能剛開始看不習(xí)慣,但是接觸接觸就喜歡了。因?yàn)椋€可以更加簡短優(yōu)秀;

Collections.sort(names, (String a, String b) -> b.compareTo(a));

為了追求極致,我們還可以讓它再短點(diǎn):{當(dāng)然過你的實(shí)現(xiàn)不是一行代碼,那么不能這么干}

names.sort((a, b) -> b.compareTo(a));

java.util.List **現(xiàn)在已經(jīng)添加了 sort 方法。而且 Java 編譯器能夠根據(jù)類型推斷機(jī)制判斷出參數(shù)類型,這樣,你連入?yún)⒌念愋投伎梢允÷岳玻趺礃樱遣皇歉杏X很**氣呢!

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);
}
}

好了!你以為這就結(jié)束了嗎,不!它還可以更短!(得益于Comparator接口中還提供了stack默認(rèn)方法,也就是說接口中不是只可有default默認(rèn)實(shí)現(xiàn),還可以有靜態(tài)方法)

names.sort(Comparator.reverseOrder());
三、函數(shù)式接口 Functional Interfaces

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可以開發(fā)出同樣功能的邏輯但是代碼卻很簡單,那么Jvm是如何進(jìn)行類型推斷,并且找到對應(yīng)的方法呢?

通過官文介紹以及我們使用發(fā)現(xiàn),并不是每個(gè)接口都可以縮寫成Lambda表達(dá)式的開發(fā)方式。其實(shí)是只有那些函數(shù)式接口(Functional Interface)才能縮寫成 Lambda 表示式。

所謂函數(shù)式接口(Functional Interface)就是只包含一個(gè)抽象方法的聲明。針對該接口類型的所有 Lambda 表達(dá)式都會與這個(gè)抽象方法匹配。{另外,只是在接口上添加default并不算抽象方法}

小編綜合來說:為了保證一個(gè)接口明確的被定義為一個(gè)函數(shù)式接口(Functional Interface),我們需要為該接口添加注解:@FunctionalInterface。這樣,一旦你添加了第二個(gè)抽象方法,編譯器會立刻拋出錯誤提示。{不填寫,但是只寫一個(gè)default也可以}

定義含有注解@FunctionalInterface的接口

@FunctionalInterface
public interface IConverter<F, T> {

T convert(F from);

}
先來一段傳統(tǒng)方式 & 簡單易懂哈,因?yàn)榭戳?xí)慣了IConverter<String, Integer> converter01 = new IConverter<String, Integer>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
稍微簡化下,化個(gè)妝 & (form),只有一個(gè)參數(shù)括號可以不要IConverter<String, Integer> converter02 = (from) -> {
return Integer.valueOf(from);
};
繼續(xù)簡化,因?yàn)樗膶?shí)現(xiàn)只有一行代碼,可以更加簡短IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
還能短點(diǎn),其實(shí)這個(gè)另類屬于下一段的內(nèi)容了,先放這有個(gè)印象IConverter<Integer, String> converter04 = String::valueOf;
四、方法和構(gòu)造函數(shù)的便捷應(yīng)用

在上面我們先加了印象片段 XX::xx,它也是Java8的新特性便捷式引用,這四個(gè)點(diǎn)可能你在其他語言里也見過。

IConverter<Integer, String> converter04 = String::valueOf;
String converted04 = converter04.convert(11);
System.out.println(converted04);

這四個(gè)點(diǎn)::的關(guān)鍵字,不只是可以引用方法和構(gòu)造函數(shù),還可以引用普通方法。

public class Something{
public String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
IConverter<String, String> converter01 = s -> String.valueOf(s.charAt(0)); //[參照物]直接把邏輯放到這調(diào)用
IConverter<String, String> converter02 = something::startsWith; //引用的方法體里面邏輯可以更多,否則只是一句代碼并不能適合所有的情況
System.out.println(converter01.convert("Java"));
System.out.println(converter02.convert("Java"));

接下來我們在使用這四個(gè)點(diǎn),來看下如何引用類的構(gòu)造器。第一我們創(chuàng)建一個(gè)這樣的類;

public class Person {
String firstName;
String lastName;

Person() {}

Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}

第二我還需要頂一個(gè)工廠類,用于生成Person對象;

@FunctionalInterface
public interface IPersonFactory<P extends Person> {

P create(String firstName, String lastName);

}

現(xiàn)在就到了用四餅::的時(shí)候了;

IPersonFactory<Person> personFactory = Person::new; //[參照物]:(firstName, lastName) -> new Person(firstName, lastName);
Person person = personFactory.create("Peter", "Parker");

提醒;工廠函數(shù)中依然只能有一個(gè)函數(shù),否則會報(bào)錯

四餅::,可以讓我們直接引用到Person類的構(gòu)造函數(shù),第二 Java 編譯器能夠根據(jù)類的簽名選中正確的構(gòu)造器去實(shí)現(xiàn) PersonFactory.create 方法。

五、Lambda作用范圍

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表達(dá)式訪問外部的變量(局部變量,成員變量,靜態(tài)變量,接口的默認(rèn)方法),它與匿名內(nèi)部類訪問外部變量非常相似。

1. 訪問局部變量

我們可以從lambda表達(dá)式的外部范圍讀取最終局部變量num;

int num = 1;
IConverter<Integer, String> stringConverter = from -> String.valueOf(from + num);
String convert = stringConverter.convert(2);
System.out.println(convert); // 3

但是這個(gè)num是不可變值,這樣改變值會報(bào)錯;

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表達(dá)式內(nèi)部修改也是不允許的;

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

2. 訪問成員變量和靜態(tài)變量

在 Lambda 表達(dá)式中訪問局部變量。與局部變量相比,在 Lambda 表達(dá)式中對成員變量和靜態(tài)變量擁有讀寫權(quán)限:

public class Lambda4 {

// 靜態(tài)變量
static int outerStaticNum;
// 成員變量
int outerNum;

void testScopes() {
IConverter<Integer, String> stringConverter1 = (from) -> {
// 對成員變量賦值
outerNum = 23;
return String.valueOf(from);
};

IConverter<Integer, String> stringConverter2 = (from) -> {
// 對靜態(tài)變量賦值
outerStaticNum = 72;
return String.valueOf(from);
};
}

}
3. 訪問默認(rèn)接口方法

還記得第一節(jié)的IFormula示例嗎?

public interface IFormula {

double calculate(int a);

// 平方
default double sqrt(int a) {
return Math.sqrt(a);
}

}

當(dāng)時(shí),我們在接口中定義了一個(gè)帶有默認(rèn)實(shí)現(xiàn)的 sqrt 求平方根方法,在匿名內(nèi)部類中我們可以很方便的訪問此方法:

IFormula formula = new IFormula() {
@Override
public double calculate(int a) {
return a * a;
}
};

但是不能通過lambda表達(dá)式訪問默認(rèn)方法,這樣的代碼沒法通過編譯;

IFormula formula = (a) -> sqrt(a * a);

帶有默認(rèn)實(shí)現(xiàn)的接口方法,是不能在 lambda 表達(dá)式中訪問的,上面這段代碼將無法被編譯通過。

六、內(nèi)置的函數(shù)式接口

JDK 1.8 API 包含了很多內(nèi)置的函數(shù)式接口。其中就包括我們在老版本中經(jīng)常見到的 Comparator 和 Runnable,Java 8 為他們都添加了 @FunctionalInterface 注解,以用來支持 Lambda 表達(dá)式。

例如我們舊版本的Jdk中常用的 Comparator 和 Runnable 外,還有一些新的函數(shù)式接口,可以通過函數(shù)注解實(shí)現(xiàn)Lamdba支持,它們很多都借鑒于知名的 Google Guava 庫。

即使你已經(jīng)熟悉這個(gè)類庫,也應(yīng)該密切關(guān)注那些接口是如何通過一些有用的方法擴(kuò)展來擴(kuò)展的:

1. Predicate 斷言

Predicate 是一個(gè)可以指定入?yún)㈩愋停⒎祷?boolean 值的函數(shù)式接口。它內(nèi)部提供了一些帶有默認(rèn)實(shí)現(xiàn)的方法,可以 被用來組合一個(gè)復(fù)雜的邏輯判斷(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否定相當(dāng)于!true

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
}
2. Functions

Function 函數(shù)式接口的作用是,我們可以為其提供一個(gè)原料,他給生產(chǎn)一個(gè)最終的產(chǎn)品。通過它提供的默認(rèn)方法,組合,鏈行處理(compose, andThen):

@Test
public void test12() {
Function<String, Integer> toInteger = Integer::valueOf; //轉(zhuǎn)Integer
Function<String, String> backToString = toInteger.andThen(String::valueOf); //轉(zhuǎn)String
Function<String, String> afterToStartsWith = backToString.andThen(new Something()::startsWith); //截取第一位
String apply = afterToStartsWith.apply("123");// "123"
System.out.println(apply);
}
3. Suppliers

Supplier 與 Function 不同,它不接受入?yún)ⅲ苯訛槲覀兩a(chǎn)一個(gè)指定的結(jié)果,有點(diǎn)像生產(chǎn)者模式:

@Test
public void test13() {
Supplier<Person> personSupplier0 = Person::new;
personSupplier0.get(); // new Person
Supplier<String> personSupplier1 = Something::test01; //這個(gè)test方法是靜態(tài)的,且無入?yún)?br /> personSupplier1.get(); // hi

Supplier<String> personSupplier2 = new Something()::test02;
}
4. Consumers

對于 Consumer,我們需要提供入?yún)ⅲ脕肀幌M(fèi),如下面這段示例代碼:

@Test
public void test14() {
// 參照物,方便知道下面的Lamdba表達(dá)式寫法
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; // 也可以通過定義類和方法的方式去調(diào)用,這樣才是實(shí)際開發(fā)的姿勢
greeter03.accept(new Person("Luke", "Skywalker")); //Hello, Luke
}
5. Comparators

Comparator 在 Java 8 之前是使用比較普遍的。Java 8 中除了將其升級成了函數(shù)式接口,還為它拓展了一些默認(rèn)方法:

@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
}
七、Optionals

第一,Optional 它不是一個(gè)函數(shù)式接口,設(shè)計(jì)它的目的是為了防止空指針異常(NullPointerException),要知道在 Java 編程中,空指針異常可是臭名昭著的。

讓我們來快速了解一下 Optional 要如何使用!你可以將 Optional 看做是包裝對象(可能是 null, 也有可能非 null)的容器。當(dāng)你定義了

一個(gè)方法,這個(gè)方法返回的對象可能是空,也有可能非空的時(shí)候,你就可以考慮用 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 流

什么是 Stream 流?

簡單來說,我們可以使用 java.util.Stream 對一個(gè)包含一個(gè)或多個(gè)元素的**做各種**作。這些**作可能是 中間**作 亦或是 終端**作。 終端**作會返回一個(gè)結(jié)果,而中間**作會返回一個(gè) Stream 流。

需要注意的是,你只能對實(shí)現(xiàn)了 java.util.Collection 接口的類做流的**作。

Stream 流支持同步執(zhí)行,也支持并發(fā)執(zhí)行。

注意:Map不支持Stream流,但是他的key和value是支持的!

讓我們先看看Stream流是如何工作的。第一,我們以字符串列表的形式創(chuàng)建一個(gè)示例;

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");
1. Filter 過濾

Filter 的入?yún)⑹且粋€(gè) Predicate, 上面已經(jīng)說到,Predicate 是一個(gè)斷言的中間**作,它能夠幫我們篩選出我們需要的**元素。它的返參同樣 是一個(gè) Stream 流,我們可以通過 foreach 終端**作,來打印被篩選的元素:

@Test
public void test17(){
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
}
2. Sorted 排序

Sorted 同樣是一個(gè)中間**作,它的返參是一個(gè) Stream 流。另外,我們可以傳入一個(gè) Comparator 用來自定義排序,如果不傳,則使用默認(rèn)的排序規(guī)則。

@Test
public void test18() {
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
}

注意;這個(gè)sorted 只是做了一個(gè)排序的視圖進(jìn)行輸出,實(shí)際沒有將List內(nèi)的數(shù)據(jù)進(jìn)行排序

System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
3. Map 轉(zhuǎn)換

中間**作映射通過給定的函數(shù)將每個(gè)元素轉(zhuǎn)換為另一個(gè)對象。例如下面的示例,通過 map 我們將每一個(gè) string 轉(zhuǎn)成大寫:

@Test
public void test19(){
stringCollection
.stream()
.map(String::toUpperCase)
.sorted(Comparator.reverseOrder()) //等同于(a, b) -> b.compareTo(a)
.forEach(System.out::println);
}

這個(gè)可以用做DTO數(shù)據(jù)對象轉(zhuǎn)換,領(lǐng)域驅(qū)動設(shè)計(jì)開發(fā)中將DTO轉(zhuǎn)為DO向后臺傳輸。

4. Match 匹配

顧名思義,match 用來做匹配**作,它的返回值是一個(gè) boolean 類型。通過 match, 我們可以方便的驗(yàn)證一個(gè) list 中是否存在某個(gè)類型的元素。

@Test
public void test20(){
// anyMatch:驗(yàn)證 list 中 string 是否有以 a 開頭的, 匹配到第一個(gè),即返回 true
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
// allMatch:驗(yàn)證 list 中 string 是否都是以 a 開頭的
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
// noneMatch:驗(yàn)證 list 中 string 是否都不是以 z 開頭的
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
}
5. Count 計(jì)數(shù)

count 是一個(gè)終端**作,它能夠統(tǒng)計(jì) stream 流中的元素總數(shù),返回值是 long 類型。

@Test
public void test21() {
// count:先對 list 中字符串開頭為 b 進(jìn)行過濾,讓后統(tǒng)計(jì)數(shù)量
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
}
6. Reduce

Reduce 中文翻譯為:減少、縮小。通過入?yún)⒌?Function,我們能夠?qū)?list 歸約成一個(gè)值。它的返回類型是 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
}
九、Parallel-Streams 并行流

如上所述,流可以是順序的,也可以是并行的。順序流上的**作在單個(gè)線程上執(zhí)行,而并行流上的**作在多個(gè)線程上并發(fā)執(zhí)行。

下面的示例演示了使用并行流來提高性能是多么的容易。親測提升了1倍性能!

第一,我們創(chuàng)建一個(gè)較大的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());
}
1. Sequential Sort 順序流排序@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();
// 納秒轉(zhuǎn)微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 – t0);
System.out.println(String.format("順序流排序耗時(shí): %d ms", millis));
//順序流排序耗時(shí): 712 ms
}
2. Parallel Sort 并行流排序@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
}

如您所見,這兩個(gè)代碼片段幾乎相同,但并行排序大約快50%。您只需將stream()更改為parallelStream()。

十、Map **

如前所講,Map是不支持 Stream 流的,因?yàn)?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 是否已經(jīng)存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, "val" + i);
}
// forEach 可以很方便地對 map 進(jìn)行遍歷**作
map.forEach((key, value) -> System.out.println(value));
}

之后我們做一個(gè)Map對象的轉(zhuǎn)換輸出;(定義兩個(gè)類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 是否已經(jīng)存在,存在則直接返回 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() 外,我們還可以很方便地對某個(gè) key 的值做相關(guān)**作:

@Test
public void test27() {
// 如下:對 key 為 3 的值,內(nèi)部會先判斷值是否存在,存在,則做 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(), 當(dāng) key 不存在時(shí),才會做相關(guān)處理
// 如下:先判斷 key 為 23 的元素是否存在,不存在,則添加
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true

// 先判斷 key 為 3 的元素是否存在,存在,則不做任何處理
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
}

關(guān)于刪除**作,JDK 8 中提供了能夠新的 remove() API:

@Test
public void test28() {
map.remove(3, "val3");
map.get(3); // val33

map.remove(3, "val33");
map.get(3); // null
}

如上代碼,只有當(dāng)給定的 key 和 value 完全匹配時(shí),才會執(zhí)行刪除**作。

關(guān)于添加方法,JDK 8 中提供了帶有默認(rèn)值的 getOrDefault() 方法:

@Test
public void test29() {
// 若 key 42 不存在,則返回 not found
map.getOrDefault(42, "not found"); // not found
}

對于 value 的合并**作也變得更加簡單:

@Test
public void test30() {
// merge 方法,會先判斷進(jìn)行合并的 key 是否存在,不存在,則會添加元素
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
// 若 key 的元素存在,則對 value 執(zhí)行拼接**作
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
}
十一、日期 Date API

Java 8 中在包 java.time 下添加了新的日期 API. 它和 Joda-Time 庫相似,但又不完全相同。接下來,我會通過一些示例代碼介紹一下新 API 中 最關(guān)鍵的特性:

1. Clock

Clock 提供對當(dāng)前日期和時(shí)間的訪問。我們可以利用它來替代 System.currentTimeMillis() 方法。另外,通過 clock.instant() 能夠獲取一個(gè) instant 實(shí)例, 此實(shí)例能夠方便地轉(zhuǎn)換成老版本中的 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
}
2. Timezones 時(shí)區(qū)

ZoneId 代表時(shí)區(qū)類。通過靜態(tài)工廠方法方便地獲取它,入?yún)⑽覀兛梢詡魅肽硞€(gè)時(shí)區(qū)編碼。另外,時(shí)區(qū)類還定義了一個(gè)偏移量,用來在當(dāng)前時(shí)刻或某時(shí)間 與目標(biāo)時(shí)區(qū)時(shí)間之間進(jìn)行轉(zhuǎn)換。

@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/Ta**ania, 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]
}
3. LocalTime

LocalTime 表示一個(gè)沒有指定時(shí)區(qū)的時(shí)間類,例如,10 p.m.或者 17:30:15,下面示例代碼中,將會使用上面創(chuàng)建的 時(shí)區(qū)對象創(chuàng)建兩個(gè) LocalTime。第二我們會比較兩個(gè)時(shí)間,并計(jì)算它們之間的小時(shí)和分鐘的不同。

@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.i**efore(now2)); // false
long hour**etween = ChronoUnit.HOURS.between(now1, now2);
long minute**etween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hour**etween); // -3
System.out.println(minute**etween); // -239
}

LocalTime 提供多個(gè)靜態(tài)工廠方法,目的是為了簡化對時(shí)間對象實(shí)例的創(chuàng)建和**作,包括對時(shí)間字符串進(jìn)行解析的**作等。

@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
}
4. LocalDate

LocalDate 是一個(gè)日期對象,例如:2014-03-11。它和 LocalTime 一樣是個(gè) final 類型對象。下面的例子演示了如何通過加減日,月,年等來計(jì)算一個(gè)新的日期。

@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 實(shí)例。(和 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
}
5. LocalDateTime

LocalDateTime 是一個(gè)日期-時(shí)間對象。你也可以將其看成是 LocalDate 和 LocalTime 的結(jié)合體。**作上,也大致相同。

@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); // 十二月
// 獲取改時(shí)間是該天中的第幾分鐘
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
}

如果再加上的時(shí)區(qū)信息,LocalDateTime 還能夠被轉(zhuǎn)換成 Instance 實(shí)例。Instance 能夠被轉(zhuǎn)換成老版本中 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 一樣。除了使用預(yù)定義的格式以外,也可以自定義格式化輸出。

@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.

十二、Annotations 注解

Java8中的注釋是可重復(fù)的。讓我們直接深入到一個(gè)例子中來解決這個(gè)問題。{在SpringBoot的啟動類中就可以看到這中類型的注解}

第一,我們定義一個(gè)包裝器注釋,它包含一個(gè)實(shí)際注釋數(shù)組:

@Repeatable(Hints.class)
public @interface Hint {
String value();
}

public @interface Hints {
Hint[] value();
}

Java 8通過聲明注釋@Repeatable,使我們能夠使用同一類型的多個(gè)注釋。

第一種形態(tài):使用注解容器(老方法)

@Test
public void test40() {
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {
}
}

第二種形態(tài):使用可重復(fù)注解(新方法)

@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
}

java編譯器使用變量2隱式地在引擎蓋下設(shè)置@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.getAnnotation**yType(Hint.class
System.out.println(hints2.length); // 2
}

盡管我們絕對不會在 Person 類上聲明 @Hints 注解,但是它的信息仍然是可以通過 getAnnotation(Hints.class) 來讀取的。 并且,getAnnotation**yType 方**更方便,因?yàn)樗x予了所有 @Hints 注解標(biāo)注的方法直接的訪問權(quán)限。

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
綜上小編綜合來說jdk8的新特性包括了;Lambda、函數(shù)式接口、四餅調(diào)用::、內(nèi)置函數(shù)(斷言、Function、生產(chǎn)者、消費(fèi)者)、Stream流、Map**特性、日期、注解等合理的組合運(yùn)行新的特性可以減少很多的編碼量,同時(shí)讓代碼更加整潔在一些新的框架中SpringBoot里如果翻看源碼可以看到很多的新特性使用

拓展知識:

jdk1.7

1.56。由于springboot2.0以后不支持jdk1.7,所以只能自降springboot的版本由2.0降為1.56。當(dāng)選用jdk1.7構(gòu)建springboot項(xiàng)目時(shí)會提示,選用jdk1.8。

原創(chuàng)文章,作者:九賢生活小編,如若轉(zhuǎn)載,請注明出處:http://www.cddhlm.com/93017.html