この記事はもともと私のブログに投稿されたものです。
私の Java 11 チュートリアル (Java 9、10、11 の新しい言語と API 機能を含む) もお読みください。
Java 8 へようこそ。このチュートリアルでは、すべての新しい言語機能を段階的に説明します。短くてシンプルなコード サンプルに基づいて、デフォルトのインターフェイス メソッド、ラムダ式、メソッド参照、反復可能な注釈の使用方法を学びます。この記事の最後では、ストリーム、関数インターフェイス、マップ拡張機能、新しい Date API などの最新の API 変更について理解できるようになります。テキストの壁はなく、コメント付きのコード スニペットの束だけです。楽しむ!
★★★ このプロジェクトが好きですか?スターを付けたり、Twitter をフォローしたり、寄付して私の仕事をサポートしてください。ありがとう! ★★★
Java 8 では、 default
キーワードを利用して非抽象メソッド実装をインターフェイスに追加できます。この機能は、仮想拡張メソッドとも呼ばれます。
最初の例は次のとおりです。
interface Formula {
double calculate ( int a );
default double sqrt ( int a ) {
return Math . sqrt ( a );
}
}
Formula
抽象メソッドcalculate
インターフェイスに加えて、デフォルトのメソッドsqrt
も定義します。具象クラスは抽象メソッドcalculate
実装するだけで済みます。デフォルトのメソッドsqrt
そのまま使用できます。
Formula formula = new Formula () {
@ Override
public double calculate ( int a ) {
return sqrt ( a * 100 );
}
};
formula . calculate ( 100 ); // 100.0
formula . sqrt ( 16 ); // 4.0
式は匿名オブジェクトとして実装されます。コードは非常に冗長で、 sqrt(a * 100)
のような単純な計算に 6 行のコードがあります。次のセクションで説明するように、Java 8 には単一メソッド オブジェクトを実装するより優れた方法があります。
Java の以前のバージョンで文字列のリストを並べ替える方法の簡単な例から始めましょう。
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.sort
指定されたリストの要素を並べ替えるためにリストとコンパレータを受け取ります。匿名のコンパレータを作成して、sort メソッドに渡すことがよくあります。
匿名オブジェクトを一日中作成する代わりに、Java 8 にははるかに短い構文であるラムダ式が付属しています。
Collections . sort ( names , ( String a , String b ) -> {
return b . compareTo ( a );
});
ご覧のとおり、コードははるかに短くなり、読みやすくなります。しかし、それはさらに短くなります:
Collections . sort ( names , ( String a , String b ) -> b . compareTo ( a ));
メソッド本体が 1 行の場合は、中括弧{}
とreturn
キーワードの両方を省略できます。しかし、それはさらに短くなります:
names . sort (( a , b ) -> b . compareTo ( a ));
List にsort
メソッドが追加されました。また、Java コンパイラーはパラメーターの型を認識しているため、それらをスキップすることもできます。ラムダ式を実際にどのように使用できるかをさらに詳しく見てみましょう。
ラムダ式は Java の型システムにどのように適合しますか?各ラムダは、インターフェイスによって指定された特定の型に対応します。いわゆる関数型インターフェイスには、抽象メソッド宣言を 1 つだけ含める必要があります。その型の各ラムダ式は、この抽象メソッドと照合されます。デフォルト メソッドは抽象メソッドではないため、関数インターフェイスにデフォルト メソッドを自由に追加できます。
インターフェイスに抽象メソッドが 1 つだけ含まれている限り、任意のインターフェイスをラムダ式として使用できます。インターフェイスが要件を満たしていることを確認するには、 @FunctionalInterface
アノテーションを追加する必要があります。コンパイラはこの注釈を認識し、インターフェイスに 2 番目の抽象メソッド宣言を追加しようとするとすぐにコンパイラ エラーをスローします。
例:
@ 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
アノテーションが省略された場合でもコードは有効であることに注意してください。
上記のコード例は、静的メソッド参照を利用することでさらに簡素化できます。
Converter < String , Integer > converter = Integer :: valueOf ;
Integer converted = converter . convert ( "123" );
System . out . println ( converted ); // 123
Java 8 では、 ::
キーワードを使用してメソッドまたはコンストラクターの参照を渡すことができます。上記の例は、静的メソッドを参照する方法を示しています。ただし、オブジェクト メソッドを参照することもできます。
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"
::
キーワードがコンストラクターに対してどのように機能するかを見てみましょう。まず、さまざまなコンストラクターを使用してサンプル クラスを定義します。
class Person {
String firstName ;
String lastName ;
Person () {}
Person ( String firstName , String lastName ) {
this . firstName = firstName ;
this . lastName = lastName ;
}
}
次に、新しい人物の作成に使用する人物ファクトリ インターフェイスを指定します。
interface PersonFactory < P extends Person > {
P create ( String firstName , String lastName );
}
ファクトリを手動で実装する代わりに、コンストラクター参照を介してすべてを結合します。
PersonFactory < Person > personFactory = Person :: new ;
Person person = personFactory . create ( "Peter" , "Parker" );
Person::new
を介して Person コンストラクターへの参照を作成します。 Java コンパイラは、 PersonFactory.create
の署名と照合して、適切なコンストラクターを自動的に選択します。
ラムダ式から外部スコープ変数にアクセスすることは、匿名オブジェクトと非常に似ています。インスタンス フィールドや静的変数だけでなく、ローカルの外部スコープから Final 変数にもアクセスできます。
ラムダ式の外側のスコープから最終的なローカル変数を読み取ることができます。
final int num = 1 ;
Converter < Integer , String > stringConverter =
( from ) -> String . valueOf ( from + num );
stringConverter . convert ( 2 ); // 3
ただし、匿名オブジェクトとは異なり、変数num
Final として宣言する必要はありません。このコードも有効です。
int num = 1 ;
Converter < Integer , String > stringConverter =
( from ) -> String . valueOf ( from + num );
stringConverter . convert ( 2 ); // 3
ただし、コードをコンパイルするにはnum
暗黙的に Final である必要があります。次のコードはコンパイルできません。
int num = 1 ;
Converter < Integer , String > stringConverter =
( from ) -> String . valueOf ( from + num );
num = 3 ;
ラムダ式内からnum
への書き込みも禁止されています。
ローカル変数とは対照的に、ラムダ式内からインスタンス フィールドと静的変数への読み取りと書き込みの両方のアクセス権があります。この動作は、匿名オブジェクトからよく知られています。
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 );
};
}
}
最初のセクションの数式の例を覚えていますか? Interface Formula
匿名オブジェクトを含む各式インスタンスからアクセスできるデフォルトのメソッドsqrt
を定義します。これはラムダ式では機能しません。
ラムダ式内からはデフォルトのメソッドにアクセスできません。次のコードはコンパイルできません。
Formula formula = ( a ) -> sqrt ( a * 100 );
JDK 1.8 API には、多くの組み込み関数インターフェイスが含まれています。それらの一部は、 Comparator
やRunnable
など、古いバージョンの Java でよく知られています。これらの既存のインターフェイスは、 @FunctionalInterface
アノテーションを介して Lambda サポートを有効にするために拡張されています。
ただし、Java 8 API には、作業を容易にする新しい機能インターフェイスも豊富にあります。これらの新しいインターフェイスの一部は、Google Guava ライブラリでよく知られています。このライブラリに精通している場合でも、いくつかの便利なメソッド拡張機能によってこれらのインターフェイスがどのように拡張されるかに注目してください。
述語は、1 つの引数のブール値関数です。このインターフェースには、述語を複雑な論理用語 (and、or、negate) に構成するためのさまざまなデフォルトのメソッドが含まれています。
Predicate < String > predicate = ( s ) -> s . length () > 0 ;
predicate . test ( "foo" ); // true
predicate . negate (). test ( "foo" ); // false
Predicate < Boolean > nonNull = Objects :: nonNull ;
Predicate < Boolean > isNull = Objects :: isNull ;
Predicate < String > isEmpty = String :: isEmpty ;
Predicate < String > isNotEmpty = isEmpty . negate ();
関数は 1 つの引数を受け取り、結果を生成します。デフォルトのメソッドを使用して、複数の関数を連鎖させることができます (compose andThen)。
Function < String , Integer > toInteger = Integer :: valueOf ;
Function < String , String > backToString = toInteger . andThen ( String :: valueOf );
backToString . apply ( "123" ); // "123"
サプライヤーは、指定されたジェネリック型の結果を生成します。関数とは異なり、サプライヤーは引数を受け入れません。
Supplier < Person > personSupplier = Person :: new ;
personSupplier . get (); // new Person
コンシューマは、単一の入力引数に対して実行される操作を表します。
Consumer < Person > greeter = ( p ) -> System . out . println ( "Hello, " + p . firstName );
greeter . accept ( new Person ( "Luke" , "Skywalker" ));
コンパレータは、古いバージョンの Java からよく知られています。 Java 8 では、さまざまなデフォルトのメソッドがインターフェースに追加されます。
Comparator < Person > comparator = ( p1 , p2 ) -> p1 . firstName . compareTo ( p2 . firstName );
Person p1 = new Person ( "John" , "Doe" );
Person p2 = new Person ( "Alice" , "Wonderland" );
comparator . compare ( p1 , p2 ); // > 0
comparator . reversed (). compare ( p1 , p2 ); // < 0
オプションは機能的なインターフェイスではありませんが、 NullPointerException
防ぐための気の利いたユーティリティです。これは次のセクションで重要な概念なので、オプションがどのように機能するかを簡単に見てみましょう。
オプションは、null または非 null の値の単純なコンテナーです。 null 以外の結果を返す可能性があるが、何も返さないことがあるメソッドを考えてみましょう。 Java 8 では、 null
を返す代わりにOptional
返します。
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"
java.util.Stream
1 つ以上の操作を実行できる要素のシーケンスを表します。ストリーム操作は中間操作または終了操作のいずれかです。端末操作は特定の型の結果を返しますが、中間操作はストリーム自体を返すため、複数のメソッド呼び出しを連続して連鎖させることができます。ストリームは、リストやセットのようなjava.util.Collection
などのソース上に作成されます (マップはサポートされていません)。ストリーム操作は、順次または並列で実行できます。
ストリームは非常に強力なので、別の Java 8 ストリーム チュートリアルを作成しました。 Web 用の同様のライブラリとして Sequeency もチェックしてください。
まず、シーケンシャル ストリームがどのように機能するかを見てみましょう。まず、文字列のリストの形式でサンプル ソースを作成します。
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" );
Java 8 のコレクションは拡張されているため、 Collection.stream()
またはCollection.parallelStream()
呼び出すだけでストリームを作成できます。次のセクションでは、最も一般的なストリーム操作について説明します。
Filter は、ストリームのすべての要素をフィルタリングするための述語を受け入れます。この操作は中間的なもので、結果に対して別のストリーム操作 ( forEach
) を呼び出すことができます。 ForEach は、フィルターされたストリーム内の各要素に対して実行されるコンシューマーを受け入れます。 ForEach は端末操作です。これはvoid
なので、別のストリーム操作を呼び出すことはできません。
stringCollection
. stream ()
. filter (( s ) -> s . startsWith ( "a" ))
. forEach ( System . out :: println );
// "aaa2", "aaa1"
Sorted は、ストリームの並べ替えられたビューを返す中間操作です。カスタムComparator
渡さない限り、要素は自然な順序で並べ替えられます。
stringCollection
. stream ()
. sorted ()
. filter (( s ) -> s . startsWith ( "a" ))
. forEach ( System . out :: println );
// "aaa1", "aaa2"
sorted
バッキングされたコレクションの順序を操作せずに、ストリームの並べ替えられたビューを作成するだけであることに注意してください。 stringCollection
の順序は変更されません。
System . out . println ( stringCollection );
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
中間操作map
、指定された関数を介して各要素を別のオブジェクトに変換します。次の例では、各文字列を大文字の文字列に変換します。ただし、 map
使用して各オブジェクトを別のタイプに変換することもできます。結果として得られるストリームのジェネリック型は、 map
に渡す関数のジェネリック型によって異なります。
stringCollection
. stream ()
. map ( String :: toUpperCase )
. sorted (( a , b ) -> b . compareTo ( a ))
. forEach ( System . out :: println );
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
さまざまな一致操作を使用して、特定の述語がストリームと一致するかどうかを確認できます。これらの操作はすべて端末操作であり、ブール値の結果を返します。
boolean anyStartsWithA =
stringCollection
. stream ()
. anyMatch (( s ) -> s . startsWith ( "a" ));
System . out . println ( anyStartsWithA ); // true
boolean allStartsWithA =
stringCollection
. stream ()
. allMatch (( s ) -> s . startsWith ( "a" ));
System . out . println ( allStartsWithA ); // false
boolean noneStartsWithZ =
stringCollection
. stream ()
. noneMatch (( s ) -> s . startsWith ( "z" ));
System . out . println ( noneStartsWithZ ); // true
Count は、ストリーム内の要素の数をlong
として返す終端操作です。
long startsWithB =
stringCollection
. stream ()
. filter (( s ) -> s . startsWith ( "b" ))
. count ();
System . out . println ( startsWithB ); // 3
この最終操作は、指定された関数を使用してストリームの要素のリダクションを実行します。結果は、削減された値を保持するOptional
です。
Optional < String > reduced =
stringCollection
. stream ()
. sorted ()
. reduce (( s1 , s2 ) -> s1 + "#" + s2 );
reduced . ifPresent ( System . out :: println );
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
上で述べたように、ストリームは順次または並列のいずれかにすることができます。順次ストリームの操作は単一のスレッドで実行されますが、並列ストリームの操作は複数のスレッドで同時に実行されます。
次の例は、並列ストリームを使用することでいかに簡単にパフォーマンスを向上できるかを示しています。
まず、一意の要素の大規模なリストを作成します。
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 ( "sequential sort took: %d ms" , millis ));
// sequential sort took: 899 ms
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: 472 ms
ご覧のとおり、両方のコード スニペットはほぼ同じですが、並列ソートは約 50% 高速です。必要なのは、 stream()
をparallelStream()
に変更することだけです。
すでに述べたように、マップはストリームを直接サポートしません。 Map
インターフェース自体では利用可能なstream()
メソッドはありませんが、 map.keySet().stream()
、 map.values().stream()
、およびmap.entrySet().stream()
。
さらに、マップは一般的なタスクを実行するためのさまざまな新しくて便利な方法をサポートしています。
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 putIfAbsent
追加の if null チェックを作成できなくなります。 forEach
マップの各値に対して操作を実行するコンシューマーを受け入れます。
この例では、関数を利用してマップ上のコードを計算する方法を示します。
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
次に、現在指定された値にマップされている場合にのみ、指定されたキーのエントリを削除する方法を学びます。
map . remove ( 3 , "val3" );
map . get ( 3 ); // val33
map . remove ( 3 , "val33" );
map . get ( 3 ); // null
もう 1 つの役立つ方法:
map . getOrDefault ( 42 , "not found" ); // not found
マップのエントリを結合するのは非常に簡単です。
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
マージでは、キーのエントリが存在しない場合はキー/値をマップに配置するか、マージ関数が呼び出されて既存の値が変更されます。
Java 8 には、 java.time
パッケージの下に新しい日付と時刻 API が含まれています。新しい Date API は Joda-Time ライブラリと同等ですが、同じではありません。次の例では、この新しい API の最も重要な部分を説明します。
時計により、現在の日付と時刻にアクセスできます。クロックはタイムゾーンを認識しており、Unix EPOCH 以降の現在時刻をミリ秒単位で取得するためにSystem.currentTimeMillis()
の代わりに使用できます。タイムライン上のこのような瞬間点は、クラスInstant
によっても表されます。インスタントを使用して、従来のjava.util.Date
オブジェクトを作成できます。
Clock clock = Clock . systemDefaultZone ();
long millis = clock . millis ();
Instant instant = clock . instant ();
Date legacyDate = Date . from ( instant ); // legacy java.util.Date
タイムゾーンはZoneId
で表されます。これらには、静的なファクトリーメソッドを介して簡単にアクセスできます。タイムゾーンは、瞬間と現地の日付と時刻の間で変換するために重要なオフセットを定義します。
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 は、タイムゾーンのない時刻を表します (例: 午後 10 時または 17:30:15)。次の例では、上で定義したタイムゾーンに対して 2 つの現地時間を作成します。次に、両方の時間を比較し、両方の時間の差を時間と分で計算します。
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 には、時刻文字列の解析など、新しいインスタンスの作成を簡素化するためのさまざまなファクトリ メソッドが付属しています。
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 とまったく同様に動作します。このサンプルでは、日、月、年を加算または減算して新しい日付を計算する方法を示します。各操作が新しいインスタンスを返すことに注意してください。
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 を解析するのと同じくらい簡単です。
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 は日付と時間を表します。上記のセクションで示した日付と時刻を 1 つのインスタンスに結合します。 LocalDateTime
不変で、LocalTime および LocalDate と同様に機能します。日時から特定のフィールドを取得するメソッドを利用できます。
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
タイムゾーンの追加情報を使用すると、インスタントに変換できます。インスタントは、 java.util.Date
型の従来の日付に簡単に変換できます。
Instant instant = sylvester
. atZone ( ZoneId . systemDefault ())
. toInstant ();
Date legacyDate = Date . from ( instant );
System . out . println ( legacyDate ); // Wed Dec 31 23:59:59 CET 2014
日付と時刻の書式設定は、日付または時刻の書式設定と同様に機能します。事前定義された形式を使用する代わりに、カスタム パターンからフォーマッタを作成できます。
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
は不変でスレッドセーフです。
パターン構文の詳細については、ここを参照してください。
Java 8 のアノテーションは反復可能です。それを理解するために、例を直接見てみましょう。
まず、実際のアノテーションの配列を保持するラッパー アノテーションを定義します。
@interface Hints {
Hint [] value ();
}
@ Repeatable ( Hints . class )
@interface Hint {
String value ();
}
Java 8 では、 @Repeatable
アノテーションを宣言することで、同じタイプの複数のアノテーションを使用できます。
@ Hints ({ @ Hint ( "hint1" ), @ Hint ( "hint2" )})
class Person {}
@ Hint ( "hint1" )
@ Hint ( "hint2" )
class Person {}
バリアント 2 を使用すると、Java コンパイラは内部で@Hints
アノテーションを暗黙的に設定します。これは、リフレクションを介してアノテーション情報を読み取るために重要です。
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 ); // 2
Person
クラスで@Hints
アノテーションを宣言したことはありませんが、 getAnnotation(Hints.class)
を介して読み取ることができます。ただし、より便利なメソッドは、すべての注釈付き@Hint
アノテーションへの直接アクセスを許可するgetAnnotationsByType
です。
さらに、Java 8 でのアノテーションの使用は、次の 2 つの新しいターゲットに拡張されています。
@ Target ({ ElementType . TYPE_PARAMETER , ElementType . TYPE_USE })
@interface MyAnnotation {}
私の Java 8 プログラミング ガイドはここで終わります。 JDK 8 API のすべての新しいクラスと機能について詳しく知りたい場合は、私の JDK8 API Explorer をチェックしてください。これは、ほんの数例を挙げると、 Arrays.parallelSort
、 StampedLock
、 CompletableFuture
など、JDK 8 のすべての新しいクラスと隠された宝石を理解するのに役立ちます。
また、あなたにとって興味深いかもしれないフォローアップ記事を私のブログにたくさん公開しました。
Twitter で私をフォローしてください。読んでいただきありがとうございます!