12

特定のクラス A と別のインターフェイス I を拡張する特定の型 T のオブジェクトを作成するファクトリを作成しようとしています。ただし、T を認識してはなりません。最低限の宣言は次のとおりです。

public class A { }
public interface I { }

これはファクトリメソッドです:

public class F {
    public static <T extends A & I> T newThing() { /*...*/ }
}

これはすべてうまくコンパイルされます。

メソッドを使用しようとすると、次のように動作します。

A $a = F.newThing();

...これはそうではありませんが:

I $i = F.newThing();

コンパイラは不平を言います:

範囲の不一致: 型 F のジェネリック メソッド newThing() は、引数 () には適用できません。推定されたタイプ I&A は、制限付きパラメーターの有効な代替ではありません

理由がわかりません。「newThing は、クラス A を拡張し、インターフェース I を実装する特定の型 T のものを返す」と明確に述べられています。A に代入するとすべてが機能します (T は A を拡張するため) が、I に代入すると機能しません (何が原因で、返されるものは明らかにAI の両方です)

また、オブジェクトを返すとき、たとえば B の型class B extends A implements Iを返す場合、 B は境界に一致しますが、戻り値の型 T にキャストする必要があります。

<T extends A & I> T newThing() {
    return (T) new B();
}

ただし、コンパイラは UncheckedCast などの警告をスローしません。

したがって、私の質問:

  • ここで何がうまくいかないのですか?
  • ファクトリメソッドでキャストによって戻り型の問題を解決する場合のように、目的の動作 (つまり、静的型 A または I の変数への割り当て) を達成する簡単な方法はありますか?
  • A への割り当てが機能するのに、I への割り当てが機能しないのはなぜですか?

--

編集:ここでは、Eclipse 3.7 を使用して完全に機能する完全なコード スニペット、JDK 6 用に設定されたプロジェクト:

public class F {
    public static class A { }
    public static interface I { }

    private static class B extends A implements I {  }

    public static <T extends A & I> T newThing() {
        return (T) new B();
}

    public static void main(String... _) {
        A $a = F.newThing();
        // I $i = F.newThing();
    }
}

編集:実行時に機能するメソッドと呼び出しの完全な例を次に示します。

public class F {
    public static class A {
        int methodA() {
            return 7;
        }
    }
    public static interface I {
        int methodI();
    }

    private static class B extends A implements I {
        public int methodI() {
            return 12;
        }
    }

    public static <T extends A & I> T newThing() {
        return (T) new B();
    }

    public static void main(String... _) {
        A $a = F.newThing();
        // I $i = F.newThing();
        System.out.println($a.methodA());
    }
}
4

4 に答える 4

10

2番目の質問について:

この場合を考えてみましょう:

 class B extends A implements I {}
 class C extends A implements I {}

現在、以下は型推論を使用しています。

<T extends A & I> T newThing() {
  return (T) new B();
}

だからあなたはこれを呼ぶことができます:

C c = F.newThing(); //T would be C here

あなたはそれが拡張するものTである可能性があり、あなたはただのインスタンスを返すことはできないことがわかります。上記の場合、キャストは。と書くことができます。これにより明らかに例外が発生するため、コンパイラは警告を発行します。-これらの警告を抑制していない限り。AIB(C)new B()Unchecked cast from B to T

于 2012-03-22T15:08:47.403 に答える
7

これはあなたが期待することをしません。 T extends A & Iは、呼び出し元Aがandを拡張する任意の型を指定できることを示し、Iそれを返します。

于 2012-03-22T15:01:23.477 に答える
4

それを説明する 1 つの方法は、型パラメーターを実際の型に置き換えることだと思います。

メソッドのパラメータ化されたシグネチャは次のとおりです。

public static <T extends A & B> T newThing(){
   return ...;
}

これ<T extends A & B>は、型パラメーターと呼ばれるものです。コンパイラは、実際に使用するときに、この値が実際の型 (型引数と呼ばれる) に実際に置き換えられることを期待します。

メソッドの場合、実際の型は型推論によって決定されます。つまり、<T extends A & B>A を拡張して B を実装する実際の既存の型に置き換える必要があります。

したがって、クラス C と D の両方が A を拡張し、B を実装するとします。署名が次のようになっているとします。

public static <T extends A & B> T newThing(T obj){
   return obj;
}

次に、型推論により、メソッドは次のように評価されます。

public static C newThing(C obj){
   return obj;
}

で呼び出す場合newThing(new C())

そして、次のようになります

public static D newThing(D obj){
   return obj;
}

で呼び出す場合newThing(new D())

これは問題なくコンパイルできます。

ただし、メソッド宣言で型推論を検証するために実際に型を提供していないため、コンパイラは型パラメーターの実際の型 (型引数) が何であるかを確認できません<T extends A & B>

実際の型は C だと思うかもしれませんが、その基準を満たすさまざまなクラスが何千もあるかもしれません。コンパイラが型引数の実際の型として使用する必要があるのはどれですか?

C と D が、A を拡張して B を実装する 2 つのクラスであるとしましょう。コンパイラは、これら 2 つの実際の型のどちらをメソッドの型引数として使用する必要がありますか?

Serializable と Closable と Comparable と Appendable を拡張する何かを言うように、使用できる既存の型さえない型引数を宣言することさえできました。

そして、おそらくそれを満たすクラスは全世界に存在しません。

そのため、ここでの型パラメーターは、使用する実際の型をコンパイラが検証するための単なる要件であり、実際の型のプレースホルダーであることを理解する必要があります。この実際の型は最後に存在する必要があり、コンパイラはそれを使用して T の外観を置き換えます。したがって、実際の型 (型引数) はコンテキストから推測可能でなければなりません。

コンパイラは、あなたが意味する実際の型がどれであるかを確実に判断できないため、基本的に、この場合は型推論によってそれを判断する方法がないため、型をキャストする必要があります。やっている。

そのため、次のような型推論を使用してメソッドを実装できます。

   public static <T extends A & B> T newThing(Class<T> t) throws Exception{
    return t.newInstance();
}

このようにして、使用する実際の型引数を実際にコンパイラに伝えます。

バイトコードが生成されるとき、コンパイラは T を実際の型に置き換える必要があることを考慮してください。このようにJavaでメソッドを書く方法はありません

public static A & B newThing(){ return ... }

右?

私は自分自身を説明したことを願っています!これを説明するのは簡単ではありません。

于 2012-03-22T15:43:09.270 に答える
0

最も簡単な解決策は、必要なクラスとインターフェイスを拡張して実装し、その型を返す抽象基本クラスを作成することです。戻り値の型をそのスーパークラスに既に制約しているため、戻り値の型を制約してこの基本クラスを拡張しても問題ありません。

例えば。

class C {}
interface I {}

abstract class BaseClass extends C implements I {}
// ^-- this line should never change. All it is telling us that we have created a
// class that combines the methods of C and I, and that concrete sub classes will
// implement the abstract methods of C and I    


class X extends BaseClass {}
class Y extends BaseClass {}

public class F {

    public static BaseClass newThing() {
        return new X();
    }


    public static void main(String[] args) {
        C c = F.newThing();
        I i = F.newThing();
    }
}
于 2012-03-22T15:30:41.943 に答える