@Wan-Derer
Зобанели на Хабре, волки́ ;((

Как с generics получить объект нужного типа?

Всем привет. Помогите с дженериками!
Допустим, есть такой код:

import lombok.Getter;

public class Test {

  public static void main(String[] args) {
    B b = getInstance();
    C c = getInstance();
  }

  public static <T extends A> T getInstance() {
    return new T();       // ОШИБКА: Type parameter 'T' cannot be instantiated directly
  }

}

@Getter
abstract class A {
  String a = "aaa";
}

@Getter
class B extends A {
  String b = "bbb";
}

@Getter
class C extends A {
  String c = "ccc";
}


Хочу получить метод getInstance(), который возвращает экземпляр типа в зависимости от вызывающего кода.
Так в лоб не получается (из-за стирания типов?). А как такие вещи делаются?
Почитал про фабрики, но что-то у меня не сложилось, вроде там немного о другом или я не до конца понял.
В общем, как правильно?
  • Вопрос задан
  • 473 просмотра
Пригласить эксперта
Ответы на вопрос 2
xez
@xez Куратор тега Java
TL Junior Roo
Дженерики тут не нужны.
class Main {
    public static void main(String[] args) {
        A b = Fabric.getInstance("B");
        A c = Fabric.getInstance("C");

        System.out.println(b.getString());
        System.out.println(c.getString());
    }

}

class Fabric {

    public static A getInstance(String className) {
        return switch (className) {
            case "B" -> new B();
            case "C" -> new C();
            default -> throw new IllegalArgumentException("Unknown class name " + className);
        };
    }
}

interface A {
    String getString();
}

class B implements A {
    @Override
    public String getString() {
        return "bbb";
    }
}

class C implements A {
    @Override
    public String getString() {
        return "ccc";
    }
}
Ответ написан
@Wan-Derer Автор вопроса
Зобанели на Хабре, волки́ ;((
В общем, выкладываю два варианта решения: одно, которое мы обсуждали с Dmitry Roo и второе, подсмотренное на SOF. Оба рабочие. Мне больше нравится второе (с Supplier<>), оно компактнее и не надо городить свою фабрику.

import lombok.Getter;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

public class Test {

  public static void main(String[] args) {

    HandleB handleB = new HandleB();
    handleB.print();

    HandleC handleC = new HandleC();
    handleC.print();

  }
}

// ТИПЫ
@Getter
abstract class A {
  private String a = "aaa";
}

@Getter
class B extends A {
  private String b = "bbb";
}

@Getter
class C extends A {
  private String c = "ccc";
}

class MyFactory{
  private final String type;

  public MyFactory(String type) {
    this.type = type;
  }

  public A getObject(){
    return switch (type){
      case "B" -> new B();
      case "C" -> new C();
      default -> throw new IllegalStateException("Unexpected value: " + type);
    };
  }

}

// КЛАССЫ для работы с типами
class HandleB {
  Commons<B> commons = new Commons<>("B");

  B instanceB = commons.getObject();
  List<B> list = commons.getList();

  void print(){
    System.out.println(instanceB.getB());
  }

}

class HandleC {
  Commons2<C> commons = new Commons2<>(C::new);

  C instanceC = commons.getObject();
  List<C> list = commons.getList();

  void print(){
    System.out.println(instanceC.getC());
  }

}

// КЛАСС с шаблонным кодом

// вариант с фабрикой
class Commons<T extends A> {

  MyFactory factory;

  public Commons(String type) {
    this.factory = new MyFactory(type);
  }

  List<T> getList() {
    List<T> list = new ArrayList<>();

    for (int i = 0; i < 3; i++) {
      T item = getObject();
      list.add(item);
    }

    return list;
  }

  T getObject() {
    return (T) factory.getObject();
  }

}

// вариант с Supplier<>
// https://stackoverflow.com/questions/299998/instantiating-object-of-type-parameter
class Commons2<T extends A> {

  private final Supplier<T> supplier;

  Commons2(Supplier<T> supplier) {
    this.supplier = supplier;
  }

  List<T> getList() {
    List<T> list = new ArrayList<>();

    for (int i = 0; i < 3; i++) {
      T item = getObject();
      list.add(item);
    }

    return list;
  }

  T getObject() {
    return supplier.get();
  }
  
}


Осталось понять как это знание прикрутить к приложению на Spring Boot.
Как бы, логично Commons оформить аннотацией Service, но компоненты в Spring по умолчанию являются Singletone, а мне надо чтобы для каждого случая использования Commons "подключался" свой Supplier.
1. Можно сделать Commons не Singletone, a Prototype и в каждом экземпляре инициализировать свой Supplier.
2. Можно оставить Singletone, а в каждый метод где надо создавать объекты передавать нужный Supplier (выглядит несколько громоздко).
3. Можно не вводить Commons в контекст Spring (не помечать его аннотацией, а оставить обычным классом) и использовать обычным Java-способом - экземляр, инициализация, использование. Тут штука в том что помимо Supplier в Commons будут и другие зависимости, которые в случае со Spring внедряются самим спрингом и их достаточно просто указать. Без спринга их надо будет внедрять руками. Как бы не проблема, но зачем нам лишняя писанина? :)

В общем, пока думаю как сделать лучше. Если есть типовой способ - подскажите :)
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы