web-dev-qa-db-ja.com

ジェネリックJavaメソッドのジェネリック型を使用して引数の型を強制できますか?

次のように、ジェネリック型を使用して、メソッドの引数が同じ型であることを確認したいと思います。

public static <T> void x(T a, T b)

このメソッドに渡される2つの引数(aとb)は、常に同じタイプである必要があると思います。しかし、驚いたことに、どの引数が渡されても、TがObjectに消去されたかのように、任意の型(プリミティブも含む)の引数をメソッドxに渡すことができました。

これまでに見つけた唯一の回避策は、次のような「extends」を使用することでした。

public static <T, U extends T> void x(T a, U b)

しかし、私はそれと一緒に暮らすことができますが、それは私が望んでいたものではありません。

ジェネリック型を使用してメソッドのすべての引数の型を強制する方法はありますか?

54
sys64738

私があなたの質問を正しく理解しているなら、あなたはこれが欲しいです:

x(10, "x");

コンパイル時に失敗します。今これを行うことを検討してください:

Integer i = 10;
String s = "x";
Object o1 = i;
Object o2 = s;
x(o1, o2);

この場合、それらは両方ともオブジェクトであり、同じタイプです。私は本当にあなたが望むものを強制する方法はないと思います-引数をObjectにキャストするとき、警告/エラーなしに2つの異なる型でそれを呼び出すことが常に可能です。

次のように使用して、使用するタイプを指定できます。

ClassName.<Type>x(obj1, obj2);

そして、それはおそらくそれを行う唯一の方法です。

22
barteks2x

私が正しく理解している場合、それを行う1つの方法は、 T のタイプを明示的に指定して、コンパイラにそのタイプを推測させるのではなく異なるタイプの2つのオブジェクトが引数として渡される場合の最も直接的なスーパークラスこのようなものを例にとります:

public class Test {
    public static void main(String[] args) {
        Test.x(5.0, 5);          // This works since type is inferred to be Number
        Test.<Integer>x(5, 5);   // This works since type is stated to be Integer
        Test.<Integer>x(5.0, 5); // This doesn't; type is stated to be Integer and Double is passed in
    }

    public static <T> void x(T a, T b) {
    }
}
30
TNT

そもそもなぜこれが問題となるのかは、私にとっては漠然としています。代わりに、型システムが役立つ方法について何か誤解しているのではないかと思います。

<T> void x(T a, T b)で何ができるでしょうか?まあ、全部ではありません。 xの本体の内部では、TObjectと同じであるため、toStringaを呼び出すようなことしかできません。それらを印刷するにはb

実際には実用的ではありません理由abは同じタイプでなければなりません。共通するタイプがあり、そのタイプはObjectまたはそのサブタイプであるというだけです。実際、<T> void x(T a, T b)が実際に汎用である必要がある明確な理由はありません。

  • メソッド本体は、abの実際のタイプが何であるかを気にしません。これは、とにかくそれらを使用できなかったためです。
  • abメソッドなので、呼び出しサイトはxvoidの実際のタイプが何であるかを気にしません。したがって、ブラックホールです。

<T> List<T> Arrays.asList(T...)のような結果が得られるメソッドの方が一般的です。

// This will cause a compile error because
// the type inferred must be compatible
// with the return assignment.
List<Integer> r = Arrays.asList(1, 1.0);

または限界:

// We don't care what the actual types of
// a and b are, just that we can call bar()
// on them.
// Note: this method does not need to be generic.
<T extends Foo> void x(T a, T b) {
    a.bar();
    a.bar();
}

または、ある種の関係を主張する境界:

// We don't care what the actual types of
// a and b are, just that we can compare
// them to each other.
<T extends Comparable<T>> T max(T a, T b) {
    return (a.compareTo(b) < 0) ? b : a;
}
14
Radiodef

メソッドを呼び出すときに、typeパラメーターを明示的に指定できます。例えば:

 <String>x("hello", "world");

ただし、しない type-parameterを明示的に指定し、Javaの型推論機能のみに依存している場合は、ジェネリックスだけでなく、一般的にはできないと思います。

メソッドパラメータのタイプは具体的なタイプではありませんが、の適用可能なタイプのセット(これもを示すものです) setは、たとえば、finalクラスの場合、1つのタイプのみで構成できます。

たとえば、このメソッドは次のとおりです。

public void x(Something a) { }

メソッドを示します。このパラメータは、タイプのセットからのタイプである必要があります。これは、Something(つまり、Somethingおよびそのすべてのサブタイプ)。

同じことがジェネリックにも当てはまります。

12

おそらく、一般的な方法で一般的なメソッドを呼び出していないため、x(Object a, Object b)の呼び出しのように扱われます。この例では:

public class Test {

  static <T> void x(T a, T b) {
  }

  public static void main(String[] args) {
    x(1, 2); // compiles
    Test.<String>x(1, 2); // does not compile
    Test.<String>x("a", "b"); // compiles
  }
}

Xへの最初の呼び出しは一般的に行われないため、コンパイルされます。 2番目の呼び出しはTStringに等しくするため、1および2Stringsではありません。 3番目の呼び出しは、Stringsを適切に渡すため、コンパイルされます。

10
GriffeyDog

これは私のために働いた

public static <T> void x(T a, T b, Class<T> cls) {
}

今これはコンパイルされます

public static void main(String[] args) throws Exception {
    x(1, 2, Integer.class);
}

そしてこれはしません

public static void main(String[] args) throws Exception {
    x(1, "", Integer.class);
}
7