Javaラムダ式でPredicateを簡単実装!実例と利用方法を徹底解説

Javaプログラミングにおいて、ラムダ式は簡潔で表現力のあるコードを書くための強力なツールです。その中でも、Predicateは関数型インターフェースの一つであり、条件を評価して真偽値を返すためのメソッドを提供します。特に、Predicateとラムダ式を組み合わせることで、コードの可読性や再利用性が向上し、複雑な条件処理を簡潔に表現することが可能になります。本記事では、Javaのラムダ式とPredicateの基本概念から、その実装や応用例までを解説し、実際の開発現場でどのように活用できるかを詳しく紹介します。これにより、Javaプログラムでより効果的に条件処理を行えるようになります。

目次

Predicateインターフェースとは

Predicateインターフェースは、Javaの標準ライブラリに含まれる関数型インターフェースで、単一の抽象メソッドtestを持ちます。このtestメソッドは、引数として与えられたオブジェクトに対して、条件を評価し、真(true)または偽(false)を返します。Predicateは、フィルタリングや条件付き処理など、多くの用途で利用され、特にストリームAPIと組み合わせて使用されることが多いです。

Predicateの基本構造

Predicateインターフェースの基本構造は非常にシンプルです。以下はその定義です。

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

このインターフェースを実装することで、条件を評価するロジックをカプセル化し、再利用可能なコードとして活用できます。たとえば、Predicate<String>を使って文字列が空でないかをチェックすることができます。

基本的な使用例

以下は、Predicateインターフェースを使って、整数が偶数であるかどうかを判定する簡単な例です。

Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4));  // 出力: true
System.out.println(isEven.test(5));  // 出力: false

このように、Predicateを使うことで、条件評価を簡潔に表現できるようになります。

ラムダ式を使用したPredicateの実装

Javaのラムダ式を使うことで、Predicateインターフェースをより簡潔に実装することができます。ラムダ式は、匿名関数として振る舞い、通常のメソッドよりも少ないコードで処理を記述することが可能です。Predicatetestメソッドに対応するラムダ式を用いることで、条件を評価するコードを短く、かつ読みやすくできます。

ラムダ式でのPredicateの記述方法

ラムダ式を使用してPredicateを実装する際の基本構文は以下の通りです。

Predicate<T> predicate = (T t) -> { 条件式; };

ここで、Tは任意の型を指し、条件式の部分には評価したいロジックを記述します。例えば、文字列が空でないかを判定するPredicateをラムダ式で実装する場合、以下のようになります。

Predicate<String> isNotEmpty = str -> str != null && !str.isEmpty();

このisNotEmptyは、文字列が空でないかどうかを評価し、trueまたはfalseを返します。

具体例: 数値の判定

以下に、数値が正の値かどうかを判定するPredicateの例を示します。

Predicate<Integer> isPositive = n -> n > 0;

このisPositiveは、引数として与えられた整数が正の値であればtrueを返し、そうでなければfalseを返します。実際に使用する際は、以下のように簡単に呼び出せます。

System.out.println(isPositive.test(10));  // 出力: true
System.out.println(isPositive.test(-5));  // 出力: false

複雑な条件の実装

ラムダ式を用いることで、複雑な条件も簡潔に記述することができます。例えば、数値が正であり、かつ偶数であるかどうかを判定するPredicateを実装する場合、次のように記述できます。

Predicate<Integer> isPositiveAndEven = n -> n > 0 && n % 2 == 0;

このように、ラムダ式を活用することで、条件に応じたPredicateを容易に作成でき、コードの可読性と保守性を向上させることができます。

Predicateのチェーン利用

JavaのPredicateインターフェースには、複数の条件を連鎖的に結合するためのメソッドが用意されています。これにより、単一のPredicateで表現できる条件が複数に分かれている場合でも、簡潔に組み合わせて複雑なロジックを構築することが可能です。代表的なメソッドには、andornegateがあります。

andメソッドでの条件の結合

andメソッドは、二つのPredicateを結合し、両方の条件が満たされた場合にのみtrueを返します。例えば、数値が正の数であり、かつ偶数であるかを判定する場合、以下のように記述できます。

Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEven = n -> n % 2 == 0;

Predicate<Integer> isPositiveAndEven = isPositive.and(isEven);

このisPositiveAndEvenは、数値が正の数であり、かつ偶数である場合にtrueを返します。

System.out.println(isPositiveAndEven.test(4));  // 出力: true
System.out.println(isPositiveAndEven.test(3));  // 出力: false
System.out.println(isPositiveAndEven.test(-2)); // 出力: false

orメソッドでの条件の結合

orメソッドは、二つのPredicateを結合し、どちらか一方の条件が満たされればtrueを返します。例えば、数値が負の数であるか、偶数であるかを判定する場合、以下のように記述できます。

Predicate<Integer> isNegative = n -> n < 0;
Predicate<Integer> isEven = n -> n % 2 == 0;

Predicate<Integer> isNegativeOrEven = isNegative.or(isEven);

このisNegativeOrEvenは、数値が負の数であるか、偶数である場合にtrueを返します。

System.out.println(isNegativeOrEven.test(-3)); // 出力: true
System.out.println(isNegativeOrEven.test(2));  // 出力: true
System.out.println(isNegativeOrEven.test(3));  // 出力: false

negateメソッドでの条件の否定

negateメソッドは、指定したPredicateの結果を否定します。つまり、元のPredicatetrueを返す場合にfalseを返し、その逆も然りです。例えば、数値が偶数でないかどうかを判定する場合、以下のように記述できます。

Predicate<Integer> isOdd = isEven.negate();

このisOddは、数値が奇数である場合にtrueを返します。

System.out.println(isOdd.test(4)); // 出力: false
System.out.println(isOdd.test(3)); // 出力: true

複数の条件を組み合わせた複雑なロジック

これらのメソッドを組み合わせることで、複雑な条件を簡潔に表現することができます。例えば、数値が正であり、かつ偶数でない(奇数である)かどうかを判定する場合、以下のように記述できます。

Predicate<Integer> isPositiveAndOdd = isPositive.and(isEven.negate());

このようにして作成したPredicateを使えば、複数の条件を効率的にチェックすることが可能となります。チェーン利用により、コードの可読性を保ちながら、柔軟で強力な条件処理を実装することができます。

Predicateの応用例: フィルタリング

Predicateを使用する最も一般的な応用例の一つが、リストやコレクション内のデータをフィルタリングすることです。JavaのストリームAPIと組み合わせることで、条件に合致する要素を効率的に抽出し、より直感的で簡潔なコードを実現できます。

リストのフィルタリング

例えば、整数のリストから偶数のみを抽出する場合、Predicateを使用して次のように実装できます。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Predicate<Integer> isEven = n -> n % 2 == 0;

List<Integer> evenNumbers = numbers.stream()
                                   .filter(isEven)
                                   .collect(Collectors.toList());

System.out.println(evenNumbers); // 出力: [2, 4, 6, 8, 10]

このコードでは、filterメソッドを用いて、リストの各要素に対してPredicateが評価され、trueを返す要素のみが結果のリストに含まれます。

複数条件のフィルタリング

Predicateのチェーン利用により、複数の条件に基づいてリストをフィルタリングすることも可能です。例えば、正の偶数のみをリストから抽出する場合は、以下のように記述できます。

Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isPositiveAndEven = isPositive.and(isEven);

List<Integer> positiveEvenNumbers = numbers.stream()
                                           .filter(isPositiveAndEven)
                                           .collect(Collectors.toList());

System.out.println(positiveEvenNumbers); // 出力: [2, 4, 6, 8, 10]

このように、複数の条件を組み合わせることで、必要なデータのみを効率的に抽出することができます。

文字列リストのフィルタリング

Predicateは数値だけでなく、文字列や他のオブジェクトにも適用できます。例えば、文字列リストから特定の文字列が含まれている要素を抽出する場合、次のように実装できます。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Edward");
Predicate<String> startsWithA = str -> str.startsWith("A");

List<String> namesStartingWithA = names.stream()
                                       .filter(startsWithA)
                                       .collect(Collectors.toList());

System.out.println(namesStartingWithA); // 出力: [Alice]

この例では、startsWithAというPredicateを使って、文字列が”A”で始まるかどうかをチェックし、その条件に合致する要素だけを抽出しています。

実世界でのフィルタリング例

実際のアプリケーション開発では、Predicateを使ったフィルタリングは非常に役立ちます。例えば、ユーザー管理システムで特定の条件に基づいてユーザーをフィルタリングする、商品リストから特定の価格帯やカテゴリーの商品を抽出するなど、多くの場面で活用できます。

以下は、商品リストから価格が1000円以上で、在庫がある商品を抽出する例です。

List<Product> products = getProductList(); // 仮のメソッドで商品リストを取得
Predicate<Product> isExpensive = p -> p.getPrice() >= 1000;
Predicate<Product> isInStock = p -> p.isInStock();

List<Product> filteredProducts = products.stream()
                                         .filter(isExpensive.and(isInStock))
                                         .collect(Collectors.toList());

このコードでは、価格が1000円以上でかつ在庫がある商品だけをリストから抽出しています。このように、PredicateとストリームAPIを組み合わせることで、データのフィルタリング処理を簡潔に行うことができます。

カスタムPredicateの作成方法

標準的なPredicateだけではカバーしきれない複雑な条件や、特定のビジネスロジックに基づいた条件判定を行いたい場合、カスタムPredicateを作成することで柔軟に対応できます。カスタムPredicateは、特定の条件に基づくロジックを再利用可能な形でカプセル化するのに最適です。

カスタムPredicateの基本的な作成方法

カスタムPredicateを作成するには、Predicateインターフェースをラムダ式やメソッド参照で実装します。例えば、カスタム条件として、文字列の長さが5文字以上かどうかを判定するPredicateを作成する場合、次のように記述できます。

Predicate<String> isLengthGreaterThanFive = str -> str.length() > 5;

このisLengthGreaterThanFiveは、与えられた文字列が5文字以上であればtrueを返します。

System.out.println(isLengthGreaterThanFive.test("Hello"));  // 出力: false
System.out.println(isLengthGreaterThanFive.test("Welcome")); // 出力: true

メソッド参照によるカスタムPredicateの作成

より複雑なカスタムPredicateを作成する場合、ラムダ式だけでなく、メソッド参照を使うことも可能です。例えば、特定の条件に合致する文字列を判定するメソッドを別途定義し、それをPredicateとして使用することができます。

public class StringUtils {
    public static boolean isAllUpperCase(String str) {
        return str.equals(str.toUpperCase());
    }
}

Predicate<String> isUpperCase = StringUtils::isAllUpperCase;

このisUpperCaseは、文字列がすべて大文字で構成されている場合にtrueを返します。

System.out.println(isUpperCase.test("HELLO"));  // 出力: true
System.out.println(isUpperCase.test("Hello"));  // 出力: false

複雑なビジネスロジックを反映したカスタムPredicate

実際のプロジェクトでは、ビジネスロジックに基づく複雑な条件判定が必要になることがあります。例えば、顧客の購入履歴から特定の条件に基づいてロイヤルティプログラムの対象者を選定する場合、以下のようにカスタムPredicateを作成することができます。

Predicate<Customer> isEligibleForLoyaltyProgram = customer -> 
    customer.getTotalPurchases() > 1000 && customer.getJoinDate().isBefore(LocalDate.now().minusYears(1));

このカスタムPredicateは、顧客の購入総額が1000ドルを超え、かつ1年以上前に会員登録している場合にtrueを返します。

Customer customer = new Customer(1500, LocalDate.of(2020, 5, 20));
System.out.println(isEligibleForLoyaltyProgram.test(customer)); // 出力: true

カスタムPredicateの再利用

カスタムPredicateを作成することで、特定の条件判定ロジックをプロジェクト内で再利用することができます。例えば、複数の場所で同じ顧客フィルタリングロジックが必要な場合、一度定義したカスタムPredicateを使い回すことができます。

List<Customer> customers = getCustomers();
List<Customer> eligibleCustomers = customers.stream()
                                            .filter(isEligibleForLoyaltyProgram)
                                            .collect(Collectors.toList());

このように、カスタムPredicateを活用することで、複雑な条件判定を簡潔に表現し、コードの再利用性と保守性を高めることができます。カスタムPredicateは、プロジェクト全体で一貫した条件判定ロジックを提供するために非常に有効です。

Predicateのパフォーマンス最適化

Predicateを使用する際、特に大規模なデータセットや複雑な条件を扱う場合には、パフォーマンスの最適化が重要です。適切に最適化されたPredicateは、コードの効率性を大幅に向上させるだけでなく、全体の処理時間を短縮し、リソースの消費を抑えることができます。

短絡評価を活用する

JavaのPredicateは、条件を評価する際に短絡評価(ショートサーキット評価)を行います。短絡評価とは、論理演算子&&||を用いた場合に、左側の条件だけで結果が決定される場合、右側の条件を評価しないという特性です。これを活用することで、不要な計算を省き、パフォーマンスを向上させることができます。

例えば、以下のようなPredicateを考えます。

Predicate<String> isNonEmptyAndStartsWithA = str -> str != null && !str.isEmpty() && str.startsWith("A");

この例では、strnullであれば、!str.isEmpty()str.startsWith("A")の評価は行われません。これにより、余計な評価を防ぎ、無駄なリソース消費を抑えることができます。

コレクション操作の最適化

ストリームAPIとPredicateを組み合わせて大量のデータを処理する場合、コレクション操作を最適化することが重要です。特に、filterメソッドで複数のPredicateをチェーンする際には、最もコストの低い条件から順に評価することで、全体の処理速度を改善できます。

例えば、複数のPredicateを組み合わせてリストをフィルタリングする場合、軽量な条件を先に評価するようにします。

Predicate<Employee> isSenior = emp -> emp.getYearsOfExperience() > 10;
Predicate<Employee> isHighSalary = emp -> emp.getSalary() > 100000;

List<Employee> seniorHighSalaryEmployees = employees.stream()
                                                    .filter(isSenior.and(isHighSalary))
                                                    .collect(Collectors.toList());

この場合、経験年数の評価が先に行われるため、少数の対象に絞り込まれ、給与の評価がより効率的に行われます。

ラムダキャプチャによるパフォーマンスへの影響

ラムダ式内で外部の変数(キャプチャ変数)を使用すると、ラムダキャプチャが発生します。キャプチャ変数は、ラムダ式が呼び出されるたびにアクセスされるため、パフォーマンスに影響を与える可能性があります。頻繁に使用されるPredicateでは、キャプチャを避けるか、必要最小限に留めることで、効率を向上させることができます。

int threshold = 10;
Predicate<Integer> isGreaterThanThreshold = n -> n > threshold; // キャプチャが発生

もしthresholdが固定値である場合、次のように直接数値を使うことで、キャプチャを避けることができます。

Predicate<Integer> isGreaterThanTen = n -> n > 10;

プリミティブ型の使用による最適化

Javaでは、オートボクシングとアンボクシングによってプリミティブ型(int、doubleなど)がオブジェクト型(Integer、Doubleなど)に変換されます。この変換は処理にオーバーヘッドを伴うため、可能な限りプリミティブ型のPredicateIntPredicateDoublePredicateなど)を使用することで、パフォーマンスを向上させることができます。

IntPredicate isEven = n -> n % 2 == 0;

このようにプリミティブ型のPredicateを使用することで、オートボクシングによるオーバーヘッドを回避し、処理速度を向上させることができます。

まとめと実践的なアドバイス

Predicateのパフォーマンス最適化は、コードの効率性を大幅に向上させるための重要な要素です。短絡評価を活用し、条件の評価順序を最適化し、ラムダキャプチャを避け、プリミティブ型を使用することで、特に大規模データセットやリアルタイム処理において、顕著な効果を得ることができます。これらの最適化戦略を実践することで、より高速で効率的なJavaプログラムを構築できるでしょう。

PredicateとOptionalの組み合わせ

Java 8で導入されたOptionalクラスは、nullを扱う際のリスクを軽減するために設計されたクラスです。OptionalPredicateを組み合わせることで、より安全で柔軟なコードを書くことができます。特に、Optional内に含まれる値が特定の条件を満たすかどうかを確認する際に、この組み合わせが非常に役立ちます。

OptionalとPredicateの基本的な組み合わせ方

OptionalPredicateを組み合わせる最も基本的な方法は、Optionalfilterメソッドを使用することです。このメソッドは、Predicateを引数に取り、Optionalの値がそのPredicateに一致する場合にはその値を含む新しいOptionalを返し、一致しない場合は空のOptionalを返します。

Optional<String> optionalName = Optional.of("Alice");

Optional<String> filteredName = optionalName.filter(name -> name.startsWith("A"));

System.out.println(filteredName); // 出力: Optional[Alice]

この例では、optionalNameが”A”で始まる場合のみ、filteredNameに値が保持されます。そうでない場合、filteredNameは空のOptionalになります。

複数の条件を組み合わせたOptionalのフィルタリング

Predicateをチェーンすることで、複数の条件を使ってOptionalをフィルタリングすることも可能です。例えば、文字列が”A”で始まり、かつその長さが5文字以上であるかどうかをチェックする場合、次のように記述できます。

Optional<String> optionalName = Optional.of("Alice");

Predicate<String> startsWithA = name -> name.startsWith("A");
Predicate<String> lengthGreaterThanFive = name -> name.length() > 5;

Optional<String> filteredName = optionalName.filter(startsWithA.and(lengthGreaterThanFive));

System.out.println(filteredName); // 出力: Optional.empty

この例では、optionalNameの値が5文字未満であるため、結果として空のOptionalが返されます。

Optionalの空チェックとPredicateの組み合わせ

Optionalが空かどうかをチェックしつつ、特定の条件を満たすかどうかを確認するには、isPresentメソッドとfilterメソッドを組み合わせることが考えられます。

Optional<String> optionalName = Optional.ofNullable(null);

boolean isValidName = optionalName.filter(name -> name.length() > 3).isPresent();

System.out.println(isValidName); // 出力: false

この例では、optionalNamenullの場合、filterメソッドは呼び出されず、直接falseが返されます。これにより、nullチェックと条件判定を1行で実現できます。

OptionalとカスタムPredicateの活用

カスタムPredicateを使用して、特定のビジネスロジックに基づいた条件をOptionalに適用することも可能です。例えば、顧客オブジェクトがVIPであるかどうかを判定するカスタムPredicateを作成し、それをOptionalと組み合わせて使用することができます。

class Customer {
    private String name;
    private boolean isVIP;

    // コンストラクタ、ゲッターなど

    public boolean isVIP() {
        return isVIP;
    }
}

Predicate<Customer> isVIPCustomer = Customer::isVIP;

Optional<Customer> customerOptional = Optional.of(new Customer("John", true));

Optional<Customer> vipCustomer = customerOptional.filter(isVIPCustomer);

System.out.println(vipCustomer.isPresent()); // 出力: true

この例では、Optionalに含まれる顧客がVIPである場合にのみ、その顧客オブジェクトがOptionalとして保持されます。

OptionalとPredicateを組み合わせるメリット

OptionalPredicateを組み合わせることで、nullチェックと条件判定を安全かつ効率的に行うことができ、コードの可読性と保守性が向上します。また、Optionalを用いることで、意図しないNullPointerExceptionの発生を防ぐことができ、より堅牢なアプリケーションを構築することが可能です。

このようなテクニックを活用することで、Javaのコードをより直感的で安全にし、複雑な条件ロジックも簡潔に表現できるようになります。

実際の開発での利用シナリオ

Predicateとラムダ式の組み合わせは、Javaプログラムにおいて幅広く活用されており、特にデータのフィルタリングや条件判定が必要な場面でその力を発揮します。ここでは、実際の開発現場でよく遭遇するシナリオにおいて、Predicateがどのように役立つかを具体的に解説します。

ユーザー認証システムでの利用

ユーザー認証システムでは、ユーザーが特定の条件を満たしているかどうかを判定する必要があります。例えば、ユーザーがアクティブなアカウントを持ち、かつ特定の権限を有しているかをチェックする場合、以下のようにPredicateを使用して効率的に実装できます。

class User {
    private boolean isActive;
    private Set<String> roles;

    // コンストラクタ、ゲッターなど

    public boolean isActive() {
        return isActive;
    }

    public boolean hasRole(String role) {
        return roles.contains(role);
    }
}

Predicate<User> isActiveUser = User::isActive;
Predicate<User> isAdmin = user -> user.hasRole("ADMIN");

Predicate<User> isActiveAdmin = isActiveUser.and(isAdmin);

User user = new User(true, Set.of("ADMIN", "USER"));

if (isActiveAdmin.test(user)) {
    System.out.println("User is an active admin.");
} else {
    System.out.println("User is not an active admin.");
}

このシナリオでは、ユーザーがアクティブであり、かつ管理者権限を持っているかどうかを効率的にチェックできます。

商品フィルタリング機能の実装

ECサイトの開発では、ユーザーが特定の条件に基づいて商品をフィルタリングできる機能が重要です。例えば、在庫があり、価格が特定の範囲内の商品を表示する場合、以下のようにPredicateを活用できます。

class Product {
    private double price;
    private boolean inStock;

    // コンストラクタ、ゲッターなど

    public double getPrice() {
        return price;
    }

    public boolean isInStock() {
        return inStock;
    }
}

Predicate<Product> isAffordable = product -> product.getPrice() <= 1000;
Predicate<Product> isAvailable = Product::isInStock;

Predicate<Product> affordableAndAvailable = isAffordable.and(isAvailable);

List<Product> products = List.of(
    new Product(800, true),
    new Product(1200, true),
    new Product(500, false)
);

List<Product> filteredProducts = products.stream()
                                         .filter(affordableAndAvailable)
                                         .collect(Collectors.toList());

filteredProducts.forEach(product -> System.out.println("Filtered product: " + product.getPrice()));

このコードは、価格が1000円以下で、かつ在庫がある商品だけを抽出するものです。商品フィルタリング機能は、ユーザー体験の向上に直結する重要な機能です。

ログデータの分析とフィルタリング

システムのログデータを分析する際には、特定の条件に合致するログエントリのみを抽出する必要があります。例えば、特定のエラーレベル(例えば、ERRORWARNING)のログだけを抽出する場合、以下のようにPredicateを活用できます。

class LogEntry {
    private String level;
    private String message;

    // コンストラクタ、ゲッターなど

    public String getLevel() {
        return level;
    }
}

Predicate<LogEntry> isErrorOrWarning = log -> log.getLevel().equals("ERROR") || log.getLevel().equals("WARNING");

List<LogEntry> logs = List.of(
    new LogEntry("INFO", "System started"),
    new LogEntry("ERROR", "Null pointer exception"),
    new LogEntry("WARNING", "Deprecated API usage")
);

List<LogEntry> filteredLogs = logs.stream()
                                  .filter(isErrorOrWarning)
                                  .collect(Collectors.toList());

filteredLogs.forEach(log -> System.out.println("Filtered log: " + log.getMessage()));

この例では、ERRORおよびWARNINGレベルのログだけを抽出しています。これにより、重要なログメッセージにすばやくアクセスでき、迅速な対応が可能となります。

条件付きメール送信の実装

マーケティングオートメーションでは、特定の条件を満たすユーザーにのみメールを送信することが求められることが多いです。例えば、特定の購買行動を取ったユーザーや、一定の期間内にアクティブであったユーザーにのみメールを送信する場合、Predicateを活用することで、対象ユーザーを効率的に選定できます。

Predicate<User> madePurchaseRecently = user -> user.getLastPurchaseDate().isAfter(LocalDate.now().minusDays(30));
Predicate<User> isEmailOptedIn = User::isEmailOptedIn;

Predicate<User> shouldSendEmail = madePurchaseRecently.and(isEmailOptedIn);

List<User> users = getUsers();

users.stream()
     .filter(shouldSendEmail)
     .forEach(user -> sendEmail(user));

このシナリオでは、過去30日以内に購入し、かつメールの受信を希望しているユーザーにのみメールを送信する条件を実装しています。

まとめ

これらのシナリオを通じて、Predicateが実際の開発現場でいかに役立つかが理解できたでしょう。Predicateを利用することで、条件付きロジックを簡潔に、かつ再利用可能な形で表現できるため、コードの保守性と拡張性が向上します。Predicateの応用範囲は広く、データフィルタリングからユーザー認証、ログ分析まで、さまざまな場面で活用することができます。

演習問題: Predicateの実装練習

ここでは、Predicateを使用したいくつかの演習問題を通じて、実装方法とその応用についてさらに理解を深めていきます。これらの練習問題を解くことで、Predicateの利用方法を実践的に習得できるようになります。

演習1: 数値リストのフィルタリング

次の問題に取り組んでみてください。整数のリストが与えられたときに、偶数かつ10より大きい値のみを含むリストを作成してください。Predicateを使用してフィルタリングを行いましょう。

List<Integer> numbers = Arrays.asList(3, 10, 15, 20, 25, 30);

// TODO: Predicateを使用して、偶数かつ10より大きい数のみを含むリストを作成
Predicate<Integer> isEvenAndGreaterThanTen = num -> num % 2 == 0 && num > 10;

List<Integer> result = numbers.stream()
                              .filter(isEvenAndGreaterThanTen)
                              .collect(Collectors.toList());

System.out.println(result); // 期待される出力: [20, 30]

演習2: 文字列のフィルタリング

次に、文字列リストから”e”を含み、かつその長さが5文字以上である文字列だけを抽出するPredicateを作成してください。

List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");

// TODO: Predicateを使用して、"e"を含みかつ5文字以上の文字列のみを抽出
Predicate<String> containsEAndLengthAtLeastFive = word -> word.contains("e") && word.length() >= 5;

List<String> filteredWords = words.stream()
                                  .filter(containsEAndLengthAtLeastFive)
                                  .collect(Collectors.toList());

System.out.println(filteredWords); // 期待される出力: ["apple", "elderberry"]

演習3: ユーザーオブジェクトのフィルタリング

最後に、次のユーザーオブジェクトのリストから、アクティブなユーザーでかつ年齢が18歳以上のユーザーだけを抽出するPredicateを作成してください。

class User {
    private String name;
    private int age;
    private boolean isActive;

    // コンストラクタ、ゲッター

    public boolean isActive() {
        return isActive;
    }

    public int getAge() {
        return age;
    }
}

List<User> users = Arrays.asList(
    new User("Alice", 20, true),
    new User("Bob", 17, true),
    new User("Charlie", 25, false),
    new User("David", 30, true)
);

// TODO: Predicateを使用して、アクティブかつ年齢が18歳以上のユーザーのみを抽出
Predicate<User> isActiveAndAdult = user -> user.isActive() && user.getAge() >= 18;

List<User> eligibleUsers = users.stream()
                                .filter(isActiveAndAdult)
                                .collect(Collectors.toList());

eligibleUsers.forEach(user -> System.out.println(user.getName())); // 期待される出力: ["Alice", "David"]

解答の確認

これらの問題を解くことで、Predicateを用いた条件付きフィルタリングの技術を実際に体験できます。コードが期待通りに動作したかどうかを確認し、もし異なる結果が得られた場合は、条件やロジックのどこに問題があるのかを見直してみてください。

さらなる挑戦

より高度な練習として、複数のPredicateを組み合わせて、複雑な条件を作成してみましょう。また、自作のPredicateを異なるシナリオで再利用してみることで、実際の開発における柔軟なコード設計を体感できます。

これらの演習を通じて、Predicateを使ったフィルタリングや条件判定のスキルを習得し、実際の開発に活かせるようにしていきましょう。

まとめ

本記事では、JavaのPredicateインターフェースとラムダ式を使った実装方法について解説しました。Predicateは、条件判定を簡潔に表現できる強力なツールであり、データのフィルタリングや複雑な条件のチェーン処理など、さまざまな場面で活用できます。実際の開発シナリオや演習問題を通じて、Predicateの基本から応用までの知識を深め、効率的で再利用可能なコードを作成するスキルを養いました。これにより、より安全で効率的なJavaプログラムを構築できるようになるでしょう。

コメント

コメントする

目次