"Javaは潰れない。"みんなすぐに気づくでしょう。
入門へようこそ。このチュートリアルでは、言語の新機能を順を追って説明します。理解しやすいコード例を用いて、デフォルトのインターフェース・メソッド、ラムダ式、メソッド参照、繰り返しアノテーションの使い方を学びます。このチュートリアルを見れば、フロー制御、関数型インターフェース、マップ拡張、新しい時間日付APIなど、新しく導入された理解できるでしょう。
インターフェースでデフォルトのメソッド実装を許可
Java 8 では、default キーワードを使用して、抽象でないメソッド実装をインターフェイス宣言に追加できます。この機能は拡張メソッドとしても知られています。最初の例を示します:
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
インターフェイス Formula では、抽象メソッド caculate に加えてデフォルト・メソッド sqrt が定義されています。デフォルト・メソッド 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
ラムダ式
文字列のリストをソートする方法を学ぶために、最も簡単な例から始めましょう。まずはJava 8のメソッドを使ってみましょう:
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 は、入力パラメータとしてリストと Comparator インターフェースを受け入れ、Comparator 実装クラスは入力リストの要素を比較することができます。通常、無名Comparatorオブジェクトを直接作成し、それを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から中括弧を省略することもできます。しかし、それは最短の書き方でもありません:
Collections.sort(names, (a, b) -> b.compareTo(a));
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 では、メソッドやコンストラクタへの参照を :: キーワードで取得できます。上記の例は、静的メソッドを参照する方法を示しています。また、オブジェクトのメソッドを参照することもできます:
キーワードを使ってコンストラクタを参照する方法を見てみましょう。まず、異なるコンストラクタ・メソッドを含むサンプルBeanを定義します:
次に、新しい人物オブジェクトを作成するための人物ファクトリーインターフェースを定義します:
そして、以前のように手作業でファクトリーを実装するのではなく、コンストラクターの参照を通じてすべてをまとめます。
Person::new を使用して Person クラスのコンストラクタへの参照を作成します。Java コンパイラは、PersonFactory.create 関数のシグニチャに合わせて適切なコンストラクタを自動的に選択し、コンストラクタの正しい形式を選択します。
#p#
ラムダの範囲
lambdab 式の外部変数に対するアクセス許可の粒度は、匿名オブジェクトの場合とよく似ています。メンバ変数やスタティック変数と同様に、ローカルの外部領域にあるローカルの最終変数にアクセスすることができます。
ローカル変数へのアクセス
ラムダ式以外の最終ローカル変数にアクセスできます:
しかし、匿名オブジェクトとは異なり、変数numはfinalである必要はありません:
しかし、numはコンパイル時に暗黙のうちにfinal変数として扱われます。次のコードは不正です:
ラムダ式の内部でnumの値を変更しようとする試みも禁止されています。
メンバ変数と静的変数へのアクセス
ローカル変数とは異なり、ラムダ式の中からメンバ変数や静的変数の読み書きにアクセスすることが可能です。このようなアクセス動作は、匿名オブジェクトによく見られるものです。
デフォルト・インターフェース・メソッドへのアクセス
セクション1の数式の例を思い出してください。 Formulaインターフェイスにはデフォルトのメソッドsqrtが定義されており、無名オブジェクトを含むFormulaのすべてのインスタンスにアクセスできます。これはラムダ式には使えません。
デフォルト・メソッドはラムダ式の中ではアクセスできません。したがって、以下のコードはコンパイルされません:
内蔵機能インターフェース
JDK 1.8 APIには、数多くの組み込み関数インターフェースがあります。ComparatorインターフェイスやRunnableインターフェイスなど、以前のバージョンのJavaでおなじみのものもあります。これらの既製のインターフェースの実装は、@FunctionalInterfaceアノテーションによってラムダ関数のサポートを有効にすることができます。
加えて、Java 8 APIは、プログラマの作業負荷を軽減するために、多くの新しい関数インターフェイスを提供します。新しいインターフェースのいくつかは、ライブラリですでによく知られています。これらのライブラリに慣れていれば、目を閉じて、これらのインタフェースがクラス・ライブラリの実装でどれだけ役割を果たしているかを考えることさえできます。
述語
述語は、1つの入力パラメータを受け取るブール型関数です。述語インターフェイスには、複雑な論理動詞を扱うためのさまざまなデフォルトメソッドが含まれています。
機能
Function インターフェースは、1 つの引数を取り、1 つの結果を返します。デフォルトのメソッドでは、複数の関数を連結することができます。
サプライヤー
Supplier インターフェースは、指定された型の結果を生成します。Functionとは異なり、Supplierは入力パラメータを持ちません。
消費者
Consumerは、入力パラメータに対して実行される操作を表します。
比較対象
Java 8では、このインターフェイスにさまざまなデフォルト・メソッドが追加されています。
オプション
あるメソッドがNULLでない値を返すこともあれば、NULL値を返すこともあると考えてください。NULLを直接返さないために、Java 8ではOptionalを返します。
#p#
ストリーム
java.util.Streamは、様々な操作を実行できる要素のシーケンスを表します。ストリーム操作には、中間操作と完了操作があります。ストリーム操作は、中間操作と完了操作のいずれかになります。完了操作が何らかの型の値を返すのに対して、中間操作はストリーム・オブジェクト自体を返します。ストリーム操作は、多くの場合、逐次または並列のいずれかで実行されます。
シーケンス・ストリームを理解することから始めましょう。まず、文字列型のリスト形式でサンプル・データを作成します:
Java 8のCollectionsクラスは強化され、Collections.stream()またはCollections.parallelStream()メソッドを呼び出すことで、ストリーム・オブジェクトを直接作成できるようになりました。以下のセクションでは、この最も一般的な操作について説明します。
フィルター
Filter は、述語インタフェース型の変数を受け入れ、すべてのストリーム・オブジェクトの要素をフィルタリングします。この操作は中間操作であるため、返された結果の上で他のストリーム操作を実行することができます。ForEach は関数インタフェース型の変数を受け入れ、各要素に対して操作を実行するために使用されます。これはストリームを返さないため、他のストリーム操作を呼び出すことはできません。
分類済み
並べ替えは、並べ替えられたストリーム・オブジェクトのビューを返す中間操作です。ストリーム・オブジェクト内の要素は、Comparator インタフェースを指定してソート規則を変更しない限り、デフォルトでは自然順序でソートされます。
sorted は、単にストリーム・オブジェクトを並べ替えたビューを作成するだけであり、元のコレクション内の要素の順序は変更されないことを覚えておくことが重要です。元の文字列コレクションの要素の順序は変更されません。
地図
マップとは、ストリーム・オブジェクトの中間的な操作のことで、メソッドを指定すると、ストリーム・オブジェクトの各要素を別のオブジェクトにマッピングします。次の例では、すべての文字列を大文字の文字列に変換しています。 それだけでなく、すべてのオブジェクトを別の型にマップすることもできます。ジェネリックな結果を持つストリーム・オブジェクトの場合、正確な型も map に渡されるジェネリック・メソッドによって決定されます。
試合
マッチ操作にはいくつかの種類があり、これらはすべて、特定のルールがストリームオブジェクトと互いにマッチするかどうかを判定するために使用されます。すべてのマッチ操作は最終的なもので、ブール値の結果のみを返します。
カウント
Count は、現在のストリーム・オブジェクトに含まれる要素の数を示す数値を返す最終操作です。
削減
この操作は、いずれかのメソッドを通じて要素にカット操作を実行するために使用できる最終操作です。この操作の結果はOptional変数で返されます。
#p#
パラレルストリーム
前述のように、ストリーム操作には逐次処理と並列処理があります。逐次操作は単一のスレッドで実行され、並列操作は複数のスレッドで実行されます。
次の例は、並列ストリームを操作に使用し、操作効率を向上させる方法を示すもので、コードは非常に簡単です。
まず、ユニークな要素を含む大きなリストを作成します:
では、このコレクションを並べ替えるのにかかった時間を測ってください。
注文の並べ替え
パラレルソート
ご覧のように、すべてのコードスニペットはほとんど同じで、唯一の違いは stream() を parallelStream() に変更したことです。
地図
すでに述べたように、マップはストリーム操作をサポートしていません。その代わりに、更新されたmapは、通常のタスクを達成するための様々な便利な新しいメソッドをサポートしています。
forEachはコンシューマー・オブジェクトを受け取るので、マップ内のすべての値に対して操作を強制します。
次の例は、関数
次に、キー値が与えられたときに、対応するキーからインスタンスを削除する方法を学びます:
もう一つの便利な方法:
マップ内のインスタンスのマージも非常に簡単です:
そうでない場合は、既存の値を変更するためにマージ関数が呼び出されます。
時間日付API
Java 8には新しい時刻日付APIが含まれており、これらの関数はjava.timeパッケージの下に配置されています。新しいtime-date APIはJoda-Timeライブラリに基づいていますが、すべてが同じというわけではありません。以下の例は、ほとんどの新しいAPIの重要な部分をカバーしています。
時計
タイムゾーン
タイムゾーン・クラスは ZoneId で表すことができます。タイムゾーンクラスのオブジェクトは、静的なファクトリーメソッドで簡単に取得できます。タイムゾーンクラスは、現在の時刻とターゲットタイムゾーンの時刻を変換するためのオフセットも定義しています。
現地時間
次の例では、上の例で定義したタイムゾーンを持つ 2 つのローカル時間オブジェクトを作成します。そして、2 つの時刻を比較し、時分単位の差を計算します。
LocalTimeは、時刻文字列を解析する操作など、時刻オブジェクトのインスタンスの作成と操作を簡素化するために設計されたいくつかのファクトリーメソッドで構成されています。
現地日付
この時刻は不変で、LocalTimeと相同です。次の例では、日、月、年のインジケータを加算または減算して新しい日付を計算する方法を示します。各操作は新しい時刻オブジェクトを返すことを覚えておいてください。
文字列を解析して LocalDate オブジェクトを作成するのは、LocalTime を解析するのと同じくらい簡単です。
#p#
ローカル日付
LocalDateTime は日付時刻を表します。LocalDateTimeは不変で、LocalTimeやLocalDateと同じように動作します。メソッドを呼び出すことで、日付時刻オブジェクトの特定のデータ・フィールドを取得できます。
LocalDateTimeは、タイムゾーン情報が追加されていれば、インスタンスインスタンスに変換することができます。
日時オブジェクトの書式設定は、日付オブジェクトや時刻オブジェクトの書式設定と同じです。定義済みの書式を使用するだけでなく、カスタム書式設定オブジェクトを作成し、そのカスタム書式に合わせることもできます。
詳細はご覧ください。
注釈
Java 8のアノテーションは繰り返しが可能です。さっそく例を見て、その意味を理解しましょう。
まず、実際のアノテーションの配列を含むラッパー・アノテーションを定義します。
バリエーション1:注釈付きコンテナの使用
バリエーション2:反復可能な注釈の使用
variant2を使うと、Javaコンパイラは@Hintを内部的に自動的に設定することができます。これはリフレクションでアノテーション情報を読み取るときに重要です。
Person クラスで @Hints アノテーションが宣言されることはありませんが、getAnnotation(Hints.class) によってその情報を読み取ることはできます。また、getAnnotationsByType メソッドを使用すると、@Hints アノテーションでアノテーションされたすべてのメソッドに直接アクセスできるため、より便利です。
まずはここに行きましょう。
このブログ記事がお役に立ち、楽しんで読んでいただけたなら幸いです。チュートリアルの全ソースコードはあります。好きなだけフォークして、Twitterでフィードバックを教えてください。





