Skip to content

APG4b Java版 第3章: 言語機能

3.00. 第3章について

第2章をすべて完了したあなたは、実際にすべての「計算処理」を行うプログラムを書ける程の知識を身につけました。

第3章では、競技プログラミングで実用的にプログラムを書くために必要な Java の言語機能について解説します。この章をマスターすると、より多くの競技プログラミングの問題を解けるようになります。


3.01. Java で使える機能の整理

キーポイント

  • Java のコードは「自分で定義したもの」「標準で使えるもの」「import したもの」の3つに分類できる
  • java.lang.* は自動的にインポートされる (String, Math, System など)
  • それ以外のクラスは import 文で明示的にインポートが必要

Java コードの3つの要素

Java プログラムで使える識別子 (変数名・クラス名・メソッド名) は、大きく3種類に分類できます。

要素1: 自分で定義した変数・メソッド

自分でプログラム中に書いた変数・配列・メソッドです。

java
public class Main {
    static int a = 0;
    static int[] l = {3, 2, 1, 1};

    static int func(int x) {
        return 2 * x;
    }

    public static void main(String[] args) {
        System.out.println(a);
        System.out.println(func(a));
    }
}

要素2: 標準で使えるクラス (java.lang パッケージ)

java.lang パッケージのクラスは自動的にインポートされるため、import 文なしで使えます。

  • String - 文字列
  • Math - 数学関数 (Math.abs(), Math.pow() など)
  • System - 入出力 (System.out.println(), System.in)
  • Integer, Long, Double - プリミティブ型のラッパークラス
  • StringBuilder - 可変文字列
java
// import なしで使える
String s = String.valueOf(42);   // int → String 変換
double d = Math.sqrt(16.0);      // 平方根
System.out.println(d);           // 4.0
int max = Integer.MAX_VALUE;     // int の最大値

要素3: import が必要なクラス

java.lang 以外のクラスは、使用前に import 文で明示的に宣言する必要があります。

java
import java.util.Scanner;       // Scanner クラスをインポート
import java.util.ArrayList;     // ArrayList クラスをインポート
import java.util.Arrays;        // Arrays クラスをインポート
import java.util.*;             // java.util の全クラスをインポート

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        Scanner sc = new Scanner(System.in);
    }
}

import java.util.* のように * を使うと、そのパッケージのすべてのクラスをまとめてインポートできます。競技プログラミングでは最初にまとめて書いておくと便利です。

よく使う import 一覧

java
import java.util.*;          // Scanner, ArrayList, HashMap, Arrays, Collections 等
import java.io.*;            // BufferedReader, PrintWriter 等 (高速入出力)
import java.math.BigInteger; // 多倍長整数
import java.math.BigDecimal; // 高精度浮動小数点

競技プログラミングでは最初に import java.util.*;import java.io.*; を書いておくことをお勧めします。

コードの読み方

見慣れない識別子が出てきたら、次の順番で確認します:

  1. 自分で定義した変数・メソッドか? (プログラム内を検索)
  2. java.lang パッケージの標準クラスか? (ドキュメント確認)
  3. import されたクラスか? (ファイル先頭の import を確認)

3.02. コレクション (組み込みコンテナ)

キーポイント

  • Java の主要コレクション: int[] / ArrayList, HashSet, HashMap
  • Python の list → Java の int[] (固定長) または ArrayList<Integer> (可変長)
  • Python の set → Java の HashSet<T>
  • Python の dict → Java の HashMap<K, V>
  • TreeSet / TreeMap はソート済みを保証する

「コレクション」とは複数のデータをまとめて管理するためのデータ構造です。Python には listsetdict などがあります。Java では ArrayListHashSetHashMap がそれぞれに対応します。

ArrayList (Python の list に対応)

Python のリスト (list) に最も近いのが ArrayList です。可変長で、インデックスによるアクセスが O(1) です。

操作JavaPython計算量
値のアクセスlist.get(i)l[i]O(1)
値の変更list.set(i, x)l[i] = xO(1)
末尾に追加list.add(x)l.append(x)O(1)
途中に挿入list.add(i, x)l.insert(i, x)O(N)
末尾を削除list.remove(list.size()-1)l.pop()O(1)
途中を削除list.remove(i) (インデックス指定)l.pop(i)O(N)
サイズlist.size()len(l)O(1)
存在確認list.contains(x)x in lO(N)
出現回数Collections.frequency(list, x)l.count(x)O(N)

コード例:

java
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> l = new ArrayList<>();
        l.add(3); l.add(1); l.add(7); l.add(1); l.add(5);
        // l = [3, 1, 7, 1, 5]

        System.out.println(l.get(2));      // 7 (インデックス2の要素)
        l.set(2, 7);
        System.out.println(l);             // [3, 1, 7, 1, 5]
        l.add(9);                          // 末尾に追加
        System.out.println(l);             // [3, 1, 7, 1, 5, 9]
        l.add(3, 8);                       // インデックス3に8を挿入
        System.out.println(l);             // [3, 1, 7, 8, 1, 5, 9]
        l.remove(l.size() - 1);            // 末尾削除
        System.out.println(l);             // [3, 1, 7, 8, 1, 5]
        l.remove(0);                       // 先頭削除 (インデックス0)
        System.out.println(l);             // [1, 7, 8, 1, 5]
        System.out.println(l.size());      // 5
        System.out.println(l.contains(1)); // true
        System.out.println(l.contains(3)); // false
    }
}

remove の注意点: ArrayList<Integer> に対して list.remove(2) を呼ぶと、インデックス 2 の要素が削除されます (値 2 ではない)。値 2 を削除したいときは list.remove(Integer.valueOf(2)) と書きます。

HashSet (Python の set に対応)

HashSet は重複のない要素の集合を管理します。要素の存在確認が O(1) と高速なのが特長です。

操作JavaPython計算量
追加s.add(x)s.add(x)O(1)
削除s.remove(x)s.remove(x)O(1)
サイズs.size()len(s)O(1)
存在確認s.contains(x)x in sO(1)

コード例:

java
import java.util.HashSet;

public class Main {
    public static void main(String[] args) {
        HashSet<Integer> s = new HashSet<>();
        s.add(1); s.add(3); s.add(5);
        s.add(2);
        s.add(3);  // 既に存在する要素を追加しても変化なし
        System.out.println(s);             // [1, 2, 3, 5] (順不定)
        s.remove(3);
        System.out.println(s.size());      // 3
        System.out.println(s.contains(2)); // true
        System.out.println(s.contains(3)); // false
    }
}

注意: HashSet は要素の順序を保証しません。出力する順番は実行するたびに変わる可能性があります。順序が必要な場合は TreeSet を使います。

Set 演算 (和集合・積集合・差集合)

Python では s | ts & ts - t で集合演算ができますが、Java ではメソッドを使います。

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        HashSet<Integer> s = new HashSet<>(Arrays.asList(1, 2));
        HashSet<Integer> t = new HashSet<>(Arrays.asList(1, 3));

        // 和集合 (s | t): s または t に含まれる要素
        HashSet<Integer> union = new HashSet<>(s);
        union.addAll(t);
        System.out.println(union);  // [1, 2, 3]

        // 積集合 (s & t): s と t の両方に含まれる要素
        HashSet<Integer> inter = new HashSet<>(s);
        inter.retainAll(t);
        System.out.println(inter);  // [1]

        // 差集合 (s - t): s にあって t にない要素
        HashSet<Integer> diff = new HashSet<>(s);
        diff.removeAll(t);
        System.out.println(diff);   // [2]
    }
}

new HashSet<>(s) で既存の HashSet のコピーを作ってから演算を行います (元の s を変更しないため)。

TreeSet (ソート済み Set)

TreeSet は要素を常にソートされた状態で管理します。最小値・最大値の取得や、ある値に最も近い要素の検索が O(log N) でできます。

java
import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        TreeSet<Integer> s = new TreeSet<>();
        s.add(5); s.add(2); s.add(8); s.add(1);
        System.out.println(s);         // [1, 2, 5, 8] (自動的に昇順)
        System.out.println(s.first());  // 1 (最小値)
        System.out.println(s.last());   // 8 (最大値)

        // x 未満の最大要素 (bisect_left の代替)
        System.out.println(s.lower(5));   // 2
        // x 以下の最大要素
        System.out.println(s.floor(5));   // 5
        // x より大きい最小要素
        System.out.println(s.higher(5));  // 8
        // x 以上の最小要素
        System.out.println(s.ceiling(3)); // 5
    }
}

これらのメソッドは Python の sortedcontainers.SortedList の操作に対応します。

リストの重複除去 (Python の list(set(l)))

重複する要素を除去するには HashSet に変換します。

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        int[] a = {30, 10, 40, 10, 50};
        ArrayList<Integer> list = new ArrayList<>();
        for (int x : a) list.add(x);

        // 重複除去 (順序不定)
        HashSet<Integer> set = new HashSet<>(list);
        System.out.println(set);  // [50, 10, 30, 40] など (順序は不定)

        // 重複除去 + 昇順ソート
        TreeSet<Integer> sorted = new TreeSet<>(list);
        System.out.println(sorted);  // [10, 30, 40, 50]
    }
}

HashMap (Python の dict に対応)

HashMap はキーと値のペアを管理します。キーによる値の取得・追加・削除が O(1) です。

操作JavaPython計算量
値のアクセスd.get(k)d[k]O(1)
値の追加・更新d.put(k, v)d[k] = vO(1)
キーの削除d.remove(k)d.pop(k)O(1)
サイズd.size()len(d)O(1)
キー存在確認d.containsKey(k)k in dO(1)
デフォルト値で取得d.getOrDefault(k, def)d.get(k, def)O(1)

コード例:

java
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> d = new HashMap<>();
        d.put("Alice", 100);   // キー "Alice" に 100 を関連付ける
        d.put("Bob", 89);
        d.put("Charlie", 95);

        System.out.println(d.get("Alice"));  // 100

        d.put("Dave", 77);     // 新しいキーを追加
        d.put("Charlie", 59);  // 既存のキーを上書き
        d.remove("Bob");       // キーを削除

        // すべてのキーと値を表示
        for (var entry : d.entrySet()) {
            System.out.println(entry.getKey() + " " + entry.getValue());
        }
    }
}

存在しないキーにアクセスすると null が返ります。 Python では KeyError が発生しますが Java では null が返るため、getOrDefault() でデフォルト値を指定することをお勧めします。

defaultdict の代わり (存在しないキーにデフォルト値を使う):

java
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        int[] l = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};

        // 各要素の出現回数をカウント
        HashMap<Integer, Integer> counter = new HashMap<>();
        for (int x : l) {
            // getOrDefault: キーが存在しなければ 0 を返す
            counter.put(x, counter.getOrDefault(x, 0) + 1);
        }
        for (var entry : counter.entrySet()) {
            System.out.println(entry.getKey() + " " + entry.getValue());
        }
    }
}

counter.getOrDefault(x, 0) は「x がキーとして存在すればその値を、なければ 0 を返す」という意味です。

または merge を使った書き方:

java
counter.merge(x, 1, Integer::sum);  // x のカウントを1増やす (よりシンプル)

TreeMap (ソート済み Map)

TreeMap はキーを常にソートされた状態で管理します。

java
import java.util.TreeMap;

TreeMap<Integer, String> map = new TreeMap<>();
map.put(3, "three");
map.put(1, "one");
map.put(4, "four");
System.out.println(map);  // {1=one, 3=three, 4=four} (キーで昇順ソート)
System.out.println(map.firstKey());  // 1 (最小キー)
System.out.println(map.lastKey());   // 4 (最大キー)

for 文での列挙

HashMap の内容を列挙するには entrySet(), keySet(), values() を使います。

java
HashMap<String, Integer> d = new HashMap<>();
d.put("Alice", 100); d.put("Bob", 89);

// キーのみを列挙
for (String k : d.keySet()) System.out.println(k);

// 値のみを列挙
for (int v : d.values()) System.out.println(v);

// キーと値を同時に列挙
for (var entry : d.entrySet()) {
    System.out.println(entry.getKey() + " " + entry.getValue());
}

練習問題

EX20. 最頻値


3.03. 書式付き出力 (String.format と printf)

キーポイント

  • String.format() で書式付き文字列を作成 (Python の f"..."format() に相当)
  • System.out.printf() で書式付き出力
  • 小数点桁数、進数、ゼロパディングなどの書式指定が可能

書式付き出力とは

「書式付き出力」とは、数値や文字列を指定した形式で出力する機能です。例えば、小数点以下を指定桁数で表示したり、整数を一定の桁数に揃えてゼロ埋めしたりすることができます。

Python では f"..." (f-string) や format() を使いますが、Java では String.format() または System.out.printf() を使います。

String.format の基本

java
String name = "Takahashi";
System.out.println(String.format("Hello, %s!", name));
// 出力: Hello, Takahashi!

%s は文字列の書式指定子です。String.format() はフォーマット文字列の %s の部分を name の値で置き換えた文字列を返します。

複数の書式指定子を使う例:

java
int intVal = 123;
double floatVal = 3.1415;
String strVal = "Hello";
System.out.printf("%d %.2f %s%n", intVal, floatVal, strVal);
// 出力: 123 3.14 Hello

%d は整数、%.2f は小数点以下2桁の浮動小数点、%s は文字列、%n は改行を表します。

書式指定子一覧

指定子意味Python 対応
%d整数 (10進数)%d または {:d}
%f浮動小数点%f または {:f}
%.2f小数点以下2桁{:.2f}
%e指数形式{:e}
%s文字列%s または {:s}
%x16進数 (小文字){:x}
%X16進数 (大文字){:X}
%o8進数{:o}
%n改行\n

小数点以下の桁数指定

競技プログラミングでは「小数点以下〇桁を出力せよ」という問題が多いです。

java
double val = 3.1415;
System.out.printf("%.2f%n", val);  // 3.14 (小数点以下2桁)
System.out.printf("%.3f%n", val);  // 3.142 (小数点以下3桁、四捨五入)
System.out.printf("%.8f%n", val);  // 3.14150000 (小数点以下8桁)

%.2f2 が小数点以下の桁数です。指定した桁数になるよう自動的に四捨五入・ゼロ埋めされます。

進法変換

整数を2進法・8進法・16進法で表示する方法です。

java
int value = 29;
System.out.printf("%s%n", Integer.toBinaryString(value)); // 2進法: 11101
System.out.printf("%o%n", value);  // 8進法: 35
System.out.printf("%x%n", value);  // 16進法 (小文字): 1d
System.out.printf("%X%n", value);  // 16進法 (大文字): 1D

2進法は Integer.toBinaryString() を使います (%b は boolean に使われるため)。

ゼロパディングと桁数指定

java
int val = 42;
System.out.printf("%05d%n", val);   // 00042 (5桁、0で埋める)
System.out.printf("%-10d|%n", val); // 42        | (左詰め10桁)
System.out.printf("%+d%n", val);    // +42 (正の数に+を付ける)
System.out.printf("%,d%n", 1234567); // 1,234,567 (3桁ごとにカンマ)

String.format を使った文字列生成

System.out.printf() は出力するだけですが、String.format() は書式化された文字列を返します。変数に格納したり、他の処理に使えます。

java
int a = 10, b = 20;
String s = String.format("%d + %d = %d", a, b, a + b);
System.out.println(s);  // 10 + 20 = 30

// 出力はしないがテキストを組み立てる場合に便利
String header = String.format("%-10s %5s", "名前", "点数");
System.out.println(header);  // 名前         点数

Python の f-string との比較

Python f-stringJava
f"Hello, {name}!"String.format("Hello, %s!", name)
f"{val:.2f}"String.format("%.2f", val)
f"{val:b}"Integer.toBinaryString(val)
f"{val:x}"String.format("%x", val)
f"{val:05d}"String.format("%05d", val)
f"{val:>10}"String.format("%10d", val)
f"{val:<10}"String.format("%-10d", val)

書式付き出力のチートシート

java
double pi = Math.PI;
System.out.printf("%.13f%n", pi);   // 3.1415926535898 (小数点以下13桁)
System.out.printf("%e%n", pi);      // 3.141593e+00 (指数形式)
System.out.printf("%o%n", 255);     // 377 (8進数)
System.out.printf("%x%n", 255);     // ff (16進数)
System.out.printf("%08d%n", 42);    // 00000042 (8桁ゼロ埋め)
System.out.printf("%+d%n", 42);     // +42 (符号付き)
System.out.printf("%.2f%%%n", 75.5); // 75.50% (%% でパーセント記号)
System.out.printf("%,d%n", 1000000); // 1,000,000 (カンマ区切り)
System.out.printf("%15d%n", 42);    //              42 (右詰め15桁)
System.out.printf("%-15d|%n", 42);  // 42             | (左詰め15桁)

3.04. ビット演算

キーポイント

  • Java のビット演算子は Python とほぼ同じ
  • & (AND), | (OR), ^ (XOR), << (左シフト), >> (右シフト), ~ (NOT)
  • >>> (符号なし右シフト) は Java 固有の演算子
  • Python と異なり、Java の >> は符号ビットを保持する (算術シフト)

ビット演算とは

コンピュータの内部では、すべての数値は0と1のビット列で表現されています。ビット演算は、そのビット列を直接操作する演算です。競技プログラミングでは状態の表現や高速化のために頻繁に使います。

ビット演算の種類

演算演算子意味例 (10進)例 (2進)
AND&両方のビットが1なら16 & 3 → 2110 & 011 = 010
OR|どちらかのビットが1なら16 | 3 → 7110 | 011 = 111
XOR^一方だけが1なら16 ^ 3 → 5110 ^ 011 = 101
左シフト<<ビット列を左にずらす3 << 2 → 1211 → 1100
右シフト>>符号ビット保持で右にずらす13 >> 2 → 31101 → 11
符号なし右シフト>>>符号ビットも0で埋める-1 >>> 1 → 2^31-1
NOT~ビットをすべて反転~6 → -7...00110 → ...11001

AND・OR・XOR 演算

java
public class Main {
    public static void main(String[] args) {
        // 6 = 110 (2進), 3 = 011 (2進)
        System.out.println(6 & 3);   // 2 (= 010)  AND
        System.out.println(6 | 3);   // 7 (= 111)  OR
        System.out.println(6 ^ 3);   // 5 (= 101)  XOR
    }
}

AND演算 (6 & 3):

  110 (6)
& 011 (3)
------
  010 (2)

各ビット位置で両方が1のときだけ1になります。

ビットシフト

左シフト << は2の掛け算、右シフト >> は2の割り算に相当します。

java
System.out.println(3 << 2);    // 12 (3 × 2² = 12)
System.out.println(13 >> 2);   // 3  (13 ÷ 2² = 3.25 → 3)
System.out.println(-3 << 2);   // -12
System.out.println(-13 >> 2);  // -4 (算術右シフト: 符号ビットを保持)
System.out.println(-1 >>> 1);  // 2147483647 (符号なし右シフト: 最上位ビットを0で埋める)

1 << N は 2^N (2のN乗) を表します。よく使うパターンです。

NOT 演算

java
System.out.println(~6);    // -7 (すべてのビットを反転。~x = -(x+1))
System.out.println(~(-7)); // 6
System.out.println(~~100); // 100 (二重否定は元の値)

~x = -(x+1) という関係があります。例えば ~6 = -(6+1) = -7 です。

演算子の優先順位

ビット演算の優先順位は算術演算より低いため、意図しない結果になりやすいです。括弧をつけるように注意してください。

~ (単項) > * / % > + - > << >> >>> > & > ^ > | > 比較演算子

java
System.out.println(1 << 5 - 1);      // 16  (まず 5-1=4 が計算され 1<<4=16)
System.out.println((1 << 5) - 1);    // 31  (まず 1<<5=32 が計算され 32-1=31)
System.out.println(1 & 2 + 3);       // 1   (まず 2+3=5 が計算され 1&5=1)
System.out.println((1 & 2) + 3);     // 3   (まず 1&2=0 が計算され 0+3=3)

ビットの取得

整数 xi ビット目 (0から数えて) が 1 かどうかを確認するパターン。

java
int x = 13;  // 2進法で 1101
for (int i = 0; i < 5; i++) {
    System.out.println((x >> i) & 1);
}
// 出力: 1 0 1 1 0
// i=0: 1101 >> 0 = 1101, &1 = 1 (下から0ビット目)
// i=1: 1101 >> 1 = 110,  &1 = 0 (下から1ビット目)
// i=2: 1101 >> 2 = 11,   &1 = 1 (下から2ビット目)
// i=3: 1101 >> 3 = 1,    &1 = 1 (下から3ビット目)
// i=4: 1101 >> 4 = 0,    &1 = 0 (下から4ビット目)

ビット全探索

N 個の要素のすべての部分集合を列挙します。2^N 通りの状態を整数 0〜2^N-1 で表現します。

java
public class Main {
    public static void main(String[] args) {
        int N = 3;
        for (int i = 0; i < (1 << N); i++) {  // 0 から 2^N-1 まで
            ArrayList<Integer> L = new ArrayList<>();
            for (int j = 0; j < N; j++) {
                if (((i >> j) & 1) == 1) {  // i の j ビット目が 1 なら
                    L.add(j);
                }
            }
            System.out.println(i + " " + L);
        }
    }
}

実行結果:

0 []
1 [0]
2 [1]
3 [0, 1]
4 [2]
5 [0, 2]
6 [1, 2]
7 [0, 1, 2]

整数 i のビットパターンで部分集合を表現します。i = 5 = 101₂ のとき、ビット0と2が立っているので {0, 2} の部分集合を表します。

N ≤ 20 程度であれば 2^N ≈ 10^6 なので、ビット全探索で全部分集合を試すことができます。

最下位ビットの取得

java
int x = 12;  // 1100
System.out.println(x & (-x));   // 4  (= 100, 最下位の1ビット)
int y = 99;  // 1100011
System.out.println(y & (-y));   // 1  (= 001, 最下位の1ビット)

x & (-x)x の最下位の 1 ビットだけを取り出せます。Fenwick Tree (Binary Indexed Tree) などで使うテクニックです。

練習問題

EX21. Bitwise Exclusive Or


3.05. 発展的な文法

キーポイント

  • 三項演算子: 条件 ? X : Y (Python の X if 条件 else Y に対応)
  • 短絡評価: && で左が false なら右は評価されない
  • ラムダ式: (引数) -> 処理 で1行のメソッドを表現 (Python の lambda に対応)
  • デフォルト引数はない → メソッドオーバーロードで代替

三項演算子

三項演算子は「条件が true であれば X、そうでなければ Y」を返す式です。

構文: 条件式 ? 真のときの値 : 偽のときの値

Python の X if 条件 else Y に対応しますが、順序が異なることに注意してください。

java
public class Main {
    public static void main(String[] args) {
        int[] result = new int[10];
        for (int i = 0; i < 10; i++) {
            // i が偶数なら i そのまま、奇数なら i * 100
            result[i] = (i % 2 == 0) ? i : i * 100;
        }
        // result = [0, 100, 2, 300, 4, 500, 6, 700, 8, 900]
        for (int x : result) System.out.print(x + " ");
        System.out.println();
    }
}

実行結果:

0 100 2 300 4 500 6 700 8 900

Python の [i if i%2==0 else i*100 for i in range(10)] に対応します。

三項演算子は if-else を1行にまとめた書き方です。シンプルな条件分岐を短く書きたいときに使います。

java
// if-else 版
int x;
if (a > b) {
    x = a;
} else {
    x = b;
}

// 三項演算子版
int x = (a > b) ? a : b;  // a と b の大きい方

短絡評価 (ショートサーキット)

&&|| には「短絡評価」という重要な性質があります。

  • A && B: A が false なら B は評価されない
  • A || B: A が true なら B は評価されない
java
public class Main {
    static boolean hello() {
        System.out.println("Hello");
        return true;
    }

    public static void main(String[] args) {
        // true || hello() → true が確定しているので hello() は呼ばれない
        if (true || hello()) {
            System.out.println("条件式はtrue");
        }
        // false && hello() → false が確定しているので hello() は呼ばれない
        if (false && hello()) {
            System.out.println("条件式はfalse");
        }
    }
}

実行結果:

条件式はtrue

hello() は1回も呼ばれていないため "Hello" は出力されません。

この性質は実用的にも重要です。例えば、配列が null でないことを確認してからアクセスする場合:

java
// arr が null でないことを先にチェック → null なら arr.length は評価されない
if (arr != null && arr.length > 0) {
    System.out.println(arr[0]);
}

ラムダ式

Java 8以降では「ラムダ式」を使って、簡潔に処理を記述できます。Python の lambda に対応します。

構文: (引数) -> 式 または (引数) -> { 処理; return 値; }

java
import java.util.function.Function;

public class Main {
    // 通常の static メソッド
    static int func1(int a) {
        return a + 10;
    }

    public static void main(String[] args) {
        // ラムダ式 (Python の lambda a: a+10 に対応)
        Function<Integer, Integer> func2 = a -> a + 10;

        System.out.println(func1(1));        // 11
        System.out.println(func2.apply(1));  // 11
    }
}

競技プログラミングでは、ラムダ式は主に sort のカスタム比較関数として使います:

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(2, 6, 11, 100));

        // 1の位でソート (ラムダ式で比較関数を指定)
        list.sort((x, y) -> (x % 10) - (y % 10));
        System.out.println(list);  // [100, 11, 2, 6]
    }
}

デフォルト引数の代替: メソッドオーバーロード

Python では def func(a, start=0): のようにデフォルト引数を使えますが、Java にはこの機能がありません。代わりに「メソッドオーバーロード」を使います。

オーバーロードとは、同じ名前で引数の型・数が異なるメソッドを複数定義することです。

java
public class Main {
    // デフォルト引数なしのバージョン (start=0 で本体を呼ぶ)
    static void addVals(int[] a) {
        addVals(a, 0);  // start = 0 でもう一方のメソッドを呼ぶ
    }

    // 引数が2つのバージョン
    static void addVals(int[] a, int start) {
        for (int i = 0; i < a.length; i++) {
            a[i] += i + start;
        }
    }

    public static void main(String[] args) {
        int[] a = {3, 1, 2};
        int[] b = {4, 5, 6};
        int[] c = {3, 3, 1};

        addVals(a);        // start = 0 で実行: a[i] += i
        addVals(b);        // start = 0 で実行: b[i] += i
        addVals(c, 1);     // start = 1 で実行: c[i] += i + 1

        System.out.println(Arrays.toString(a));  // [3, 2, 4]
        System.out.println(Arrays.toString(b));  // [4, 6, 8]
        System.out.println(Arrays.toString(c));  // [4, 5, 4]
    }
}

addVals(a)addVals(a, 0) を呼ぶことで、Python の def addVals(a, start=0): と同じ動作を実現しています。


3.06. いろいろなソート

キーポイント

  • Arrays.sort(a) は配列を昇順にソート (in-place)
  • Collections.sort(list) は ArrayList を昇順にソート (in-place)
  • 降順ソートは Comparator.reverseOrder() を使う
  • カスタムソートは Comparator または ラムダ式で定義

ソートとは

ソートとはデータを特定の順序 (昇順・降順・独自の順序) に並べ替えることです。Java には効率的なソートアルゴリズムが標準ライブラリに含まれています。

配列のソート

Arrays.sort() は配列の要素を昇順 (小さい順) に並べ替えます。元の配列を直接変更します (in-place)。

java
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] a = {5, 4, 1, 3, 2};
        Arrays.sort(a);
        System.out.println(Arrays.toString(a));  // [1, 2, 3, 4, 5]
    }
}

Python の a.sort() または sorted(a) に対応しますが、Java の Arrays.sort() は常に in-place (元の配列を変更) です。ソート前の配列を保持したい場合は先にコピーしてください。

ArrayList のソート

Collections.sort() または list.sort() を使います。

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(5, 4, 1, 3, 2));

        // コピーを作ってからソート (元のリストを変更しない)
        ArrayList<Integer> sorted = new ArrayList<>(list);
        Collections.sort(sorted);
        System.out.println(list);    // [5, 4, 1, 3, 2] (変化なし)
        System.out.println(sorted);  // [1, 2, 3, 4, 5]

        // in-place ソート
        list.sort(null);  // null は自然順序 (昇順) を使うことを意味する
        System.out.println(list);    // [1, 2, 3, 4, 5]
    }
}

文字列のソート

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // 文字列のソート (辞書順)
        ArrayList<String> words = new ArrayList<>(Arrays.asList("banana", "apple", "cherry"));
        Collections.sort(words);
        System.out.println(words);  // [apple, banana, cherry]
    }
}

降順ソート

逆順 (大きい順) にソートするには Comparator.reverseOrder() を使います。

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
        list.sort(Comparator.reverseOrder());
        System.out.println(list);  // [4, 3, 2, 1]

        // int[] の降順ソート (プリミティブ配列は reverseOrder が使えないので変換が必要)
        int[] a = {3, 1, 4, 1, 5};
        Integer[] boxed = Arrays.stream(a).boxed().toArray(Integer[]::new);
        Arrays.sort(boxed, Comparator.reverseOrder());
        System.out.println(Arrays.toString(boxed));  // [5, 4, 3, 1, 1]
    }
}

int[] (プリミティブ型の配列) には Comparator が使えません。Integer[] に変換 (boxed()) してから降順ソートします。

カスタムソート (key 引数の代替)

Python の l.sort(key=lambda x: x%10) のように、独自の基準でソートするには Comparator をラムダ式で定義します。

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(2, 6, 11, 100));

        // 1の位でソート (Python の key=lambda x: x%10 に対応)
        list.sort((x, y) -> (x % 10) - (y % 10));
        System.out.println(list);  // [100, 11, 2, 6]
        // 1の位: 100→0, 11→1, 2→2, 6→6
    }
}

ラムダ式 (x, y) -> (x % 10) - (y % 10) は「x の1の位と y の1の位を比較する関数」です。戻り値が負なら x が先、正なら y が先にきます。

比較関数によるソート (Comparator)

より複雑な比較が必要な場合は、メソッドとして定義した比較関数を使えます。

java
import java.util.*;

public class Main {
    // 絶対値でソートする比較関数
    static int compareByAbs(int x, int y) {
        return Math.abs(x) - Math.abs(y);
    }

    public static void main(String[] args) {
        ArrayList<Integer> A = new ArrayList<>(Arrays.asList(-3, 1, 4, 2, -5));
        A.sort((x, y) -> compareByAbs(x, y));
        System.out.println(A);  // [1, 2, -3, 4, -5]
        // 絶対値: 1, 2, 3, 4, 5 の順
    }
}

多項目ソート (複数のキーでソート)

複数の基準でソートする場合も、ラムダ式の中で複数の比較を行います。

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // [名前, 得点] の2次元配列を得点降順、同点なら名前昇順でソート
        String[][] data = {{"Alice", "85"}, {"Bob", "90"}, {"Carol", "85"}};
        Arrays.sort(data, (a, b) -> {
            int scoreA = Integer.parseInt(a[1]);
            int scoreB = Integer.parseInt(b[1]);
            if (scoreA != scoreB) return scoreB - scoreA;  // 得点降順
            return a[0].compareTo(b[0]);                    // 得点が同じなら名前昇順
        });
        for (String[] d : data) System.out.println(Arrays.toString(d));
    }
}

実行結果:

[Bob, 90]
[Alice, 85]
[Carol, 85]

Bob が得点90で1位。Alice と Carol は同点85だが名前順で Alice が先になります。

Python の data.sort(key=lambda x: (-int(x[1]), x[0])) に対応します。

2次元配列 (ペア) のソート

ペアを特定の列でソートする場合:

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // 先頭要素でソート
        int[][] pairs = {{3, 5}, {1, 8}, {2, 3}};
        Arrays.sort(pairs, (x, y) -> x[0] - y[0]);
        for (int[] p : pairs) System.out.println(Arrays.toString(p));
        // [1, 8], [2, 3], [3, 5]
    }
}

練習問題

EX22. 2つ目の値でソート

APG4bPython の教材を Java 向けに書き直したものです。