JavaRush /Java Blog /Random-JA /Java 8 の機能 – 究極のガイド (パート 2)
0xFF
レベル 9
Донецк

Java 8 の機能 – 究極のガイド (パート 2)

Random-JA グループに公開済み
Java 8 の機能 – The ULTIMATE Guide という 記事の翻訳の第 2 部です。前編はこちら(リンクは変更される場合があります)。 Java 8 の機能 – 究極ガイド (パート 2) - 1

5. Java 8 ライブラリの新機能

Java 8 では、最新の同時実行性、関数型プログラミング、日付/時刻などのサポートを強化するために、多くの新しいクラスが追加され、既存のクラスが拡張されました。

5.1. クラス オプション

有名なNullPointerException は、Java アプリケーション障害の最も一般的な原因です。ずっと前に、Google の優れたプロジェクトGuava がOptional解決策として提示しましたNullPointerException。これにより、コードが null チェックによって汚染されるのを防ぎ、その結果、よりクリーンなコードの作成が促進されます。Google からインスピレーションを得た Guava クラスはOptionalJava 8 の一部になりました。 Optionalこれは単なるコンテナです。や何らかの type を含めることТも、単に null にすることもできます。明示的な null チェックが正当化されなくなるように、多くの便利なメソッドが提供されます。詳細については、公式ドキュメントを参照してください。null を使用した場合と使用しない場合の 2 つの小さな使用例を見てみましょうOptional
Optional<String> fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
このメソッドは、インスタンスにnull 以外の値が含まれている場合はtrueisPresent()を返し、それ以外の場合はfalse を返します。このメソッドには、結果にnull が含まれる場合のフォールバック メカニズムが含まれており、デフォルト値を生成する関数を受け入れます。map ()メソッドは現在の値を変換し、新しいインスタンスを返します。このメソッドはに似ていますが、関数の代わりにデフォルト値を受け取ります。このプログラムの出力は次のとおりです。 OptionalorElseGet()OptionalOptionalOptionalorElse()orElseGet()
Full Name is set? false
Full Name: [none]
Hey Stranger!
別の例を簡単に見てみましょう。
Optional<String> firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
System.out.println();
結果は次のようになります。
First Name is set? true
First Name: Tom
Hey Tom!
詳細については、公式ドキュメントを参照してください。

5.2. ストリーム

新しく追加された Stream API ( java.util.stream) は、Java での実際の関数型スタイルのプログラミングを導入します。これは Java ライブラリへの最も包括的な追加であり、Java 開発者の効率が大幅に向上し、効率的でクリーンで簡潔なコードを作成できるようになります。Stream API を使用すると、コレクションの処理がはるかに簡単になります (後で説明するように、これに限定されません)。簡単なクラスを例として考えてみましょうTask
public class Streams  {
    private enum Status {
        OPEN, CLOSED
    };

    private static final class Task {
        private final Status status;
        private final Integer points;

        Task( final Status status, final Integer points ) {
            this.status = status;
            this.points = points;
        }

        public Integer getPoints() {
            return points;
        }

        public Status getStatus() {
            return status;
        }

        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }
}
タスクには何らかのポイント (または擬似的な難易度) があり、OPENまたはCLOSEのいずれかになります。遊べる小さな問題集をご紹介します。
final Collection<Task> tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 )
);
私たちが調べようとしている最初の質問は、OPENタスクに現在いくつのポイントが含まれているかということです。Java 8 より前では、この問題に対する通常の解決策は、 iterator を使用することでしたforeach。しかし、Java 8 では、答えはストリームです。これは、順次および並列の集計操作をサポートする一連の要素です。
// Подсчет общего количества очков всех активных задач с использованием sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );
コンソール出力は次のようになります。
Total points: 18
ここで何が起こっているのか見てみましょう。まず、タスクのコレクションがストリーミング表現に変換されます。この操作により、CLOSEDfilterステータスを持つすべてのタスクがフィルターで除外されます。次のステップでは、各インスタンスのメソッドを使用して、ストリームs をストリーム s に変換します。最後に、メソッド を使用してすべてのポイントが合計され、最終結果が得られます。次の例に進む前に、スレッドについて留意すべき点がいくつかあります (詳細はこちら)。操作は中間操作最終操作に分かれます。 中間操作は新しいストリームを返します。これらは常に遅延型であり、 などの中間操作を実行するとき、実際にはフィルタリングを実行せず、代わりに新しいストリームを作成します。完了すると、指定された述語に一致する元のストリームの要素が含まれます。 およびなどの有限操作をストリーム経由で渡して、結果または副作用を生成することができます。最終操作が完了すると、ストリームは使用されたとみなされ、再度使用することはできません。ほとんどの場合、最終操作は基礎となるデータ ソースの走査を完了する傾向があります。スレッドのもう 1 つの貴重な機能は、すぐに使える並列プロセスのサポートです。すべての問題のスコアの合計を求めるこの例を見てみましょう。 mapToIntTaskIntegerTask::getPointsTasksumstreamfilterforEachsum
// Calculate total points of all tasks
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints )
   .reduce( 0, Integer::sum );

System.out.println( "Total points (all tasks): " + totalPoints );
これは最初の例とよく似ていますが、すべてのタスクを並行して処理し、 メソッドを使用して最終結果を計算しようとしている点が異なりますreduce。コンソール出力は次のとおりです。
Total points (all tasks): 26.0
多くの場合、特定の基準に従って要素をグループ化する必要があります。この例では、スレッドがこれにどのように役立つかを示します。
// Группировка задач по их статусу
final Map<Status, List<Task>> map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
コンソール出力は次のようになります。
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
問題の例を終了するには、合計ポイントに基づいて、コレクション内の各問題の全体的なパーセンテージ (または重み) を計算しましょう。
// Подсчет веса каждой задачи (How процент от общего количества очков)
final Collection<String> result = tasks
    .stream()                                        // Stream<String>
    .mapToInt( Task::getPoints )                     // IntStream
    .asLongStream()                                  // LongStream
    .mapToDouble( points -> points / totalPoints )   // DoubleStream
    .boxed()                                         // Stream<Double>
    .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
    .mapToObj( percentage -> percentage + "%" )      // Stream<String>
    .collect( Collectors.toList() );                 // List<String>

System.out.println( result );
コンソール出力は次のようになります。
[19%, 50%, 30%]
最後に、前に述べたように、Stream API は Java コレクションだけを対象としたものではありません。テキスト ファイルを 1 行ずつ読み取るなどの一般的な I/O 操作は、ストリーム処理の使用に非常に適しています。これを証明する小さな例を次に示します。
final Path path = new File( filename ).toPath();
try( Stream<String> lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}
onConsoleスレッド上で呼び出される メソッドは、追加のプライベート ハンドラーを備えた同等のスレッドを返します。close()プライベート ハンドラーは、スレッド上でメソッドが呼び出されるときに呼び出されます。Java 8 のStream API とラムダ参照メソッド、およびデフォルトおよび静的メソッドは、最新のソフトウェア開発パラダイムへの答えです。詳細については、公式ドキュメントを参照してください。

5.3. 日付/時刻 API (JSR 310)

Java 8 は、新しい日付と時刻 API (JSR 310)を提供することにより、日付と時刻の管理に新しい外観をもたらします。日付と時刻の操作は、Java 開発者にとって最大の問題点の 1 つです。標準java.util.Dateに従ってもjava.util.Calendar状況は一般に改善されませんでした (おそらく、さらに混乱を招いたかもしれません)。こうして、Java の優れた日付/時刻 API の代替となるJoda-Timeが誕生しました。Java 8 (JSR 310) の新しい日付/時刻 API は、Joda-Timeから多大な影響を受けており、その利点を最大限に活用しています。新しいパッケージには、日付、時刻、日付/時刻、タイム ゾーン、期間、および時刻操作のすべてのクラスが含まれています。API 設計では不変性を非常に重視しており、変更は許可されていません (これは から学んだ厳しい教訓です)。変更が必要な場合は、対応するクラスの新しいインスタンスが返されます。主なクラスとその使用例を見てみましょう。最初のクラスは、タイムゾーンを使用して現在の瞬間、日付、時刻へのアクセスを提供します。との代わりに使用できます。 java.timejava.util.CalendarClockClockSystem.currentTimeMillis()TimeZone.getDefault()
// Получить системное время How смещение UTC
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
コンソール出力の例:
2014-04-12T15:19:29.282Z
1397315969360
私たちが検討する他の新しいクラスはLocaleDateと ですLocalTimeLocaleDateISO-8601 暦体系のタイムゾーンを含まない日付部分のみが含まれます。したがって、LocalTimeタイムコードの一部のみが含まれます。
// получить местную date и время время
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );

System.out.println( date );
System.out.println( dateFromClock );

// получить местную date и время время
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );

System.out.println( time );
System.out.println( timeFromClock );
コンソール出力の例:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTimeLocaleDateと を連結しLocalTime、 および には ISO-8601 カレンダー システムの日付と時刻が含まれますが、タイムゾーンは含まれません。簡単な例を以下に示します。
// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );

System.out.println( datetime );
System.out.println( datetimeFromClock );
コンソール出力の例:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
特定のタイムゾーンの日付/時刻が必要な場合は、ZonedDateTime. ISO-8601 カレンダー システムの日付と時刻が含まれます。さまざまなタイムゾーンの例をいくつか示します。
// Получение даты/времени для временной зоны
final ZonedDateTime zonedDatetime = ZonedDateTime.now();
final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );

System.out.println( zonedDatetime );
System.out.println( zonedDatetimeFromClock );
System.out.println( zonedDatetimeFromZone );
コンソール出力の例:
2014-04-12T11:47:01.017-04:00[America/New_York]
2014-04-12T15:47:01.017Z
2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
最後に、クラスDuration: 秒とナノ秒のタイム スパンを見てみましょう。これにより、2 つの日付間の計算が非常に簡単になります。これを行う方法を見てみましょう。
// Получаем разницу между двумя датами
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );

final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );
上の例では、2014 年 4 月 16 日と2015 年4 月 16 日の 2 つの日付の間の期間 (日と時間) を計算します。コンソール出力の例を次に示します。
Duration in days: 365
Duration in hours: 8783
Java 8 の新しい日付/時刻の全体的な印象は非常に肯定的です。その理由の一部は、変更が実戦でテストされた基盤 (Joda-Time) に基づいているためであり、一部は、今回この問題が真剣に再検討され、開発者の声が聞かれたためです。詳細については、公式ドキュメントを参照してください。

5.4. ナスホルン JavaScript エンジン

Java 8 には、新しい Nashorn JavaScript エンジンが付属しており、これを使用すると、JVM 上で特定の種類の JavaScript アプリケーションを開発および実行できます。Nashorn JavaScript エンジンは、Java と JavaScript の対話を可能にする同じルール セットに従う javax.script.ScriptEngine の単なる別の実装です。ここに小さな例を示します。
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );

System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
コンソール出力の例:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2

5.5. Base64

最後に、Java 8 のリリースにより、 Base64 エンコーディングのサポートがJava 標準ライブラリに組み込まれました。これは非常に簡単に使用でき、サンプルはこれを示しています。
package com.javacodegeeks.java8.base64;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";

        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );

        final String decoded = new String(
            Base64.getDecoder().decode( encoded ),
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}
プログラムのコンソール出力には、エンコードされたテキストとデコードされたテキストの両方が表示されます。
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
URL フレンドリーなエンコーダー/デコーダーや、MIME フレンドリーなエンコーダー/デコーダー ( Base64.getUrlEncoder()/ Base64.getUrlDecoder()Base64.getMimeEncoder()/ Base64.getMimeDecoder()) のクラスもあります。

5.6. 並列アレイ

Java 8 リリースでは、配列の並列処理のための新しいメソッドが多数追加されています。おそらくこれらの中で最も重要なものはparallelSort()、マルチコア マシンでのソートを大幅に高速化できる です。以下の小さな例は、新しいメソッド ファミリ ( parallelXxx) の動作を示しています。
package com.javacodegeeks.java8.parallel.arrays;

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;

public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];

        Arrays.parallelSetAll( arrayOfLong,
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();

        Arrays.parallelSort( arrayOfLong );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}
この小さなコードでは、parallelSetAll()配列に 20,000 個のランダムな値を埋めるメソッドが使用されています。この後、適用されますparallelSort()。プログラムは、配列が実際にソートされていることを示すために、ソートの前後で最初の 10 個の要素を出力します。プログラムの出力例は次のようになります (配列の要素はランダムであることに注意してください)。
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793

5.7. 平行度

java.util.concurrent.ConcurrentHashMap新しく追加されたストリーム オブジェクトとラムダ式に基づく集計操作をサポートするために、新しいメソッドがクラスに追加されました。java.util.concurrent.ForkJoinPoolまた、共有プーリングをサポートする新しいメソッドがクラスに追加されました( Java 同時実行に関する無料コースも参照してください)。読み取り/書き込み制御用の 3 つのアクセス モードを備えた機能ベースのロックを提供する新しいクラスjava.util.concurrent.locks.StampedLockが追加されました (これは、あまり良くないクラスのより良い代替手段と考えることができますjava.util.concurrent.locks.ReadWriteLock)。パッケージに追加された新しいクラスjava.util.concurrent.atomic:
  • ダブルアキュムレータ
  • 二重加算器
  • ロングアキュムレータ
  • 長加算器

6. Java ランタイム環境 (JVM) の新機能

この領域はPermGen廃止され、Metaspace (JEP 122) に置き換えられました。JVM オプション-XX:PermSizeおよび は、それぞれおよび-XX:MaxPermSizeに置き換えられました。 -XX:MetaSpaceSize-XX:MaxMetaspaceSize

7. 結論

未来はここにあります: Java 8 は、開発者の生産性を向上させる機能を提供することにより、プラットフォームを前進させました。実稼働システムを Java 8 に移行するにはまだ時期尚早ですが、今後数か月間で導入が徐々に進み始めるはずです。ただし、今こそ、Java 8 との互換性を備えたコード ベースの準備を開始し、安全性と安定性が十分に確保された時点で Java 8 の変更を組み込む準備を整える時期です。コミュニティが Java 8 を受け入れたことの証として、Pivo​​tal は最近、Java 8 の実稼働サポートを備えた Spring Framework をリリースしましたJava 8 のエキサイティングな新機能についてコメント欄に入力してください。

8. 情報源

Java 8 機能のさまざまな側面について詳しく説明する追加リソース:
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION