APG4b Java版 第3章: 言語機能
3.00. 第3章について
第2章をすべて完了したあなたは、実際にすべての「計算処理」を行うプログラムを書ける程の知識を身につけました。
第3章では、競技プログラミングで実用的にプログラムを書くために必要な Java の言語機能について解説します。この章をマスターすると、より多くの競技プログラミングの問題を解けるようになります。
3.01. Java で使える機能の整理
キーポイント
- Java のコードは「自分で定義したもの」「標準で使えるもの」「import したもの」の3つに分類できる
java.lang.*は自動的にインポートされる (String,Math,Systemなど)- それ以外のクラスは
import文で明示的にインポートが必要
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 パッケージ)
インポートなしで使えるクラス:
String- 文字列Math- 数学関数System- 入出力Integer,Long,Double- プリミティブ型のラッパーStringBuilder- 可変文字列
// import なしで使える
int[] sorted = {1, 3, 2};
String s = String.valueOf(42);
double d = Math.sqrt(16.0);
System.out.println(d); // 4.0要素3: import が必要なクラス
import java.util.Scanner;
import java.util.ArrayList;
import java.util.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 一覧
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.*; を最初に書いておくと便利です。
コードの読み方
見慣れない識別子が出てきたら:
- 自分で定義した変数・メソッドか? (
=やstaticを探す) 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はソート済みを保証する
ArrayList (Python の list に対応)
| 操作 | 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);
System.out.println(l.get(2)); // 7
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); // 先頭削除
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
}
}HashSet (Python の set に対応)
| 操作 | 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);
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 演算 (和集合・積集合・差集合)
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)
HashSet<Integer> union = new HashSet<>(s);
union.addAll(t);
System.out.println(union); // [1, 2, 3]
// 積集合 (s & t)
HashSet<Integer> inter = new HashSet<>(s);
inter.retainAll(t);
System.out.println(inter); // [1]
// 差集合 (s - t)
HashSet<Integer> diff = new HashSet<>(s);
diff.removeAll(t);
System.out.println(diff); // [2]
}
}TreeSet (ソート済み Set)
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 未満の最大要素
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 の list(set(l)))
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 に対応)
| 操作 | 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);
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());
}
}
}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) {
counter.put(x, counter.getOrDefault(x, 0) + 1);
}
for (var entry : counter.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}または merge を使った書き方:
counter.merge(x, 1, Integer::sum); // x のカウントを1増やすTreeMap (ソート済み Map)
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()); // 4for 文での列挙
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"..."に相当)System.out.printf()で書式付き出力- 小数点桁数、進数、ゼロパディングなどの書式指定が可能
String.format の基本
String name = "Takahashi";
System.out.println(String.format("Hello, %s!", name));
// 出力: Hello, Takahashi!int intVal = 123;
double floatVal = 3.1415;
String strVal = "Hello";
System.out.printf("%d %.2f %s%n", intVal, floatVal, strVal);
// 出力: 123 3.14 Hello書式指定子一覧
| 指定子 | 意味 | Python 対応 |
|---|---|---|
%d | 整数 (10進数) | %d |
%f | 浮動小数点 | %f |
%.2f | 小数点以下2桁 | :.2f |
%e | 指数形式 | :e |
%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
System.out.printf("%.3f%n", val); // 3.142
System.out.printf("%.8f%n", val); // 3.14150000進法変換
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ゼロパディングと桁数指定
int val = 42;
System.out.printf("%05d%n", val); // 00042 (5桁、ゼロ埋め)
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 を使った文字列生成
int a = 10, b = 20;
String s = String.format("%d + %d = %d", a, b, a + b);
System.out.println(s); // 10 + 20 = 30Python の 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); // 小数点以下13桁
System.out.printf("%e%n", pi); // 指数形式
System.out.printf("%o%n", 255); // 8進数
System.out.printf("%x%n", 255); // 16進数 (ff)
System.out.printf("%08d%n", 42); // ゼロパディング (00000042)
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); // 右詰め15桁
System.out.printf("%-15d|%n", 42); // 左詰め15桁3.04. ビット演算
キーポイント
- Java のビット演算子は Python とほぼ同じ
&(AND),|(OR),^(XOR),<<(左シフト),>>(右シフト),~(NOT)>>>(符号なし右シフト) は Java 固有の演算子- Python と異なり、Java の
>>は符号ビットを保持する (算術シフト)
ビット演算の種類
| 演算 | 演算子 | 意味 | 例 (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) {
System.out.println(6 & 3); // 2
System.out.println(6 | 3); // 7
System.out.println(6 ^ 3); // 5
}
}ビットシフト
System.out.println(3 << 2); // 12 (3 * 2^2)
System.out.println(13 >> 2); // 3 (13 / 2^2)
System.out.println(-3 << 2); // -12
System.out.println(-13 >> 2); // -4 (Python と同じ)
System.out.println(-1 >>> 1); // 2147483647 (符号なし右シフト)NOT 演算
System.out.println(~6); // -7
System.out.println(~(-7)); // 6
System.out.println(~~100); // 100
// ~x = -(x+1) と等価演算子の優先順位 (上ほど高い)
~ (単項) > * / % > + - > << >> >>> > & > ^ > | > 比較演算子 > && > ||
System.out.println(1 << 5 - 1); // 16 (5-1=4, 1<<4)
System.out.println((1 << 5) - 1); // 31
System.out.println(1 & 2 + 3); // 1 (2+3=5, 1&5=1)
System.out.println((1 & 2) + 3); // 3ビットの取得
int x = 13; // 2進法で 1101
for (int i = 0; i < 5; i++) {
System.out.println((x >> i) & 1);
}
// 出力: 1 0 1 1 0 (ビット 0, 1, 2, 3, 4)ビット全探索
N 個の要素の全部分集合を列挙します (2^N 通り):
public class Main {
public static void main(String[] args) {
int N = 3;
for (int i = 0; i < (1 << N); i++) {
ArrayList<Integer> L = new ArrayList<>();
for (int j = 0; j < N; j++) {
if (((i >> j) & 1) == 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]最下位ビットの取得
int x = 12; // 1100
System.out.println(x & (-x)); // 4 (最下位の1ビット)
int y = 99; // 1100011
System.out.println(y & (-y)); // 1集合のビット演算 (ナップサック問題の例)
public class Main {
public static void main(String[] args) {
int[] A = {3, 5, 7, 11};
int W = 20;
// long を使ってビット列で到達可能な重量を管理
long s = 1; // ビット0が立っている = 重量0が実現可能
for (int a : A) {
s |= s << a;
}
// W以下の実現可能な重量を出力
for (int w = 0; w <= W; w++) {
if (((s >> w) & 1) == 1) {
System.out.println(w + " は実現可能");
}
}
}
}練習問題
EX21. Bitwise Exclusive Or
3.05. 発展的な文法
キーポイント
- 三項演算子:
条件 ? X : Y(Python のX if 条件 else Yに対応) - 短絡評価:
&&で左がfalseなら右は評価されない - ラムダ式:
(引数) -> 処理で1行のメソッドを表現 (Python のlambdaに対応) - デフォルト引数はない → メソッドオーバーロードで代替
三項演算子
「条件が true であれば X、そうでなければ Y」を返す式:
public class Main {
public static void main(String[] args) {
int[] result = new int[10];
for (int i = 0; i < 10; i++) {
result[i] = (i % 2 == 0) ? i : i * 100;
}
// 出力: [0, 100, 2, 300, 4, 500, 6, 700, 8, 900]
for (int x : result) System.out.print(x + " ");
System.out.println();
}
}Python の内包表記との比較:
- Python:
[i if i%2==0 else i*100 for i in range(10)] - Java: 配列 + ループ + 三項演算子
短絡評価 (ショートサーキット)
public class Main {
static boolean hello() {
System.out.println("Hello");
return true;
}
public static void main(String[] args) {
if (true || hello()) { // hello() は呼ばれない
System.out.println("条件式はtrue");
}
if (false && hello()) { // hello() は呼ばれない
System.out.println("条件式はfalse");
}
}
}実行結果:
条件式はtrue|| で左が true なら右は評価されず、&& で左が false なら右は評価されません。
ラムダ式
Java 8以降では (引数) -> 処理 の形でラムダ式を書けます。
import java.util.function.Function;
public class Main {
// 通常のメソッド
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
}
}ラムダ式は主に 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の位でソート
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 {
static void addVals(int[] a) {
addVals(a, 0); // デフォルト値 0 でもう一方を呼ぶ
}
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 (デフォルト)
addVals(b);
addVals(c, 1); // start = 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]
}
}キーワード引数の代替
Java にはキーワード引数がないため、オーバーロードや Builder パターンで対応します。
3.06. いろいろなソート
キーポイント
Arrays.sort(a)は配列を昇順にソート (in-place)Collections.sort(list)は ArrayList を昇順にソート (in-place)- 降順ソートは
Comparator.reverseOrder()を使う - カスタムソートは
Comparatorまたは ラムダ式で定義
メソッドの違い
Arrays.sort(a): 配列の中身を昇順に変更Arrays.sort(a, comparator): 配列を指定した順序でソート (プリミティブ配列では使えない)
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]
}
}ArrayList のソート
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]
list.sort(null); // 昇順ソート (in-place)
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]
// int[] の配列 (行ごとに先頭要素でソート)
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]
}
}降順ソート
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]
}
}カスタムソート (key 引数の代替)
Python の l.sort(key=lambda x: x%10) に対応:
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]
}
}比較関数によるソート (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]
}
}多項目ソート (複数のキーでソート)
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]練習問題
EX22. 2つ目の値でソート