Java 8 指南

轉自: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

Lambda表達式

咱們先來說一個簡單的例子:在 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做用域

經過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

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>

Streams

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

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

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

中間運算 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

Reduce

這個末端運算使用給定的函數對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"

Parallel Streams

正如上述提到的,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()。

Map

正如已經提到的,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,不然合併函數將會被調用來改變現有的值。

Date API

Java 8在包java.time包含一個全新的日期和時間的API。這個新的Date API堪比Joda-Time庫,然而,它們並不徹底相同。以下的例子將會覆蓋這個新API的大多數重要的部分。

Clock

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

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

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

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 表明日期-時間。它將如上所示的日期和時間合併爲一個實例。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 時而不可變的且是線程安全的。

模式語法的細節能夠點擊這裏

Annotations/

在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 等等。

我但願這個文章對你有用,並且你喜歡閱讀。

相關文章
相關標籤/搜索