はじめに
記事は”Oracle Certified Java Programmer, Gold SE 11認定資格(Java Gold SE11)”合格に向けた学習備忘録のまとめです。
< この学習備忘録について >
内容や重要な点が抜けていた場合には適宜更新していきます
Java Gold合格において重要な単元
Java Goldの学習においては、下記の単元が非常に重要となってきます。
- 関数型インタフェース
- ラムダ式
- JavaストリームAPI
- 並列処理
ですが、今回は上記にはないものの、これまた重要な箇所であるコレクションとジェネリックスについて取り上げたいと思います。
私と同じようにJava Goldの合格を目指して勉強されている皆様にとって、少しでもお役に立てれば幸いです。
List, Set, Map:コレクションの三大インタフェースと実装クラス
Javaのコレクションフレームワークは、データを効率的に扱うための仕組みです。その中心となるのがList, Set, Mapの3つの主要なインタフェースです。それぞれの特徴と、代表的な実装クラスを整理します。
インタフェース | 特徴 | 代表的な実装クラス |
List | 順序を保持し、要素の重複を許容する。インデックスで要素を管理。 | ArrayList: 内部的に配列でデータを保持。要素へのアクセスが高速 LinkedList: 要素が数珠つなぎ(双方向リスト)でデータを保持。要素の追加・削除が高速 |
Set | 順序を保持せず、要素の重複を許容しない。一意な要素の集合。 | HashSet:hashCode() メソッドで要素を管理。高速だが順序は保証されないTreeSet: 要素を自動でソートして格納 (自然順序付け or Comparator) |
Map | キー(Key)と値(Value)のペアでデータを管理。キーの重複は許容しない。 | HashMap:hashCode() でキーを管理。高速だが順序は保証されないTreeMap: キーを自動でソートして格納。 |
- 「重複データも順番通りに格納したい」なら
ArrayList
- 「ユニークな値だけを高速に扱いたい」なら
HashSet
- 「IDと名前のようにペアで管理したい」なら
HashMap
といったように、実現したいことに合わせて最適なコレクションを選択することが基本になります。
【深掘り】Map
インタフェースとProperties
クラス
Mapの要素を効率的に処理するentrySet()
Map
に格納されたすべてのキーと値のペアを処理したい場合、entrySet()
メソッドを使うのが最も効率的で標準的な方法です。map.entrySet()
を呼び出すと、Map
内の各エントリ(キーと値のペア)がMap.Entry<K, V>
オブジェクトとして格納されたSet
が返されます。このSet
を拡張for文でループさせることで、各エントリからキーと値を個別に取り出すことができます。
Map<Integer, String> map = new HashMap<>();
map.put(1, "Apple");
map.put(2, "Banana");
// entrySet()を使ってMapの全要素をループ処理
for (Map.Entry<Integer, String> entry : map.entrySet()) {
Integer key = entry.getKey();
String value = entry.getValue();
System.out.println("Key: " + key + ", Value: " + value);
}
Propertiesクラスとget / getPropertyメソッドの違い
Map
の中でも特殊な実装クラスとしてProperties
クラスがあります。これは主に設定ファイル(.propertiesファイル)の読み書きに使われ、キーと値を常にString型として扱うという特徴があります。Properties
はHashtable
のサブクラスでありMap
インタフェースを実装しているため、get
メソッドも使えますが、Properties
クラス固有のgetProperty
メソッドも持っています。この2つには重要な違いがあります。
メソッド | get(Object key) | getProperty(String key) |
所属 | Map インタフェース | Properties クラス |
特徴 | Map の汎用的な値取得メソッド | String のキーでString の値を取得するProperties 専用メソッド |
キーが存在しない場合 | nullを返す | nullを返す ただし、デフォルト値を指定できるオーバーロード版がある。 |
コードで見る違い
import java.util.Properties;
public class PropertiesExample {
public static void main(String[] args) {
Properties props = new Properties();
props.setProperty("db.user", "myuser");
// getPropertyの便利な使い方:デフォルト値の指定
String pass = props.getProperty("db.pass", "default_pass"); // "default_pass"
System.out.println("db.pass: " + pass);
}
}
getProperty(key, defaultValue)
は、キーが存在しなかった場合にnullを返す代わりにデフォルト値を返すことができます。これにより、NullPointerExceptionを避け、より安全で簡潔なコードを書くことが可能になります。
ListとQueue:インタフェース型で変わるremoveメソッドの挙動
LinkedList
クラスはList
インタフェースとQueue
インタフェースの両方を実装しています。そのため、どちらの型の変数でインスタンスを扱うかによって、同じremove
メソッドでも挙動が異なります。
// Listとして扱った場合
List<Integer> list = new LinkedList<>();
list.add(100); list.add(200);
list.remove(1); // List<E>の E remove(int index) が呼ばれる
System.out.println(list); //-> 出力結果:[100]
// Queueとして扱った場合
Queue<Integer> queue = new LinkedList<>();
queue.add(100); queue.add(200);
queue.remove(1); // Queue<E>の boolean remove(Object o) が呼ばれる
System.out.println(queue); //-> 出力結果:[100, 200]
List
として扱った場合、remove
メソッドはインデックスを引数に取るため、インデックス1の要素(200)が削除されます。
一方、Queue
として扱った場合、remove
メソッドはオブジェクトを引数に取ります。このコードではInteger
オブジェクトの1という値を持つ要素を探しますが、存在しないため何も削除されません。
同じインスタンスでも、どのインタフェース型として扱うかによって呼び出されるメソッドのシグネチャが異なる、というポリモーフィズムの重要なポイントです。
Comparable vs Comparator:役割とTreeSetにおける優先順位
オブジェクトの順序付けを行うComparable
とComparator
。
それぞれの役割とパッケージの違いは明確に覚えておきたいポイントです。TreeSet
は、コンストラクタでComparator
が指定されていればそれを優先し、指定がなければ要素のComparable
(自然順序付け)を利用します。
Comparable (java.langパッケージ): オブジェクトの「自然順序付け」
そのオブジェクト自身が持つ、本質的な比較ルールを定義します。(compareTo
メソッド)
Comparator (java.utilパッケージ): オブジェクトの「外部からの順序付け」
オブジェクトの自然順序付けとは異なる基準でソートしたい場合に、比較ルールを外部から定義します。(compare
メソッド)
【深掘り】ComparableとComparatorの技術的な違い
さらに、両者の技術的な違いをメソッドのシグネチャと戻り値の観点から詳しく見ていきましょう。
Comparable<T> インタフェース
- メソッド:
int compareTo(T other)
- 戻り値のルール
・負の値:this(自身)がother(引数)より小さい(前に来る)場合
・ゼロ:this(自身)とother(引数)が等しい場合
・正の値: this(自身)がother(引数)より大きい(後に来る)場合 - オーバーライド記法
Comparable
を実装する場合、クラス定義でインタフェースを実装し、クラス内でcompareTo
メソッドをオーバーライドします。
// 学生番号で並び替えるStudentクラス
public class Student implements Comparable<Student> {
private int studentId;
// ... コンストラクタなど ...
// compareToメソッドをオーバーライド
@Override
public int compareTo(Student other) {
// 自分自身(this)の学生番号と、比較対象(other)の学生番号を比較
return this.studentId - other.studentId; // 昇順ソート
}
}
Comparator<T> インタフェース
- メソッド:
int compare(T other1,
T other2
) - 戻り値のルール
・負の値:other1(第一引数)がother2(第二引数)より小さい(前に来る)場合
・ゼロ:other1(第一引数)がother2(第二引数)が等しい場合
・正の値: other1(第一引数)がother2(第二引数)より大きい(前に来る)場合 - オーバーライド記法(クラス作成)
Comparatorの
ロジックを再利用したい場合、専用のクラスを作成してcompare
メソッドをオーバーライドします。
// 学生の名前で並び替える、専用のComparatorクラス
public class StudentNameComparator implements Comparator<Student> {
// compareメソッドをオーバーライド
@Override
public int compare(Student s1, Student s2) {
return s1.getName().compareTo(s2.getName());
}
}
- オーバーライド記法(ラムダ式)
一度しか使わないような簡単な比較ロジックの場合、ラムダ式を使ってその場でComparator
を実装するのが現代的で簡潔な記法です。
List<Student> students = new ArrayList<>();
// ... studentsに要素を追加 ...
// ラムダ式を使い、その場でComparatorを実装して名前順でソートする
students.sort((s1, s2) -> s1.getName().compareTo(s2.getName()));
このラムダ式(s1, s2) -> s1.getName().compareTo(s2.getName())
は、Comparator
インタフェースのcompare
メソッドの実装そのものです。
ジェネリックスの型ルール整理
型安全性を保証するジェネリックスの、重要なシンタックスとルールを整理します。
インスタンス化:Raw型とダイヤモンド演算子
// Raw型:型引数を省略した書き方。非推奨。
new Foo(true);
// ダイヤモンド演算子:コンパイラによる型推論。推奨される書き方。
new Foo<>(10);
ワイルドカードと非変性(Invariance)
ジェネリックスでは、たとえ継承関係にあっても、異なる型引数を持つクラス同士に互換性はありません。これを「非変性(Invariance)」と呼びます。
// StringはObjectのサブクラスだが、これはコンパイルエラーになる
List<Object> obj = new ArrayList<String>();
これは、List<Object>
にInteger
などString
以外のオブジェクトが追加されるのを防ぎ、型安全性を保つための重要なルールです。
まとめ
今回は「コレクションとジェネリックス」について、学習した重要ポイントを備忘録として整理しました。
- List, Set, Mapの特性を理解し、用途に応じて使い分ける
- インタフェースの型がメソッドの挙動を決定する
- ComparableとComparatorの役割と優先順位
- ジェネリックスの型安全性に関する厳密なルール
これらの基本原則をしっかり理解しておくことが、応用的な問題を解く上での土台になると感じています。この記事が、同じように学習している皆さんの知識の整理に役立てば幸いです!
コメント