JavaRush /Java Blog /Random-JA /コーヒーブレイク#155。Java の関数トップ 10

コーヒーブレイク#155。Java の関数トップ 10

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

Java の関数トップ 10

出典: DZone この記事では、開発者が日常業務でよく使用する 10 個の Java プログラミング機能をリストします。 コーヒーブレイク#155。 Java のトップ 10 関数 - 1

1. コレクションファクトリーメソッド

コレクションは、プログラミングで最も一般的に使用される機能の 1 つです。それらは、オブジェクトを保管し、渡すためのコンテナとして使用されます。コレクションはオブジェクトの並べ替え、検索、繰り返しにも使用されるため、プログラマの作業が大幅に楽になります。これらには、List、Set、Map などのいくつかの基本インターフェイスといくつかの実装があります。コレクションとマップを作成する従来の方法は、多くの開発者にとって冗長に見えるかもしれません。そのため、Java 9 ではいくつかの簡潔なファクトリー メソッドが導入されました。 リスト:
List countries = List.of("Bangladesh", "Canada", "United States", "Tuvalu");
セット
Set countries = Set.of("Bangladesh", "Canada", "United States", "Tuvalu");
地図:
Map countriesByPopulation = Map.of("Bangladesh", 164_689_383,
                                                            "Canada", 37_742_154,
                                                            "United States", 331_002_651,
                                                            "Tuvalu", 11_792);
ファクトリ メソッドは、不変のコンテナを作成する場合に非常に便利です。ただし、変更可能なコレクションを作成する場合は、従来のアプローチを使用することをお勧めします。

2. ローカル型推論

Java 10 では、ローカル変数の型推論が追加されました。これ以前は、開発者はオブジェクトの宣言と初期化の際に型を 2 回指定する必要がありました。とても疲れました。次の例を見てください。
Map> properties = new HashMap<>();
ここには両面の情報の種類が示されます。これを 1 か所で定義すると、コード リーダーと Java コンパイラは、それが Map 型である必要があることを簡単に理解します。ローカル型推論はまさにそれを行います。以下に例を示します。
var properties = new HashMap>();
これで、すべてが 1 回だけ書かれるようになり、コードの見た目もそれほど悪くなくなりました。また、メソッドを呼び出して結果を変数に格納すると、コードはさらに短くなります。例:
var properties = getProperties();
そしてさらに:
var countries = Set.of("Bangladesh", "Canada", "United States", "Tuvalu");
ローカル型推論は便利な機能のように見えますが、これを批判する人もいます。開発者の中には、これにより可読性が低下すると主張する人もいます。そして、これは簡潔さよりも重要です。

3. 高度なスイッチ式

従来の switch ステートメントは Java の初期から存在しており、当時の C や C++ を思い出させます。これは問題ありませんでしたが、言語が進化するにつれて、この演算子は Java 14 までは何の改善も提供しませんでした。もちろん、いくつかの欠点がありました。最も悪名高いのはフォールスルーです。この問題を解決するために、開発者は、大部分が定型コードである Break ステートメントを使用しました。ただし、Java 14 では、はるかに多くの関数リストを備えた改良版の switch ステートメントが導入されました。これで、break ステートメントを追加する必要がなくなり、失敗の問題が解決されました。さらに、switch ステートメントは値を返すことができます。つまり、それを式として使用し、変数に割り当てることができます。
int day = 5;
String result = switch (day) {
    case 1, 2, 3, 4, 5 -> "Weekday";
    case 6, 7 -> "Weekend";
    default -> "Unexpected value: " + day;
};

4. 記録

Records は Java 16 で導入された比較的新しい機能ですが、主に不変オブジェクトの作成により、多くの開発者が非常に便利であると感じています。多くの場合、あるメソッドから別のメソッドに値を保存したり渡したりするために、プログラム内にデータ オブジェクトが必要になります。たとえば、x、y、z 座標を転送するクラスは次のように記述します。
package ca.bazlur.playground;

import java.util.Objects;

public final class Point {
    private final int x;
    private final int y;
    private final int z;

    public Point(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public int x() {
        return x;
    }

    public int y() {
        return y;
    }

    public int z() {
        return z;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (obj == null || obj.getClass() != this.getClass()) return false;
        var that = (Point) obj;
        return this.x == that.x &&
                this.y == that.y &&
                this.z == that.z;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y, z);
    }

    @Override
    public String toString() {
        return "Point[" +
                "x=" + x + ", " +
                "y=" + y + ", " +
                "z=" + z + ']';
    }

}
クラスが冗長すぎるようです。エントリの助けを借りて、このすべてのコードをより簡潔なバージョンに置き換えることができます。
package ca.bazlur.playground;

public record Point(int x, int y, int z) {
}

5.オプション

メソッドとは、条件を定義する契約です。パラメーターをその型と戻り値の型で指定します。次に、メソッドが呼び出されたときに、規約に従って動作することを期待します。ただし、多くの場合、指定された型の値ではなく、メソッドからの null が返されます。これは間違いです。これを解決するために、イニシエーターは通常、値が null であるかどうかに関係なく、if 条件を使用して値をテストします。例:
public class Playground {

    public static void main(String[] args) {
        String name = findName();
        if (name != null) {
            System.out.println("Length of the name : " + name.length());
        }
    }

    public static String findName() {
        return null;
    }
}
上記のコードを見てください。findNameメソッドはStringを返すことになっていますが、null を返します。イニシエーターは問題に対処するために、最初に null をチェックする必要があります。イニシエーターがこれを行うのを忘れた場合、最終的にNullPointerExceptionが発生します。一方、メソッドのシグネチャが復帰しない可能性を示している場合、これですべての混乱が解決されます。ここで、 Optionalが役立ちます。
import java.util.Optional;

public class Playground {

    public static void main(String[] args) {
        Optional optionalName = findName();
        optionalName.ifPresent(name -> {
            System.out.println("Length of the name : " + name.length());
        });
    }

    public static Optional findName() {
        return Optional.empty();
    }
}
ここでは、値を返さないようにOptionalオプションを使用してfindNameメソッド を書き直しました。これにより、プログラマーに事前に警告が送信され、問題が解決されます。

6. Java 日付時刻 API

すべての開発者は、日付と時刻の計算に関して多かれ少なかれ混乱します。これは誇張ではありません。これは主に、日付と時刻を操作するための優れた Java API が欠如していたことが原因でした。Java 8 では、日付と時刻に関連するすべての問題を解決する優れた API セットが java.time パッケージに導入されたため、この問題はなくなりました。java.time パッケージには、タイム ゾーンを含むほとんどの問題を解決する多くのインターフェイスとクラスが含まれています。このパッケージで最も一般的に使用されるクラスは次のとおりです。
  • ローカル日付
  • 現地時間
  • ローカル日時
  • 間隔
  • 期間
  • ゾーン日時
java.time パッケージのクラスの使用例:
import java.time.LocalDate;
import java.time.Month;

public class Playground3 {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2022, Month.APRIL, 4);
        System.out.println("year = " + date.getYear());
        System.out.println("month = " + date.getMonth());
        System.out.println("DayOfMonth = " + date.getDayOfMonth());
        System.out.println("DayOfWeek = " + date.getDayOfWeek());
        System.out.println("isLeapYear = " + date.isLeapYear());
    }
}
LocalTime クラスを使用して時間を計算する例:
LocalTime time = LocalTime.of(20, 30);
int hour = time.getHour();
int minute = time.getMinute();
time = time.withSecond(6);
time = time.plusMinutes(3);
タイムゾーンを追加する:
ZoneId zone = ZoneId.of("Canada/Eastern");
LocalDate localDate = LocalDate.of(2022, Month.APRIL, 4);
ZonedDateTime zonedDateTime = date.atStartOfDay(zone);

7.NullPointerException

すべての開発者は NullPointerException を嫌います。StackTrace が問題の正確な内容について有益な情報を提供しない場合、特に困難になる可能性があります。これを実証するために、サンプル コードを見てみましょう。
package com.bazlur;

public class Main {

    public static void main(String[] args) {
        User user = null;
        getLengthOfUsersName(user);
    }

    public static void getLengthOfUsersName(User user) {
        System.out.println("Length of first name: " + user.getName().getFirstName());
    }
}

class User {
    private Name name;
    private String email;

    public User(Name name, String email) {
        this.name = name;
        this.email = email;
    }

   //getter
   //setter
}

class Name {
    private String firstName;
    private String lastName;

    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

   //getter
   //setter
}
この部分の基本的なメソッドを見てください。次にNullPointerException がスローされることがわかります。Java 14 より前のバージョンでコードを実行してコンパイルすると、次の StackTrace が得られます。
Exception in thread "main" java.lang.NullPointerException
at com.bazlur.Main.getLengthOfUsersName(Main.java:11)
at com.bazlur.Main.main(Main.java:7)
ここには、 NullPointerException が発生した 場所と理由に関する情報がほとんどありません。しかし、Java 14 以降のバージョンでは、StackTrace でより多くの情報を取得できるため、非常に便利です。Java 14 では次のようになります。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "ca.bazlur.playground.User.getName()" because "user" is null
at ca.bazlur.playground.Main.getLengthOfUsersName(Main.java:12)
at ca.bazlur.playground.Main.main(Main.java:8)

8. 完成可能な未来

私たちはプログラムを 1 行ずつ作成し、通常は 1 行ずつ実行します。ただし、プログラムを高速化するために並列実行が必要な場合があります。これには通常、Java スレッドを使用します。Java スレッド プログラミングは、必ずしも並列プログラミングに関するものではありません。代わりに、独立して実行され、多くの場合は非同期でも実行される、いくつかの独立したプログラム モジュールを構成できるようになります。ただし、スレッドプログラミングは、特に初心者にとっては非常に困難です。このため、Java 8 では、プログラムの一部を非同期で実行できるシンプルな API が提供されています。例を見てみましょう。3 つの REST API を呼び出して、その結果を結合する必要があるとします。それらを 1 つずつ呼び出すことができます。それぞれの受信に約 200 ミリ秒かかる場合、受信にかかる合計時間は 600 ミリ秒かかります。これらを並行して実行できたらどうなるでしょうか? 最近のプロセッサは通常マルチコアであるため、3 つの異なるプロセッサ上で 3 つの REST 呼び出しを簡単に処理できます。CompletableFuture を使用すると、これを簡単に行うことができます。
package ca.bazlur.playground;

import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class SocialMediaService {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        var service = new SocialMediaService();

        var start = Instant.now();
        var posts = service.fetchAllPost().get();
        var duration = Duration.between(start, Instant.now());

        System.out.println("Total time taken: " + duration.toMillis());
    }

    public CompletableFuture> fetchAllPost() {
        var facebook = CompletableFuture.supplyAsync(this::fetchPostFromFacebook);
        var linkedIn = CompletableFuture.supplyAsync(this::fetchPostFromLinkedIn);
        var twitter = CompletableFuture.supplyAsync(this::fetchPostFromTwitter);

        var futures = List.of(facebook, linkedIn, twitter);

        return CompletableFuture.allOf(futures.toArray(futures.toArray(new CompletableFuture[0])))
                .thenApply(future -> futures.stream()
                        .map(CompletableFuture::join)
                        .toList());
    }
    private String fetchPostFromTwitter() {
        sleep(200);
        return "Twitter";
    }

    private String fetchPostFromLinkedIn() {
        sleep(200);
        return "LinkedIn";
    }

    private String fetchPostFromFacebook() {
        sleep(200);
        return "Facebook";
    }

    private void sleep(int millis) {
        try {
            TimeUnit.MILLISECONDS.sleep(millis);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

9. ラムダ式

ラムダ式はおそらく Java 言語の最も強力な機能です。彼らは私たちのコードの書き方を変えました。ラムダ式は、引数を受け取って値を返すことができる匿名関数のようなものです。関数を変数に代入し、それを引数としてメソッドに渡すと、メソッドはそれを返すことができます。彼には肉体がある。このメソッドとの唯一の違いは、名前がないことです。表現は短く簡潔です。通常、定型コードはあまり含まれていません。拡張子が .java のディレクトリ内のすべてのファイルをリストする必要がある例を見てみましょう。
var directory = new File("./src/main/java/ca/bazlur/playground");
String[] list = directory.list(new FilenameFilter() {
    @Override
    public boolean accept(File dir, String name) {
        return name.endsWith(".java");
    }
});
このコード部分をよく見ると、匿名の内部クラスlist() がメソッドに渡されています。そして、内部クラスには、ファイルをフィルタリングするためのロジックを配置しました。基本的に、私たちはロジックの周囲のパターンではなく、ロジックのこの部分に興味があります。ラムダ式を使用すると、テンプレート全体を削除でき、必要なコードを作成できます。以下に例を示します。
var directory = new File("./src/main/java/ca/bazlur/playground");
String[] list = directory.list((dir, name) -> name.endsWith(".java"));
もちろん、これはほんの一例であり、ラムダ式には他にも多くの利点があります。

10. ストリームAPI

私たちの日常業務において、一般的なタスクの 1 つはデータセットの処理です。フィルタリング、変換、結果の収集など、いくつかの一般的な操作があります。Java 8 より前では、このような操作は本質的に必須でした。私たちは、意図 (つまり、何を達成したいのか) とそれをどのように実現したいのかに応じてコードを作成する必要がありました。ラムダ式と Stream API の発明により、データ処理関数を宣言的に作成できるようになりました。意図を示すだけであり、結果がどのように得られるかを記述する必要はありません。例を次に示します。書籍のリストがあり、Java 書籍のすべての名前をカンマで区切って並べ替えて検索したいと考えています。
public static String getJavaBooks(List books) {
    return books.stream()
            .filter(book -> Objects.equals(book.language(), "Java"))
            .sorted(Comparator.comparing(Book::price))
            .map(Book::name)
            .collect(Collectors.joining(", "));
}
上記のコードはシンプルで読みやすく、簡潔です。ただし、以下に代替の命令型コードを示します。
public static String getJavaBooksImperatively(List books) {
    var filteredBook = new ArrayList();
    for (Book book : books) {
        if (Objects.equals(book.language(), "Java")){
            filteredBook.add(book);
        }
    }
    filteredBook.sort(new Comparator() {
        @Override
        public int compare(Book o1, Book o2) {
            return Integer.compare(o1.price(), o2.price());
        }
    });

    var joiner = new StringJoiner(",");
    for (Book book : filteredBook) {
        joiner.add(book.name());
    }

    return joiner.toString();
}
どちらのメソッドも同じ値を返しますが、違いが明確にわかります。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION