Многоразовый интерфейс, использующий дженерики


Я хочу создать один класс, который ссылается на тип сервиса, использующего интерфейс. Сервис может иметь различные реализации. Различные реализации будут обрабатывать различные типы запросов. В прошлом я бы определил интерфейс примерно так:

public interface I_Test
{
    public String get(String key, Enum type);
}

И реализуем его следующим образом:

public class Test_1 implements I_Test
{
    public String get(String key, Enum type)
    {
        Enum_1 t1 = (Enum_1)type;

        switch(t1)
        {
            case NAME:
                return "Garry";
            case DOB:
                return "1966";
            default:
                throw new IllegalArgumentException("Unkown type [" + type + "]");
        }
    }
}

Хорошо то, что я могу использовать другую реализацию моего интерфейса для удовлетворения различных потребностей. Плохие-это у меня типа бросил, а так есть риск по во время выполнения.

Я надеялся, что дженерики смогут решить эту проблему, поэтому я сделал следующее:

public interface I_Test<T extends Enum>
{
    public String get(String key, T type);
}

И это:

public class Test_1 implements I_Test<Enum_1>
{
    public String get(String key, Enum_1 type)
    {
        switch(type)
        {
            case NAME:
                return "Garry";
            case DOB:
                return "1966";
            default:
                throw new IllegalArgumentException("Unkown type [" + type + "]");
        }
    }
}

Но когда я иду использовать вещь, я получаю предупреждения о безопасности типа, если я не объявляю свою переменную с типом, который я намереваюсь использовать, например:

I_Test<Enum_1> t1 = new Test_1();

Это действительно беспокоит меня, потому что весь смысл создания интерфейса I_Test состоял в том, чтобы я мог использовать различные реализации, но, похоже, мне нужно зафиксироваться на определенном типе во время компиляции, чтобы избежать этого внимание!

Есть ли способ написать многоразовый интерфейс, который использует дженерики без этого раздражающего предупреждения?

3 2

3 ответа:

Смысл дженериков в том, чтобы гарантировать, что ваш код более надежен (в том, что касается безопасности типов). С помощью generics вы можете узнать о несовместимости типов во время компиляции, а не во время выполнения. Когда вы определяете свой интерфейс как I_Test<T extends Enum>, вы в основном говорите, что вам нужно, чтобы интерфейс был обобщен в соответствии с определенным типом. Вот почему Java дает вам предупреждение.

Вы получите такое же предупреждение, если сделаете что-то подобное Map myMap = new HashMap<string>();.

В Java вы фактически указываете типы, и они не выводятся из того, что находится на RHS (если только вы не делаете что-то вроде Integer i = 1, но это автобоксинг). Поскольку вы обобщили свой интерфейс, когда вы объявляете что-то с помощью этого интерфейса, вам нужно указать тип для использования (для обобщения).

При создании экземпляра универсального типа компилятор преобразует эти типы с помощью так называемого"стирания типов". Здесь компилятор удаляет всю информацию, связанную с параметры типа и аргументы типа. Java делает это для поддержания совместимости с более старым кодом, который был написан до того, как в Java появились универсальные программы.

Таким образом, I_Test<Enum_1> фактически переводится в необработанный Тип I_Test во время компиляции. Использование необработанного типа обычно считается плохой практикой (отсюда и "раздражающее предупреждение"). Компилятор сообщает вам, что у него недостаточно информации для выполнения проверки типов, и поэтому он не может обеспечить безопасность типов (потому что вы использовали raw тип).

Чтобы узнать больше о дженериках, взгляните на следующее:

Дженериков составляет о компиляции предупреждений. Если они вам не нужны, не используйте их.

Сказав это, вы можете создавать различные, неродовые подинтерфейсы, например:

public interface Enum_1_Test extends I_Test<Enum_1> {
  ...
}

, а затем объявите свой класс как

public class Test_1 implements Enum_1_Test
Но я не уверен, что это очень полезно. Как правило, вы хотите использовать универсальные методы, Если у вас есть одна реализация, которая работает для многих типов входных данных, и использовать старый добрый полиморфизм, если вы хотите отдельную реализацию для каждого входного сигнала тип.

Raw I_Test поддерживает любой тип перечисления в качестве аргумента, в то время как реализацияTest_1 поддерживает только ограниченное подмножество (Enum_1), это связано с тем, что Test_1 указан как реализация I_Test только для одного типа перечисления.

Вот пример, почему компилятор выдает предупреждение, следующий код компилируется, так как необработанный тип I_Test принимает любое перечисление, однако, поскольку Test_1 поддерживает только Enum_1, он вызовет исключение приведения класса.

enum MyEnum{A}
I_Test t1 = new Test_1();//warning here
t1.get("",MyEnum.A);//Exception at runtime, but compiles fine

Если вы укажете универсальный тип, это вызовет ошибка компиляции, это предпочтительнее исключений среды выполнения.

enum MyEnum{A}
I_Test<Enum_1> t1 = new Test_1();
t1.get("",MyEnum.A);//Does not compile