Java Tips | コレクション:最小値取得

Java Java
スポンサーリンク

「最小値取得」は“いちばん小さいものを安全に一発で取り出す”ユーティリティ

業務コードでは、「最小値」が欲しい場面もかなり多いです。
最小金額、最古の日付、最小スコア、最小バージョン番号…。

毎回 for 文で

int min = Integer.MAX_VALUE;
for (int v : values) {
    if (v < min) {
        min = v;
    }
}
Java

と書いても動きますが、
null や空コレクション、オブジェクトの比較などが絡むと、
一気にバグりやすく、読みにくくなります。

そこで「最小値取得」をユーティリティとして切り出しておくと、
“最小値をどう扱うか”のルールを一箇所に閉じ込められます。
以降は、「最小値を取りたい」と思ったときに、そのユーティリティを呼ぶだけで済む世界を目指します。


基本形:数値 List から最小値を取得する

Stream を使った最小限の書き方

まずは、List<Integer> から最小値を取りたいケースです。

import java.util.List;

public class MinBasic {

    public static void main(String[] args) {
        List<Integer> scores = List.of(70, 85, 90);

        int min = scores.stream()
                        .mapToInt(Integer::intValue)
                        .min()
                        .orElse(0); // 空なら 0 とする

        System.out.println(min); // 70
    }
}
Java

ここで押さえてほしいポイントは二つあります。

一つ目は、mapToInt で「int 専用のストリーム」に変換してから min() を呼んでいることです。
min() は「最小値を計算する」メソッドですが、
データが一件もない場合は「最小値が存在しない」ので OptionalInt を返します。

二つ目は、orElse(0) で「空だったときのデフォルト値」を決めていることです。
ここをどうするか(0にするのか、例外にするのか、nullにするのか)は、
プロジェクト全体でルールを決めておくと、コードがブレません。


最小値取得をユーティリティメソッドにまとめる

「null や空 List をどう扱うか」を一箇所に閉じ込める

同じような最小値取得を何度も書くなら、
ユーティリティにしてしまうとスッキリします。

import java.util.List;

public final class Mins {

    private Mins() {}

    public static int minIntOrDefault(List<Integer> source, int defaultValue) {
        if (source == null || source.isEmpty()) {
            return defaultValue;
        }
        return source.stream()
                     .mapToInt(Integer::intValue)
                     .min()
                     .orElse(defaultValue);
    }
}
Java

使い方はこうなります。

List<Integer> scores = List.of(70, 85, 90);

int min = Mins.minIntOrDefault(scores, 0); // 空なら 0
System.out.println(min); // 70
Java

ここでの重要ポイントは、「null と空 List の扱いをユーティリティ側で決めている」ことです。
呼び出し側は「最小値が欲しい」「空なら 0 でいい」とだけ考えればよく、
null チェックや Optional の扱いを毎回書かなくて済みます。


オブジェクト一覧から「特定の項目の最小値」を取得する

例:商品一覧から「最小価格」を取り出す

業務では、単なる List<Integer> よりも、
「オブジェクトの特定のフィールドの最小値」が欲しいことが多いです。

class Item {
    private final String name;
    private final int price;
    public Item(String name, int price) {
        this.name = name;
        this.price = price;
    }
    public String getName() { return name; }
    public int getPrice() { return price; }
}
Java

これを集計するユーティリティは、こう書けます。

import java.util.List;
import java.util.function.ToIntFunction;

public final class Mins {

    private Mins() {}

    public static <T> int minIntOrDefault(
            List<T> source,
            ToIntFunction<? super T> mapper,
            int defaultValue
    ) {
        if (source == null || source.isEmpty()) {
            return defaultValue;
        }
        return source.stream()
                     .mapToInt(mapper)
                     .min()
                     .orElse(defaultValue);
    }
}
Java

使い方はこうです。

List<Item> items = List.of(
        new Item("A", 100),
        new Item("B", 250),
        new Item("C", 180)
);

int minPrice = Mins.minIntOrDefault(items, Item::getPrice, 0);
System.out.println(minPrice); // 100
Java

ここで深掘りしたい重要ポイントは三つです。

一つ目は、「ToIntFunction<? super T> mapper が“どの項目を最小化するか”を表している」ことです。
Item::getPrice を渡すことで、「価格の最小値が欲しい」という意図が明確になります。

二つ目は、「ユーティリティ側は“どう最小値を計算するか”だけを知っていて、“何を最小化するか”は呼び出し側が決める」構造になっていることです。
これにより、同じメソッドで「価格」「数量」「スコア」など、何でも最小値を取れます。

三つ目は、「空や null の扱いを一箇所に閉じ込めている」ことです。
プロジェクト全体で「空なら 0」「空なら -1」など、ルールを統一できます。


「最小の要素そのもの」を取得する(Comparator 利用)

例:最も安い商品オブジェクトを取得する

「最小値」ではなく、「最小値を持つオブジェクトそのもの」が欲しいことも多いです。
例えば、「最も安い商品」を取りたい場合。

import java.util.Comparator;
import java.util.List;

public class MinItemSample {

    public static void main(String[] args) {
        List<Item> items = List.of(
                new Item("A", 100),
                new Item("B", 250),
                new Item("C", 80)
        );

        Item minItem = items.stream()
                            .min(Comparator.comparingInt(Item::getPrice))
                            .orElse(null); // 空なら null

        System.out.println(minItem.getName()); // C
    }
}
Java

ここでの重要ポイントは二つです。

一つ目は、min(Comparator.comparingInt(Item::getPrice)) という一行が
「価格を基準に最小の要素を選ぶ」というルールを表していることです。

二つ目は、戻り値が Optional<Item> なので、
orElse(null)orElseThrow(...) で「空だったときの扱い」を決める必要があることです。

これもユーティリティにしてしまうと、呼び出し側が楽になります。

import java.util.Comparator;
import java.util.List;

public final class Mins {

    private Mins() {}

    public static <T> T minOrNull(List<T> source, Comparator<? super T> comparator) {
        if (source == null || source.isEmpty()) {
            return null;
        }
        return source.stream()
                     .min(comparator)
                     .orElse(null);
    }
}
Java

使い方はこうです。

Item minItem = Mins.minOrNull(items, Comparator.comparingInt(Item::getPrice));
Java

「最小の値」ではなく「最小の要素」が欲しいときは、
このパターンを素直に使うと読みやすくなります。


null を含むデータの最小値取得

「null を無視する」か「null を最大扱いにする」か

現実のデータには null が混ざります。
最小値取得で大事なのは、「null をどう扱うか」を決めることです。

例えば、「null は無視して最小値を取る」なら、こう書きます。

public static Integer minIntIgnoreNull(List<Integer> source) {
    if (source == null || source.isEmpty()) {
        return null;
    }
    return source.stream()
                 .filter(v -> v != null)
                 .mapToInt(Integer::intValue)
                 .min()
                 .orElseThrow(); // null 以外が一つもなければ例外
}
Java

あるいは、「null は最大扱い(とても大きな値)にする」なら、
mapToInt の前に変換してしまう方法もあります。

public static int minIntTreatNullAsMax(List<Integer> source) {
    if (source == null || source.isEmpty()) {
        return 0;
    }
    return source.stream()
                 .mapToInt(v -> v == null ? Integer.MAX_VALUE : v)
                 .min()
                 .orElse(0);
}
Java

ここでの重要ポイントは、
「null の扱いも“最小値取得のルール”の一部」だと意識することです。

ユーティリティに閉じ込めておけば、
呼び出し側は「このメソッドは null をどう扱うのか」を意識せずに済みます。


最小値取得と「ビジネスルール」の結びつけ方

「最小日付」「最小バージョン」「最小ステータス」など

最小値取得は、単なる数値だけでなく、
日付やバージョン、ステータスなどにもよく使います。

例えば、「最も古い注文日」を取りたいなら、こうです。

import java.time.LocalDate;
import java.util.Comparator;
import java.util.List;

class Order {
    private final String id;
    private final LocalDate orderedAt;
    public Order(String id, LocalDate orderedAt) {
        this.id = id;
        this.orderedAt = orderedAt;
    }
    public String getId() { return id; }
    public LocalDate getOrderedAt() { return orderedAt; }
}

List<Order> orders = List.of(
        new Order("A001", LocalDate.of(2024, 1, 10)),
        new Order("A002", LocalDate.of(2024, 1, 5)),
        new Order("A003", LocalDate.of(2024, 1, 20))
);

Order oldest =
        Mins.minOrNull(orders, Comparator.comparing(Order::getOrderedAt));
Java

ここでのポイントは、
「何を“最小”とみなすかはビジネスルールそのもの」だということです。

日付なら「一番古いもの」、
バージョンなら「一番低いもの」、
ステータスなら「優先度が一番低いもの」など、
Comparator と組み合わせることで、
“ビジネス上の最小”をそのままコードに表現できます。


まとめ:最小値取得ユーティリティで身につけてほしい感覚

最小値取得は、
単に「一番小さいものを探すテクニック」ではなく、
「空・null・オブジェクト・比較ルールを含めて、“最小”をどう定義するかをコードに刻む作業」です。

数値 List なら mapToInt(...).min().orElse(デフォルト) の形を基本として覚える。
オブジェクト一覧では、「どの項目を最小化するか」を関数(Item::getPrice など)で渡す汎用ユーティリティにする。
「最小値そのもの」だけでなく、「最小の要素そのもの」を min(Comparator...) で取得するパターンも押さえる。
null や空コレクションの扱い(0にする、nullを返す、例外にする)をユーティリティ側で統一する。
日付やステータスなど、「ビジネス上の最小」を Comparator で表現する感覚を持つ。

もしあなたのコードのどこかに、
毎回 for 文で最小値を手作業で探している箇所があるなら、
それを一度「最小値取得ユーティリティ+Stream」に置き換えられないか眺めてみてください。

その小さな整理が、
「“最小”という概念を、迷いなく安全に扱えるエンジニア」への、
確かな一歩になります。

タイトルとURLをコピーしました