久久亚洲精品国产精品_羞羞漫画在线版免费阅读网页漫画_国产精品久久久久久久久久久久_午夜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(文后有鏈接),在這個結構上繼續(xù)完善了說明和功能,做了41個單元測試案例,方便新人學習。以下內容很干,對于一個萌新小白來說,學習jdk1.8的新特性,基本看一遍就知道個7788了,在熟讀兩遍最后跟著寫一遍,那么在實際項目中就可以運用了。不過!新特性,雖然很好。但如果想用,那么自己一定要看看相對應的源碼并多練習,否則真的容易給自己搞暈,又很難閱讀。

零、回顧一個抽象類

在jdk1.8之前,因為接口里只能做方法定義不能有方法的實現,因此我們通常會在抽象類里面實現默認的方法{一般這個默認的方法是抽象后公用的方法,不需要每一個繼承者都去實現,只需調用即可}。就像下面這樣;

在定義的時候;

public abstract class AFormula {

abstract double calculate(int a);

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

}

在使用的時候;

@Test
public void test_00() {
AFormula aFormula = new AFormula() {
@Override
double calculate(int a) {
return a * a;
}
};
System.out.println(aFormula.calculate(2)); //求平方:4
System.out.println(aFormula.sqrt(2)); //求開方:1.4142135623730951
}
一、在接口中提供默認的方法實現(有點像抽象類)

在jdk1.8里面,不僅可以定義接口,還可以在接口中提供默認的實現。這一個小小的改變卻讓整個抽象設計都隨著改變了!

在定義的時候;{default 關鍵字必須}

public interface IFormula {

double calculate(int a);

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

}

在使用的時候(一);

@Test
public void test_01() {
IFormula formula = new IFormula() {
@Override
public double calculate(int a) {
return a * a;
}
};
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}

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

a; a是一個入參名稱,可以其他任何名字->a*a; 箭頭指向是具體的實現但是,這樣其實不太適合加日志了@Test
public void test_02() {
// 入參a 和 實現
IFormula formula = a -> a * a;
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}
二、Lambda 表達式

因為有接口中可以增加默認的方法實現,那么Java肯定是因為要簡化開發(fā)才出現的這么個設計。所以你會從各個我們以前的List、Set等等所有接口中看到默認的方法實現。

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

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 方法,入參是一個 List **,和一個 Comparator 比較器,以便對給定的 List **進行排序。上面的示例代碼創(chuàng)建了一個匿名內部類作為入參,這種類似的**作在我們日常的工作中隨處可見。

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

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

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

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

為了追求極致,我們還可以讓它再短點:{當然過你的實現不是一行代碼,那么不能這么干}

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

java.util.List **現在已經添加了 sort 方法。而且 Java 編譯器能夠根據類型推斷機制判斷出參數類型,這樣,你連入參的類型都可以省略啦,怎么樣,是不是感覺很**氣呢!

java.util.List.sort

default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}

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

names.sort(Comparator.reverseOrder());
三、函數式接口 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是如何進行類型推斷,并且找到對應的方法呢?

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

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

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

定義含有注解@FunctionalInterface的接口

@FunctionalInterface
public interface IConverter<F, T> {

T convert(F from);

}
先來一段傳統方式 & 簡單易懂哈,因為看習慣了IConverter<String, Integer> converter01 = new IConverter<String, Integer>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
稍微簡化下,化個妝 & (form),只有一個參數括號可以不要IConverter<String, Integer> converter02 = (from) -> {
return Integer.valueOf(from);
};
繼續(xù)簡化,因為他的實現只有一行代碼,可以更加簡短IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
還能短點,其實這個另類屬于下一段的內容了,先放這有個印象IConverter<Integer, String> converter04 = String::valueOf;
四、方法和構造函數的便捷應用

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

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

這四個點::的關鍵字,不只是可以引用方法和構造函數,還可以引用普通方法。

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

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

public class Person {
String firstName;
String lastName;

Person() {}

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

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

@FunctionalInterface
public interface IPersonFactory<P extends Person> {

P create(String firstName, String lastName);

}

現在就到了用四餅::的時候了;

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

提醒;工廠函數中依然只能有一個函數,否則會報錯

四餅::,可以讓我們直接引用到Person類的構造函數,第二 Java 編譯器能夠根據類的簽名選中正確的構造器去實現 PersonFactory.create 方法。

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

1. 訪問局部變量

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

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

但是這個num是不可變值,這樣改變值會報錯;

int num = 1;
IConverter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num = 3;

Variable used in lambda expression should be final or effectively final

另外在lambda表達式內部修改也是不允許的;

int num = 1;
IConverter<Integer, String> converter = (from) -> {
String value = String.valueOf(from + num);
num = 3;
return value;
};

Variable used in lambda expression should be final or effectively final

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

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

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. 訪問默認接口方法

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

public interface IFormula {

double calculate(int a);

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

}

當時,我們在接口中定義了一個帶有默認實現的 sqrt 求平方根方法,在匿名內部類中我們可以很方便的訪問此方法:

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

但是不能通過lambda表達式訪問默認方法,這樣的代碼沒法通過編譯;

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

帶有默認實現的接口方法,是不能在 lambda 表達式中訪問的,上面這段代碼將無法被編譯通過。

六、內置的函數式接口

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

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

即使你已經熟悉這個類庫,也應該密切關注那些接口是如何通過一些有用的方法擴展來擴展的:

1. Predicate 斷言

Predicate 是一個可以指定入參類型,并返回 boolean 值的函數式接口。它內部提供了一些帶有默認實現的方法,可以 被用來組合一個復雜的邏輯判斷(and, or, negate):

@Test
public void test11() {
Predicate<String> predicate = (s) -> s.length() > 0;

boolean foo0 = predicate.test("foo"); // true
boolean foo1 = predicate.negate().test("foo"); // negate否定相當于!true

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

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

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

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

Supplier 與 Function 不同,它不接受入參,直接為我們生產一個指定的結果,有點像生產者模式:

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

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

對于 Consumer,我們需要提供入參,用來被消費,如下面這段示例代碼:

@Test
public void test14() {
// 參照物,方便知道下面的Lamdba表達式寫法
Consumer<Person> greeter01 = new Consumer<Person>() {
@Override
public void accept(Person p) {
System.out.println("Hello, " + p.firstName);
}
};
Consumer<Person> greeter02 = (p) -> System.out.println("Hello, " + p.firstName);
greeter02.accept(new Person("Luke", "Skywalker")); //Hello, Luke
Consumer<Person> greeter03 = new MyConsumer<Person>()::accept; // 也可以通過定義類和方法的方式去調用,這樣才是實際開發(fā)的姿勢
greeter03.accept(new Person("Luke", "Skywalker")); //Hello, Luke
}
5. Comparators

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

@Test
public void test15(){
Comparator<Person> comparator01 = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Comparator<Person> comparator02 = Comparator.comparing(p -> p.firstName); //等同于上面的方式
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator01.compare(p1, p2); // > 0
comparator02.reversed().compare(p1, p2); // < 0
}
七、Optionals

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

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

一個方法,這個方法返回的對象可能是空,也有可能非空的時候,你就可以考慮用 Optional 來包裝它,這也是在 Java 8 被推薦使用的做法。

@Test
public void test16(){
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Optional<Person> optionalPerson = Optional.of(new Person());
optionalPerson.ifPresent(s -> System.out.println(s.firstName));
}
八、Stream 流

什么是 Stream 流?

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

需要注意的是,你只能對實現了 java.util.Collection 接口的類做流的**作。

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

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

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

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 的入參是一個 Predicate, 上面已經說到,Predicate 是一個斷言的中間**作,它能夠幫我們篩選出我們需要的**元素。它的返參同樣 是一個 Stream 流,我們可以通過 foreach 終端**作,來打印被篩選的元素:

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

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

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

注意;這個sorted 只是做了一個排序的視圖進行輸出,實際沒有將List內的數據進行排序

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

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

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

這個可以用做DTO數據對象轉換,領域驅動設計開發(fā)中將DTO轉為DO向后臺傳輸。

4. Match 匹配

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

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

count 是一個終端**作,它能夠統計 stream 流中的元素總數,返回值是 long 類型。

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

Reduce 中文翻譯為:減少、縮小。通過入參的 Function,我們能夠將 list 歸約成一個值。它的返回類型是 Optional 類型。

@Test
public void test22() {
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2
}
九、Parallel-Streams 并行流

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

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

第一,我們創(chuàng)建一個較大的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();
// 納秒轉微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 – t0);
System.out.println(String.format("順序流排序耗時: %d ms", millis));
//順序流排序耗時: 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
}

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

十、Map **

如前所講,Map是不支持 Stream 流的,因為 Map 接口并沒有像 Collection 接口那樣,定義了 stream() 方法。但是,我們可以對其 key, values, entry 使用 流**作,如 map.keySet().stream(), map.values().stream() 和 map.entrySet().stream().

另外, JDK 8 中對 map 提供了一些其他新特性:

@Test
public void test25() {
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 與老版不同的是,putIfAbent() 方法在 put 之前, 不用在寫if null continue了
// 會判斷 key 是否已經存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, "val" + i);
}
// forEach 可以很方便地對 map 進行遍歷**作
map.forEach((key, value) -> System.out.println(value));
}

之后我們做一個Map對象的轉換輸出;(定義兩個類BeanA、BeanB)

@Test
public void test26() {
Map<Integer, BeanA> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 與老版不同的是,putIfAbent() 方法在 put 之前, 不用在寫if null continue了
// 會判斷 key 是否已經存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, new BeanA(i, "明明" + i, i + 20, "89021839021830912809" + i));
}
Stream<BeanB> beanBStream00 = map.values().stream().map(new Function<BeanA, BeanB>() {
@Override
public BeanB apply(BeanA beanA) {
return new BeanB(beanA.getName(), beanA.getAge());
}
});
Stream<BeanB> beanBStream01 = map.values().stream().map(beanA -> new BeanB(beanA.getName(), beanA.getAge()));
beanBStream01.forEach(System.out::println);
}

除了上面的 putIfAbsent() 和 forEach() 外,我們還可以很方便地對某個 key 的值做相關**作:

@Test
public void test27() {
// 如下:對 key 為 3 的值,內部會先判斷值是否存在,存在,則做 value + key 的拼接**作
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33

// 先判斷 key 為 9 的元素是否存在,存在,則做刪除**作
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false

// computeIfAbsent(), 當 key 不存在時,才會做相關處理
// 如下:先判斷 key 為 23 的元素是否存在,不存在,則添加
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true

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

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

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

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

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

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

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

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

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

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

1. Clock

Clock 提供對當前日期和時間的訪問。我們可以利用它來替代 System.currentTimeMillis() 方法。另外,通過 clock.instant() 能夠獲取一個 instant 實例, 此實例能夠方便地轉換成老版本中的 java.util.Date 對象。

@Test
public void test31(){
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // 老版本 java.util.Date
}
2. Timezones 時區(qū)

ZoneId 代表時區(qū)類。通過靜態(tài)工廠方法方便地獲取它,入參我們可以傳入某個時區(qū)編碼。另外,時區(qū)類還定義了一個偏移量,用來在當前時刻或某時間 與目標時區(qū)時間之間進行轉換。

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

@Test
public void test33(){
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.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 提供多個靜態(tài)工廠方法,目的是為了簡化對時間對象實例的創(chuàng)建和**作,包括對時間字符串進行解析的**作等。

@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 是一個日期對象,例如:2014-03-11。它和 LocalTime 一樣是個 final 類型對象。下面的例子演示了如何通過加減日,月,年等來計算一個新的日期。

@Test
public void test35(){
LocalDate today = LocalDate.now();
// 今天加一天
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
// 明天減兩天
LocalDate yesterday = tomorrow.minusDays(2);
// 2014 年七月的第四天
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // 星期五
}

也可以直接解析日期字符串,生成 LocalDate 實例。(和 LocalTime **作一樣簡單)

@Test
public void test36(){
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
}
5. LocalDateTime

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

@Test
public void test37(){
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // 星期三
Month month = sylvester.getMonth();
System.out.println(month); // 十二月
// 獲取改時間是該天中的第幾分鐘
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
}

如果再加上的時區(qū)信息,LocalDateTime 還能夠被轉換成 Instance 實例。Instance 能夠被轉換成老版本中 java.util.Date 對象。

@Test
public void test38(){
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
}

格式化 LocalDateTime 對象就和格式化 LocalDate 或者 LocalTime 一樣。除了使用預定義的格式以外,也可以自定義格式化輸出。

@Test
public void test39(){
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy – HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 – 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 – 07:13
}

Unlike java.text.NumberFormat the new DateTimeFormatter is immutable and thread-safe.

For details on the pattern syntax read here.

十二、Annotations 注解

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

第一,我們定義一個包裝器注釋,它包含一個實際注釋數組:

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

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

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

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

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

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

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

java編譯器使用變量2隱式地在引擎蓋下設置@Hints注釋。這對于通過反射讀取注釋信息很重要。

@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint); // null
Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length); // 2
Hint[] hints2 = Person.class.getAnnotation**yType(Hint.class
System.out.println(hints2.length); // 2
}

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

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

拓展知識:

jdk1.7

1.56。由于springboot2.0以后不支持jdk1.7,所以只能自降springboot的版本由2.0降為1.56。當選用jdk1.7構建springboot項目時會提示,選用jdk1.8。

前沿拓展:

jdk1.7

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


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

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

前言

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

零、回顧一個抽象類

在jdk1.8之前,因為接口里只能做方法定義不能有方法的實現,因此我們通常會在抽象類里面實現默認的方法{一般這個默認的方法是抽象后公用的方法,不需要每一個繼承者都去實現,只需調用即可}。就像下面這樣;

在定義的時候;

public abstract class AFormula {

abstract double calculate(int a);

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

}

在使用的時候;

@Test
public void test_00() {
AFormula aFormula = new AFormula() {
@Override
double calculate(int a) {
return a * a;
}
};
System.out.println(aFormula.calculate(2)); //求平方:4
System.out.println(aFormula.sqrt(2)); //求開方:1.4142135623730951
}
一、在接口中提供默認的方法實現(有點像抽象類)

在jdk1.8里面,不僅可以定義接口,還可以在接口中提供默認的實現。這一個小小的改變卻讓整個抽象設計都隨著改變了!

在定義的時候;{default 關鍵字必須}

public interface IFormula {

double calculate(int a);

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

}

在使用的時候(一);

@Test
public void test_01() {
IFormula formula = new IFormula() {
@Override
public double calculate(int a) {
return a * a;
}
};
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}

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

a; a是一個入參名稱,可以其他任何名字->a*a; 箭頭指向是具體的實現但是,這樣其實不太適合加日志了@Test
public void test_02() {
// 入參a 和 實現
IFormula formula = a -> a * a;
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}
二、Lambda 表達式

因為有接口中可以增加默認的方法實現,那么Java肯定是因為要簡化開發(fā)才出現的這么個設計。所以你會從各個我們以前的List、Set等等所有接口中看到默認的方法實現。

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

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 方法,入參是一個 List **,和一個 Comparator 比較器,以便對給定的 List **進行排序。上面的示例代碼創(chuàng)建了一個匿名內部類作為入參,這種類似的**作在我們日常的工作中隨處可見。

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

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

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

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

為了追求極致,我們還可以讓它再短點:{當然過你的實現不是一行代碼,那么不能這么干}

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

java.util.List **現在已經添加了 sort 方法。而且 Java 編譯器能夠根據類型推斷機制判斷出參數類型,這樣,你連入參的類型都可以省略啦,怎么樣,是不是感覺很**氣呢!

java.util.List.sort

default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}

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

names.sort(Comparator.reverseOrder());
三、函數式接口 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是如何進行類型推斷,并且找到對應的方法呢?

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

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

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

定義含有注解@FunctionalInterface的接口

@FunctionalInterface
public interface IConverter<F, T> {

T convert(F from);

}
先來一段傳統方式 & 簡單易懂哈,因為看習慣了IConverter<String, Integer> converter01 = new IConverter<String, Integer>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
稍微簡化下,化個妝 & (form),只有一個參數括號可以不要IConverter<String, Integer> converter02 = (from) -> {
return Integer.valueOf(from);
};
繼續(xù)簡化,因為他的實現只有一行代碼,可以更加簡短IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
還能短點,其實這個另類屬于下一段的內容了,先放這有個印象IConverter<Integer, String> converter04 = String::valueOf;
四、方法和構造函數的便捷應用

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

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

這四個點::的關鍵字,不只是可以引用方法和構造函數,還可以引用普通方法。

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

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

public class Person {
String firstName;
String lastName;

Person() {}

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

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

@FunctionalInterface
public interface IPersonFactory<P extends Person> {

P create(String firstName, String lastName);

}

現在就到了用四餅::的時候了;

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

提醒;工廠函數中依然只能有一個函數,否則會報錯

四餅::,可以讓我們直接引用到Person類的構造函數,第二 Java 編譯器能夠根據類的簽名選中正確的構造器去實現 PersonFactory.create 方法。

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

1. 訪問局部變量

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

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

但是這個num是不可變值,這樣改變值會報錯;

int num = 1;
IConverter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num = 3;

Variable used in lambda expression should be final or effectively final

另外在lambda表達式內部修改也是不允許的;

int num = 1;
IConverter<Integer, String> converter = (from) -> {
String value = String.valueOf(from + num);
num = 3;
return value;
};

Variable used in lambda expression should be final or effectively final

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

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

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. 訪問默認接口方法

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

public interface IFormula {

double calculate(int a);

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

}

當時,我們在接口中定義了一個帶有默認實現的 sqrt 求平方根方法,在匿名內部類中我們可以很方便的訪問此方法:

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

但是不能通過lambda表達式訪問默認方法,這樣的代碼沒法通過編譯;

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

帶有默認實現的接口方法,是不能在 lambda 表達式中訪問的,上面這段代碼將無法被編譯通過。

六、內置的函數式接口

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

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

即使你已經熟悉這個類庫,也應該密切關注那些接口是如何通過一些有用的方法擴展來擴展的:

1. Predicate 斷言

Predicate 是一個可以指定入參類型,并返回 boolean 值的函數式接口。它內部提供了一些帶有默認實現的方法,可以 被用來組合一個復雜的邏輯判斷(and, or, negate):

@Test
public void test11() {
Predicate<String> predicate = (s) -> s.length() > 0;

boolean foo0 = predicate.test("foo"); // true
boolean foo1 = predicate.negate().test("foo"); // negate否定相當于!true

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

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

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

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

Supplier 與 Function 不同,它不接受入參,直接為我們生產一個指定的結果,有點像生產者模式:

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

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

對于 Consumer,我們需要提供入參,用來被消費,如下面這段示例代碼:

@Test
public void test14() {
// 參照物,方便知道下面的Lamdba表達式寫法
Consumer<Person> greeter01 = new Consumer<Person>() {
@Override
public void accept(Person p) {
System.out.println("Hello, " + p.firstName);
}
};
Consumer<Person> greeter02 = (p) -> System.out.println("Hello, " + p.firstName);
greeter02.accept(new Person("Luke", "Skywalker")); //Hello, Luke
Consumer<Person> greeter03 = new MyConsumer<Person>()::accept; // 也可以通過定義類和方法的方式去調用,這樣才是實際開發(fā)的姿勢
greeter03.accept(new Person("Luke", "Skywalker")); //Hello, Luke
}
5. Comparators

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

@Test
public void test15(){
Comparator<Person> comparator01 = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Comparator<Person> comparator02 = Comparator.comparing(p -> p.firstName); //等同于上面的方式
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator01.compare(p1, p2); // > 0
comparator02.reversed().compare(p1, p2); // < 0
}
七、Optionals

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

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

一個方法,這個方法返回的對象可能是空,也有可能非空的時候,你就可以考慮用 Optional 來包裝它,這也是在 Java 8 被推薦使用的做法。

@Test
public void test16(){
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Optional<Person> optionalPerson = Optional.of(new Person());
optionalPerson.ifPresent(s -> System.out.println(s.firstName));
}
八、Stream 流

什么是 Stream 流?

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

需要注意的是,你只能對實現了 java.util.Collection 接口的類做流的**作。

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

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

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

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 的入參是一個 Predicate, 上面已經說到,Predicate 是一個斷言的中間**作,它能夠幫我們篩選出我們需要的**元素。它的返參同樣 是一個 Stream 流,我們可以通過 foreach 終端**作,來打印被篩選的元素:

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

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

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

注意;這個sorted 只是做了一個排序的視圖進行輸出,實際沒有將List內的數據進行排序

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

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

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

這個可以用做DTO數據對象轉換,領域驅動設計開發(fā)中將DTO轉為DO向后臺傳輸。

4. Match 匹配

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

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

count 是一個終端**作,它能夠統計 stream 流中的元素總數,返回值是 long 類型。

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

Reduce 中文翻譯為:減少、縮小。通過入參的 Function,我們能夠將 list 歸約成一個值。它的返回類型是 Optional 類型。

@Test
public void test22() {
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2
}
九、Parallel-Streams 并行流

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

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

第一,我們創(chuàng)建一個較大的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();
// 納秒轉微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 – t0);
System.out.println(String.format("順序流排序耗時: %d ms", millis));
//順序流排序耗時: 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
}

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

十、Map **

如前所講,Map是不支持 Stream 流的,因為 Map 接口并沒有像 Collection 接口那樣,定義了 stream() 方法。但是,我們可以對其 key, values, entry 使用 流**作,如 map.keySet().stream(), map.values().stream() 和 map.entrySet().stream().

另外, JDK 8 中對 map 提供了一些其他新特性:

@Test
public void test25() {
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 與老版不同的是,putIfAbent() 方法在 put 之前, 不用在寫if null continue了
// 會判斷 key 是否已經存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, "val" + i);
}
// forEach 可以很方便地對 map 進行遍歷**作
map.forEach((key, value) -> System.out.println(value));
}

之后我們做一個Map對象的轉換輸出;(定義兩個類BeanA、BeanB)

@Test
public void test26() {
Map<Integer, BeanA> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 與老版不同的是,putIfAbent() 方法在 put 之前, 不用在寫if null continue了
// 會判斷 key 是否已經存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, new BeanA(i, "明明" + i, i + 20, "89021839021830912809" + i));
}
Stream<BeanB> beanBStream00 = map.values().stream().map(new Function<BeanA, BeanB>() {
@Override
public BeanB apply(BeanA beanA) {
return new BeanB(beanA.getName(), beanA.getAge());
}
});
Stream<BeanB> beanBStream01 = map.values().stream().map(beanA -> new BeanB(beanA.getName(), beanA.getAge()));
beanBStream01.forEach(System.out::println);
}

除了上面的 putIfAbsent() 和 forEach() 外,我們還可以很方便地對某個 key 的值做相關**作:

@Test
public void test27() {
// 如下:對 key 為 3 的值,內部會先判斷值是否存在,存在,則做 value + key 的拼接**作
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33

// 先判斷 key 為 9 的元素是否存在,存在,則做刪除**作
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false

// computeIfAbsent(), 當 key 不存在時,才會做相關處理
// 如下:先判斷 key 為 23 的元素是否存在,不存在,則添加
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true

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

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

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

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

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

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

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

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

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

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

1. Clock

Clock 提供對當前日期和時間的訪問。我們可以利用它來替代 System.currentTimeMillis() 方法。另外,通過 clock.instant() 能夠獲取一個 instant 實例, 此實例能夠方便地轉換成老版本中的 java.util.Date 對象。

@Test
public void test31(){
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // 老版本 java.util.Date
}
2. Timezones 時區(qū)

ZoneId 代表時區(qū)類。通過靜態(tài)工廠方法方便地獲取它,入參我們可以傳入某個時區(qū)編碼。另外,時區(qū)類還定義了一個偏移量,用來在當前時刻或某時間 與目標時區(qū)時間之間進行轉換。

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

@Test
public void test33(){
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.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 提供多個靜態(tài)工廠方法,目的是為了簡化對時間對象實例的創(chuàng)建和**作,包括對時間字符串進行解析的**作等。

@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 是一個日期對象,例如:2014-03-11。它和 LocalTime 一樣是個 final 類型對象。下面的例子演示了如何通過加減日,月,年等來計算一個新的日期。

@Test
public void test35(){
LocalDate today = LocalDate.now();
// 今天加一天
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
// 明天減兩天
LocalDate yesterday = tomorrow.minusDays(2);
// 2014 年七月的第四天
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // 星期五
}

也可以直接解析日期字符串,生成 LocalDate 實例。(和 LocalTime **作一樣簡單)

@Test
public void test36(){
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
}
5. LocalDateTime

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

@Test
public void test37(){
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // 星期三
Month month = sylvester.getMonth();
System.out.println(month); // 十二月
// 獲取改時間是該天中的第幾分鐘
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
}

如果再加上的時區(qū)信息,LocalDateTime 還能夠被轉換成 Instance 實例。Instance 能夠被轉換成老版本中 java.util.Date 對象。

@Test
public void test38(){
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
}

格式化 LocalDateTime 對象就和格式化 LocalDate 或者 LocalTime 一樣。除了使用預定義的格式以外,也可以自定義格式化輸出。

@Test
public void test39(){
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy – HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 – 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 – 07:13
}

Unlike java.text.NumberFormat the new DateTimeFormatter is immutable and thread-safe.

For details on the pattern syntax read here.

十二、Annotations 注解

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

第一,我們定義一個包裝器注釋,它包含一個實際注釋數組:

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

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

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

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

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

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

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

java編譯器使用變量2隱式地在引擎蓋下設置@Hints注釋。這對于通過反射讀取注釋信息很重要。

@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint); // null
Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length); // 2
Hint[] hints2 = Person.class.getAnnotation**yType(Hint.class
System.out.println(hints2.length); // 2
}

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

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

拓展知識:

jdk1.7

1.56。由于springboot2.0以后不支持jdk1.7,所以只能自降springboot的版本由2.0降為1.56。當選用jdk1.7構建springboot項目時會提示,選用jdk1.8。

前沿拓展:

jdk1.7

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


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

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

前言

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

零、回顧一個抽象類

在jdk1.8之前,因為接口里只能做方法定義不能有方法的實現,因此我們通常會在抽象類里面實現默認的方法{一般這個默認的方法是抽象后公用的方法,不需要每一個繼承者都去實現,只需調用即可}。就像下面這樣;

在定義的時候;

public abstract class AFormula {

abstract double calculate(int a);

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

}

在使用的時候;

@Test
public void test_00() {
AFormula aFormula = new AFormula() {
@Override
double calculate(int a) {
return a * a;
}
};
System.out.println(aFormula.calculate(2)); //求平方:4
System.out.println(aFormula.sqrt(2)); //求開方:1.4142135623730951
}
一、在接口中提供默認的方法實現(有點像抽象類)

在jdk1.8里面,不僅可以定義接口,還可以在接口中提供默認的實現。這一個小小的改變卻讓整個抽象設計都隨著改變了!

在定義的時候;{default 關鍵字必須}

public interface IFormula {

double calculate(int a);

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

}

在使用的時候(一);

@Test
public void test_01() {
IFormula formula = new IFormula() {
@Override
public double calculate(int a) {
return a * a;
}
};
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}

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

a; a是一個入參名稱,可以其他任何名字->a*a; 箭頭指向是具體的實現但是,這樣其實不太適合加日志了@Test
public void test_02() {
// 入參a 和 實現
IFormula formula = a -> a * a;
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}
二、Lambda 表達式

因為有接口中可以增加默認的方法實現,那么Java肯定是因為要簡化開發(fā)才出現的這么個設計。所以你會從各個我們以前的List、Set等等所有接口中看到默認的方法實現。

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

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 方法,入參是一個 List **,和一個 Comparator 比較器,以便對給定的 List **進行排序。上面的示例代碼創(chuàng)建了一個匿名內部類作為入參,這種類似的**作在我們日常的工作中隨處可見。

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

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

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

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

為了追求極致,我們還可以讓它再短點:{當然過你的實現不是一行代碼,那么不能這么干}

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

java.util.List **現在已經添加了 sort 方法。而且 Java 編譯器能夠根據類型推斷機制判斷出參數類型,這樣,你連入參的類型都可以省略啦,怎么樣,是不是感覺很**氣呢!

java.util.List.sort

default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}

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

names.sort(Comparator.reverseOrder());
三、函數式接口 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是如何進行類型推斷,并且找到對應的方法呢?

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

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

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

定義含有注解@FunctionalInterface的接口

@FunctionalInterface
public interface IConverter<F, T> {

T convert(F from);

}
先來一段傳統方式 & 簡單易懂哈,因為看習慣了IConverter<String, Integer> converter01 = new IConverter<String, Integer>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
稍微簡化下,化個妝 & (form),只有一個參數括號可以不要IConverter<String, Integer> converter02 = (from) -> {
return Integer.valueOf(from);
};
繼續(xù)簡化,因為他的實現只有一行代碼,可以更加簡短IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
還能短點,其實這個另類屬于下一段的內容了,先放這有個印象IConverter<Integer, String> converter04 = String::valueOf;
四、方法和構造函數的便捷應用

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

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

這四個點::的關鍵字,不只是可以引用方法和構造函數,還可以引用普通方法。

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

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

public class Person {
String firstName;
String lastName;

Person() {}

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

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

@FunctionalInterface
public interface IPersonFactory<P extends Person> {

P create(String firstName, String lastName);

}

現在就到了用四餅::的時候了;

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

提醒;工廠函數中依然只能有一個函數,否則會報錯

四餅::,可以讓我們直接引用到Person類的構造函數,第二 Java 編譯器能夠根據類的簽名選中正確的構造器去實現 PersonFactory.create 方法。

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

1. 訪問局部變量

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

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

但是這個num是不可變值,這樣改變值會報錯;

int num = 1;
IConverter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num = 3;

Variable used in lambda expression should be final or effectively final

另外在lambda表達式內部修改也是不允許的;

int num = 1;
IConverter<Integer, String> converter = (from) -> {
String value = String.valueOf(from + num);
num = 3;
return value;
};

Variable used in lambda expression should be final or effectively final

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

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

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. 訪問默認接口方法

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

public interface IFormula {

double calculate(int a);

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

}

當時,我們在接口中定義了一個帶有默認實現的 sqrt 求平方根方法,在匿名內部類中我們可以很方便的訪問此方法:

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

但是不能通過lambda表達式訪問默認方法,這樣的代碼沒法通過編譯;

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

帶有默認實現的接口方法,是不能在 lambda 表達式中訪問的,上面這段代碼將無法被編譯通過。

六、內置的函數式接口

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

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

即使你已經熟悉這個類庫,也應該密切關注那些接口是如何通過一些有用的方法擴展來擴展的:

1. Predicate 斷言

Predicate 是一個可以指定入參類型,并返回 boolean 值的函數式接口。它內部提供了一些帶有默認實現的方法,可以 被用來組合一個復雜的邏輯判斷(and, or, negate):

@Test
public void test11() {
Predicate<String> predicate = (s) -> s.length() > 0;

boolean foo0 = predicate.test("foo"); // true
boolean foo1 = predicate.negate().test("foo"); // negate否定相當于!true

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

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

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

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

Supplier 與 Function 不同,它不接受入參,直接為我們生產一個指定的結果,有點像生產者模式:

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

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

對于 Consumer,我們需要提供入參,用來被消費,如下面這段示例代碼:

@Test
public void test14() {
// 參照物,方便知道下面的Lamdba表達式寫法
Consumer<Person> greeter01 = new Consumer<Person>() {
@Override
public void accept(Person p) {
System.out.println("Hello, " + p.firstName);
}
};
Consumer<Person> greeter02 = (p) -> System.out.println("Hello, " + p.firstName);
greeter02.accept(new Person("Luke", "Skywalker")); //Hello, Luke
Consumer<Person> greeter03 = new MyConsumer<Person>()::accept; // 也可以通過定義類和方法的方式去調用,這樣才是實際開發(fā)的姿勢
greeter03.accept(new Person("Luke", "Skywalker")); //Hello, Luke
}
5. Comparators

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

@Test
public void test15(){
Comparator<Person> comparator01 = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Comparator<Person> comparator02 = Comparator.comparing(p -> p.firstName); //等同于上面的方式
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator01.compare(p1, p2); // > 0
comparator02.reversed().compare(p1, p2); // < 0
}
七、Optionals

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

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

一個方法,這個方法返回的對象可能是空,也有可能非空的時候,你就可以考慮用 Optional 來包裝它,這也是在 Java 8 被推薦使用的做法。

@Test
public void test16(){
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Optional<Person> optionalPerson = Optional.of(new Person());
optionalPerson.ifPresent(s -> System.out.println(s.firstName));
}
八、Stream 流

什么是 Stream 流?

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

需要注意的是,你只能對實現了 java.util.Collection 接口的類做流的**作。

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

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

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

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 的入參是一個 Predicate, 上面已經說到,Predicate 是一個斷言的中間**作,它能夠幫我們篩選出我們需要的**元素。它的返參同樣 是一個 Stream 流,我們可以通過 foreach 終端**作,來打印被篩選的元素:

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

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

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

注意;這個sorted 只是做了一個排序的視圖進行輸出,實際沒有將List內的數據進行排序

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

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

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

這個可以用做DTO數據對象轉換,領域驅動設計開發(fā)中將DTO轉為DO向后臺傳輸。

4. Match 匹配

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

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

count 是一個終端**作,它能夠統計 stream 流中的元素總數,返回值是 long 類型。

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

Reduce 中文翻譯為:減少、縮小。通過入參的 Function,我們能夠將 list 歸約成一個值。它的返回類型是 Optional 類型。

@Test
public void test22() {
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2
}
九、Parallel-Streams 并行流

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

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

第一,我們創(chuàng)建一個較大的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();
// 納秒轉微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 – t0);
System.out.println(String.format("順序流排序耗時: %d ms", millis));
//順序流排序耗時: 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
}

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

十、Map **

如前所講,Map是不支持 Stream 流的,因為 Map 接口并沒有像 Collection 接口那樣,定義了 stream() 方法。但是,我們可以對其 key, values, entry 使用 流**作,如 map.keySet().stream(), map.values().stream() 和 map.entrySet().stream().

另外, JDK 8 中對 map 提供了一些其他新特性:

@Test
public void test25() {
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 與老版不同的是,putIfAbent() 方法在 put 之前, 不用在寫if null continue了
// 會判斷 key 是否已經存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, "val" + i);
}
// forEach 可以很方便地對 map 進行遍歷**作
map.forEach((key, value) -> System.out.println(value));
}

之后我們做一個Map對象的轉換輸出;(定義兩個類BeanA、BeanB)

@Test
public void test26() {
Map<Integer, BeanA> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 與老版不同的是,putIfAbent() 方法在 put 之前, 不用在寫if null continue了
// 會判斷 key 是否已經存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, new BeanA(i, "明明" + i, i + 20, "89021839021830912809" + i));
}
Stream<BeanB> beanBStream00 = map.values().stream().map(new Function<BeanA, BeanB>() {
@Override
public BeanB apply(BeanA beanA) {
return new BeanB(beanA.getName(), beanA.getAge());
}
});
Stream<BeanB> beanBStream01 = map.values().stream().map(beanA -> new BeanB(beanA.getName(), beanA.getAge()));
beanBStream01.forEach(System.out::println);
}

除了上面的 putIfAbsent() 和 forEach() 外,我們還可以很方便地對某個 key 的值做相關**作:

@Test
public void test27() {
// 如下:對 key 為 3 的值,內部會先判斷值是否存在,存在,則做 value + key 的拼接**作
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33

// 先判斷 key 為 9 的元素是否存在,存在,則做刪除**作
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false

// computeIfAbsent(), 當 key 不存在時,才會做相關處理
// 如下:先判斷 key 為 23 的元素是否存在,不存在,則添加
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true

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

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

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

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

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

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

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

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

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

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

1. Clock

Clock 提供對當前日期和時間的訪問。我們可以利用它來替代 System.currentTimeMillis() 方法。另外,通過 clock.instant() 能夠獲取一個 instant 實例, 此實例能夠方便地轉換成老版本中的 java.util.Date 對象。

@Test
public void test31(){
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // 老版本 java.util.Date
}
2. Timezones 時區(qū)

ZoneId 代表時區(qū)類。通過靜態(tài)工廠方法方便地獲取它,入參我們可以傳入某個時區(qū)編碼。另外,時區(qū)類還定義了一個偏移量,用來在當前時刻或某時間 與目標時區(qū)時間之間進行轉換。

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

@Test
public void test33(){
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.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 提供多個靜態(tài)工廠方法,目的是為了簡化對時間對象實例的創(chuàng)建和**作,包括對時間字符串進行解析的**作等。

@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 是一個日期對象,例如:2014-03-11。它和 LocalTime 一樣是個 final 類型對象。下面的例子演示了如何通過加減日,月,年等來計算一個新的日期。

@Test
public void test35(){
LocalDate today = LocalDate.now();
// 今天加一天
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
// 明天減兩天
LocalDate yesterday = tomorrow.minusDays(2);
// 2014 年七月的第四天
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // 星期五
}

也可以直接解析日期字符串,生成 LocalDate 實例。(和 LocalTime **作一樣簡單)

@Test
public void test36(){
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
}
5. LocalDateTime

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

@Test
public void test37(){
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // 星期三
Month month = sylvester.getMonth();
System.out.println(month); // 十二月
// 獲取改時間是該天中的第幾分鐘
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
}

如果再加上的時區(qū)信息,LocalDateTime 還能夠被轉換成 Instance 實例。Instance 能夠被轉換成老版本中 java.util.Date 對象。

@Test
public void test38(){
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
}

格式化 LocalDateTime 對象就和格式化 LocalDate 或者 LocalTime 一樣。除了使用預定義的格式以外,也可以自定義格式化輸出。

@Test
public void test39(){
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy – HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 – 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 – 07:13
}

Unlike java.text.NumberFormat the new DateTimeFormatter is immutable and thread-safe.

For details on the pattern syntax read here.

十二、Annotations 注解

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

第一,我們定義一個包裝器注釋,它包含一個實際注釋數組:

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

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

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

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

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

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

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

java編譯器使用變量2隱式地在引擎蓋下設置@Hints注釋。這對于通過反射讀取注釋信息很重要。

@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint); // null
Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length); // 2
Hint[] hints2 = Person.class.getAnnotation**yType(Hint.class
System.out.println(hints2.length); // 2
}

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

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

拓展知識:

jdk1.7

1.56。由于springboot2.0以后不支持jdk1.7,所以只能自降springboot的版本由2.0降為1.56。當選用jdk1.7構建springboot項目時會提示,選用jdk1.8。

前沿拓展:

jdk1.7

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


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

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

前言

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

零、回顧一個抽象類

在jdk1.8之前,因為接口里只能做方法定義不能有方法的實現,因此我們通常會在抽象類里面實現默認的方法{一般這個默認的方法是抽象后公用的方法,不需要每一個繼承者都去實現,只需調用即可}。就像下面這樣;

在定義的時候;

public abstract class AFormula {

abstract double calculate(int a);

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

}

在使用的時候;

@Test
public void test_00() {
AFormula aFormula = new AFormula() {
@Override
double calculate(int a) {
return a * a;
}
};
System.out.println(aFormula.calculate(2)); //求平方:4
System.out.println(aFormula.sqrt(2)); //求開方:1.4142135623730951
}
一、在接口中提供默認的方法實現(有點像抽象類)

在jdk1.8里面,不僅可以定義接口,還可以在接口中提供默認的實現。這一個小小的改變卻讓整個抽象設計都隨著改變了!

在定義的時候;{default 關鍵字必須}

public interface IFormula {

double calculate(int a);

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

}

在使用的時候(一);

@Test
public void test_01() {
IFormula formula = new IFormula() {
@Override
public double calculate(int a) {
return a * a;
}
};
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}

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

a; a是一個入參名稱,可以其他任何名字->a*a; 箭頭指向是具體的實現但是,這樣其實不太適合加日志了@Test
public void test_02() {
// 入參a 和 實現
IFormula formula = a -> a * a;
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}
二、Lambda 表達式

因為有接口中可以增加默認的方法實現,那么Java肯定是因為要簡化開發(fā)才出現的這么個設計。所以你會從各個我們以前的List、Set等等所有接口中看到默認的方法實現。

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

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 方法,入參是一個 List **,和一個 Comparator 比較器,以便對給定的 List **進行排序。上面的示例代碼創(chuàng)建了一個匿名內部類作為入參,這種類似的**作在我們日常的工作中隨處可見。

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

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

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

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

為了追求極致,我們還可以讓它再短點:{當然過你的實現不是一行代碼,那么不能這么干}

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

java.util.List **現在已經添加了 sort 方法。而且 Java 編譯器能夠根據類型推斷機制判斷出參數類型,這樣,你連入參的類型都可以省略啦,怎么樣,是不是感覺很**氣呢!

java.util.List.sort

default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}

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

names.sort(Comparator.reverseOrder());
三、函數式接口 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是如何進行類型推斷,并且找到對應的方法呢?

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

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

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

定義含有注解@FunctionalInterface的接口

@FunctionalInterface
public interface IConverter<F, T> {

T convert(F from);

}
先來一段傳統方式 & 簡單易懂哈,因為看習慣了IConverter<String, Integer> converter01 = new IConverter<String, Integer>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
稍微簡化下,化個妝 & (form),只有一個參數括號可以不要IConverter<String, Integer> converter02 = (from) -> {
return Integer.valueOf(from);
};
繼續(xù)簡化,因為他的實現只有一行代碼,可以更加簡短IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
還能短點,其實這個另類屬于下一段的內容了,先放這有個印象IConverter<Integer, String> converter04 = String::valueOf;
四、方法和構造函數的便捷應用

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

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

這四個點::的關鍵字,不只是可以引用方法和構造函數,還可以引用普通方法。

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

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

public class Person {
String firstName;
String lastName;

Person() {}

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

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

@FunctionalInterface
public interface IPersonFactory<P extends Person> {

P create(String firstName, String lastName);

}

現在就到了用四餅::的時候了;

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

提醒;工廠函數中依然只能有一個函數,否則會報錯

四餅::,可以讓我們直接引用到Person類的構造函數,第二 Java 編譯器能夠根據類的簽名選中正確的構造器去實現 PersonFactory.create 方法。

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

1. 訪問局部變量

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

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

但是這個num是不可變值,這樣改變值會報錯;

int num = 1;
IConverter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num = 3;

Variable used in lambda expression should be final or effectively final

另外在lambda表達式內部修改也是不允許的;

int num = 1;
IConverter<Integer, String> converter = (from) -> {
String value = String.valueOf(from + num);
num = 3;
return value;
};

Variable used in lambda expression should be final or effectively final

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

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

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. 訪問默認接口方法

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

public interface IFormula {

double calculate(int a);

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

}

當時,我們在接口中定義了一個帶有默認實現的 sqrt 求平方根方法,在匿名內部類中我們可以很方便的訪問此方法:

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

但是不能通過lambda表達式訪問默認方法,這樣的代碼沒法通過編譯;

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

帶有默認實現的接口方法,是不能在 lambda 表達式中訪問的,上面這段代碼將無法被編譯通過。

六、內置的函數式接口

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

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

即使你已經熟悉這個類庫,也應該密切關注那些接口是如何通過一些有用的方法擴展來擴展的:

1. Predicate 斷言

Predicate 是一個可以指定入參類型,并返回 boolean 值的函數式接口。它內部提供了一些帶有默認實現的方法,可以 被用來組合一個復雜的邏輯判斷(and, or, negate):

@Test
public void test11() {
Predicate<String> predicate = (s) -> s.length() > 0;

boolean foo0 = predicate.test("foo"); // true
boolean foo1 = predicate.negate().test("foo"); // negate否定相當于!true

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

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

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

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

Supplier 與 Function 不同,它不接受入參,直接為我們生產一個指定的結果,有點像生產者模式:

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

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

對于 Consumer,我們需要提供入參,用來被消費,如下面這段示例代碼:

@Test
public void test14() {
// 參照物,方便知道下面的Lamdba表達式寫法
Consumer<Person> greeter01 = new Consumer<Person>() {
@Override
public void accept(Person p) {
System.out.println("Hello, " + p.firstName);
}
};
Consumer<Person> greeter02 = (p) -> System.out.println("Hello, " + p.firstName);
greeter02.accept(new Person("Luke", "Skywalker")); //Hello, Luke
Consumer<Person> greeter03 = new MyConsumer<Person>()::accept; // 也可以通過定義類和方法的方式去調用,這樣才是實際開發(fā)的姿勢
greeter03.accept(new Person("Luke", "Skywalker")); //Hello, Luke
}
5. Comparators

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

@Test
public void test15(){
Comparator<Person> comparator01 = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Comparator<Person> comparator02 = Comparator.comparing(p -> p.firstName); //等同于上面的方式
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator01.compare(p1, p2); // > 0
comparator02.reversed().compare(p1, p2); // < 0
}
七、Optionals

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

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

一個方法,這個方法返回的對象可能是空,也有可能非空的時候,你就可以考慮用 Optional 來包裝它,這也是在 Java 8 被推薦使用的做法。

@Test
public void test16(){
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Optional<Person> optionalPerson = Optional.of(new Person());
optionalPerson.ifPresent(s -> System.out.println(s.firstName));
}
八、Stream 流

什么是 Stream 流?

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

需要注意的是,你只能對實現了 java.util.Collection 接口的類做流的**作。

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

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

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

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 的入參是一個 Predicate, 上面已經說到,Predicate 是一個斷言的中間**作,它能夠幫我們篩選出我們需要的**元素。它的返參同樣 是一個 Stream 流,我們可以通過 foreach 終端**作,來打印被篩選的元素:

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

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

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

注意;這個sorted 只是做了一個排序的視圖進行輸出,實際沒有將List內的數據進行排序

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

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

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

這個可以用做DTO數據對象轉換,領域驅動設計開發(fā)中將DTO轉為DO向后臺傳輸。

4. Match 匹配

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

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

count 是一個終端**作,它能夠統計 stream 流中的元素總數,返回值是 long 類型。

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

Reduce 中文翻譯為:減少、縮小。通過入參的 Function,我們能夠將 list 歸約成一個值。它的返回類型是 Optional 類型。

@Test
public void test22() {
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2
}
九、Parallel-Streams 并行流

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

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

第一,我們創(chuàng)建一個較大的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();
// 納秒轉微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 – t0);
System.out.println(String.format("順序流排序耗時: %d ms", millis));
//順序流排序耗時: 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
}

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

十、Map **

如前所講,Map是不支持 Stream 流的,因為 Map 接口并沒有像 Collection 接口那樣,定義了 stream() 方法。但是,我們可以對其 key, values, entry 使用 流**作,如 map.keySet().stream(), map.values().stream() 和 map.entrySet().stream().

另外, JDK 8 中對 map 提供了一些其他新特性:

@Test
public void test25() {
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 與老版不同的是,putIfAbent() 方法在 put 之前, 不用在寫if null continue了
// 會判斷 key 是否已經存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, "val" + i);
}
// forEach 可以很方便地對 map 進行遍歷**作
map.forEach((key, value) -> System.out.println(value));
}

之后我們做一個Map對象的轉換輸出;(定義兩個類BeanA、BeanB)

@Test
public void test26() {
Map<Integer, BeanA> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 與老版不同的是,putIfAbent() 方法在 put 之前, 不用在寫if null continue了
// 會判斷 key 是否已經存在,存在則直接返回 value, 否則 put, 再返回 value
map.putIfAbsent(i, new BeanA(i, "明明" + i, i + 20, "89021839021830912809" + i));
}
Stream<BeanB> beanBStream00 = map.values().stream().map(new Function<BeanA, BeanB>() {
@Override
public BeanB apply(BeanA beanA) {
return new BeanB(beanA.getName(), beanA.getAge());
}
});
Stream<BeanB> beanBStream01 = map.values().stream().map(beanA -> new BeanB(beanA.getName(), beanA.getAge()));
beanBStream01.forEach(System.out::println);
}

除了上面的 putIfAbsent() 和 forEach() 外,我們還可以很方便地對某個 key 的值做相關**作:

@Test
public void test27() {
// 如下:對 key 為 3 的值,內部會先判斷值是否存在,存在,則做 value + key 的拼接**作
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33

// 先判斷 key 為 9 的元素是否存在,存在,則做刪除**作
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false

// computeIfAbsent(), 當 key 不存在時,才會做相關處理
// 如下:先判斷 key 為 23 的元素是否存在,不存在,則添加
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true

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

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

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

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

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

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

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

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

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

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

1. Clock

Clock 提供對當前日期和時間的訪問。我們可以利用它來替代 System.currentTimeMillis() 方法。另外,通過 clock.instant() 能夠獲取一個 instant 實例, 此實例能夠方便地轉換成老版本中的 java.util.Date 對象。

@Test
public void test31(){
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // 老版本 java.util.Date
}
2. Timezones 時區(qū)

ZoneId 代表時區(qū)類。通過靜態(tài)工廠方法方便地獲取它,入參我們可以傳入某個時區(qū)編碼。另外,時區(qū)類還定義了一個偏移量,用來在當前時刻或某時間 與目標時區(qū)時間之間進行轉換。

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

@Test
public void test33(){
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.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 提供多個靜態(tài)工廠方法,目的是為了簡化對時間對象實例的創(chuàng)建和**作,包括對時間字符串進行解析的**作等。

@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 是一個日期對象,例如:2014-03-11。它和 LocalTime 一樣是個 final 類型對象。下面的例子演示了如何通過加減日,月,年等來計算一個新的日期。

@Test
public void test35(){
LocalDate today = LocalDate.now();
// 今天加一天
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
// 明天減兩天
LocalDate yesterday = tomorrow.minusDays(2);
// 2014 年七月的第四天
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // 星期五
}

也可以直接解析日期字符串,生成 LocalDate 實例。(和 LocalTime **作一樣簡單)

@Test
public void test36(){
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
}
5. LocalDateTime

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

@Test
public void test37(){
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // 星期三
Month month = sylvester.getMonth();
System.out.println(month); // 十二月
// 獲取改時間是該天中的第幾分鐘
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
}

如果再加上的時區(qū)信息,LocalDateTime 還能夠被轉換成 Instance 實例。Instance 能夠被轉換成老版本中 java.util.Date 對象。

@Test
public void test38(){
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
}

格式化 LocalDateTime 對象就和格式化 LocalDate 或者 LocalTime 一樣。除了使用預定義的格式以外,也可以自定義格式化輸出。

@Test
public void test39(){
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy – HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 – 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 – 07:13
}

Unlike java.text.NumberFormat the new DateTimeFormatter is immutable and thread-safe.

For details on the pattern syntax read here.

十二、Annotations 注解

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

第一,我們定義一個包裝器注釋,它包含一個實際注釋數組:

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

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

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

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

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

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

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

java編譯器使用變量2隱式地在引擎蓋下設置@Hints注釋。這對于通過反射讀取注釋信息很重要。

@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint); // null
Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length); // 2
Hint[] hints2 = Person.class.getAnnotation**yType(Hint.class
System.out.println(hints2.length); // 2
}

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

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

拓展知識:

jdk1.7

1.56。由于springboot2.0以后不支持jdk1.7,所以只能自降springboot的版本由2.0降為1.56。當選用jdk1.7構建springboot項目時會提示,選用jdk1.8。

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