Подтвердить что ты не робот

Ограничения Java Generics или неправильное использование?

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

  public class Pair<E extends AClass>{
      private E var1;
      private E var2; 
  }

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

  public class Pair<E extends AClass>{
      private E var1;
      private E var2; 

      public Pair(){
           var1 = invoke constructor of type E; 
           var2 = invoke constructor of type E 
      }
  }

Здесь, очевидно, существует ряд проблем:

  • Чтобы создать экземпляр переменных, я должен как-то узнать его точный тип и вызвать этот конкретный конструктор типов; в лучшем случае это означает, что в конструкторе есть довольно большой оператор if else, например:

     public Pair(){
           if(var1 instanceof SpecificType1){
              var1 = new SpecificType1(); 
              var2 = new SpecificType2();
           }
      }
    
  • Даже если я сделаю так, как описано выше, у меня возникнут некоторые проблемы, потому что var1 объявлен типа E, и я получу ошибку несоответствия типа при попытке создать экземпляр SpecficType1 и назначить приведенный объект var1/var2. Чтобы заставить его работать, я должен отбросить его на E:

       var1 = (E)new SpecificType1();
    

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

Является ли это ограничением дженериков в java или этот сценарий является плохим для использования Generics?

4b9b3361

Ответ 1

Чтобы создать экземпляр переменных, я должен как-то узнать его точный тип и вызвать этот конкретный конструктор типов; в лучшем случае это означает, что в конструкторе есть довольно большой оператор if else, например:

У вас будут проблемы до этого.

   if(var1 instanceof SpecificType1){
      var1 = new SpecificType1(); 
      var2 = new SpecificType2();
   }

var1 в этом пункте null, поэтому var1 instanceof T false для всех T.


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

Вызывающий должен предоставить некоторый контекст, чтобы рассказать вам, как инициализировать var1 и var2, и типичный способ предоставить этот контекст через аргументы конструктора.


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

Может

void init(Class<E> type) {
  if (type.isAssignableFrom(ConcreteType1.class)) {
    var1 = type.cast(new ConcreteType1(...));
    var2 = type.cast(new ConcreteType1(...));
  } else { /* other branches */ }
}

Это не идеально, так как вы все еще не можете отличить E extends List<String> от E extends List<Number>, но он может быть достаточно хорош для вашего случая, а .cast даст вам безопасный тип для E.


В качестве альтернативы, Guava, Guice и связанные с ними библиотеки предоставляют такие функции, как интерфейс Supplier<E>, который может пригодиться в методе init.

Ответ 2

Вы не можете создать экземпляр общего типа. Что произойдет, если, например, общий тип SomeAbstractClass? Что будет создано? (это не причина, это просто интуиция)

Однако для создания объекта можно использовать java reflection API, но вам понадобится конкретный объект класса для него.

Более элегантной альтернативой является абстрактный шаблон дизайна factory и передать объект factory к вашей паре и использовать его для создания необходимого объекта.


Пример кода:

public class Pair<S> {
    public final S var1; 
    public final S var2;
    public Pair(Factory<S> builder) {
        var1 = builder.build();
        var2 = builder.build();

    }
}

public interface Factory<S> { 
    public S build();
}

public class IntegerBuilder implements Factory<Integer> {
    private int element = 5;
    public Integer build() {
        return new Integer(element++);
    }
}

Ответ 3

Если фреймворк должен был создать экземпляр, он будет делать это как необработанный тип, что эквивалентно new Pair() без параметров типа.

Я думаю, вам нужно создать простые однострочные классы, например:

class SpecificType1Pair extends Pair<SpecificType1> {}

и передать их в структуру. Вы можете получить фактический параметр типа getClass().getGenericSuperclass()).getActualTypeArguments()[0]. Ваша пара классов будет выглядеть так:

public abstract class Pair<E extends AClass> {
    private E var1;
    private E var2;

    public Pair() {
        ParameterizedType superclass = (ParameterizedType) getClass().getGenericSuperclass();
        @SuppressWarnings("unchecked")
        Class<E> clazz = (Class<E>) superclass.getActualTypeArguments()[0];
        try {
            var1 = clazz.newInstance();
            var2 = clazz.newInstance();
        } catch (InstantiationException e) {
            handle(e);
        } catch (IllegalAccessException e) {
            handle(e);
        }
    }
}