Javaのラムダ式で実現するコレクションの集計と統計処理

Javaのプログラミングにおいて、コレクションの集計や統計処理は、データ処理の基本的かつ重要な要素です。これを効率的に行うために、Java 8から導入されたラムダ式が大きな役割を果たしています。ラムダ式を活用することで、従来の冗長なコードを簡潔に書くことができ、コレクション操作がより直感的に行えるようになりました。本記事では、Javaのラムダ式を使って、コレクションに対する集計や統計処理をどのように実装するかについて詳しく解説します。初心者から上級者まで、実践的な例を通じて、ラムダ式の強力な機能をマスターしましょう。

目次

ラムダ式の基本概念

ラムダ式は、Java 8で導入された機能で、関数型プログラミングを可能にする新しい構文です。ラムダ式は、匿名関数とも呼ばれ、簡潔なコードを書くために使用されます。これにより、コードの可読性が向上し、冗長な記述を避けることができます。

ラムダ式の構文

ラムダ式の基本的な構文は以下の通りです:

(引数リスト) -> { 処理内容 }

例えば、2つの整数を足し合わせるラムダ式は次のように書けます:

(int a, int b) -> { return a + b; }

ラムダ式では、引数の型を省略することも可能で、コードがさらに簡潔になります:

(a, b) -> a + b

ラムダ式の基本的な使い方

ラムダ式は、主にJavaの関数型インターフェースと一緒に使用されます。関数型インターフェースは、1つの抽象メソッドを持つインターフェースのことを指します。例えば、java.util.functionパッケージには多くの関数型インターフェースが含まれています。

以下は、ラムダ式を使った簡単な例です:

// 引数をそのまま返すラムダ式
Function<String, String> identity = x -> x;
System.out.println(identity.apply("Hello, Lambda!"));

この例では、Functionインターフェースを使用して、引数をそのまま返すラムダ式を定義しています。ラムダ式を使うことで、従来の匿名クラスを使った書き方よりも簡潔に処理を記述できます。

ラムダ式の理解は、これから進めるコレクションの集計や統計処理の基礎となるため、しっかりと押さえておきましょう。

コレクションフレームワークの紹介

Javaのコレクションフレームワークは、データを効率的に格納、操作するための標準的なデータ構造を提供します。リスト、セット、マップなどのコレクションは、プログラム内で多数のデータを扱う際に不可欠なツールです。このフレームワークを理解することは、効果的なデータ操作やラムダ式を使った処理の土台となります。

リスト (List)

Listは、順序付きの要素の集合で、重複を許します。ArrayListLinkedListが代表的な実装クラスです。リストは、要素をインデックスで管理し、要素の追加や削除が頻繁に行われる場合に適しています。

List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");

セット (Set)

Setは、重複しない要素の集合を保持するデータ構造です。順序は保証されず、同一の要素が複数回追加されることはありません。HashSetTreeSetが代表的な実装クラスです。重複を許さない集合を管理する場合に適しています。

Set<String> set = new HashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Apple"); // 重複する要素は追加されない

マップ (Map)

Mapは、キーと値のペアを管理するデータ構造です。キーは一意であり、値は重複してもかまいません。HashMapTreeMapが代表的な実装クラスです。キーに基づいて値を効率的に検索する場合に適しています。

Map<String, Integer> map = new HashMap<>();
map.put("Apple", 10);
map.put("Banana", 20);

コレクションフレームワークとラムダ式の連携

Javaのコレクションフレームワークは、ラムダ式と組み合わせることで、複雑なデータ操作を簡潔に表現できます。例えば、StreamAPIとラムダ式を使って、コレクションのフィルタリング、マッピング、集計などの処理を効果的に実装できます。

これから解説するラムダ式を使った集計や統計処理は、これらのコレクションを対象に行います。まずは、各コレクションの特性を理解し、適切な場面で使い分けることが重要です。

ラムダ式を用いたフィルタリング

コレクション内のデータを特定の条件に基づいて抽出するフィルタリングは、データ処理の基本的な操作の一つです。Javaのラムダ式とStreamAPIを組み合わせることで、フィルタリングを簡潔かつ直感的に行うことができます。

基本的なフィルタリングの例

ラムダ式を使って、リスト内の要素をフィルタリングする基本的な例を見てみましょう。ここでは、文字列リストから文字数が5文字以上のものだけを抽出します。

List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date");

List<String> filteredFruits = fruits.stream()
    .filter(fruit -> fruit.length() >= 5)
    .collect(Collectors.toList());

System.out.println(filteredFruits); // [Apple, Banana, Cherry]

この例では、filterメソッドにラムダ式を渡し、文字数が5以上の要素だけをフィルタリングしています。結果はcollectメソッドを使ってリストに再収集されています。

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

ラムダ式を使って、複数の条件を組み合わせてフィルタリングすることも可能です。以下は、文字数が5文字以上で、かつ文字が”A”で始まるフルーツを抽出する例です。

List<String> filteredFruits = fruits.stream()
    .filter(fruit -> fruit.length() >= 5 && fruit.startsWith("A"))
    .collect(Collectors.toList());

System.out.println(filteredFruits); // [Apple]

このように、&&演算子を使って複数の条件を結合することで、より複雑なフィルタリングを行うことができます。

フィルタリングの応用例

実践的な例として、数値リストから偶数のみを抽出するケースを考えます。ラムダ式を使うことで、短くて分かりやすいコードで実装できます。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> evenNumbers = numbers.stream()
    .filter(num -> num % 2 == 0)
    .collect(Collectors.toList());

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

この例では、filterメソッドを使ってリスト内の偶数を抽出しています。ラムダ式を活用することで、フィルタリング処理が非常にシンプルになります。

ラムダ式を用いたフィルタリングは、データの選別や抽出を効率的に行うための強力なツールです。これを習得することで、コレクション操作が一層効果的に行えるようになります。

ラムダ式を用いたマッピング

マッピングは、コレクション内の要素を別の形式に変換する操作です。Javaのラムダ式を使うことで、要素ごとの変換処理を簡潔に記述でき、データの再構成が容易になります。マッピングは、データの正規化や形式の変換など、さまざまな場面で活用されます。

基本的なマッピングの例

まず、リスト内の文字列をすべて大文字に変換する簡単な例を見てみましょう。この処理をラムダ式で行うと、非常に短く記述できます。

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

List<String> upperCaseFruits = fruits.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());

System.out.println(upperCaseFruits); // [APPLE, BANANA, CHERRY]

この例では、mapメソッドを使って、各要素にtoUpperCaseメソッドを適用しています。結果として、リスト内のすべての文字列が大文字に変換されます。

オブジェクトの変換

ラムダ式を使ったマッピングは、オブジェクトのプロパティを抽出したり、異なるオブジェクトに変換する場合にも有用です。例えば、Personクラスのリストから名前だけを抽出して、新しいリストを作成する例を考えてみましょう。

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    String getName() {
        return name;
    }
}

List<Person> people = Arrays.asList(
    new Person("Alice", 30),
    new Person("Bob", 25),
    new Person("Charlie", 35)
);

List<String> names = people.stream()
    .map(Person::getName)
    .collect(Collectors.toList());

System.out.println(names); // [Alice, Bob, Charlie]

ここでは、PersonオブジェクトのリストからgetNameメソッドを使って名前だけを抽出し、新しいリストに格納しています。このように、ラムダ式を使って特定のプロパティを簡単に取り出すことができます。

数値変換の例

マッピングは数値の変換にも利用されます。たとえば、整数のリストからそれぞれの平方値を計算する場合、次のように実装できます。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> squares = numbers.stream()
    .map(n -> n * n)
    .collect(Collectors.toList());

System.out.println(squares); // [1, 4, 9, 16, 25]

この例では、mapメソッドにラムダ式を渡して、各整数をその平方に変換しています。ラムダ式によるマッピングは、数値の操作を簡潔に表現できる強力な手段です。

マッピングとフィルタリングの組み合わせ

フィルタリングとマッピングは、組み合わせて使うことが多いです。例えば、偶数のリストからその平方値を求める場合、次のようにフィルタリングとマッピングを連続して適用します。

List<Integer> squaresOfEvenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * n)
    .collect(Collectors.toList());

System.out.println(squaresOfEvenNumbers); // [4, 16]

このコードは、最初に偶数を抽出し、その後に平方を計算しています。こうした組み合わせにより、複雑なデータ操作を簡潔に実装できます。

ラムダ式を使ったマッピングは、データの変換処理をシンプルに表現できる重要な手法です。これにより、データ操作が一層柔軟で効率的に行えるようになります。

集計処理の実装例

データを集約して要約情報を生成する集計処理は、プログラム開発において頻繁に使用される操作です。Javaのラムダ式とStreamAPIを活用することで、シンプルかつ効率的な集計処理が可能になります。ここでは、いくつかの基本的な集計処理の実装例を紹介します。

要素の合計を計算する

リスト内の数値要素の合計を求める場合、StreamAPIのreduceメソッドを使用します。reduceは、ストリームのすべての要素を1つの値に集約するために使用されます。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
    .reduce(0, (a, b) -> a + b);

System.out.println(sum); // 15

この例では、ラムダ式(a, b) -> a + bを使って、リスト内のすべての要素を加算しています。reduceメソッドの第一引数である0は初期値を意味し、リストが空の場合に返されます。

平均値を計算する

リスト内の数値の平均を計算するためには、mapToIntaverageメソッドを組み合わせて使用します。

double average = numbers.stream()
    .mapToInt(Integer::intValue)
    .average()
    .orElse(0.0);

System.out.println(average); // 3.0

この例では、mapToIntメソッドを使って数値リストをIntStreamに変換し、averageメソッドで平均値を計算しています。orElseメソッドは、ストリームが空の場合に返すデフォルト値を指定しています。

要素の数をカウントする

ストリーム内の要素数をカウントする場合は、countメソッドを使用します。これは、フィルタリングやマッピングの後に要素数を確認する際に便利です。

long count = numbers.stream()
    .count();

System.out.println(count); // 5

このコードでは、リスト内の要素数を数えています。countメソッドは、ストリーム内の要素数を長整数(long型)で返します。

最小値・最大値を求める

リスト内の最小値や最大値を求めるためには、minmaxメソッドを使用します。

int min = numbers.stream()
    .min(Integer::compare)
    .orElse(Integer.MAX_VALUE);

int max = numbers.stream()
    .max(Integer::compare)
    .orElse(Integer.MIN_VALUE);

System.out.println("Min: " + min); // Min: 1
System.out.println("Max: " + max); // Max: 5

この例では、minメソッドで最小値、maxメソッドで最大値をそれぞれ求めています。orElseメソッドは、ストリームが空の場合に返すデフォルト値を設定しています。

複数の集計処理を同時に実行する

複数の集計処理を同時に行う場合、CollectorsクラスのsummarizingIntメソッドを利用することで、合計、平均、最大値、最小値、カウントを一度に取得できます。

IntSummaryStatistics stats = numbers.stream()
    .collect(Collectors.summarizingInt(Integer::intValue));

System.out.println("Sum: " + stats.getSum());       // Sum: 15
System.out.println("Average: " + stats.getAverage()); // Average: 3.0
System.out.println("Min: " + stats.getMin());       // Min: 1
System.out.println("Max: " + stats.getMax());       // Max: 5
System.out.println("Count: " + stats.getCount());   // Count: 5

この方法では、一度に複数の統計情報を効率的に取得できます。

これらの集計処理の例は、Javaのラムダ式とStreamAPIを使用することで、複雑な処理も簡潔に書けることを示しています。これにより、コードの可読性が向上し、メンテナンスも容易になります。

統計処理の実装例

統計処理は、データ分析や報告などで頻繁に用いられる重要な操作です。Javaでは、ラムダ式とStreamAPIを活用することで、さまざまな統計情報を簡単に計算できます。ここでは、基本的な統計処理の実装例をいくつか紹介します。

中央値の計算

中央値は、データセットの中間に位置する値です。データをソートして、中央の値を見つけることで計算します。偶数個のデータがある場合は、中央の2つの要素の平均を取ります。

List<Integer> numbers = Arrays.asList(7, 1, 5, 3, 9, 4, 2);

List<Integer> sortedNumbers = numbers.stream()
    .sorted()
    .collect(Collectors.toList());

double median;
int size = sortedNumbers.size();
if (size % 2 == 0) {
    median = (sortedNumbers.get(size / 2 - 1) + sortedNumbers.get(size / 2)) / 2.0;
} else {
    median = sortedNumbers.get(size / 2);
}

System.out.println("Median: " + median); // Median: 4.0

この例では、リストをソートした後、中央値を計算しています。データ数が偶数の場合、中央の2つの値の平均を取ることに注意してください。

標準偏差の計算

標準偏差は、データの散らばり具合を示す指標です。データの分散を計算し、その平方根を取ることで求められます。

double average = numbers.stream()
    .mapToInt(Integer::intValue)
    .average()
    .orElse(0.0);

double variance = numbers.stream()
    .mapToDouble(num -> Math.pow(num - average, 2))
    .average()
    .orElse(0.0);

double standardDeviation = Math.sqrt(variance);

System.out.println("Standard Deviation: " + standardDeviation); // Standard Deviation: 2.58...

このコードでは、平均を計算し、各要素と平均の差の平方の平均を求めています。その後、平方根を取ることで標準偏差を計算しています。

モード(最頻値)の計算

モードは、データセット内で最も頻繁に現れる値を指します。リスト内の要素の出現回数をカウントし、最も頻度の高い要素を求めます。

Map<Integer, Long> frequencyMap = numbers.stream()
    .collect(Collectors.groupingBy(num -> num, Collectors.counting()));

int mode = frequencyMap.entrySet().stream()
    .max(Map.Entry.comparingByValue())
    .get()
    .getKey();

System.out.println("Mode: " + mode); // Mode: 2

この例では、各要素の出現回数をマップに集計し、その中で最も頻度が高い要素を見つけています。

四分位範囲(IQR)の計算

四分位範囲は、データの中央50%の範囲を示す指標です。第1四分位数(Q1)と第3四分位数(Q3)の差として計算します。

double q1 = sortedNumbers.get(size / 4);
double q3 = sortedNumbers.get(size * 3 / 4);

double iqr = q3 - q1;

System.out.println("Interquartile Range (IQR): " + iqr); // IQR: 4.0

この例では、ソートされたデータリストからQ1とQ3を取得し、その差を計算して四分位範囲を求めています。

複数の統計量を一度に計算する

Javaでは、複数の統計量を一度に計算することも可能です。例えば、平均、分散、標準偏差などをまとめて計算する方法を示します。

IntSummaryStatistics stats = numbers.stream()
    .collect(Collectors.summarizingInt(Integer::intValue));

double variance = numbers.stream()
    .mapToDouble(num -> Math.pow(num - stats.getAverage(), 2))
    .average()
    .orElse(0.0);

double standardDeviation = Math.sqrt(variance);

System.out.println("Sum: " + stats.getSum()); // Sum: 31
System.out.println("Average: " + stats.getAverage()); // Average: 4.43...
System.out.println("Min: " + stats.getMin()); // Min: 1
System.out.println("Max: " + stats.getMax()); // Max: 9
System.out.println("Count: " + stats.getCount()); // Count: 7
System.out.println("Variance: " + variance); // Variance: 6.53...
System.out.println("Standard Deviation: " + standardDeviation); // Standard Deviation: 2.56...

このコードでは、基本的な統計量と共に、分散と標準偏差を計算しています。

これらの統計処理の実装例を通じて、Javaのラムダ式とStreamAPIを使用して複雑な統計計算がいかに簡単に行えるかがわかります。これにより、データ分析や高度なデータ処理が効率的に実装可能になります。

ストリームAPIとの連携

JavaのストリームAPIは、データの処理を効率的かつ直感的に行うための強力なツールです。ラムダ式と組み合わせることで、コレクションや配列などのデータソースに対してフィルタリング、マッピング、集計、統計処理を簡潔に実装できます。ここでは、ストリームAPIとラムダ式を連携させた実践的なコレクション処理の例を紹介します。

ストリームAPIの基本的な使い方

ストリームAPIは、データの処理を連鎖的に行うために、ストリームという抽象的なデータパイプラインを提供します。ストリームは、一連の操作を通じてデータを処理し、最終的な結果を得るために使用されます。

List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date");

List<String> result = fruits.stream()
    .filter(fruit -> fruit.length() > 5)
    .map(String::toUpperCase)
    .sorted()
    .collect(Collectors.toList());

System.out.println(result); // [BANANA, CHERRY]

この例では、ストリームを使って、5文字以上のフルーツ名をフィルタリングし、大文字に変換した後、アルファベット順にソートしています。collectメソッドで処理結果をリストに収集します。

ストリームの中間操作と終端操作

ストリームには、中間操作と終端操作の2種類があります。中間操作は、ストリームを変換する操作であり、終端操作はストリームを消費して最終的な結果を生成します。

  • 中間操作の例
  • filter:条件に合致する要素だけを残す
  • map:各要素を別の形式に変換する
  • sorted:要素をソートする
  • 終端操作の例
  • collect:ストリームをリストやセットに変換する
  • forEach:各要素に対してアクションを実行する
  • reduce:要素を1つの結果に集約する
long count = fruits.stream()
    .filter(fruit -> fruit.startsWith("A"))
    .count();

System.out.println(count); // 1

この例では、中間操作としてfilterを使い、「A」で始まるフルーツをフィルタリングしています。countは終端操作として、フィルタリングされた要素の数を返します。

パラレルストリームによる並列処理

ストリームAPIは、簡単に並列処理を実行できるparallelStreamメソッドも提供しています。これにより、大量のデータを効率的に処理することができます。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int sum = numbers.parallelStream()
    .reduce(0, Integer::sum);

System.out.println(sum); // 55

この例では、parallelStreamを使用して、リスト内の数値を並列で加算しています。並列処理を使うことで、大規模なデータセットでも高速に処理を行うことができます。

ストリームの短絡操作

ストリームには、すべての要素を処理する前に結果を得る短絡操作もあります。例えば、条件を満たす最初の要素を見つけるためにfindFirstfindAnyを使用します。

Optional<String> result = fruits.stream()
    .filter(fruit -> fruit.startsWith("C"))
    .findFirst();

result.ifPresent(System.out::println); // Cherry

この例では、「C」で始まる最初のフルーツを検索しています。findFirstは、ストリーム内の最初の要素を返す終端操作です。

ストリームAPIとラムダ式の効果的な組み合わせ

ストリームAPIとラムダ式を組み合わせることで、データ処理を効率的かつ可読性の高いコードで実装できます。これにより、複雑なデータ操作もシンプルに記述できるようになります。

List<String> filteredAndSortedFruits = fruits.stream()
    .filter(fruit -> fruit.contains("a"))
    .map(String::toLowerCase)
    .distinct()
    .sorted()
    .collect(Collectors.toList());

System.out.println(filteredAndSortedFruits); // [banana, date]

このコードでは、ラムダ式を使って、特定の文字を含むフルーツをフィルタリングし、小文字に変換した後、重複を除いてソートしています。複数の操作を組み合わせることで、複雑な処理を1行で実装可能です。

ストリームAPIとラムダ式の連携は、Javaにおけるモダンなデータ処理の中心となります。これを活用することで、データ操作が簡潔かつ効率的に行えるようになり、より高度なプログラムの開発が可能になります。

実践的な応用例

Javaのラムダ式とストリームAPIを使用すると、さまざまな実践的なシナリオでデータ処理を効率的に行うことができます。ここでは、実際の開発で役立つ応用例をいくつか紹介します。これらの例を通じて、ラムダ式とストリームAPIを活用した高度なデータ処理方法を理解しましょう。

売上データの集計と分析

売上データを集計し、各商品の総売上額や平均売上額を計算することは、ビジネスにおいてよく行われる操作です。以下の例では、ラムダ式とストリームAPIを使って、売上データを集計し、分析しています。

class Sale {
    String product;
    int quantity;
    double price;

    Sale(String product, int quantity, double price) {
        this.product = product;
        this.quantity = quantity;
        this.price = price;
    }

    double getTotal() {
        return quantity * price;
    }

    String getProduct() {
        return product;
    }
}

List<Sale> sales = Arrays.asList(
    new Sale("Apple", 10, 1.5),
    new Sale("Banana", 20, 1.0),
    new Sale("Apple", 15, 1.5),
    new Sale("Orange", 30, 2.0)
);

// 各商品の総売上額を計算
Map<String, Double> totalSales = sales.stream()
    .collect(Collectors.groupingBy(Sale::getProduct, 
        Collectors.summingDouble(Sale::getTotal)));

System.out.println(totalSales); // {Apple=37.5, Banana=20.0, Orange=60.0}

// 各商品の平均売上額を計算
Map<String, Double> averageSales = sales.stream()
    .collect(Collectors.groupingBy(Sale::getProduct, 
        Collectors.averagingDouble(Sale::getTotal)));

System.out.println(averageSales); // {Apple=18.75, Banana=20.0, Orange=60.0}

この例では、売上データのリストから、各商品の総売上額と平均売上額を計算しています。groupingByを使って商品ごとにデータをグループ化し、summingDoubleaveragingDoubleを使って集計処理を行っています。

ログデータの解析

ログデータの解析は、システムの状態を監視し、問題を特定するために重要です。以下の例では、ログデータからエラーメッセージを抽出し、頻度の高いエラーを特定します。

List<String> logs = Arrays.asList(
    "INFO: User logged in",
    "ERROR: Database connection failed",
    "INFO: Data processed successfully",
    "ERROR: File not found",
    "ERROR: Database connection failed"
);

// エラーメッセージを抽出
Map<String, Long> errorFrequency = logs.stream()
    .filter(log -> log.startsWith("ERROR"))
    .collect(Collectors.groupingBy(log -> log, Collectors.counting()));

System.out.println(errorFrequency); 
// {ERROR: Database connection failed=2, ERROR: File not found=1}

// 最も頻度の高いエラーメッセージを特定
String mostFrequentError = errorFrequency.entrySet().stream()
    .max(Map.Entry.comparingByValue())
    .map(Map.Entry::getKey)
    .orElse("No errors");

System.out.println("Most Frequent Error: " + mostFrequentError); 
// Most Frequent Error: ERROR: Database connection failed

この例では、ログデータから「ERROR」で始まるメッセージをフィルタリングし、それらの頻度をカウントしています。さらに、最も頻度の高いエラーメッセージを特定することで、問題の優先度を判断できるようにしています。

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

ユーザーデータを分析し、特定の条件に合致するユーザーを抽出する操作は、多くのアプリケーションで必要です。以下の例では、特定の年齢範囲に該当するユーザーを抽出し、そのリストを生成しています。

class User {
    String name;
    int age;

    User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    int getAge() {
        return age;
    }

    String getName() {
        return name;
    }
}

List<User> users = Arrays.asList(
    new User("Alice", 30),
    new User("Bob", 25),
    new User("Charlie", 35),
    new User("Dave", 40)
);

// 30歳以上35歳以下のユーザーを抽出
List<String> selectedUsers = users.stream()
    .filter(user -> user.getAge() >= 30 && user.getAge() <= 35)
    .map(User::getName)
    .collect(Collectors.toList());

System.out.println(selectedUsers); // [Alice, Charlie]

この例では、ユーザーリストから30歳以上35歳以下のユーザーをフィルタリングし、その名前をリストに収集しています。このようなフィルタリングは、ターゲットオーディエンスの分析やセグメンテーションに役立ちます。

商品の在庫管理

在庫管理システムでは、特定の商品が在庫切れかどうかを判定し、必要なアクションを取ることが求められます。以下の例では、在庫が少ない商品を抽出し、アラートを生成します。

class Product {
    String name;
    int stock;

    Product(String name, int stock) {
        this.name = name;
        this.stock = stock;
    }

    int getStock() {
        return stock;
    }

    String getName() {
        return name;
    }
}

List<Product> products = Arrays.asList(
    new Product("Laptop", 5),
    new Product("Smartphone", 0),
    new Product("Tablet", 2)
);

// 在庫が1未満の商品を抽出
List<String> lowStockProducts = products.stream()
    .filter(product -> product.getStock() < 1)
    .map(Product::getName)
    .collect(Collectors.toList());

System.out.println("Low Stock Products: " + lowStockProducts); // [Smartphone]

この例では、在庫が1未満の商品のリストを抽出しています。これにより、在庫切れの商品を特定し、迅速な対応が可能になります。

これらの実践的な応用例を通じて、Javaのラムダ式とストリームAPIが、データ処理においてどれほど強力で柔軟なツールであるかがわかります。これらのテクニックを活用することで、効率的なデータ管理や分析が実現し、ビジネスの価値を向上させることができます。

演習問題

ここでは、これまでに学んだラムダ式とストリームAPIを使って、Javaのコレクション処理を実践するための演習問題を提供します。これらの問題に取り組むことで、実践的なスキルを身につけることができます。各問題の詳細な説明と期待される結果も記載しています。

演習問題1: 数字リストのフィルタリングと集計

問題: 整数のリストが与えられています。このリストから偶数のみを抽出し、その平均値を計算してください。

ヒント: filterメソッドで偶数を抽出し、mapToIntaverageメソッドを使って平均値を計算します。

期待される結果:

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

double average = numbers.stream()
    .filter(n -> n % 2 == 0)
    .mapToInt(Integer::intValue)
    .average()
    .orElse(0.0);

System.out.println("Average of even numbers: " + average); // Output: 20.0

演習問題2: 文字列リストのマッピングとフィルタリング

問題: 文字列のリストが与えられています。このリストから文字数が5文字以上の文字列を抽出し、大文字に変換した結果をリストとして返してください。

ヒント: filterメソッドで5文字以上の文字列をフィルタリングし、mapメソッドで大文字に変換します。

期待される結果:

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

List<String> result = words.stream()
    .filter(word -> word.length() >= 5)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

System.out.println(result); // Output: [APPLE, BANANA, CHERRY]

演習問題3: 商品リストの在庫チェック

問題: 商品名と在庫数を持つProductクラスのリストが与えられています。このリストから在庫が5未満の商品を抽出し、その商品名のリストを生成してください。

ヒント: filterメソッドで在庫が少ない商品をフィルタリングし、mapメソッドで商品名を抽出します。

期待される結果:

class Product {
    String name;
    int stock;

    Product(String name, int stock) {
        this.name = name;
        this.stock = stock;
    }

    String getName() {
        return name;
    }

    int getStock() {
        return stock;
    }
}

List<Product> products = Arrays.asList(
    new Product("Laptop", 4),
    new Product("Smartphone", 10),
    new Product("Tablet", 3)
);

List<String> lowStockProducts = products.stream()
    .filter(product -> product.getStock() < 5)
    .map(Product::getName)
    .collect(Collectors.toList());

System.out.println(lowStockProducts); // Output: [Laptop, Tablet]

演習問題4: 人物リストの年齢集計

問題: 名前と年齢を持つPersonクラスのリストが与えられています。このリストから、全員の年齢の合計を計算してください。

ヒント: mapToIntメソッドを使って年齢を抽出し、sumメソッドで合計を計算します。

期待される結果:

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    int getAge() {
        return age;
    }
}

List<Person> people = Arrays.asList(
    new Person("Alice", 30),
    new Person("Bob", 25),
    new Person("Charlie", 35)
);

int totalAge = people.stream()
    .mapToInt(Person::getAge)
    .sum();

System.out.println("Total age: " + totalAge); // Output: 90

演習問題5: テキストデータの統計処理

問題: 文章のリストが与えられています。このリストから各文章の単語数を計算し、最も多くの単語を持つ文章を特定してください。

ヒント: mapメソッドで各文章の単語数を計算し、maxメソッドで最大値を特定します。

期待される結果:

List<String> sentences = Arrays.asList(
    "Java is a powerful language",
    "Stream API is very useful",
    "Lambda expressions make code concise and readable"
);

String longestSentence = sentences.stream()
    .max(Comparator.comparingInt(sentence -> sentence.split(" ").length))
    .orElse("No sentences");

System.out.println("Longest sentence: " + longestSentence); 
// Output: "Lambda expressions make code concise and readable"

これらの演習問題を解くことで、Javaのラムダ式とストリームAPIを活用したコレクション処理のスキルを向上させることができます。自分のペースで取り組み、理解を深めていきましょう。

まとめ

本記事では、Javaのラムダ式とストリームAPIを活用したコレクションの集計と統計処理について詳しく解説しました。これらの強力なツールを使用することで、コードの簡潔さと可読性が大幅に向上し、複雑なデータ処理も効率的に実装できることが分かりました。実践的な応用例や演習問題を通じて、ラムダ式とストリームAPIの具体的な使用方法を学ぶことができたでしょう。今後のプロジェクトにおいて、これらのテクニックを活用し、より効率的なデータ操作を実現してください。

コメント

コメントする

目次