JavaRush /Java Blog /Random-JA /Java 8 ガイド。1部。
ramhead
レベル 13

Java 8 ガイド。1部。

Random-JA グループに公開済み

「Java はまだ生きており、人々はそれを理解し始めています。」

Java 8 へようこそ。このガイドでは、この言語のすべての新機能を段階的に説明します。短くて単純なコード例を通じて、インターフェイスのデフォルト メソッドラムダ式参照メソッド、および反復可能なアノテーションの使用方法を学びます。この記事を最後まで読むと、ストリーム、関数インターフェイス、関連付け拡張機能、新しい Date API などの API の最新の変更点について理解できるようになります。退屈なテキストの壁はなく、コメントされたコード スニペットの束だけです。楽しむ!

インターフェースのデフォルトメソッド

Java 8 では、デフォルトの キーワードを使用して、インターフェースに実装された非抽象メソッドを追加できます。 この機能は、拡張メソッドとも呼ばれます。最初の例は次のとおりです。 抽象メソッド Calculate interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } に加えて、 Formulaインターフェースはデフォルトのメソッド sqrtも定義します。 Formulaインターフェースを実装するクラスは、抽象 計算メソッドのみを実装します。デフォルトの sqrtメソッドは、そのまま使用できます。 オブジェクトは匿名オブジェクトとして実装されます。コードは非常に印象的です。単純に sqrt(a * 100)を計算するための 6 行のコードです。次のセクションでさらに詳しく説明しますが、Java 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

ラムダ式

Java の初期バージョンで文字列の配列を並べ替える方法の簡単な例から始めましょう。 統計ヘルパー メソッド Collections.sort は、リストと Comparator を受け取り、指定されたリストの要素を並べ替えます 多くの場合、匿名コンパレータを作成し、それをsortメソッドに渡します。Java 8 では、常に匿名オブジェクトを作成する代わりに、はるかに少ない構文、 ラムダ式を使用できるようになりました。 ご覧のとおり、コードははるかに短く、読みやすくなっています。ただし、ここではさらに短くなります。1 行のメソッドの場合、中かっこ {}と returnキーワードを削除できます。ただし、ここでコードがさらに短くなります。Java コンパイラはパラメータの型を認識しているため、パラメータを省略することもできます。ここで、ラムダ式が実際にどのように使用できるかを詳しく見てみましょう。 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(names, (String a, String b) -> { return b.compareTo(a); }); Collections.sort(names, (String a, String b) -> b.compareTo(a)); Collections.sort(names, (a, b) -> b.compareTo(a));

機能インターフェイス

ラムダ式は Java の型システムにどのように適合しますか? 各ラムダは、インターフェイスによって定義された特定の型に対応します。そして、いわゆる関数型インターフェイスには、宣言された抽象メソッドが 1 つだけ含まれている必要があります。指定された型のすべてのラムダ式は、この抽象メソッドに対応します。デフォルト メソッドは抽象メソッドではないため、関数インターフェイスにデフォルト メソッドを自由に追加できます。インターフェイスに抽象メソッドが 1 つだけ含まれている場合は、任意のインターフェイスをラムダ式として使用できます。インターフェースがこれらの条件を確実に満たすようにするには、 @FunctionalInterfaceアノテーションを追加する必要があります。コンパイラは、このアノテーションによって、インターフェイスにメソッドを 1 つだけ含める必要があることが通知され、このインターフェイス内で 2 番目の抽象メソッドが見つかった場合はエラーがスローされます。例: @FunctionalInterfaceアノテーションが宣言されていない 場合でも、このコードは有効であることに注意してください。 @FunctionalInterface interface Converter { T convert(F from); } Converter converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123

メソッドとコンストラクターへの参照

上記の例は、統計メソッド参照を使用することでさらに簡略化できます。Java 8 では、 ::キーワード シンボル を使用してメソッドとコンストラクターへの参照を渡すことができます。上の例は、統計的手法がどのように使用できるかを示しています。しかし、オブジェクトのメソッドを参照することもできます。 :: の使用がコンストラクターでどのように機能するかを見てみましょう。まず、さまざまなコンストラクターを使用した例を定義しましょう。 次に、新しい 人物オブジェクトを作成するための PersonFactoryファクトリ インターフェイス を定義します。 Converter converter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted); // 123 class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); } } Something something = new Something(); Converter 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 create(String firstName, String lastName); } ファクトリを手動で実装する代わりに、コンストラクター参照を使用してすべてを結び付けます。 PERSON::newを介してPersonクラス のコンストラクターへの参照を作成します。Java コンパイラは、コンストラクタのシグネチャとPersonFactory.createメソッドのシグネチャを比較することにより、適切なコンストラクタを自動的に呼び出します。 PersonFactory personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");

ラムダ領域

ラムダ式から外部スコープ変数へのアクセスを構成することは、匿名オブジェクトからアクセスすることと似ています。 ローカル スコープからFinal変数にアクセスできるだけでなく、インスタンス フィールドや集計変数にもアクセスできます。
ローカル変数へのアクセス
ラムダ式のスコープから、 final 修飾子を使用してローカル変数を読み取ることができます。ただし、匿名オブジェクトとは異なり、ラムダ式からアクセスできるように変数を Final として宣言する必要はありません。このコードも正しいです。 ただし、 num変数は不変のままでなければなりません。コードコンパイルの暗黙的な 最終値になります。次のコードはコンパイルできません。 ラムダ式内の num への 変更も許可されません。 final int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3 int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3 int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); num = 3;
インスタンスフィールドと統計変数へのアクセス
ローカル変数とは異なり、ラムダ式内のインスタンス フィールドと統計変数を読み取り、変更できます。この動作は匿名オブジェクトからわかっています。 class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { Converter stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }
インターフェースのデフォルトメソッドへのアクセス
最初のセクションの 数式インスタンス を使用した例を覚えていますか? Formulaインターフェースは、匿名オブジェクトを含む、 Formulaのすべてのインスタンスからアクセスできるデフォルトの sqrtメソッドを定義します。これはラムダ式では機能しません。ラムダ式内ではデフォルトのメソッドにアクセスできません。次のコードはコンパイルできません。 Formula formula = (a) -> sqrt( a * 100);

組み込みの機能インターフェイス

JDK 1.8 API には、多くの組み込み関数インターフェイスが含まれています。それらの一部は、以前のバージョンの Java からよく知られています。たとえば、 ComparatorRunnable などです。 これらのインターフェイスは、 @FunctionalInterfaceアノテーションを使用してラムダ サポートを含むように拡張されています。しかし、Java 8 API には、作業を容易にする新しい機能インターフェイスも豊富にあります。これらのインターフェイスの一部は、 Google の Guavaライブラリでよく知られています。このライブラリに精通している場合でも、いくつかの便利な拡張メソッドを使用して、これらのインターフェイスがどのように拡張されるかを詳しく見てみる必要があります。
述語
述語は 1 つの引数を持つブール関数です。このインターフェイスには、述語を使用して複雑な論理式 (and、or、negate) を作成するためのさまざまなデフォルト メソッドが含まれています。 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();
機能
関数は 1 つの引数を受け取り、結果を生成します。デフォルトのメソッドを使用して、複数の関数を 1 つのチェーンに結合することができます (compose andThen)。 Function toInteger = Integer::valueOf; Function backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
サプライヤー
サプライヤーは、何らかのタイプの結果 (インスタンス) を返します。関数とは異なり、プロバイダーは引数を受け取りません。 Supplier personSupplier = Person::new; personSupplier.get(); // new Person
消費者
コンシューマは、単一の引数を持つインターフェイス メソッドを表します。 Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
コンパレータ
コンパレータは、以前のバージョンの Java から知られています。Java 8 では、さまざまなデフォルトのメソッドをインターフェースに追加できます。 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 インターフェイスは、 nullまたは null 以外の値を格納する単純なコンテナです。メソッドが値を返すことも、何も返さないこともできると想像してください。 Java 8 では、 nullを返す代わりに、 Optional のインスタンスを返します。 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

ストリーム

java.util.Stream は、 1 つまたは複数の操作が実行される要素のシーケンスです。各ストリーム操作は中間または終了のいずれかです。端末操作は特定の型の結果を返しますが、中間操作はストリーム オブジェクト自体を返し、メソッド呼び出しのチェーンを作成できます。 Stream は、リストおよびセットのjava.util.Collectionのようなインターフェイスです(マップはサポートされていません)。各 Stream 操作は、順次または並列で実行できます。ストリームがどのように機能するかを見てみましょう。まず、文字列のリストの形式でサンプル コードを作成します。 Java 8 のコレクションは強化されており、 Collection.stream()または Collection.ParallelStream() を呼び出すだけで非常に簡単にストリームを作成できるようになりました。次のセクションでは、最も重要な単純なストリーム操作について説明します。 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");
フィルター
Filter はストリームのすべての要素をフィルタリングするための述語を受け入れます。この操作は中間的なものであり、結果として得られる (フィルターされた) 結果に対して他のストリーム操作 (forEach など) を呼び出すことができます。ForEach は、すでにフィルタリングされたストリームの各要素に対して実行される操作を受け入れます。ForEach は端末操作です。また、他のオペレーションを呼び出すことはできません。 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" ソートすると、コレクション自体に影響を与えることなく、ストリームのソートされた表現が作成されることに注意してください。 stringCollection要素の順序は変更されません。 System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
地図
中間のマップ操作では、結果の関数を使用して各要素を別のオブジェクトに変換します。次の例では、各文字列を大文字の文字列に変換します。ただし、マップを使用して各オブジェクトを別のタイプに変換することもできます。結果として得られるストリーム オブジェクトのタイプは、マップに渡す関数のタイプによって異なります。 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 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 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()に変更することだけです。

地図

すでに述べたように、マップはストリームをサポートしていません。その代わりに、マップは一般的な問題を解決するための新しくて便利な方法をサポートし始めました。 上記のコードは直感的に理解できるはずです。putIfAbsent 、追加の null チェックを記述しないよう警告します。 forEach は、マップ値ごとに実行する関数を受け入れます。この例は、関数を使用してマップ値に対して操作がどのように実行されるかを示しています。 次に、特定の値にマップされている場合にのみ、特定のキーのエントリを削除する方法を学びます: もう 1 つの良い方法: マップ エントリのマージは非常に簡単です: Merging指定されたキーのエントリがない場合はキー/値をマップに挿入するか、マージ関数が呼び出されて既存のエントリの値が変更されます。 Map map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.putIfAbsent(i, "val" + i); } map.forEach((id, val) -> System.out.println(val)); 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 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
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION