Javaの無名関数、ラムダ式、Stream API

2025-08-03

Java 8で導入されたラムダ式無名関数Stream APIは、Javaプログラミングのパラダイムを大きく変革しました。これらの機能を理解することで、より簡潔で読みやすいコードを書けるようになります。この記事では、初学者の方にもわかりやすく、これらの概念を詳細に解説します。

無名クラスとは何か

ラムダ式を理解する前に、まず無名クラス(匿名クラス)について理解する必要があります。無名クラスとは、その場で定義され、名前を持たないクラスのことです。

無名クラスの基本構文

次のコードは、無名クラスを使ってインターフェースGreetingをその場で実装する例です。

GreetingインターフェースはsayHelloメソッドを持ち、mainメソッド内で無名クラスを使ってインスタンスを生成しています。この無名クラスはsayHelloメソッドをオーバーライドし、引数として受け取った名前に対して「こんにちは、〇〇さん!」と出力します。

// インターフェースの定義
interface Greeting {
    void sayHello(String name);
}

// 無名クラスの使用例
public class AnonymousClassExample {
    public static void main(String[] args) {
        // 無名クラスのインスタンスを作成
        Greeting greeting = new Greeting() {
            @Override
            public void sayHello(String name) {
                System.out.println("こんにちは、" + name + "さん!");
            }
        };

        greeting.sayHello("山田"); // こんにちは、山田さん!
    }
}

つまり、クラスを定義せずに一時的な実装を簡潔に記述できる無名クラスの活用例です。

無名クラスの特徴

  • 名前がない:クラス宣言がないため、再利用できない
  • その場でインスタンス化:定義と同時にインスタンスを作成
  • コードの冗長性:シンプルな実装でも多くのボイラープレートコードが必要

無名クラスは便利ですが、簡単なインターフェースの実装に対してはコードが冗長になるという問題がありました。この問題を解決するために導入されたのがラムダ式です。

ラムダ式の基本

ラムダ式は、Java 8で導入された機能で、関数型インターフェースを簡潔に実装する方法を提供します。

ラムダ式の構文

(パラメータ) -> { 処理 }

または、処理が1行の場合は以下の記述になります。

(パラメータ) -> 式

無名クラスとラムダ式の比較

最初のgreeting1は、インターフェースGreetingを無名クラスで実装していますが、greeting2では同じ処理をラムダ式で表現し、コードを短くしています。

さらにgreeting3では、引数の型宣言と波括弧を省略して最も簡潔な形にしています。
つまり、ラムダ式を使うことで、関数型インターフェースの実装をより読みやすく、短く書けることを示した例です。

// 無名クラスでの実装
Greeting greeting1 = new Greeting() {
    @Override
    public void sayHello(String name) {
        System.out.println("こんにちは、" + name + "さん!");
    }
};

// ラムダ式での実装
Greeting greeting2 = (String name) -> {
    System.out.println("こんにちは、" + name + "さん!");
};

// さらに簡略化したラムダ式
Greeting greeting3 = name -> System.out.println("こんにちは、" + name + "さん!");

ラムダ式の様々な形式

次のコードは、ラムダ式の基本的な使い方をまとめた例です。

  1. Runnableを使った引数なしのラムダ式では、簡潔にスレッド処理などの実行内容を定義しています。
  2. Greetingインターフェースでは、引数が1つのラムダ式を用いて挨拶文を出力します(型推論により型を省略)。
  3. MathOperationインターフェースでは、引数が複数あるラムダ式として加算処理を定義しています。
  4. さらに、波括弧 {} を使うことで複数行の処理を含むラムダ式を記述し、計算過程と結果の出力を行っています。
import java.util.Arrays;
import java.util.List;

public class LambdaExamples {
    public static void main(String[] args) {
        // 1. 引数なしのラムダ式
        Runnable runnable = () -> System.out.println("Hello, World!");

        // 2. 引数が1つのラムダ式(型推論により型宣言を省略可能)
        Greeting greeting = name -> System.out.println("Hello, " + name);

        // 3. 引数が複数のラムダ式
        MathOperation addition = (a, b) -> a + b;

        // 4. 複数行の処理を含むラムダ式
        MathOperation complexOperation = (a, b) -> {
            int result = a + b;
            System.out.println("計算結果: " + result);
            return result;
        };

        // 使用例
        runnable.run();
        greeting.sayHello("Taro");
        System.out.println("加算結果: " + addition.operate(5, 3));
        complexOperation.operate(10, 20);
    }
}

interface MathOperation {
    int operate(int a, int b);
}

これらの例を通じて、ラムダ式がコードの簡潔化・可読性の向上・関数的な記述の実現に役立つことを示しています。

関数型インターフェース

ラムダ式を使用するためには、関数型インターフェースが必要です。関数型インターフェースとは、抽象メソッドを1つだけ持つインターフェースのことです。

主な関数型インターフェース

Javaでは、よく使用される関数型インターフェースがjava.util.functionパッケージで提供されています。Functionは文字列を数値に変換し、Predicateは文字列の長さを判定、Consumerはデータを出力、Supplierは乱数を生成します。さらに、UnaryOperatorは文字列を大文字に変換し、BinaryOperatorは2つの数値から最大値を求めます。

import java.util.function.*;

public class FunctionalInterfacesExample {
    public static void main(String[] args) {
        // 1. Function - T型の引数を受け取り、R型の結果を返す
        Function stringToInt = s -> Integer.parseInt(s);
        System.out.println("文字列から数値: " + stringToInt.apply("123"));

        // 2. Predicate - T型の引数を受け取り、booleanを返す
        Predicate isLong = s -> s.length() > 5;
        System.out.println("長い文字列ですか: " + isLong.test("Hello World"));

        // 3. Consumer - T型の引数を受け取り、戻り値なし
        Consumer printer = s -> System.out.println("消費: " + s);
        printer.accept("データ");

        // 4. Supplier - 引数なしでT型の結果を返す
        Supplier randomSupplier = () -> Math.random();
        System.out.println("乱数: " + randomSupplier.get());

        // 5. UnaryOperator - T型の引数を受け取り、T型の結果を返す
        UnaryOperator toUpper = s -> s.toUpperCase();
        System.out.println("大文字変換: " + toUpper.apply("hello"));

        // 6. BinaryOperator - 2つのT型引数を受け取り、T型の結果を返す
        BinaryOperator max = (a, b) -> a > b ? a : b;
        System.out.println("最大値: " + max.apply(10, 20));
    }
}

これにより、Javaの関数型インターフェースを使うことで、データの変換・判定・出力・生成などの処理を簡潔に記述できることが示されています。

メソッド参照

ラムダ式をさらに簡潔に書く方法としてメソッド参照があります。最初に、ラムダ式でnames.forEach(name -> System.out.println(name))と書いている処理を、System.out::printlnというメソッド参照でより簡潔に表現しています。

さらに、Integer::parseIntのように静的メソッドを参照したり、prefix::concatインスタンスメソッドを参照したりできます。また、ArrayList::newのようにコンストラクタ参照を使うことで、新しいオブジェクト生成を簡潔に記述できます。

import java.util.Arrays;
import java.util.List;

public class MethodReferenceExample {
    public static void main(String[] args) {
        List names = Arrays.asList("Alice", "Bob", "Charlie");

        // ラムダ式
        names.forEach(name -> System.out.println(name));

        // メソッド参照(より簡潔)
        names.forEach(System.out::println);

        // 静的メソッドの参照
        Function parser = Integer::parseInt;

        // インスタンスメソッドの参照
        String prefix = "Hello, ";
        Function greeter = prefix::concat;

        // コンストラクタ参照
        Supplier> listSupplier = ArrayList::new;
    }
}

このように、メソッド参照を用いることで、ラムダ式をより短く、読みやすいコードにできます。

Stream APIの基礎

Stream APIは、コレクション処理を宣言的に行うためのAPIです。従来のforループに比べて、より読みやすく、並列処理が容易になります。

Streamの基本操作

Streamの操作は、中間操作終端操作に分類されます。最初の部分では、通常のforループを使って、リスト内の名前のうち「A」で始まるものを抽出し、大文字に変換して新しいリストに追加しています。
一方、Streamを使った方法では、names.stream()でストリームを生成し、filter()で条件を絞り込み、map()で大文字に変換し、最後にcollect()でリストとして結果を取得します。

import java.util.*;
import java.util.stream.*;

public class StreamBasicExample {
    public static void main(String[] args) {
        List names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");

        // 従来の方法(forループ)
        List result1 = new ArrayList<>();
        for (String name : names) {
            if (name.startsWith("A")) {
                result1.add(name.toUpperCase());
            }
        }
        System.out.println("従来の方法: " + result1);

        // Streamを使用した方法
        List result2 = names.stream()           // ストリームの生成
            .filter(name -> name.startsWith("A"))       // 中間操作:フィルタリング
            .map(String::toUpperCase)                   // 中間操作:変換
            .collect(Collectors.toList());              // 終端操作:結果の収集

        System.out.println("Streamを使用: " + result2);
    }
}

この例から、Stream APIを使うことでデータ処理をより宣言的・簡潔に記述できることがわかります。

Streamの生成方法

list.stream()コレクションから、Arrays.stream(array)配列から、Stream.of()明示的な値からストリームを作成できます。

また、IntStream.range(1, 10)のように数値の範囲からストリームを生成したり、Files.lines()を使ってファイルの各行をストリームとして扱うことも可能です。さらに、Stream.iterate(0, n -> n + 2)では無限ストリームを作成でき、limit()で要素数を制限して利用します。

import java.util.*;
import java.util.stream.*;

public class StreamCreationExample {
    public static void main(String[] args) {
        // 1. コレクションから生成
        List list = Arrays.asList("a", "b", "c");
        Stream stream1 = list.stream();

        // 2. 配列から生成
        String[] array = {"a", "b", "c"};
        Stream stream2 = Arrays.stream(array);

        // 3. Stream.of()で生成
        Stream stream3 = Stream.of("a", "b", "c");

        // 4. 数値範囲から生成
        IntStream intStream = IntStream.range(1, 10); // 1から9まで

        // 5. ファイルから生成
        // try (Stream lines = Files.lines(Paths.get("file.txt"))) {
        //     lines.forEach(System.out::println);
        // }

        // 6. 無限ストリームの生成
        Stream infiniteStream = Stream.iterate(0, n -> n + 2);

        System.out.println("数値範囲のストリーム:");
        intStream.limit(5).forEach(System.out::println);
    }
}

このように、Stream APIを使うことで、さまざまなデータソースを柔軟に扱えることが示されています。

Streamの操作メソッド

Stream APIには多くの便利なメソッドが用意されています。主要なメソッドを見ていきましょう。

中間操作

次のコードは、**Java Stream APIの代表的な中間操作(Intermediate Operations)**をまとめて紹介しています。

  • filter()は条件に合う要素(例:「A」で始まる名前)だけを抽出します。
  • map()は要素を別の形式(ここでは名前の長さ)に変換します。
  • sorted()はストリーム内の要素をソートします。
  • distinct()は重複した要素を取り除きます。
  • limit()は指定した数の要素のみを処理します。
  • skip()は先頭から指定数の要素をスキップします。
import java.util.*;
import java.util.stream.*;

public class IntermediateOperations {
    public static void main(String[] args) {
        List names = Arrays.asList(
            "Alice", "Bob", "Charlie", "David", "Eve", "Anna", "Alex"
        );

        // filter - 条件に合う要素のみを通過させる
        System.out.println("Aで始まる名前:");
        names.stream()
            .filter(name -> name.startsWith("A"))
            .forEach(System.out::println);

        // map - 要素を別の形式に変換する
        System.out.println("\n名前の長さ:");
        names.stream()
            .map(String::length)
            .forEach(length -> System.out.print(length + " "));

        // sorted - 要素をソートする
        System.out.println("\n\nソートされた名前:");
        names.stream()
            .sorted()
            .forEach(name -> System.out.print(name + " "));

        // distinct - 重複を除去する
        List withDuplicates = Arrays.asList("A", "B", "A", "C", "B");
        System.out.println("\n\n重複除去:");
        withDuplicates.stream()
            .distinct()
            .forEach(System.out::print);

        // limit - 指定した数まで要素を制限する
        System.out.println("\n\n最初の3つの要素:");
        names.stream()
            .limit(3)
            .forEach(name -> System.out.print(name + " "));

        // skip - 指定した数をスキップする
        System.out.println("\n\n最初の2つをスキップ:");
        names.stream()
            .skip(2)
            .forEach(name -> System.out.print(name + " "));
    }
}

これらの操作は元のデータを変更せず、新しいストリームを生成する点が特徴で、データ変換や抽出を簡潔に記述できることを示しています。

終端操作

次のコードは、**Java Stream APIの代表的な終端操作(Terminal Operations)**をまとめた例です。

  • forEach():各要素に対して処理を実行(ここでは要素を出力)。
  • collect():条件に合う要素をコレクション(例:偶数リスト)にまとめる。
  • toArray():ストリームを配列に変換。
  • reduce():すべての要素を1つの結果(ここでは合計値)にまとめる。
  • count():条件を満たす要素の数を数える。
  • anyMatch / allMatch / noneMatch:特定の条件を満たす要素の有無を判定。
  • findFirst / findAny:条件に合う最初(またはいずれか)の要素を取得。
  • min() / max():最小値・最大値を求める。
import java.util.*;
import java.util.stream.*;

public class TerminalOperations {
    public static void main(String[] args) {
        List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // forEach - 各要素に対して処理を実行
        System.out.println("各要素を表示:");
        numbers.stream()
            .forEach(n -> System.out.print(n + " "));

        // collect - 結果をコレクションにまとめる
        List evenNumbers = numbers.stream()
            .filter(n -> n % 2 == 0)
            .collect(Collectors.toList());
        System.out.println("\n\n偶数のリスト: " + evenNumbers);

        // toArray - 配列に変換
        Integer[] array = numbers.stream()
            .toArray(Integer[]::new);
        System.out.println("配列: " + Arrays.toString(array));

        // reduce - 要素を結合して単一の結果を生成
        Optional sum = numbers.stream()
            .reduce((a, b) -> a + b);
        System.out.println("合計: " + sum.orElse(0));

        // count - 要素の数を数える
        long count = numbers.stream()
            .filter(n -> n > 5)
            .count();
        System.out.println("5より大きい数の個数: " + count);

        // anyMatch / allMatch / noneMatch - 条件チェック
        boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);
        boolean allPositive = numbers.stream().allMatch(n -> n > 0);
        boolean noNegative = numbers.stream().noneMatch(n -> n < 0);

        System.out.println("偶数を含む: " + hasEven);
        System.out.println("全て正の数: " + allPositive);
        System.out.println("負の数を含まない: " + noNegative);

        // findFirst / findAny - 要素の検索
        Optional firstEven = numbers.stream()
            .filter(n -> n % 2 == 0)
            .findFirst();
        System.out.println("最初の偶数: " + firstEven.orElse(-1));

        // min / max - 最小値/最大値
        Optional min = numbers.stream().min(Integer::compare);
        Optional max = numbers.stream().max(Integer::compare);
        System.out.println("最小値: " + min.orElse(-1));
        System.out.println("最大値: " + max.orElse(-1));
    }
}

これらの操作はストリームを消費して最終的な結果を得る処理であり、データ集計・分析・検索などに広く利用されます。

実践的な使用例

ここまで学んだ知識を活用して、より実践的な例を見ていきましょう。

例1:従業員データの処理

次のコードは、Stream APIを使って従業員データを集計・分析する実践例です。

  1. filter()を使って**「開発部」の従業員だけを抽出**し表示します。
  2. 給与が60万円以上の従業員の名前をリスト化します。
  3. groupingBy()averagingDouble()を組み合わせて、部署ごとの平均給与を計算します。
  4. 年齢でソートし、limit(3)年齢上位3名を抽出します。
  5. mapToDouble()sum()全従業員の総給与額を算出します。
  6. groupingBy()counting()を使い、部署ごとの従業員数をカウントします。
import java.util.*;
import java.util.stream.*;

class Employee {
    private String name;
    private String department;
    private double salary;
    private int age;

    public Employee(String name, String department, double salary, int age) {
        this.name = name;
        this.department = department;
        this.salary = salary;
        this.age = age;
    }

    // ゲッター
    public String getName() { return name; }
    public String getDepartment() { return department; }
    public double getSalary() { return salary; }
    public int getAge() { return age; }

    @Override
    public String toString() {
        return String.format("%s(%s): ¥%,.0f, %d歳", name, department, salary, age);
    }
}

public class EmployeeManagement {
    public static void main(String[] args) {
        List employees = Arrays.asList(
            new Employee("山田太郎", "営業部", 500000, 28),
            new Employee("鈴木花子", "開発部", 600000, 32),
            new Employee("佐藤健二", "営業部", 450000, 25),
            new Employee("高橋みなみ", "開発部", 700000, 35),
            new Employee("伊藤博文", "人事部", 550000, 40),
            new Employee("渡辺さくら", "開発部", 650000, 29)
        );

        // 1. 開発部の従業員のみを表示
        System.out.println("開発部の従業員:");
        employees.stream()
            .filter(e -> "開発部".equals(e.getDepartment()))
            .forEach(System.out::println);

        // 2. 給与が600,000円以上の従業員の名前をリスト化
        List highSalaryEmployees = employees.stream()
            .filter(e -> e.getSalary() >= 600000)
            .map(Employee::getName)
            .collect(Collectors.toList());
        System.out.println("\n高給与従業員: " + highSalaryEmployees);

        // 3. 部署ごとの平均給与を計算
        System.out.println("\n部署別平均給与:");
        Map avgSalaryByDept = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.averagingDouble(Employee::getSalary)
            ));
        avgSalaryByDept.forEach((dept, avg) -> 
            System.out.printf("%s: ¥%,.0f\n", dept, avg));

        // 4. 年齢でソートして上位3名を表示
        System.out.println("\n年齢上位3名:");
        employees.stream()
            .sorted((e1, e2) -> e2.getAge() - e1.getAge())
            .limit(3)
            .forEach(System.out::println);

        // 5. 全従業員の総給与額を計算
        double totalSalary = employees.stream()
            .mapToDouble(Employee::getSalary)
            .sum();
        System.out.printf("\n総人件費: ¥%,.0f\n", totalSalary);

        // 6. 部署ごとの従業員数をカウント
        System.out.println("\n部署別従業員数:");
        Map employeeCountByDept = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.counting()
            ));
        employeeCountByDept.forEach((dept, count) -> 
            System.out.printf("%s: %d人\n", dept, count));
    }
}

このように、Stream APIを活用することで、データのフィルタリング・変換・集計・グルーピングといった処理を簡潔で読みやすく記述できることを示しています。

例2:並列ストリームによる性能向上

このコードは、Stream APIにおける逐次処理と並列処理の性能比較を行う例です。

IntStream.range(0, 10_000_000)で0から999万までの整数を生成し、filter()で偶数を数える処理を、通常のstream()(逐次処理)とparallelStream()(並列処理)でそれぞれ実行しています。
System.currentTimeMillis()で処理時間を計測し、実行速度の違いを比較します。

import java.util.*;
import java.util.stream.*;

public class ParallelStreamExample {
    public static void main(String[] args) {
        // 大きなリストを作成
        List numbers = IntStream.range(0, 10_000_000)
            .boxed()
            .collect(Collectors.toList());

        // 逐次処理の実行時間計測
        long startTime = System.currentTimeMillis();
        long sequentialCount = numbers.stream()
            .filter(n -> n % 2 == 0)
            .count();
        long sequentialTime = System.currentTimeMillis() - startTime;

        // 並列処理の実行時間計測
        startTime = System.currentTimeMillis();
        long parallelCount = numbers.parallelStream()
            .filter(n -> n % 2 == 0)
            .count();
        long parallelTime = System.currentTimeMillis() - startTime;

        System.out.println("逐次処理: " + sequentialTime + "ms, 結果: " + sequentialCount);
        System.out.println("並列処理: " + parallelTime + "ms, 結果: " + parallelCount);
        System.out.println("並列処理の速度向上: " + 
            (sequentialTime / (double)parallelTime) + "倍");
    }
}

結果として、マルチコアCPUを活用する並列処理の方が高速に動作する傾向がありますが、データ量や環境によっては並列化によるオーバーヘッドで逆に遅くなることもあることを理解する実験的なサンプルです。

まとめ

Javaのモダンなプログラミングを支える3つの重要な概念として、無名クラス・ラムダ式・関数型インターフェース・Stream APIを詳しく解説しました。

無名クラスは、その場でインターフェースを実装できる名前のないクラスであり、ラムダ式は関数型インターフェースを簡潔に実装できる構文で、(パラメータ) -> 式の形式で記述し、型推論やメソッド参照によってさらに簡略化できます。

関数型インターフェースには、FunctionPredicateConsumerSupplierなどがあり、それぞれ引数や戻り値の扱いが異なります。さらに、Stream APIを用いることで、filtermapなどの中間操作、forEachcollectなどの終端操作を通じて、宣言的かつ並列処理にも対応したコレクション操作が簡潔に実現できます。

ストリームの主なメリットは、コードを簡潔かつ読みやすく記述できる点に加え、「何をするか」に焦点を当てた宣言的プログラミングが可能になることです。また、parallelStream()を使うだけで簡単に並列処理を実現できるほか、遅延評価により必要なときだけ処理が実行されるため、効率的なデータ処理が行えます。

ストリームを扱う際の注意点として、一度終端操作を実行したストリームは再利用できないこと、並列ストリームは必ずしも性能向上につながるとは限らず、状況に応じて効果が異なること、さらにデバッグがやや難しくなる場合があることが挙げられます。

これらの機能をマスターすることで、Javaでの開発効率が大幅に向上し、よりモダンで保守性の高いコードを書けるようになります。実際のプロジェクトで積極的に活用してみてください。

演習問題

初級問題(3問)

初級問題1:基本的なラムダ式

問題: 整数のリストから偶数だけを抽出して表示するプログラムを、ラムダ式を使用して作成してください。

List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// ここにコードを記述

初級問題2:文字列処理

問題: 文字列のリストを受け取り、すべての文字列を大文字に変換して新しいリストを作成するプログラムを、ラムダ式とStream APIを使用して作成してください。

List words = Arrays.asList("apple", "banana", "cherry", "date");
// ここにコードを記述

初級問題3:フィルタリングと変換

問題: 従業員オブジェクトのリストから、給与が50万円以上の従業員の名前だけを抽出してリストを作成してください。

class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() { return name; }
    public double getSalary() { return salary; }
}

List employees = Arrays.asList(
    new Employee("山田", 450000),
    new Employee("佐藤", 550000),
    new Employee("鈴木", 600000),
    new Employee("高橋", 480000)
);
// ここにコードを記述

中級問題(6問)

中級問題1:条件に基づいたグループ化

問題: 学生のリストを成績でグループ化するプログラムを作成してください。成績は「優」(80点以上)、「良」(60-79点)、「可」(60点未満)とします。

class Student {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() { return name; }
    public int getScore() { return score; }
}

List students = Arrays.asList(
    new Student("太郎", 85),
    new Student("花子", 72),
    new Student("次郎", 58),
    new Student("さくら", 90),
    new Student("健二", 65)
);
// ここにコードを記述

中級問題2:カスタムソート

問題: 商品オブジェクトのリストを価格の昇順、同じ価格の場合は名前のアルファベット順でソートするプログラムを作成してください。

class Product {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() { return name; }
    public double getPrice() { return price; }
}

List products = Arrays.asList(
    new Product("Notebook", 1200.0),
    new Product("Pen", 1.5),
    new Product("Pencil", 1.5),
    new Product("Eraser", 0.5),
    new Product("Ruler", 2.0)
);
// ここにコードを記述

中級問題3:数値計算

問題: 整数のリストから、奇数のみを抽出し、それぞれを2乗した値の合計を計算するプログラムを作成してください。

List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// ここにコードを記述

中級問題4:文字列の結合

問題: 文字列のリストを受け取り、長さが3文字以上の文字列だけをカンマ区切りで結合するプログラムを作成してください。

List words = Arrays.asList("cat", "dog", "elephant", "fox", "ant", "bird");
// ここにコードを記述

中級問題5:最大値・最小値の検出

問題: 従業員リストから、最も給与が高い従業員と最も給与が低い従業員を見つけるプログラムを作成してください。

List employees = Arrays.asList(
    new Employee("山田", 450000),
    new Employee("佐藤", 550000),
    new Employee("鈴木", 600000),
    new Employee("高橋", 480000),
    new Employee("伊藤", 520000)
);
// ここにコードを記述

中級問題6:複数条件のフィルタリング

問題: 在庫商品のリストから、価格が1000円以上で在庫数が10以上の商品だけを抽出するプログラムを作成してください。

class InventoryItem {
    private String name;
    private double price;
    private int quantity;

    public InventoryItem(String name, double price, int quantity) {
        this.name = name;
        this.price = price;
        this.quantity = quantity;
    }

    public String getName() { return name; }
    public double getPrice() { return price; }
    public int getQuantity() { return quantity; }
}

List inventory = Arrays.asList(
    new InventoryItem("Laptop", 120000, 5),
    new InventoryItem("Mouse", 1500, 15),
    new InventoryItem("Keyboard", 3500, 8),
    new InventoryItem("Monitor", 25000, 12),
    new InventoryItem("USB Cable", 800, 20)
);
// ここにコードを記述

上級問題(3問)

上級問題1:階層的なデータ処理

問題: 部門と従業員の階層構造から、各部门の平均給与と従業員数を計算するプログラムを作成してください。

class Department {
    private String name;
    private List employees;

    public Department(String name, List employees) {
        this.name = name;
        this.employees = employees;
    }

    public String getName() { return name; }
    public List getEmployees() { return employees; }
}

List departments = Arrays.asList(
    new Department("開発部", Arrays.asList(
        new Employee("山田", 550000),
        new Employee("佐藤", 600000),
        new Employee("鈴木", 520000)
    )),
    new Department("営業部", Arrays.asList(
        new Employee("高橋", 480000),
        new Employee("伊藤", 450000)
    )),
    new Department("人事部", Arrays.asList(
        new Employee("渡辺", 500000),
        new Employee("中村", 530000),
        new Employee("小林", 510000)
    ))
);
// ここにコードを記述

上級問題2:複雑なデータ変換

問題: 注文データから、商品カテゴリ別の売上金額合計と平均単価を計算するプログラムを作成してください。

class OrderItem {
    private String category;
    private String productName;
    private double price;
    private int quantity;

    public OrderItem(String category, String productName, double price, int quantity) {
        this.category = category;
        this.productName = productName;
        this.price = price;
        this.quantity = quantity;
    }

    public String getCategory() { return category; }
    public String getProductName() { return productName; }
    public double getPrice() { return price; }
    public int getQuantity() { return quantity; }
    public double getTotal() { return price * quantity; }
}

List orderItems = Arrays.asList(
    new OrderItem("Electronics", "Laptop", 120000, 2),
    new OrderItem("Electronics", "Smartphone", 80000, 3),
    new OrderItem("Books", "Java Guide", 3500, 5),
    new OrderItem("Books", "Python Guide", 3000, 4),
    new OrderItem("Clothing", "T-Shirt", 2500, 10),
    new OrderItem("Clothing", "Jeans", 5000, 6)
);
// ここにコードを記述

上級問題3:パイプライン処理の最適化

問題: 大規模なデータセットから、特定の条件を満たすデータを効率的に処理するプログラムを作成してください。並列ストリームを使用して性能を最適化し、結果を適切に収集してください。

// 1から1,000,000までの整数リストを作成
List largeDataset = IntStream.rangeClosed(1, 1_000_000)
    .boxed()
    .collect(Collectors.toList());

// 以下の条件を満たす数値を探し、結果をリストとして収集:
// 1. 素数である
// 2. 数字の各桁の和が10以上
// 3. 見つかった素数のうち、大きい方から10個を表示

// ここにコードを記述(素数判定のヘルパーメソッドが必要になります)

解答例のヒント

必要なインポート

import java.util.*;
import java.util.stream.*;
import java.util.function.*;

素数判定ヘルパーメソッド(上級問題3用)

private static boolean isPrime(int number) {
    if (number < 2) return false;
    if (number == 2) return true;
    if (number % 2 == 0) return false;

    for (int i = 3; i * i <= number; i += 2) {
        if (number % i == 0) return false;
    }
    return true;
}

private static int sumOfDigits(int number) {
    int sum = 0;
    while (number > 0) {
        sum += number % 10;
        number /= 10;
    }
    return sum;
}

演習問題 解答例

初級問題 解答例

初級問題1:基本的なラムダ式

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Beginner1 {
    public static void main(String[] args) {
        List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // ラムダ式を使用して偶数だけを抽出して表示
        List evenNumbers = numbers.stream()
            .filter(n -> n % 2 == 0)
            .collect(Collectors.toList());

        System.out.println("偶数: " + evenNumbers);

        // または直接表示
        numbers.stream()
            .filter(n -> n % 2 == 0)
            .forEach(n -> System.out.print(n + " "));
    }
}

初級問題2:文字列処理

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Beginner2 {
    public static void main(String[] args) {
        List words = Arrays.asList("apple", "banana", "cherry", "date");

        // すべての文字列を大文字に変換して新しいリストを作成
        List upperCaseWords = words.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());

        System.out.println("大文字変換: " + upperCaseWords);

        // ラムダ式を使用した場合
        List upperCaseWords2 = words.stream()
            .map(s -> s.toUpperCase())
            .collect(Collectors.toList());

        System.out.println("大文字変換(ラムダ): " + upperCaseWords2);
    }
}

初級問題3:フィルタリングと変換

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() { return name; }
    public double getSalary() { return salary; }
}

public class Beginner3 {
    public static void main(String[] args) {
        List employees = Arrays.asList(
            new Employee("山田", 450000),
            new Employee("佐藤", 550000),
            new Employee("鈴木", 600000),
            new Employee("高橋", 480000)
        );

        // 給与が50万円以上の従業員の名前だけを抽出
        List highSalaryEmployees = employees.stream()
            .filter(e -> e.getSalary() >= 500000)
            .map(Employee::getName)
            .collect(Collectors.toList());

        System.out.println("給与50万円以上の従業員: " + highSalaryEmployees);

        // 詳細情報も表示したい場合
        employees.stream()
            .filter(e -> e.getSalary() >= 500000)
            .forEach(e -> System.out.println(e.getName() + ": ¥" + e.getSalary()));
    }
}

中級問題 解答例

中級問題1:条件に基づいたグループ化

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class Student {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() { return name; }
    public int getScore() { return score; }
}

public class Intermediate1 {
    public static void main(String[] args) {
        List students = Arrays.asList(
            new Student("太郎", 85),
            new Student("花子", 72),
            new Student("次郎", 58),
            new Student("さくら", 90),
            new Student("健二", 65)
        );

        // 成績でグループ化
        Map> groupedByGrade = students.stream()
            .collect(Collectors.groupingBy(student -> {
                if (student.getScore() >= 80) return "優";
                else if (student.getScore() >= 60) return "良";
                else return "可";
            }));

        // 結果を表示
        groupedByGrade.forEach((grade, studentList) -> {
            System.out.println(grade + ":");
            studentList.forEach(student -> 
                System.out.println("  " + student.getName() + " - " + student.getScore() + "点"));
        });

        // 各成績の人数をカウント
        Map countByGrade = students.stream()
            .collect(Collectors.groupingBy(student -> {
                if (student.getScore() >= 80) return "優";
                else if (student.getScore() >= 60) return "良";
                else return "可";
            }, Collectors.counting()));

        System.out.println("\n成績別人数: " + countByGrade);
    }
}

中級問題2:カスタムソート

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Product {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() { return name; }
    public double getPrice() { return price; }

    @Override
    public String toString() {
        return name + " - ¥" + price;
    }
}

public class Intermediate2 {
    public static void main(String[] args) {
        List products = Arrays.asList(
            new Product("Notebook", 1200.0),
            new Product("Pen", 1.5),
            new Product("Pencil", 1.5),
            new Product("Eraser", 0.5),
            new Product("Ruler", 2.0)
        );

        // 価格の昇順、同じ価格の場合は名前のアルファベット順でソート
        List sortedProducts = products.stream()
            .sorted((p1, p2) -> {
                // まず価格で比較
                int priceComparison = Double.compare(p1.getPrice(), p2.getPrice());
                if (priceComparison != 0) {
                    return priceComparison;
                }
                // 価格が同じ場合は名前で比較
                return p1.getName().compareTo(p2.getName());
            })
            .collect(Collectors.toList());

        System.out.println("ソート結果:");
        sortedProducts.forEach(System.out::println);

        // Comparatorの組み合わせを使用した別の方法
        List sortedProducts2 = products.stream()
            .sorted(java.util.Comparator
                .comparing(Product::getPrice)
                .thenComparing(Product::getName))
            .collect(Collectors.toList());

        System.out.println("\nソート結果(Comparator使用):");
        sortedProducts2.forEach(System.out::println);
    }
}

中級問題3:数値計算

import java.util.Arrays;
import java.util.List;

public class Intermediate3 {
    public static void main(String[] args) {
        List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 奇数のみを抽出し、それぞれを2乗した値の合計を計算
        int sumOfSquaredOdds = numbers.stream()
            .filter(n -> n % 2 != 0)  // 奇数をフィルタリング
            .map(n -> n * n)          // 2乗に変換
            .reduce(0, Integer::sum); // 合計を計算

        System.out.println("奇数の2乗の合計: " + sumOfSquaredOdds);

        // mapToIntを使用したより効率的な方法
        int sumOfSquaredOdds2 = numbers.stream()
            .filter(n -> n % 2 != 0)
            .mapToInt(n -> n * n)
            .sum();

        System.out.println("奇数の2乗の合計(mapToInt使用): " + sumOfSquaredOdds2);

        // 各ステップの確認
        System.out.println("\n処理の詳細:");
        numbers.stream()
            .filter(n -> n % 2 != 0)
            .map(n -> n * n)
            .forEach(n -> System.out.print(n + " "));
    }
}

中級問題4:文字列の結合

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Intermediate4 {
    public static void main(String[] args) {
        List words = Arrays.asList("cat", "dog", "elephant", "fox", "ant", "bird");

        // 長さが3文字以上の文字列だけをカンマ区切りで結合
        String result = words.stream()
            .filter(s -> s.length() >= 3)  // 3文字以上をフィルタリング
            .collect(Collectors.joining(", "));  // カンマ区切りで結合

        System.out.println("結果: " + result);

        // 各ステップの確認
        System.out.println("\nフィルタリング結果:");
        words.stream()
            .filter(s -> s.length() >= 3)
            .forEach(s -> System.out.print(s + " "));

        // 大文字に変換して結合するバリエーション
        String result2 = words.stream()
            .filter(s -> s.length() >= 3)
            .map(String::toUpperCase)
            .collect(Collectors.joining(" - "));

        System.out.println("\n大文字変換して結合: " + result2);
    }
}

中級問題5:最大値・最小値の検出

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() { return name; }
    public double getSalary() { return salary; }

    @Override
    public String toString() {
        return name + ": ¥" + salary;
    }
}

public class Intermediate5 {
    public static void main(String[] args) {
        List employees = Arrays.asList(
            new Employee("山田", 450000),
            new Employee("佐藤", 550000),
            new Employee("鈴木", 600000),
            new Employee("高橋", 480000),
            new Employee("伊藤", 520000)
        );

        // 最も給与が高い従業員
        Optional highestPaid = employees.stream()
            .max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));

        // 最も給与が低い従業員
        Optional lowestPaid = employees.stream()
            .min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));

        System.out.println("最も給与が高い従業員: " + 
            highestPaid.map(Employee::toString).orElse("見つかりません"));
        System.out.println("最も給与が低い従業員: " + 
            lowestPaid.map(Employee::toString).orElse("見つかりません"));

        // Comparatorを使用した別の方法
        Optional highestPaid2 = employees.stream()
            .max(java.util.Comparator.comparing(Employee::getSalary));

        Optional lowestPaid2 = employees.stream()
            .min(java.util.Comparator.comparing(Employee::getSalary));

        System.out.println("\nComparator使用:");
        System.out.println("最も給与が高い従業員: " + 
            highestPaid2.map(Employee::toString).orElse("見つかりません"));
        System.out.println("最も給与が低い従業員: " + 
            lowestPaid2.map(Employee::toString).orElse("見つかりません"));

        // 給与の範囲も表示
        double maxSalary = employees.stream()
            .mapToDouble(Employee::getSalary)
            .max()
            .orElse(0);

        double minSalary = employees.stream()
            .mapToDouble(Employee::getSalary)
            .min()
            .orElse(0);

        System.out.printf("\n給与範囲: ¥%,.0f - ¥%,.0f\n", minSalary, maxSalary);
    }
}

中級問題6:複数条件のフィルタリング

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class InventoryItem {
    private String name;
    private double price;
    private int quantity;

    public InventoryItem(String name, double price, int quantity) {
        this.name = name;
        this.price = price;
        this.quantity = quantity;
    }

    public String getName() { return name; }
    public double getPrice() { return price; }
    public int getQuantity() { return quantity; }

    @Override
    public String toString() {
        return String.format("%s - ¥%,.0f (在庫: %d個)", name, price, quantity);
    }
}

public class Intermediate6 {
    public static void main(String[] args) {
        List inventory = Arrays.asList(
            new InventoryItem("Laptop", 120000, 5),
            new InventoryItem("Mouse", 1500, 15),
            new InventoryItem("Keyboard", 3500, 8),
            new InventoryItem("Monitor", 25000, 12),
            new InventoryItem("USB Cable", 800, 20)
        );

        // 価格が1000円以上で在庫数が10以上の商品を抽出
        List filteredItems = inventory.stream()
            .filter(item -> item.getPrice() >= 1000)  // 価格が1000円以上
            .filter(item -> item.getQuantity() >= 10) // 在庫数が10以上
            .collect(Collectors.toList());

        System.out.println("条件に合致する商品:");
        filteredItems.forEach(System.out::println);

        // 単一のfilterで複数条件を指定する方法
        List filteredItems2 = inventory.stream()
            .filter(item -> item.getPrice() >= 1000 && item.getQuantity() >= 10)
            .collect(Collectors.toList());

        System.out.println("\n単一filter使用:");
        filteredItems2.forEach(System.out::println);

        // 合計在庫金額も計算
        double totalInventoryValue = filteredItems.stream()
            .mapToDouble(item -> item.getPrice() * item.getQuantity())
            .sum();

        System.out.printf("\n該当商品の在庫総金額: ¥%,.0f\n", totalInventoryValue);
    }
}

上級問題 解答例

上級問題1:階層的なデータ処理

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() { return name; }
    public double getSalary() { return salary; }
}

class Department {
    private String name;
    private List employees;

    public Department(String name, List employees) {
        this.name = name;
        this.employees = employees;
    }

    public String getName() { return name; }
    public List getEmployees() { return employees; }
}

public class Advanced1 {
    public static void main(String[] args) {
        List departments = Arrays.asList(
            new Department("開発部", Arrays.asList(
                new Employee("山田", 550000),
                new Employee("佐藤", 600000),
                new Employee("鈴木", 520000)
            )),
            new Department("営業部", Arrays.asList(
                new Employee("高橋", 480000),
                new Employee("伊藤", 450000)
            )),
            new Department("人事部", Arrays.asList(
                new Employee("渡辺", 500000),
                new Employee("中村", 530000),
                new Employee("小林", 510000)
            ))
        );

        // 各部门の平均給与と従業員数を計算
        Map departmentStats = departments.stream()
            .collect(Collectors.toMap(
                Department::getName,
                dept -> {
                    double avgSalary = dept.getEmployees().stream()
                        .mapToDouble(Employee::getSalary)
                        .average()
                        .orElse(0);
                    int employeeCount = dept.getEmployees().size();
                    return new DepartmentStats(avgSalary, employeeCount);
                }
            ));

        // 結果を表示
        System.out.println("部門別統計:");
        departmentStats.forEach((deptName, stats) -> {
            System.out.printf("%s: 平均給与 ¥%,.0f, 従業員数 %d人\n", 
                deptName, stats.getAverageSalary(), stats.getEmployeeCount());
        });

        // 別の方法: 部門ごとに詳細を計算
        System.out.println("\n部門別詳細:");
        departments.forEach(dept -> {
            double avgSalary = dept.getEmployees().stream()
                .mapToDouble(Employee::getSalary)
                .average()
                .orElse(0);
            double totalSalary = dept.getEmployees().stream()
                .mapToDouble(Employee::getSalary)
                .sum();

            System.out.printf("%s:\n", dept.getName());
            System.out.printf("  従業員数: %d人\n", dept.getEmployees().size());
            System.out.printf("  平均給与: ¥%,.0f\n", avgSalary);
            System.out.printf("  総人件費: ¥%,.0f\n", totalSalary);
            System.out.println("  従業員一覧:");
            dept.getEmployees().forEach(emp -> 
                System.out.printf("    - %s: ¥%,.0f\n", emp.getName(), emp.getSalary()));
        });
    }

    // 統計情報を保持するためのヘルパークラス
    static class DepartmentStats {
        private double averageSalary;
        private int employeeCount;

        public DepartmentStats(double averageSalary, int employeeCount) {
            this.averageSalary = averageSalary;
            this.employeeCount = employeeCount;
        }

        public double getAverageSalary() { return averageSalary; }
        public int getEmployeeCount() { return employeeCount; }
    }
}

上級問題2:複雑なデータ変換

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class OrderItem {
    private String category;
    private String productName;
    private double price;
    private int quantity;

    public OrderItem(String category, String productName, double price, int quantity) {
        this.category = category;
        this.productName = productName;
        this.price = price;
        this.quantity = quantity;
    }

    public String getCategory() { return category; }
    public String getProductName() { return productName; }
    public double getPrice() { return price; }
    public int getQuantity() { return quantity; }
    public double getTotal() { return price * quantity; }
}

// カテゴリ別統計情報を保持するクラス
class CategoryStats {
    private double totalSales;
    private double averagePrice;
    private long productCount;

    public CategoryStats(double totalSales, double averagePrice, long productCount) {
        this.totalSales = totalSales;
        this.averagePrice = averagePrice;
        this.productCount = productCount;
    }

    public double getTotalSales() { return totalSales; }
    public double getAveragePrice() { return averagePrice; }
    public long getProductCount() { return productCount; }
}

public class Advanced2 {
    public static void main(String[] args) {
        List orderItems = Arrays.asList(
            new OrderItem("Electronics", "Laptop", 120000, 2),
            new OrderItem("Electronics", "Smartphone", 80000, 3),
            new OrderItem("Books", "Java Guide", 3500, 5),
            new OrderItem("Books", "Python Guide", 3000, 4),
            new OrderItem("Clothing", "T-Shirt", 2500, 10),
            new OrderItem("Clothing", "Jeans", 5000, 6)
        );

        // 商品カテゴリ別の売上金額合計と平均単価を計算
        Map categoryStats = orderItems.stream()
            .collect(Collectors.groupingBy(
                OrderItem::getCategory,
                Collectors.collectingAndThen(
                    Collectors.toList(),
                    list -> {
                        double totalSales = list.stream()
                            .mapToDouble(OrderItem::getTotal)
                            .sum();
                        double averagePrice = list.stream()
                            .mapToDouble(OrderItem::getPrice)
                            .average()
                            .orElse(0);
                        long productCount = list.stream()
                            .map(OrderItem::getProductName)
                            .distinct()
                            .count();
                        return new CategoryStats(totalSales, averagePrice, productCount);
                    }
                )
            ));

        // 結果を表示
        System.out.println("カテゴリ別売上統計:");
        categoryStats.forEach((category, stats) -> {
            System.out.printf("%s:\n", category);
            System.out.printf("  売上合計: ¥%,.0f\n", stats.getTotalSales());
            System.out.printf("  平均単価: ¥%,.0f\n", stats.getAveragePrice());
            System.out.printf("  商品点数: %d点\n", stats.getProductCount());
        });

        // 詳細な内訳も表示
        System.out.println("\nカテゴリ別詳細内訳:");
        Map> itemsByCategory = orderItems.stream()
            .collect(Collectors.groupingBy(OrderItem::getCategory));

        itemsByCategory.forEach((category, items) -> {
            System.out.printf("\n%s:\n", category);
            items.forEach(item -> 
                System.out.printf("  %s: %,d個 × ¥%,.0f = ¥%,.0f\n", 
                    item.getProductName(), item.getQuantity(), item.getPrice(), item.getTotal()));
        });

        // 全カテゴリの総合計
        double grandTotal = orderItems.stream()
            .mapToDouble(OrderItem::getTotal)
            .sum();

        System.out.printf("\n全カテゴリ総売上: ¥%,.0f\n", grandTotal);
    }
}

上級問題3:パイプライン処理の最適化

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Advanced3 {
    public static void main(String[] args) {
        // 1から1,000,000までの整数リストを作成
        List largeDataset = IntStream.rangeClosed(1, 1_000_000)
            .boxed()
            .collect(Collectors.toList());

        System.out.println("データ処理開始...");
        long startTime = System.currentTimeMillis();

        // 並列ストリームを使用して効率的に処理
        List result = largeDataset.parallelStream()
            .filter(Advanced3::isPrime)           // 素数である
            .filter(n -> sumOfDigits(n) >= 10)    // 数字の各桁の和が10以上
            .sorted((a, b) -> b.compareTo(a))     // 降順にソート
            .limit(10)                            // 大きい方から10個
            .collect(Collectors.toList());

        long endTime = System.currentTimeMillis();

        System.out.println("条件を満たす素数の大きい方から10個:");
        result.forEach(n -> 
            System.out.printf("%d (各桁の和: %d)\n", n, sumOfDigits(n)));

        System.out.printf("\n処理時間: %dミリ秒\n", endTime - startTime);

        // 逐次処理との比較
        System.out.println("\n逐次処理との比較:");
        long sequentialStart = System.currentTimeMillis();

        List sequentialResult = largeDataset.stream()
            .filter(Advanced3::isPrime)
            .filter(n -> sumOfDigits(n) >= 10)
            .sorted((a, b) -> b.compareTo(a))
            .limit(10)
            .collect(Collectors.toList());

        long sequentialEnd = System.currentTimeMillis();

        System.out.printf("逐次処理時間: %dミリ秒\n", sequentialEnd - sequentialStart);
        System.out.printf("並列処理の速度向上: %.2f倍\n", 
            (double)(sequentialEnd - sequentialStart) / (endTime - startTime));
    }

    // 素数判定メソッド
    private static boolean isPrime(int number) {
        if (number < 2) return false;
        if (number == 2) return true;
        if (number % 2 == 0) return false;

        for (int i = 3; i * i <= number; i += 2) {
            if (number % i == 0) return false;
        }
        return true;
    }

    // 各桁の和を計算するメソッド
    private static int sumOfDigits(int number) {
        int sum = 0;
        while (number > 0) {
            sum += number % 10;
            number /= 10;
        }
        return sum;
    }
}

上級問題3のさらに最適化されたバージョン

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Advanced3Optimized {
    public static void main(String[] args) {
        // IntStreamを直接使用してボクシングを回避
        System.out.println("最適化版データ処理開始...");
        long startTime = System.currentTimeMillis();

        List result = IntStream.rangeClosed(1, 1_000_000)
            .parallel()                          // 並列処理
            .filter(Advanced3Optimized::isPrime) // 素数である
            .filter(n -> sumOfDigits(n) >= 10)   // 数字の各桁の和が10以上
            .boxed()                             // Integerにボクシング
            .sorted((a, b) -> b.compareTo(a))    // 降順にソート
            .limit(10)                           // 大きい方から10個
            .collect(Collectors.toList());

        long endTime = System.currentTimeMillis();

        System.out.println("最適化版結果:");
        result.forEach(n -> 
            System.out.printf("%d (各桁の和: %d)\n", n, sumOfDigits(n)));

        System.out.printf("最適化版処理時間: %dミリ秒\n", endTime - startTime);

        // 統計情報も表示
        long primeCount = IntStream.rangeClosed(1, 1_000_000)
            .parallel()
            .filter(Advanced3Optimized::isPrime)
            .count();

        long conditionCount = IntStream.rangeClosed(1, 1_000_000)
            .parallel()
            .filter(Advanced3Optimized::isPrime)
            .filter(n -> sumOfDigits(n) >= 10)
            .count();

        System.out.printf("\n統計情報:\n");
        System.out.printf("1から1,000,000までの素数の数: %,d\n", primeCount);
        System.out.printf("条件を満たす素数の数: %,d\n", conditionCount);
        System.out.printf("条件を満たす割合: %.2f%%\n", 
            (conditionCount * 100.0 / primeCount));
    }

    // 最適化された素数判定メソッド
    private static boolean isPrime(int number) {
        if (number < 2) return false;
        if (number == 2) return true;
        if (number == 3) return true;
        if (number % 2 == 0) return false;
        if (number % 3 == 0) return false;

        // 6k ± 1の形の数のみをチェック
        for (int i = 5; i * i <= number; i += 6) {
            if (number % i == 0) return false;
            if (number % (i + 2) == 0) return false;
        }
        return true;
    }

    // 各桁の和を計算するメソッド
    private static int sumOfDigits(int number) {
        int sum = 0;
        while (number > 0) {
            sum += number % 10;
            number /= 10;
        }
        return sum;
    }
}