Help us understand the problem. What is going on with this article?

ジェネリクスについてパッとわかったときの考え方

More than 1 year has passed since last update.

概要

ジェネリクスは引数だよってことです。
ジェネリクスについていまいちしっくり来てなかった時に、「あーなるほどー」って思えた考えをまとめときます。

参考にしたサイト

先人たちの知恵に感謝です。
http://d.hatena.ne.jp/Nagise/20101105/1288938415

Javaの環境

java version 1.8.0_181
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

src
└example
  └java
    └generics
      ├ Main.java
      └ GenericsSample.java

値の引数といえばメソッドですよね

丸括弧 ()←これのことです

メソッドといえばこの丸括弧 () です。
この丸括弧()は、メソッド宣言の場所と、メソッド呼び出しの場所で同じ記号なんだけど違う意味で使ってます。
「メソッドで使う値」を示すための情報ですね。

Main.java
package example.java.generics;

public class Main {
    public static void main(String[] args) {
        int result = plus(2, 5); // ここで  「()」は「この引数の値でメソッドを実行しますよ」という宣言(実引数)
        System.out.println(result);
    }

    public static int plus(int T, int S) { // ここで 「()」は「この引数名で引数を受け取りますよ」という宣言(仮引数)
        return T + S;
    } // T, S の 範囲ここまで
}

もちろんちゃんと実行できます
function_2.JPG

型の引数といえばジェネリクスですよね

山括弧 <>←これのことです

じゃあ山括弧<>といえば・・・?そう、ジェネリクスです。
この山括弧<>は、ジェネリクス宣言の場所と、ジェネリクス呼び出し(あえてこういう言い方してます)の場所で同じ記号なんだけど違う意味で使ってます。
「クラスやメソッドで使う型」を示すための情報ですね。

GenericsSsample.java
package example.java.generics;

public class GenericsSample {
    // ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数)
    public <T> void callGenerics(T value){
        System.out.println(value); // 引数の値は何かな?
        System.out.println(value.getClass().getName()); // 引数の型は何かな?
    } // T の 範囲ここまで
}
Main.java
package example.java.generics;

public class Main {
    public static void main(String[] args) {
        GenericsSample genericsSample = new GenericsSample();

        // ここで 「<>」 は「この型の値でメソッドを実行しますよ」という宣言(実引数)
        // ここで  「()」は「この引数の値でメソッドを実行しますよ」という宣言(実引数)
        // 違う種類の実引数をそれぞれの記号で指定しているだけ
        genericsSample.<Integer>callGenerics(1234);
    }
}

ちゃんと引数で設定した値と型になっています
generics_3.JPG
generics_8.JPG

型の値と、引数で渡した値の型(わかりづらいですね、、)が違う場合

Main.java
package example.java.generics;

public class Main {
    public static void main(String[] args) {
        GenericsSample genericsSample = new GenericsSample();
        genericsSample.<String>callGenerics(1234);
    }
}

不穏な空気
generics_6.JPG

無事失敗します
generics_7.JPG

型 → 値の順番で書くよね

そもそもJavaでは

//型 変数の順番
String variable;

のように「型 → 順番」で書くので、型を表す引数のジェネリクス・値を表す引数の順番も「ジェネリクス → 値」になるのも納得感がある。

// 型 → 値
// voidの前に型を表すジェネリクスが来ているのは、戻り値の型がジェネリクスで指定した引数になる可能性(この場合は「T」)があるから(たぶん)
public <T> void callGenerics(T value)

// 型 → 値
genericsSample.<String>callGenerics(1234);

クラスに指定するジェネリクス

メソッドと同じく、クラスに「この引数名で型を受け取りますよ」という宣言(仮引数) がついているだけです。

GenericsSample.java
package example.java.generics;

// ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数)
public class GenericsSample<T> {

    // クラスで指定した型の引数T のスコープがクラス内のメソッドまで続いている
    public void callGenerics(T value){
        System.out.println(value); // 引数の値は何かな?
        System.out.println(value.getClass().getName()); // 引数の型は何かな?
    }
} // <T> の 範囲ここまで
Main.java
package example.java.generics;

public class Main {
    public static void main(String[] args) {
        // ここで 「<>」 は「この型の値でクラスをインスタンス化しますよ」という宣言(実引数)
        // new GenericsSample<Integer> は new GenericsSample<> に省略しても大丈夫(GenericsSample<Integer> なんだからIntegerなんでしょ?と推論してくれる)
        GenericsSample<Integer> genericsSample = new GenericsSample<Integer>();

        // ここで  「()」は「この引数の値でメソッドを実行しますよ」という宣言(実引数)
        genericsSample.callGenerics(1234);
    }
}

generics_15.JPG
generics_9.JPG

もちろん違う型をメソッドに指定すると。。。

Main.java
package example.java.generics;

public class Main {
    public static void main(String[] args) {
        // ここで 「<>」 は「この型の値でクラスをインスタンス化しますよ」という宣言(実引数)
        // new GenericsSample<Integer> は new GenericsSample<> に省略しても大丈夫(GenericsSample<Integer> なんだからIntegerなんでしょ?と推論してくれる)
        GenericsSample<Integer> genericsSample = new GenericsSample<Integer>();

        // ここで  「()」は「この引数の値でメソッドを実行しますよ」という宣言(実引数)
        genericsSample.callGenerics("test");
    }
}

不穏な空気
generics_10.JPG

無事失敗します
generics_11.JPG

<>がクラスにもメソッドにも同じ引数名でついてると。。

GenericsSample.java
package example.java.generics;

// ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数)
public class GenericsSample<T> {

    // ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数)
    // クラスで指定した型の引数T のスコープがクラス内のメソッドまで続いている ... ??
    public <T> void callGenerics(T value){
        System.out.println(value); // 引数の値は何かな?
        System.out.println(value.getClass().getName()); // 引数の型は何かな?
    }
} // <T> の 範囲ここまで ... ??
Main.java
package example.java.generics;

public class Main {
    public static void main(String[] args) {
        // ここで 「<>」 は「この型の値でクラスをインスタンス化しますよ」という宣言(実引数)
        // new GenericsSample<Integer> は new GenericsSample<> に省略しても大丈夫(GenericsSample<Integer> なんだからIntegerなんでしょ?と推論してくれる)
        GenericsSample<Integer> genericsSample = new GenericsSample<Integer>();

        // ここで  「()」は「この引数の値でメソッドを実行しますよ」という宣言(実引数)
        genericsSample.callGenerics("test");
    }
}

全然不穏じゃない
generics_12.JPG
generics_13.JPG

失敗しま...せん
generics_14.JPG

この原因は、ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数)

  • クラス宣言時に
  • メソッド宣言時に

と、同じ引数名で指定してしまったため。メソッド内では新しいローカルの型の変数で上書きしてしまった。
↓のような感じ。

GenericsSample.java
package example.java.generics;

// ここで 「<>」 は「この引数名で型を受け取りますよ」という宣言(仮引数)
public class GenericsSample<T> {

    // クラス内では、クラスのインスタンス時に指定したジェネリクスの実引数(new GenericsSample<Integer> のInteger)が有効

    // クラスで指定した型の引数T のスコープがメソッド時に指定したジェネリクスの実引数(今回はString)で上書きされてしまった
    // genericsSample.callGenerics("test"); の"test" から 「"test"はStringだから<T> のTはStringだ!」と推論された)
    public <T> void callGenerics(T value){
        System.out.println(value); // 引数の値は何かな?
        System.out.println(value.getClass().getName()); // 引数の型は何かな?
    }
}

staticなクラス宣言時には指定できないの?

できません。
staticじゃないクラスの時は

// ここで 「<>」 は「この型の値でクラスをインスタンス化しますよ」という宣言(実引数)
// new GenericsSample<Integer> は new GenericsSample<> に省略しても大丈夫(GenericsSample<Integer> なんだからIntegerなんでしょ?と推論してくれる)

とできましたが、staticクラスの時はクラスのインスタンス化とかできないですよね?
実引数を指定して作り出すタイミングがないので無理です。。
(でもstaticなメソッドの単位なら呼び出し時に実引数指定できるからジェネリクスの型引数を指定できるよ!)

結論

ただの引数だからあわてずに。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away