Java - Как бороться с стиранием типа в конструкторах? - программирование
Подтвердить что ты не робот

Java - Как бороться с стиранием типа в конструкторах?

Скажем, у меня есть два конструктора в моем классе:

public User (List<Source1> source){
...
}

public User (List<Source2> source) {
...
}

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

В Java вы не можете этого сделать из-за стирания типа - Java не примет два конструктора, которые имеют в качестве параметров List <? > .

Итак, каков способ обойти это? Что такое решение, которое не переполняет, но по-прежнему уважает базовое ОО? Кажется неправильным создавать конструкцию метода factory или другого интерфейса вокруг этого только потому, что Java не имеет поддержки сильных дженериков.

Вот мои возможности:

1) Примите List<?> в качестве параметра для конструктора и проанализируйте конструктор, какую логику вам нужно, или выбросите исключение, если оно не относится к принятым типам.

2) Создайте класс, который принимает List, создает соответствующий объект User и возвращает его.

3) Создайте обертки вокруг List<Source1> и List<Source2>, которые могут быть переданы вместо конструктора User.

4) Подкласс этого парня с двумя классами, где вся функциональность наследуется, за исключением конструктора. Конструктор одного принимает Source1, другой принимает Source2.

5) Оберните этого парня строителем, где два разных метода построения для двух разных источников данных для создания экземпляра.

Мои вопросы таковы:

1) Нужно ли делать это с ошибкой с Java или преднамеренным дизайнерским решением? Что такое интуиция?

2) Какое решение является самым сильным с точки зрения поддержания хорошего кода без внедрения ненужной сложности? Почему?

Этот вопрос схож: Проектирование конструкторов вокруг стирания стилей в Java, но не вникает в особенности, он просто предлагает различные варианты работы.

4b9b3361

Ответ 1

Обычный подход заключается в использовании factory методов:

public static User createFromSource1(List<Source1> source) {
    User user = new User();
    // build your User object knowing you have Source1 data
    return user;
}

public static User createFromSource2(List<Source2> source) {
    User user = new User();
    // build your User object knowing you have Source2 data
    return user;
}

Если вам нужна конструкция с использованием Source1 или Source2 (т.е. у вас нет конструктора по умолчанию), вы просто спрячете свой конструктор, заставляя клиентов использовать ваши методы factory:

private User () {
    // Hide the constructor
}

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

Ответ 2

1: сохранение обратной совместимости с стиранием.

2: Может ли ваш класс использовать дженерики? Что-то вроде этого:

public class User<T> {
    private List<T> whatever;
    public User(List<T> source){
       ....
    }
}

Я не уверен, что это то, что вы подразумевали под (2)

Ответ 3

public class User<T> {

    public User(List<T> source){

    }

}

или, возможно, лучше:

public class User<T extends SomeTypeOfYours> {

    public User(List<T> source){

    }
}

Колебание SomeTypeOfYours является супертипом Source1 и Source2.

Ответ 4

Мне нравится идея Factory в целом или обобщение User (как предложено @Markus Mikkolainen), но одной из возможных альтернатив было бы передать класс списка в качестве второго аргумента и включить его, например

public User<List<?> source, Class<?> clazz) {
   switch(clazz) {
      case Source1.class: initForSource1(); break;
      case Source2.class: initForSource2(); break;
      default: throw new IllegalArgumentException();
   }
}

<?> может быть чем-то другим, если существует некоторый общий класс предков. Я могу представить много случаев, когда это плохая идея, но несколько, где это может быть приемлемо.

Ответ 5

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

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

public User(List<Source1>, Source1 instance) {
    // ignore instance
}

public User(List<Source2>, Source2 instance) {
    // ignore instance
}

Это немного хромает, поскольку вы можете заменить эти дополнительные параметры чем-нибудь (например, Integer и String, или просто один из них опустить второй параметр), и он все равно будет работать. Кроме того, дополнительный параметр игнорируется - он существует только для того, чтобы отличать конструкторы. Тем не менее, это позволяет коду работать без добавления дополнительных методов или специального кода.