web-dev-qa-db-ja.com

Java Generics with Enumsを使用する

pdate:助けてくれたすべての人に感謝-これに対する答えは、より複雑なコードで気づかなかったことと、Java5の共変戻り値型について知らなかったことにあります。

元の投稿:

私は今朝何かをいじっています。私はこの問題全体に別の方法で取り組むことができたことができることを知っていますが、なぜ私が以前の方法で機能していないのかを理解することに夢中になっています期待しています。これについて少し読んだ後、理解に近づいていないことに気づいたので、私が愚かであるのか、ここで実際に理解できないことがあるのか​​を確認するための質問として提示します。 。

次のようなカスタムイベント階層を作成しました。

public abstract class AbstractEvent<S, T extends Enum<T>>
{
    private S src;
    private T id;

    public AbstractEvent(S src, T id)
    {
        this.src = src;
        this.id = id;
    }

    public S getSource()
    {
        return src;
    }

    public T getId()
    {
        return id;
    }
}

そのような具体的な実装では:

public class MyEvent
extends AbstractEvent<String, MyEvent.Type>
{
    public enum Type { SELECTED, SELECTION_CLEARED };

    public MyEvent(String src, Type t)
    {
        super(src, t);
    }
}

次に、次のようなイベントを作成します。

fireEvent(new MyEvent("MyClass.myMethod", MyEvent.Type.SELECTED));

ここで、fireEventは次のように定義されています。

protected void fireEvent(MyEvent event)
{
    for(EventListener l : getListeners())
    {
        switch(event.getId())
        {
            case SELECTED:
                l.selected(event);
                break;
            case SELECTION_CLEARED:
                l.unselect(event);
                break;
         }
    }
}

だから私はこれはかなり単純だと思いましたが、event.getId()の呼び出しの結果、コンパイラーがEnumsをオンにできないこと、変換可能なint値またはenum定数のみを通知することがわかりました。

MyEventに次のメソッドを追加することができます。

public Type getId()
{
    return super.getId();
}

これを実行すると、すべてが期待どおりに機能します。私はこれの回避策を見つけることに興味があるだけではありません(私は明らかに回避策を持っているためです)、私は、これがうまくいかない理由について人々が持っているかもしれない洞察に興味を持っています。

18
Chris Boran

Yishaiは正解です。魔法のフレーズは「 共変の戻り値のタイプ 」です。これは、Java 5.0以降)-Enumをオンにすることはできませんが、 Enumを拡張するTypeクラスをオンにします。MyEventによって継承されるAbstractEventのメソッドは、型消去の対象になります。これをオーバーライドすることで、getId()の結果をTypeクラスにリダイレクトします。 Javaは実行時に処理できます。

11
Jason S

これはジェネリックとは関係ありません。 Java内の列挙型の切り替えステートメントは、特定の列挙型の値のみを使用できるため、実際に列挙型名を指定するのはprohibitedです。 。これはうまくいくはずです:

switch(event.getId()) {
   case SELECTED:
         l.selected(event);
         break;
   case SELECTION_CLEARED:
         l.unselect(event);
         break;
}

更新:わかりました、コピー/貼り付け、コンパイルして実行した実際のコード(依存関係なしでコンパイルするには、コードを少し変更する必要がありました)です。エラーはありません。

AbstractEvent.Java

public abstract class AbstractEvent<S, T extends Enum<T>> {
    private S src;
    private T id;

    public AbstractEvent(S src, T id) {
        this.src = src;
        this.id = id;
    }

    public S getSource() {
        return src;
    }

    public T getId() {
        return id;
    }
}

MyEvent.Java

public class MyEvent extends AbstractEvent<String, MyEvent.Type> {
    public enum Type { SELECTED, SELECTION_CLEARED };

    public MyEvent(String src, Type t) {
        super(src, t);
    }
}

Test.Java

public class Test {
  public static void main(String[] args) {
      fireEvent(new MyEvent("MyClass.myMethod", MyEvent.Type.SELECTED));
  }

  private static void fireEvent(MyEvent event) {
        switch(event.getId()) {
            case SELECTED:
                System.out.println("SELECTED");
                break;
            case SELECTION_CLEARED:
                System.out.println("UNSELECTED");
                break;
         }
    }
}

これはJava 1.5でコンパイルして実行します。ここで何が欠けていますか?

8
ChssPly76

私にとってはうまくいきます

完全なカットアンドペーストテストプログラム:

enum MyEnum {
    A, B
}
class Abstract<E extends Enum<E>> {
    private final E e;
    public Abstract(E e) {
        this.e = e;
    }
    public E get() {
        return e;
    }
}
class Derived extends Abstract<MyEnum> {
    public Derived() {
        super(MyEnum.A);
    }
    public static int sw(Derived derived) {
        switch (derived.get()) {
            case A: return 1;
            default: return 342;
        }
    }
}

特殊なコンパイラを使用していますか?

非常に簡潔に言うと、TはEnum定数ではなくEnumクラスに消去されるため、コンパイルされたステートメントは、次のシグネチャのように、getIDがEnumの結果に切り替えているように見えます。

Enum getId();

特定のタイプでオーバーライドすると、戻り値が変更され、スイッチをオンにすることができます。

編集:抵抗が気になったので、いくつかのコードを書き直しました:

public enum Num {
    ONE,
    TWO
}
public abstract class Abstract<T extends Enum<T>> {
    public abstract T getId();
}

public abstract class Real extends Abstract<Num> {

}

public static void main(String[] args) throws Exception {
    Method m = Real.class.getMethod("getId");
    System.out.println(m.getReturnType().getName());
}

結果は、NumではなくJava.lang.Enumになります。 Tはコンパイル時にEnumに消去されるため、オンにすることはできません。

編集:

私は誰もが作業しているコードサンプルをテストしましたが、それらも私にとっては機能するので、これはより複雑な実際のコードの問題の根底にあると思いますが、投稿されたサンプルは実際にコンパイルされ、正常に実行されます。

2
Yishai

私はこれを試してみました(あなたのコードをコピーして貼り付けました)そしてコンパイラエラーを複製することはできません。

public abstract class AbstractEvent<S, T extends Enum<T>>
{
    private S src;
    private T id;

    public AbstractEvent(S src, T id)
    {
        this.src = src;
        this.id = id;
    }

    public S getSource()
    {
        return src;
    }

    public T getId()
    {
        return id;
    }
}

そして

public class MyEvent extends AbstractEvent<String, MyEvent.Type>
{

    public enum Type
    {
        SELECTED, SELECTION_CLEARED
    };

    public MyEvent( String src, Type t )
    {
        super( src, t );
    }


}

そして

public class TestMain
{
    protected void fireEvent( MyEvent event )
    {
        switch ( event.getId() )
        {
            case SELECTED:
            break;
            case SELECTION_CLEARED:
            break;
        }
    }
}
1
Trevor Harrison