Java 8の機能と例

Java 8は2014年3月18日にリリースされました。それはかなり昔のことですが、まだ多くのプロジェクトがJava 8で実行されています。それは多くの新機能を備えたメジャーリリースだったからです。Java 8のすべてのエキサイティングで重要な機能を例を交えて見てみましょう。

Java 8の機能のクイック概要

重要なJava 8の機能のいくつかは次のとおりです。

  1. Iterableインターフェース内のforEach()メソッド
  2. インターフェース内のデフォルトおよび静的メソッド
  3. 関数型インターフェースおよびラムダ式
  4. コレクションに対する一括データ操作のためのJava Stream API
  5. Java Time API
  6. Collection APIの改善
  7. 並行性APIの改善
  8. Java IOの改善

これらのJava 8の機能について簡単に見てみましょう。これらの機能をより理解しやすくするために、いくつかのコードスニペットを提供します。

1. Iterableインターフェース内のforEach()メソッド

コレクションをトラバースする必要がある場合、イテレータを作成する必要があります。その唯一の目的は、コレクション内の各要素を反復処理することです。コレクション内の要素ごとにループ内のビジネスロジックがあります。イテレータが適切に使用されていない場合、ConcurrentModificationExceptionが発生する可能性があります。

Java 8では、ビジネスロジックに焦点を当ててコードを記述できるように、java.lang.IterableインターフェースにforEachメソッドが導入されました。forEachメソッドは、java.util.function.Consumerオブジェクトを引数として受け取るため、ビジネスロジックを再利用できる別の場所に配置できます。簡単な例を使ってforEachの使用法を見てみましょう。

package com.journaldev.java8.foreach;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.lang.Integer;

public class Java8ForEachExample {

	public static void main(String[] args) {
		
		//サンプルコレクションの作成
		List<Integer> myList = new ArrayList<Integer>();
		for(int i=0; i<10; i++) myList.add(i);
		
		//イテレータを使用してトラバース
		Iterator<Integer> it = myList.iterator();
		while(it.hasNext()){
			Integer i = it.next();
			System.out.println("Iterator Value::"+i);
		}
		
		//匿名クラスを使用してIterableのforEachメソッドを介してトラバース
		myList.forEach(new Consumer<Integer>() {

			public void accept(Integer t) {
				System.out.println("forEach anonymous class Value::"+t);
			}

		});
		
		//Consumerインターフェースの実装によるトラバース
		MyConsumer action = new MyConsumer();
		myList.forEach(action);
		
	}

}

//再利用可能なConsumerの実装
class MyConsumer implements Consumer<Integer>{

	public void accept(Integer t) {
		System.out.println("Consumer impl Value::"+t);
	}
}

行数は増えるかもしれませんが、forEachメソッドは反復処理とビジネスロジックの論理を別々の場所に配置するので、関心の分離とクリーンなコードが得られます。

2. インターフェース内のデフォルトメソッドと静的メソッド

forEachメソッドの詳細を注意深く読むと、それがIterableインターフェースで定義されていることに気付きますが、インターフェースにはメソッド本体を持てないことを知っています。Java 8から、インターフェースに実装されたメソッドを持つように拡張されました。メソッド実装を持つインターフェースを作成するには、defaultキーワードとstaticキーワードを使用できます。Iterableインターフェース内のforEachメソッドの実装は次のようになります:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

私たちは、Javaがクラスでの多重継承を提供していないことを知っています。なぜなら、それがダイヤモンド問題を引き起こすからです。では、インターフェースは抽象クラスに似ているので、どのように扱われるのでしょうか?

解決策は、このシナリオでコンパイラが例外をスローし、インターフェースを実装するクラスで実装ロジックを提供する必要があるということです。

package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface1 {

	void method1(String str);
	
	default void log(String str){
		System.out.println("I1 logging::"+str);
	}
	
	static void print(String str){
		System.out.println("Printing "+str);
	}
	
	// Objectメソッドをオーバーライドしようとすると、コンパイル時エラーが発生します
	// "デフォルトメソッドはjava.lang.Objectからメソッドをオーバーライドできません"
	
// default String toString(){
// return "i1";
// }
	
}
package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface2 {

	void method2();
	
	default void log(String str){
		System.out.println("I2 logging::"+str);
	}

}

両方のインターフェースには、実装ロジックを持つ共通のメソッドlog()があることに注意してください。

package com.journaldev.java8.defaultmethod;

public class MyClass implements Interface1, Interface2 {

	@Override
	public void method2() {
	}

	@Override
	public void method1(String str) {
	}

	// MyClassには独自のlog()実装がないとコンパイルされません
	@Override
	public void log(String str){
		System.out.println("MyClass logging::"+str);
		Interface1.print("abc");
	}
	
}

あなたが見ているように、Interface1には、MyClass.log()メソッドの実装で使用される静的メソッド実装があります。Java 8では、デフォルトおよび静的メソッドがCollection APIで大量に使用され、デフォルトメソッドはコードが後方互換性を保つために追加されます。

階層内の任意のクラスに同じシグネチャのメソッドがある場合、デフォルトメソッドは無関係になります。 Objectは基本クラスなので、インターフェースにequals()、hashCode()デフォルトメソッドがある場合、それは無関係になります。そのため、より明確にするために、インターフェースにObjectデフォルトメソッドを持つことは許可されていません。

Java 8でのインターフェースの変更の詳細については、「Java 8 インターフェースの変更」をお読みください。

3. 関数型インターフェースとラムダ式

上記のインターフェースコードに気付くと、@FunctionalInterfaceアノテーションがあります。関数インターフェースはJava 8で導入された新しい概念です。正確に1つの抽象メソッドを持つインターフェースは、関数インターフェースになります。

@FunctionalInterfaceアノテーションは、インターフェースを関数インターフェースとしてマークするために使用する必要はありません。

関数インターフェースの主要な利点の1つは、ラムダ式を使用してインスタンス化できる可能性です。インターフェースを匿名クラスでインスタンス化することもできますが、コードが膨大になります。

Runnable r = new Runnable(){
			@Override
			public void run() {
				System.out.println("My Runnable");
			}};

関数インターフェースには1つのメソッドしかないため、ラムダ式で簡単にメソッドの実装を提供できます。メソッド引数とビジネスロジックを提供するだけです。たとえば、上記の実装をラムダ式を使用して書くことができます:

Runnable r1 = () -> {
			System.out.println("My Runnable");
		};

メソッドの実装に1つのステートメントしかない場合、波括弧も不要です。たとえば、上記のInterface1匿名クラスは、次のようにしてラムダを使用してインスタンス化できます:

Interface1 i1 = (s) -> System.out.println(s);
		
i1.method1("abc");

ラムダ式は、関数インターフェースの匿名クラスを簡単に作成する手段です。ラムダ式を使用することによるランタイムの利点はありませんので、いくつかの余分な行を書くことを気にせず慎重に使用します。

A new package java.util.function has been added with bunch of functional interfaces to provide target types for lambda expressions and method references. Lambda expressions are a huge topic, I will write a separate article on that in the future.

詳しいチュートリアルはこちらをご覧ください:Java 8 Lambda Expressions Tutorial

4. コレクションに対する一括データ操作のためのJava Stream API

A new java.util.stream has been added in Java 8 to perform filter/map/reduce like operations with the collection. Stream API will allow sequential as well as parallel execution. This is one of the best features for me because I work a lot with Collections and usually with Big Data, we need to filter out them based on some conditions.

Collection インターフェースは、順次および並列実行のための Stream を取得するための stream() および parallelStream() デフォルトメソッドで拡張されています。簡単な例でその使用法を見てみましょう。

package com.journaldev.java8.stream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {

	public static void main(String[] args) {
		
		List<Integer> myList = new ArrayList<>();
		for(int i=0; i<100; i++) myList.add(i);
		
		// 順次 Stream
		Stream<Integer> sequentialStream = myList.stream();
		
		// 並列 Stream
		Stream<Integer> parallelStream = myList.parallelStream();
		
		// Stream API でのラムダの使用、フィルターの例
		Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
		// forEach でのラムダの使用
		highNums.forEach(p -> System.out.println("High Nums parallel="+p));
		
		Stream<Integer> highNumsSeq = sequentialStream.filter(p -> p > 90);
		highNumsSeq.forEach(p -> System.out.println("High Nums sequential="+p));

	}

}

上記の例コードを実行すると、以下のような出力が得られます:

High Nums parallel=91
High Nums parallel=96
High Nums parallel=93
High Nums parallel=98
High Nums parallel=94
High Nums parallel=95
High Nums parallel=97
High Nums parallel=92
High Nums parallel=99
High Nums sequential=91
High Nums sequential=92
High Nums sequential=93
High Nums sequential=94
High Nums sequential=95
High Nums sequential=96
High Nums sequential=97
High Nums sequential=98
High Nums sequential=99

並列処理の値が順序通りでないことに注意してください。したがって、巨大なコレクションで作業する際に並列処理は非常に役立ちます。

この投稿では、Stream API のすべてをカバーすることは不可能ですが、Stream API に関するすべての情報は Java 8 Stream API の例チュートリアル で読むことができます。

5. Java Time API

Java での日付、時刻、およびタイムゾーンの扱いは常に難しかったです。Java での日付と時刻の標準的なアプローチやAPIがありませんでした。Java 8 のうれしい追加機能の1つは、時刻を扱うプロセスを簡素化する java.time パッケージです。

Java Time API パッケージを見るだけで、非常に使いやすいことがわかります。いくつかのサブパッケージがあり、java.time.format は日付と時刻を印刷および解析するためのクラスを提供し、java.time.zone はタイムゾーンとその規則のサポートを提供します。

新しい Time API は、月や曜日のための整数定数ではなく、列挙型を優先します。役立つクラスの1つは、日時オブジェクトを文字列に変換するための DateTimeFormatter です。完全なチュートリアルは、Java Date Time API の例チュートリアル を参照してください。

6. Collection API の改善

既にforEach()メソッドとStream APIをコレクションで見てきました。 コレクションAPIに追加された新しいメソッドは次のとおりです:

  • Iterator デフォルトメソッド forEachRemaining(Consumer action) は、指定されたアクションを、すべての要素が処理されるかアクションが例外をスローするまで、残りの各要素に対して実行します。
  • Collection デフォルトメソッド removeIf(Predicate filter) は、指定された述語を満たすこのコレクションのすべての要素を削除します。
  • Collection spliterator() メソッドは、要素を順次または並行してトラバースするために使用できるSpliteratorインスタンスを返します。
  • Map replaceAll()compute()merge() メソッド。
  • Key Collisionsを持つHashMapクラスのパフォーマンス改善

7. Concurrency API の改善

重要なConcurrent APIの強化機能は次のとおりです:

  • ConcurrentHashMap compute()、forEach()、forEachEntry()、forEachKey()、forEachValue()、merge()、reduce()、およびsearch() メソッド。
  • CompletableFuture は、明示的に完了することができます(その値と状態を設定します)。
  • Executors newWorkStealingPool() メソッドは、利用可能なすべてのプロセッサを対象の並行性レベルとして使用して、ワークスティーリングスレッドプールを作成します。

8. Java IOの改善点

私が知っているいくつかのIOの改善点は、

  • Files.list(Path dir):ディレクトリ内のエントリを要素とする、遅延読み込みされるStreamを返す。
  • Files.lines(Path path):ファイルからすべての行をStreamとして読み込む。
  • Files.find():指定された開始ファイルをルートとするファイルツリー内を検索し、Pathで遅延読み込みされるStreamを返す。
  • BufferedReader.lines():このBufferedReaderから読み込まれた行を要素とするStreamを返す。

その他のJava 8 Core APIの改善点

役立つかもしれないいくつかのその他のAPIの改善点は、

  1. ThreadLocal:インスタンスを簡単に作成するための静的メソッドwithInitial(Supplier supplier)。
  2. Comparator インターフェースは、自然な順序、逆順などのために多くのデフォルトメソッドと静的メソッドで拡張されました。
  3. Integer、Long、およびDoubleラッパークラスにmin()、max()、およびsum()メソッドが追加されました。
  4. BooleanクラスのlogicalAnd()、logicalOr()、およびlogicalXor()メソッド。
  5. ZipFile .stream()メソッドを使用して、ZIPファイルエントリの順序付きStreamを取得します。エントリはZIPファイルの中央ディレクトリに現れる順序でStreamに表示されます。
  6. Mathクラスのいくつかのユーティリティメソッド。
  7. jjsコマンドはNashornエンジンを呼び出すために追加されました。
  8. jdepsコマンドはクラスファイルを解析するために追加されました。
  9. JDBC-ODBCブリッジが削除されました。
  10. PermGenメモリスペースが削除されました。

これで、Java 8の機能と例示プログラムに関するすべてです。もしJava 8の重要な機能を見逃している場合は、コメントでお知らせください。

Source:
https://www.digitalocean.com/community/tutorials/java-8-features-with-examples