轉自:http://www.importnew.com/20557.htmlhtml
2014年3月16日java
「Java is still not dead—and people are starting to figure that out.」編程
歡迎閱讀我對 Java 8 的介紹。本指南將一步步地經過全部的新的語言特性來引導你認識Java 8。在簡短的示例代碼的幫助下,你將會學習到如何使用默認的接口方法、lambda表達式、方法引用以及可重複的註解。在文章的最後,你將會熟悉最新的API變化,例如:streams、函數式接口、map 擴展以及新的 Date API。c#
沒有過多的文本 — 僅僅是一些具備註釋的代碼片斷。一塊兒享受吧!api
Java 8 使咱們可以使用default 關鍵字給接口增長非抽象的方法實現。這個特性也被叫作 擴展方法(Extension Methods)。以下例所示:安全
1
2
3
4
5
6
|
interface
Formula {
double
calculate(
int
a);
default
double
sqrt(
int
a) {
return
Math.sqrt(a);
}
}
|
除了抽象方法calculate ,接口 Formula 一樣定義了默認的方法 sqrt。具體類只須要實現抽象方法calculate。默認的方法sqrt能夠在其未實現時「開箱即用」。併發
1
2
3
4
5
6
7
8
|
Formula formula =
new
Formula() {
@Override
public
double
calculate(
int
a) {
return
sqrt(a *
100
);
}
};
formula.calculate(
100
);
// 100.0
formula.sqrt(
16
);
// 4.0
|
formula 被建立的像一個匿名對象。代碼看起來很囉嗦:對一個簡單的sqrt(a * 100)計算需6行。正如咱們在下一節將要看到的,對只有單方法的類的實現,在 Java 8中有個更佳的方式。app
咱們先來說一個簡單的例子:在 Java 以前的版本中是如何排序一個字符串list的:dom
1
2
3
4
5
6
7
8
|
List names = Arrays.asList(
"peter"
,
"anna"
,
"mike"
,
"xenia"
);
Collections.sort(names,
new
Comparator() {
@Override
public
int
compare(String a, String b) {
return
b.compareTo(a);
}
});
|
靜態方法Collections.sort 接受一個list和比較方法來對給定的list元素排序。你老是會發現你須要建立匿名的比較方法而且傳遞給排序方法。ide
不一樣於成天建立匿名對象,Java 8有一個簡短的多的語法:lambda表達式:
1
2
3
|
Collections.sort(names, (String a, String b) -> {
return
b.compareTo(a);
});
|
正如你所見,代碼更短也更易於閱讀,並且它還能夠更短:
1
|
Collections.sort(names, (String a, String b) -> b.compareTo(a));
|
對於一行的方法體,你能夠省略{} 和return 關鍵字,並且它還能夠更短:
1
|
Collections.sort(names, (a, b) -> b.compareTo(a));
|
Java 編譯器知道參數類型,因此你也能夠省略它們。下面讓咱們一同深刻探究下lambda表達式是如何被更普遍地使用的。
lambda表達式是如何符合 Java 類型系統的?每一個lambda對應於一個給定的類型,用一個接口來講明。而這個被稱爲函數式接口(functional interface)的接口必須僅僅包含一個抽象方法聲明。每一個那個類型的lambda表達式都將會被匹配到這個抽象方法上。所以默認的方法並非抽象的,你能夠給你的函數式接口自由地增長默認的方法。
咱們可使用任意的接口做爲lambda表達式,只要這個接口只包含一個抽象方法。爲了保證你的接口知足需求,你須要增長@FunctionalInterface 註解。編譯器知道這個註解,一旦你試圖給這個接口增長第二個抽象方法聲明時,它將拋出一個編譯器錯誤。例如:
1
2
3
4
5
6
7
|
@FunctionalInterface
interface
Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert(
"123"
);
System.out.println(converted);
// 123
|
請記住若是@FunctionalInterface 這個註解被遺漏,此代碼依然有效。
經過使用靜態方法引用,如上的示例代碼能夠被進一步的簡化:
1
2
3
|
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert(
"123"
);
System.out.println(converted);
// 123
|
Java 8使你可以經過::關鍵字傳遞對方法或者構造器的引用。如上例子告訴咱們如何引用一個靜態的方法。可是咱們也能夠引用對象的方法:
1
2
3
4
5
6
7
8
9
|
class
Something {
String startsWith(String s) {
return
String.valueOf(s.charAt(
0
));
}
}
Something something =
new
Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert(
"Java"
);
System.out.println(converted);
// "J"
|
咱們一塊兒來看看::關鍵字是如何爲構造器工做的。首先,咱們定義一個具備多個不一樣構造器的示例bean:
1
2
3
4
5
6
7
8
9
10
11
|
class
Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this
.firstName = firstName;
this
.lastName = lastName;
}
}
|
其次,咱們指定一個Person的工廠接口,它用來建立新的Person:
1
2
3
4
|
interface
PersonFactory</pre>
{
P create(String firstName, String lastName);
}
|
爲避免手工實現這個工廠,咱們經過構造器引用將全部事情鏈接起來:
1
2
|
PersonFactory personFactory = Person::
new
;
Person person = personFactory.create(
"Peter"
,
"Parker"
);
|
咱們經過Person::new建立了一個對 Person 構造器的引用。Java編譯器經過匹配PersonFactory.create標記自動選擇合適的構造器。
經過lambda表達式訪問做用域變量很是相似於匿名對象。你能夠經過局部做用域以及實例域和靜態變量來訪問final變量。
咱們能夠經過lambda表達式的做用域讀到final類型的局部變量:
1
2
3
4
5
|
final
int
num =
1
;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(
2
);
|
然而不一樣於匿名對象,變量num 並不須要必須被聲明爲 final,以下代碼一樣有效:
1
2
3
4
5
|
int
num =
1
;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(
2
);
|
然而,爲了代碼能夠編譯, num 必須隱含爲final類型,以下代碼不會編譯經過:
1
2
3
4
|
int
num =
1
;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num =
3
;
|
在lambda表達式裏對 num 賦值也一樣是被禁止的。
區別於局部變量,咱們在lambda表達式裏對實例域和靜態變量具備讀寫權限。這種行爲在匿名對象中是衆所周知的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class
Lambda4 {
static
int
outerStaticNum;
int
outerNum;
void
testScopes() {
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum =
23
;
return
String.valueOf(from);
};
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum =
72
;
return
String.valueOf(from);
};
}
}
|
還記得第一節的formula 例子嗎?接口Formula 定義了一個默認的方法sqrt ,它能夠被每一個formula 實例(包含匿名對象)來訪問。lambda表達式對此並不奏效。
默認的方法不能在lambda表達式內部被訪問到,以下代碼不能經過編譯:
1
|
Formula formula = (a) -> sqrt( a *
100
);
|
JDK 1.8 API 包含不少內置的功能接口。其中一些在舊的 Java 版本中就衆所周知了,例如Comparator 以及 Runnable。經過@FunctionalInterface標記,這些現有的接口已被擴展爲lambda所能支持的。
然而 Java 8 API 一樣擁有衆多新的功能接口來使你的生活更加簡單。這些新接口中的一些從Google Guava 庫中已經廣爲人知。即便你對此庫很熟悉,你也應該仔細看看那些接口是如何經過一些有用的方法所擴展的。
謂詞是單參數的布爾值函數。該接口包含多個默認的方法使謂詞轉換成複雜的邏輯表達式(與,或,非)
1
2
3
4
5
6
7
8
9
10
|
Predicate predicate = (s) -> s.length() >
0
;
predicate.test(
"foo"
);
// true
predicate.negate().test(
"foo"
);
// false
Predicate nonNull = Objects::nonNull;
Predicate isNull = Objects::isNull;
Predicate isEmpty = String::isEmpty;
Predicate isNotEmpty = isEmpty.negate();
|
函數接受單一參數,產出結果。默認的方法可被用來將多個函數連接起來(compose,andThen)。
1
2
3
4
|
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply(
"123"
);
// "123"
|
生產者產生一個給定的泛型類型的結果。區別於函數,生產者不接受參數。
1
2
|
Supplier personSupplier = Person::
new
;
personSupplier.get();
// new Person
|
消費者表明了將要對一個單一輸入參數採起的運算。
1
2
3
|
Consumer greeter = (p) -> System.out.println(
"Hello, "
+ p.firstName);
greeter.accept(
new
Person(
"Luke"
,
"Skywalker"
));
|
比較器在較老的 Java版本中衆所周知。Java 8給這個接口增長了多個默認的方法。
1
2
3
4
5
6
7
8
9
|
Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 =
new
Person(
"John"
,
"Doe"
);
Person p2 =
new
Person(
"Alice"
,
"Wonderland"
);
comparator.compare(p1, p2);
// > 0
comparator.reversed().compare(p1, p2);
// < 0
|
Optionals 並非函數式接口,而是一個避免空指針異常NullPointerException的俏皮工具。這是一個下一節用到的重要的概念,因此讓咱們快速地看一下它是如何工做的。
Optional 是對空或者非空的一個值的簡單的容器。想象一下,一個應該返回非空值結果的方法卻有時候什麼也沒返回。在Java 8 中,你將返回一個Optional 而不是null。
1
2
3
4
5
6
7
8
9
|
Optional optional = Optional.of(
"bam"
);
optional.isPresent();
// true
optional.get();
// "bam"
optional.orElse(
"fallback"
);
// "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(
0
))); <i>
// "b"</i>
|
java.util.Stream 表明元素的一個序列,一個或者多個運算能夠在這個序列上運行。Stream運算能夠是中間的(intermediate),也但是末端的(terminal)。末端運算返回具備特定類型的結果,中間運算返回 stream 自身,因此聶藝將多個方法調用串聯在一行。Streams是在一個源上建立的,例如,一個java.util.Collection 相似的lists或者ses(不支持maps)。Sream運算能夠被順序執行或者並行執行。
讓咱們一塊兒看看順序streams是如何工做的。首先,咱們建立一個字符串類型的list做爲示例源:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
List stringCollection =
new
ArrayList<>();
stringCollection.add(
"ddd2"
);
stringCollection.add(
"aaa2"
);
stringCollection.add(
"bbb1"
);
stringCollection.add(
"aaa1"
);
stringCollection.add(
"bbb3"
);
stringCollection.add(
"ccc"
);
stringCollection.add(
"bbb2"
);
stringCollection.add(
"ddd1"
);
|
在Java 8 中Collections 被擴展了,於是你能夠簡單地經過調用Collection.stream() 或者 Collection.parallelStream()建立 streams 。以下節將解釋最廣泛的流運算。
Filter 接受一個謂詞來過濾出流中全部的元素。此運算是一箇中間運算,它可使咱們在結果上調用其它的stream運算(forEach)。forEach 接受一個能夠對每一個流過濾出的元素進行操做的消費者。forEach 是一個末端運算,換句話說,咱們不能再調用其餘的流運算
1
2
3
4
5
6
7
8
9
|
stringCollection
.stream()
.filter((s) -> s.startsWith(
"a"
))
.forEach(System.out::println);
// "aaa2", "aaa1"
|
Sorted 是一個返回流的排序視圖的中間運算。除非你傳遞一個定製的Comparator ,元素將被以天然順序進行排序。
1
2
3
4
5
6
7
8
9
10
11
|
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith(
"a"
))
.forEach(System.out::println);
// "aaa1", "aaa2"
|
請記住,sorted 真的僅僅對此stream建立一個排序的視圖,它並不操縱背後的彙集(collection)。stringCollection 的順序並未改變:
1
2
3
|
System<b>.</b>out<b>.</b>println<b>(</b>stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bb<i>b2, ddd1</i>
|
中間運算 map 將每一個元素經過給定的函數轉變爲其它對象。以下示例講每一個string轉換爲一個大寫字母的string。可是你也可使用map 將每一個對象轉換爲其它了下。轉換結果的類型依賴於你傳遞給map 的類型。
1
2
3
4
5
6
7
8
9
10
11
|
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "A<i>AA2", "AAA1"</i>
|
多個匹配運算能夠被用來檢驗是否一個特定的謂詞與某stream匹配。全部的這些運算都爲末端運算,而且返回一個布爾值結果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
boolean
anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith(
"a"
));
System.out.println(anyStartsWithA);
// true
boolean
allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith(
"a"
));
System.out.println(allStartsWithA);
// false
boolean
noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith(
"z"
));
System.out.println(noneStartsWithZ);
// true
|
計數是一個末端運算,以long類型返回在stream中的元素的數目。
1
2
3
4
5
6
7
8
9
10
11
|
long
startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith(
"b"
))
.count();
System.out.println(startsWithB);
// 3
|
這個末端運算使用給定的函數對stream的元素進行一個減縮運算。結果是一個保存有減縮值的Optional 。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Optional<b><</b>String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 +
"#"
+ s2);
reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
|
正如上述提到的,streams能夠是順序或者並行的。在順序streams上的操做是在一個單線程中完成的,然而在並行streams上的操做時在多個線程上併發完成的。
以下例子證實了經過使用併發流是如何簡單地提升運算性能的。
首先,咱們建立一個無重複元素的大的list:
1
2
3
4
5
6
7
8
9
10
11
|
int
max <b>=</b>
1000000
;
List values =
new
ArrayList<>(max);
for
(
int
i =
0
; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
|
如今咱們對須要多少時間來完成對其排序進行統計。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
long
t0 <b>=</b> System<b>.</b>nanoTime();
long
count = values.stream().sorted().count();
System.out.println(count);
long
t1 = System.nanoTime();
long
millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format(
"sequential sort took: %d ms"
, millis));
// sequential sort took: 899 ms
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
long
t0 <b>=</b> System<b>.</b>nanoTime<b>();</b>
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: 472 ms
|
正如你所看到的,兩個代碼片斷幾乎相同,可是並行排序快了將近50%。而全部你所須要作的僅僅是將stream() 改成 parallelStream()。
正如已經提到的,maps並不支持streams。然而,maps如今支持多種新的有用的方法來完成普通的任務。
1
2
|
Map<Integer, String> map =
new
HashMap<>();
for
(
int
i =
0
; i <
10
; i++) { map.putIfAbsent(i,
"val"
+ i); } map.forEach((id, val) -> System.out.println(val));
|
如上的代碼應該是意義很明確的:putIfAbsent避免了咱們寫多餘的null檢查;forEach 接受一個消費者去對每一個map的值作運算。
這個例子顯示瞭如何利用函數在map上進行操做。
1
2
3
4
5
6
7
8
|
map.computeIfPresent(
3
, (num, val) -> val + num);
map.get(
3
);
// val33
map.computeIfPresent(
9
, (num, val) ->
null
);
map.containsKey(
9
);
// false
map.computeIfAbsent(
23
, num ->
"val"
+ num);
map.containsKey(
23
);
// true
map.computeIfAbsent(
3
, num ->
"bam"
);
map.get(
3
);
// val33
|
接下來,咱們學習如何對給定的key刪除entry(只有它對應到了一個給定的值時生效):
1
2
3
4
|
map.remove(
3
,
"val3"
);
map.get(
3
);
// val33
map.remove(
3
,
"val33"
);
map.get(
3
);
// null
|
另外一個有用的方法:
1
|
map.getOrDefault(
42
,
"not found"
);
|
合併map的記錄很方便:
1
2
3
4
5
|
map.merge(
9
,
"val9"
, (value, newValue) -> value.concat(newValue));
map.get(
9
);
// val9
map.merge(
9
,
"concat"
, (value, newValue) -> value.concat(newValue));
map.get(
9
);
// val9concat
|
若是沒有相應記錄,合併會將key/value 對放入map,不然合併函數將會被調用來改變現有的值。
Java 8在包java.time包含一個全新的日期和時間的API。這個新的Date API堪比Joda-Time庫,然而,它們並不徹底相同。以下的例子將會覆蓋這個新API的大多數重要的部分。
Clock 提供對如今時間和日期的訪問。Clocks 能感知時區,可能被用來替代System.currentTimeMillis() 來獲取如今毫秒數。這樣一個在時間線上即時的點也被類Instant所表示。Instant能夠被用來建立java.util.Date 對象。
1
2
3
4
5
|
Clock clock = Clock.systemDefaultZone();
long
millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant);
// legacy java.util.Date
|
Timezones由一個ZoneId表明。他們能夠被靜態工廠方法很容易地訪問。 Timezones 定義偏移量,這對於instants 和本地日期、時間之間的轉換很是重要。
1
2
3
4
5
6
7
8
9
10
|
System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids
ZoneId zone1 = ZoneId.of(
"Europe/Berlin"
);
ZoneId zone2 = ZoneId.of(
"Brazil/East"
);
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
|
LocalTime 表明一個不帶時區的時間,例如:10pm 或者 17:30:15。以下示例爲如上定義的時區建立兩個本地時間 。那麼,咱們就能夠比較兩個時間,而且計算出兩個時間間的時間差(小時或者分鐘爲單位)。
1
2
3
4
5
6
7
8
9
10
|
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2));
// false
long
hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long
minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween);
// -3
System.out.println(minutesBetween);
// -239
|
伴隨着LocalTime ,這裏存在多種工廠方法來簡化新實例的建立,也包含對時間字符串的解析。
1
2
3
4
5
6
7
8
9
10
|
LocalTime late = LocalTime.of(
23
,
59
,
59
);
System.out.println(late);
// 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse(
"13:37"
, germanFormatter);
System.out.println(leetTime);
// 13:37
|
LocalDate 表明一個明確的日期,例如2014-03-11。它是不可變的,而且很是相似LocalTime。 這個示例將證實如何經過增長或者減小日期、月份、年來計算出一個新的日期。記住每次操做將返回一個新的實例。
1
2
3
4
5
6
7
|
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(
1
, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(
2
);
LocalDate independenceDay = LocalDate.of(
2014
, Month.JULY,
4
);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek);
// FRIDAY
|
從字符串中解析LocalDate就像解析 LocalTime同樣簡單:
1
2
3
4
5
6
7
8
9
10
11
|
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse(
"24.12.2014"
, germanFormatter);
System.out.println(xmas);
// 2014-12-24
|
LocalDateTime 表明日期-時間。它將如上所示的日期和時間合併爲一個實例。LocalDateTime 是不可變的,它相似LocalTime和 LocalDate。咱們可使用方法獲取日期-時間中特定的域:
1
2
3
4
5
6
7
|
LocalDateTime sylvester = LocalDateTime.of(
2014
, Month.DECEMBER,
31
,
23
,
59
,
59
);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek);
// WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month);
// DECEMBER
long
minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);
// 1439
|
知道額外的時區信息,它就能夠轉換爲一個instant,instant能夠很容易地轉換爲java.util.Date 類型的日期。
1
2
3
4
5
6
7
8
9
|
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate);
// Wed Dec 31 23:59:59 CET 2014
|
形式化日期-時間的工做就像形式化日期或者時間同樣。咱們能夠從客戶化的模式來建立格式(formatter),而不是使用預先定義的格式。
1
2
3
4
5
6
7
8
9
10
11
|
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern(
"MMM dd, yyyy - HH:mm"
);
LocalDateTime parsed = LocalDateTime.parse(
"Nov 03, 2014 - 07:13"
, formatter);
String string = formatter.format(parsed);
System.out.println(string);
// Nov 03, 2014 - 07:13
|
區別於 java.text.NumberFormat ,新的DateTimeFormatter 時而不可變的且是線程安全的。
模式語法的細節能夠點擊這裏。
在Java8 中Annotations是可重複的。讓咱們經過一個具體的例子來理解它。
首先,咱們定義一個包裝器註解,它擁有一組真正的註解:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@interface
Hints {
Hint[] value();
}
@Repeatable
(Hints.
class
)
@interface
Hint {
String value();
}
|
Java 8 中,經過聲明註解@Repeatable使我可以使用多個具備相同類型的註解。
變體 1: 使用容器註解(守舊派)
1
2
|
@Hints
({
@Hint
(
"hint1"
),
@Hint
(
"hint2"
)})
class
Person {}
|
變體 2: 使用可重複的註解(新派)
1
2
3
|
@Hint
(
"hint1"
)
@Hint
(
"hint2"
)
class
Person {}
|
使用變體2,Java編譯器能夠隱式地設置 @Hints註解。這對於經過反射來閱讀註解信息是很重要的。
1
2
3
4
5
6
7
8
|
Hint hint = Person.
class
.getAnnotation(Hint.
class
);
System.out.println(hint);
// null
Hints hints1 = Person.
class
.getAnnotation(Hints.
class
);
System.out.println(hints1.value().length);
// 2
Hint[] hints2 = Person.
class
.getAnnotationsByType(Hint.
class
);
System.out.println(hints2.length);
|
儘管咱們從不在Person類上聲明@Hints 註解,它依然經過getAnnotation(Hints.class)可讀。然而,更多便捷的方法是 getAnnotationsByType,它將具備對全部帶註釋的@Hint 註解直接訪問的能力。
此外,在Java8中對註解的使用被擴大到兩個新的targets:
1
2
|
@Target
({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface
MyAnnotation {}
|
我對Java 8 編程的指南就到這裏。確定還有更多的東西值得探究。由你決定去探索在Java 8 編程中全部其它偉大的改進,例如,Arrays.parallelSort、StampedLock以及 CompletableFuture 等等。
我但願這個文章對你有用,並且你喜歡閱讀。