2014-01-07 2 views
7

Я параметризированный интерфейс:Как дженерики управляются перечислениями?

public interface MyInterface<T> { 
    void run(T e); 
} 

И классы, реализующие интерфейс:

public class MyClass1 implements MyInterface<SomeOtherClass1> { 
    public void run(SomeOtherClass1 e) { 
     // do some stuff with e 
    } 
} 

public class MyClass2 implements MyInterface<SomeOtherClass2> { 
    public void run(SomeOtherClass2 e) { 
     // do some stuff with e 
    } 
} 

число различных MyClass * X * является известным и исчерпывающим, и есть только один экземпляр каждого MyClass * X *, так что я хотел бы использовать перечисление:

public enum MyEnum { 
    MY_CLASS_1, 
    MY_CLASS_2; 
} 

чтобы быть в состоянии к и se MyEnum.MY_CLASS_1.run(someOtherClass1); (например, у меня был бы каждый экземпляр MyInterface в одном месте). Возможно ли это (и если да, то как)? Потому что я совсем застрял теперь ...


То, что я пытался до сих пор:

public enum MyEnum { 

    MY_CLASS_1(new MyClass1()), 
    MY_CLASS_2(new MyClass2()); 

    private MyInterface<?> instance; 

    private MyEnum(MyInterface<?> instance) { 
     this.instance = instance; 
    } 

    public void run(/* WhichType? */ e) { 
     instance.run(e); 
    } 

} 

В описанном выше способе, при использовании типа объекта для e параметра:

public void run(Object e) { 
    instance.run(e); 
    //  ^^^ 
    // The method run(capture#3-of ?) in the type MyInterface<capture#3-of ?> is not applicable for the arguments (Object) 
} 

Проблема, я думаю, с этим полем private MyInterface<?> instance: Мне нужно знать, как параметрируется экземпляр, используя что-то вроде private MyInterface<T> instance, но я не могу найти рабочего решения .. .

Короче говоря, я застрял;)


PS: так как run методы тела могут быть довольно долго, я стараюсь избегать анонимных классов в перечислении:

public enum MyEnum { 

    MY_CLASS_1 { 
     /* any method, etc. */ 
    }, 

    MY_CLASS_2 { 
     /* any method, etc. */ 
    }, 

} 

MyEnum тогда станет совершенно нечитаемым.

ответ

5

Невозможно. Это одна из ограничений перечисления, которую я нахожу наиболее раздражающей, но все, что вы можете сделать, это попытаться обойти ее (как это было бы в Java до 5.0).

Только само перечисление может реализовать интерфейс, и общие данные должны быть указаны на уровне перечисления, поэтому только Object или какой-то общий интерфейс для этих двух будет применяться в вашем случае.

Объявление любого аспекта, который вы хотите обработать полиморфно (метод run(), в вашем примере) внутри самого enum (и переопределения поведения в каждой константе) обычно является лучшим решением. Конечно, вам нужно ослабить требования к безопасности вашего типа.

Если вы хотите, чтобы эти стратегии были разделены, вам все равно нужен метод run(Object) внутри перечисления, который будет определен в каждой константе с некоторым явным приведением, поскольку вы просто не можете иметь разные сигнатуры методов для экземпляра enum (или даже если вы можете, они не будут видны как таковые со стороны).


намек о том, как обмануть компилятор, если вы действительно хотите сделать это, а не редизайн или явных слепков для каждого экземпляра:

enum MyEnum implements MyInterface<Object> { 
    MY_CLASS_1(new MyClass1()), 
    MY_CLASS_2(new MyClass2()); 

    // you may also drop generics entirely: MyInterface delegate 
    // and you won't need that cast in the constructor any more 
    private final MyInterface<Object> delegate; 

    MyEnum(MyInterface<?> delegate) { 
     this.delegate = (MyInterface<Object>) delegate; 
    } 

    @Override 
    public void run(Object e) { 
     delegate.run(e); 
    } 
} 

выше будет работать, и вы получите a ClassCastException (как и ожидалось), если вы попытаетесь использовать MyEnum.MY_CLASS_1.run() с чем-то отличным от SomeOtherClass1.

+0

Хорошее объяснение, но мне не нравится решение для его отсутствия безопасности типа. –

+0

@PaulBellora: Мне тоже это не нравится :). И это даже не решение, а просто обходное решение. Он подходит только в том случае, если вы не можете жить без переименования (перебирать значения, получать значения по имени, ординалам, шкафам переключения и т. Д.) –

+0

@ sp00m: Я отредактировал образец кода обратно, показывая параметризованную версию как первый вариант , чтобы решить вашу конкретную проблему: «Мне нужно знать, как настроен экземпляр *» –

2

Как Costi points out, перечислены сами can't be generic. Однако я думаю, что я могу определить, где вы пошло не так в вашем проекте:

Существует только один экземпляр каждого MyClass X, поэтому я хотел бы использовать перечисление:

public enum MyEnum { 
    MY_CLASS_1, 
    MY_CLASS_2; 
} 

Вы говорите, что каждый из этих классов является одноэлементным. Таким образом, они должны на самом деле каждый будет перечисление:

public enum MyClass1 implements MyInterface<SomeOtherClass1> { 

    INSTANCE; 

    @Override 
    public void run(SomeOtherClass1 e) { 
     // do some stuff with e 
    } 
} 

public enum MyClass2 implements MyInterface<SomeOtherClass2> { 

    INSTANCE; 

    @Override 
    public void run(SomeOtherClass2 e) { 
     // do some stuff with e 
    } 
} 

Это имеет смысл, потому что если вы думаете об этом, вам не нужно перечислять эти две реализации, так что нет никакой необходимости для них жить вместе. Достаточно использовать Josh Bloch's enum pattern для каждого из них по отдельности.

+1

+1 Это имеет большой смысл (и именно так, например, «Функции» реализованы в Гуаве - по той же причине). Тем не менее, он становится немного грязным, если вам действительно нужно сослаться на эти экземпляры коллективно (как я упоминал в другом комментарии). –

+0

На самом деле, я мог бы использовать эти синглтоны для замены MyClass1, MyClass2 и т. Д., Но цель MyEnum заключалась в том, чтобы иметь все доступные экземпляры MyInterface в одном и том же месте, так как в моем случае есть известный исчерпывающий список. Я уточню свой вопрос, чтобы быть более ясным в этом вопросе, спасибо в любом случае;) – sp00m

Смежные вопросы