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: 自分で定義した変数・メソッド
自分でプログラム中に書いた変数・配列・メソッドです。
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- 可変文字列
// 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 文で明示的に宣言する必要があります。
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 一覧
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.*; を書いておくことをお勧めします。
コードの読み方
見慣れない識別子が出てきたら、次の順番で確認します:
- 自分で定義した変数・メソッドか? (プログラム内を検索)
java.langパッケージの標準クラスか? (ドキュメント確認)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 には list、set、dict などがあります。Java では ArrayList、HashSet、HashMap がそれぞれに対応します。
ArrayList (Python の list に対応)
Python のリスト (list) に最も近いのが ArrayList です。可変長で、インデックスによるアクセスが O(1) です。
| 操作 | Java | Python | 計算量 |
|---|---|---|---|
| 値のアクセス | list.get(i) | l[i] | O(1) |
| 値の変更 | list.set(i, x) | l[i] = x | O(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 l | O(N) |
| 出現回数 | Collections.frequency(list, x) | l.count(x) | O(N) |
コード例:
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) と高速なのが特長です。
| 操作 | Java | Python | 計算量 |
|---|---|---|---|
| 追加 | 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 s | O(1) |
コード例:
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 | t、s & t、s - t で集合演算ができますが、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) でできます。
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 に変換します。
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) です。
| 操作 | Java | Python | 計算量 |
|---|---|---|---|
| 値のアクセス | d.get(k) | d[k] | O(1) |
| 値の追加・更新 | d.put(k, v) | d[k] = v | O(1) |
| キーの削除 | d.remove(k) | d.pop(k) | O(1) |
| サイズ | d.size() | len(d) | O(1) |
| キー存在確認 | d.containsKey(k) | k in d | O(1) |
| デフォルト値で取得 | d.getOrDefault(k, def) | d.get(k, def) | O(1) |
コード例:
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 の代わり (存在しないキーにデフォルト値を使う):
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 を使った書き方:
counter.merge(x, 1, Integer::sum); // x のカウントを1増やす (よりシンプル)TreeMap (ソート済み Map)
TreeMap はキーを常にソートされた状態で管理します。
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() を使います。
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 の基本
String name = "Takahashi";
System.out.println(String.format("Hello, %s!", name));
// 出力: Hello, Takahashi!%s は文字列の書式指定子です。String.format() はフォーマット文字列の %s の部分を name の値で置き換えた文字列を返します。
複数の書式指定子を使う例:
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} |
%x | 16進数 (小文字) | {:x} |
%X | 16進数 (大文字) | {:X} |
%o | 8進数 | {:o} |
%n | 改行 | \n |
小数点以下の桁数指定
競技プログラミングでは「小数点以下〇桁を出力せよ」という問題が多いです。
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桁)%.2f の 2 が小数点以下の桁数です。指定した桁数になるよう自動的に四捨五入・ゼロ埋めされます。
進法変換
整数を2進法・8進法・16進法で表示する方法です。
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進法 (大文字): 1D2進法は Integer.toBinaryString() を使います (%b は boolean に使われるため)。
ゼロパディングと桁数指定
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() は書式化された文字列を返します。変数に格納したり、他の処理に使えます。
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-string | Java |
|---|---|
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) |
書式付き出力のチートシート
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なら1 | 6 & 3 → 2 | 110 & 011 = 010 |
| OR | | | どちらかのビットが1なら1 | 6 | 3 → 7 | 110 | 011 = 111 |
| XOR | ^ | 一方だけが1なら1 | 6 ^ 3 → 5 | 110 ^ 011 = 101 |
| 左シフト | << | ビット列を左にずらす | 3 << 2 → 12 | 11 → 1100 |
| 右シフト | >> | 符号ビット保持で右にずらす | 13 >> 2 → 3 | 1101 → 11 |
| 符号なし右シフト | >>> | 符号ビットも0で埋める | -1 >>> 1 → 2^31-1 | |
| NOT | ~ | ビットをすべて反転 | ~6 → -7 | ...00110 → ...11001 |
AND・OR・XOR 演算
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の割り算に相当します。
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 演算
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 です。
演算子の優先順位
ビット演算の優先順位は算術演算より低いため、意図しない結果になりやすいです。括弧をつけるように注意してください。
~ (単項) > * / % > + - > << >> >>> > & > ^ > | > 比較演算子
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)ビットの取得
整数 x の i ビット目 (0から数えて) が 1 かどうかを確認するパターン。
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 で表現します。
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 なので、ビット全探索で全部分集合を試すことができます。
最下位ビットの取得
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 に対応しますが、順序が異なることに注意してください。
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 900Python の [i if i%2==0 else i*100 for i in range(10)] に対応します。
三項演算子は if-else を1行にまとめた書き方です。シンプルな条件分岐を短く書きたいときに使います。
// 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 は評価されない
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");
}
}
}実行結果:
条件式はtruehello() は1回も呼ばれていないため "Hello" は出力されません。
この性質は実用的にも重要です。例えば、配列が null でないことを確認してからアクセスする場合:
// arr が null でないことを先にチェック → null なら arr.length は評価されない
if (arr != null && arr.length > 0) {
System.out.println(arr[0]);
}ラムダ式
Java 8以降では「ラムダ式」を使って、簡潔に処理を記述できます。Python の lambda に対応します。
構文: (引数) -> 式 または (引数) -> { 処理; return 値; }
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 のカスタム比較関数として使います:
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 にはこの機能がありません。代わりに「メソッドオーバーロード」を使います。
オーバーロードとは、同じ名前で引数の型・数が異なるメソッドを複数定義することです。
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)。
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() を使います。
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]
}
}文字列のソート
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() を使います。
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 をラムダ式で定義します。
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)
より複雑な比較が必要な場合は、メソッドとして定義した比較関数を使えます。
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 の順
}
}多項目ソート (複数のキーでソート)
複数の基準でソートする場合も、ラムダ式の中で複数の比較を行います。
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次元配列 (ペア) のソート
ペアを特定の列でソートする場合:
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つ目の値でソート