この記事では、メソッドが複数のオブジェクトタイプを受け入れるようにするためにジェネリクスとObjectを使用する際の違いについて説明します。
まず、具体的な例を挙げてみましょう。例えば、Javaの数値型(Double、Float、Byte、Short、Integer、Long)に対するソートアルゴリズムを考えます。
方法1: 各数値型ごとにメソッドを定義する
この方法では6つの異なるメソッドが必要となります。
class Sorter {
void sortIntegers(Integer[] array) {
// ソートロジック
}
void sortDoubles(Double[] array) {
// ソートロジック
}
// その他の数値型に対するメソッドも同様に定義
}
方法2: Objectを使用して汎用的なメソッドを定義する
この方法では、1つのメソッドで全ての数値型を受け取ることができます。
class Sorter {
void sortObjects(Object[] array) {
Number[] numbers = (Number[]) array;
// ソートロジック
}
}
方法3: ジェネリクスを使用する
ジェネリクスを利用すると、コードはさらに柔軟になります。
class Sorter {
<T> void sortGenerics(T[] array) {
Number[] numbers = (Number[]) array;
// ソートロジック
}
}
テストコード
次に、これらのメソッドがどのように動作するか確認します。
public static void main(String[] args) {
Sorter sorter = new Sorter();
sorter.sortIntegers(new Integer[]{2, 3});
sorter.sortObjects(new Integer[]{2, 3});
sorter.sortGenerics(new Integer[]{2, 3});
}
問題なく動作しますが、String配列を渡すとランタイムエラーが発生します。
public static void main(String[] args) {
Sorter sorter = new Sorter();
sorter.sortObjects(new String[]{"a", "b"}); // 動作するが、キャスト時にClassCastExceptionが発生
sorter.sortGenerics(new String[]{"a", "b"}); // 動作するが、キャスト時にClassCastExceptionが発生
}
ジェネリクスによる改善
ジェネリクスを利用して、コンパイル時に型チェックを行うことができます。
class Sorter {
<T extends Number> void sortNumbers(T[] array) {
Number[] numbers = (Number[]) array;
// ソートロジック
}
}
このようにすることで、不適切な型の配列を渡すとコンパイルエラーが発生し、問題を早期に見つけることができます。
まとめると:
- Objectを使用すると、実行時に型チェックが必要となるため、ランタイムエラーのリスクがあります。
- ジェネリクスを使用すると、コンパイル時に型チェックを行い、安全性が向上します。