🌾

[Java] ジェネリック(1) ジェネリック文法を使用する理由

2024/10/28に公開

ジェネリック文法

ジェネリックプログラミングは、データ型に依存せず、1つの値が複数の異なるデータ型を持つことができる技術に重点を置くことで再利用性を高めることができるプログラミング方式です。

ジェネリック登場以前のコーディング方式と問題点

なんでも保存できて使える「ドラえもんなんでもボックス」を作るクラスを作成する
クラス名 : BeforeBox

//BeforeBox Class : どんなデータでも入れたり出したりできる箱
public class BeforeBox {
   private Object obj;


   //どんなデータでも保存できるメソッド
   public void setData(Object obj){
       this.obj = obj;
   }


   //どんなデータでもreturnができるメソッド
   public Object getData(){
       return obj;
   }


}

Apple Class, Orange Class 作る

Apple Class
public class Apple {


   public String toString(){
       return "アップル";
   }


}
Orange Class
public class Orange {


   public String toString(){
       return "オレンジ";
   }
}

mainメソッドを持つBeforeBoxTestクラスを生成する

public class BeforeBoxTest {
   public static void main(String[] args) {


       //BeforeBox オブジェクト生成
       BeforeBox box1 = new BeforeBox();
       BeforeBox box2 = new BeforeBox();
       BeforeBox box3 = new BeforeBox();
       BeforeBox box4 = new BeforeBox();

       box1.setData("アップル");
       box2.setData(1210);
       box3.setData(new Apple());
       box4.setData(new Orange());

       //保存されたデータを取得する
       //型変換
       String data1 = (String) box1.getData();
       String data11 = box1.getData().toString();
       int data2 = (int)box2.getData();
       Apple data3 = (Apple) box3.getData();
       Orange data4 = (Orange) box4.getData();

       System.out.println(data1);
       System.out.println(data11);
       System.out.println(data2);
       System.out.println(data3);
       System.out.println(data4);


   }
}

結果:ジェネリックを使用しなかった場合の欠点

  • データを返す際に 毎回型変換作業が必要 です。
  • 開発者のミスによって発生する ランタイムエラーが発生する確率が高いです。
    --> エラーを捕まえるのが難しいです。

例えば、Appleとして受け取ろうとしたデータをOrangeとして受け取ってしまう可能性があります。特に、タイプを誤って入力してもコーディングエラーは発生しません(コンパイルエラーはありません)。出力時にはエラーが発生します(ランタイムエラー)。

(参考)
コンパイル例外:コードを実行する前に発生するエラー、赤色表示 o、発見が容易
ランタイム例外:コードを実行すると発生するエラー、赤色表示 x、発見が難しい

ジェネリック文法が適用されたドラえもんなんでもボックス

ドラえもんのなんでもボックスクラスを生成
**クラス名 : Box< T >
**

クラス名の後ろに< T >をつける理由: Tは データ型ではなくまだデータ型が決まってないジェネリック文法のことをコンパイルに教える。
<>の中にはTではなくなんでも定義できる。
しかし、ある程度決まって使っている。(下記を参考)
T : まだ決まってない Type [データ型]
E : まだ決まってない Element [配列の要素]

//Generic 文法が適用された何でも保存してリターンできるボックス
// Box<T> : ジェネリック文法
public class Box<T> {
       private T doraemon;


       //どんなデータでも保存できるメソッド
       public void setData(T doraemon){
           this.doraemon = doraemon;
       }


       //どんなデータでも返すことができるメソッド
       public T getData(){
           return doraemon;
       }




   }

Testクラス
ジェネリック文法が適用されたクラスのデータ型は、オブジェクト生成時に決定される。

public class BoxTest {
   public static void main(String[] args) {


       //Box オブジェクト生成と同時にデータ型を決める
       Box<String> box1 = new Box<>();
       Box<Integer> box2 = new Box<>();
       Box<Apple> box3 = new Box<>();
       Box<Orange> box4 = new Box<>();


       box1.setData("제네릭~~~"); 
       box2.setData(111);
       box3.setData(new Apple());
       box4.setData(new Orange());


       String data1 = box1.getData();
       Integer data2 = box2.getData();
       Apple data3 = box3.getData();
       Orange data4 = box4.getData();


       System.out.println("== 제네릭문법 적용 결과 ==");
       System.out.println(data1);
       System.out.println(data2);
       System.out.println(data3);
       System.out.println(data4);




        //リストを生成する際の文法は同じです😲
        //リスト ==> ジェネリックで作成されています
        //ちなみに List は List<E> で作成されています。
       List<String> list = new ArrayList<>();
   }
}


結果:ジェネリック文法を使用する理由(利点)

*オブジェクト生成時にデータ型が決定されたため、リターンタイプが定まっています!
*型変換の必要性なし
*コンパイルエラーを捕捉することができます。

これを見るとわかるように、オブジェクト生成時にデータ型が決定されたため、オブジェクト使用時にデータ型がこのように表示されます 👍👍👍

参考 : Map<>

Mapもジェネリック文法で作られています
Map<String, Integer> map = new HashMap<>();

Mapのように二つのデータ型が入る時の様子

//それぞれのデータ型を別々に指定したいとき
public class DoubleBox<L,R> {
   private L left;
   private R right;
  
}

よく使われるジェネリック型宣言文字

T : Type
E : Element
K : Key
V : Value
N : Number

Discussion