Могу ли я заставить конструктор установить более строгую границу для своего универсального типа?

#java #generics #constructor #type-bounds

#java #общие #constructor #границы типов

Вопрос:

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

Возможно ли иметь конструктор, который устанавливает более строгие границы для этого универсального типа?
Например, есть конструктор, который заставляет универсальный тип быть String .

 public class GenericClass<T extends Serializable> {
    public GenericClass() {
        // normal constructor
    }

    public GenericClass(String argument) {
        // Can I force this constructor to construct a `GenericClass<String>`?
    }
}

// The first constructor can have any type
GenericClass<String> stringInstance = new GenericClass<>();
GenericClass<Integer> intInstance = new GenericClass<>();

// The second constructor is limited to `GenericClass<String>`
stringInstance = new GenericClass<>("with generic type String is okay");
intInstance = new GenericClass<>("with other generic type is not okay");
  

Я хотел бы, чтобы последняя строка завершилась ошибкой из-за несовместимых типов.

Возможно ли это?

Комментарии:

1. Кажется, вы слишком усложняете. Если вы хотите принудительно String , просто не используйте generics.

2. В большинстве случаев я хочу иметь универсальный тип. Просто эта строка используется по умолчанию. В моем реальном случае (не в примере, который я привел здесь) конструктор, который выдает строковый вариант, на самом деле намного проще, чем обычный случай. Поскольку я предполагаю, что сейчас это String, хотя теоретически это может быть что угодно, я получаю предупреждения о «непроверенных» приведениях.

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

Ответ №1:

 public GenericClass(String argument)
  

Проблема в том, как компилятор должен знать, что String есть T ? Между параметром и параметром универсального типа нет связи, и нет способа указать ее. Вы могли бы использовать

 public GenericClass(T argument)
  

и сконструировать его с

 new GenericClass<>("foo");
  

но это позволило бы GenericClass создавать экземпляры объектов любого типа.

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

 class GenericClass<T extends Serializable> {
    public GenericClass() {

    }
}

class StringClass extends GenericClass<String> {
    public StringClass(String argument) {

    }
}
  

Вы можете ввести интерфейс и заставить оба класса реализовать его, если хотите избежать использования наследования. Это то, что я бы сделал.

Ответ №2:

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

 public class GenericClass<T extends Serializable> {
    public GenericClass() {
        // normal constructor
    }

    public GenericClass(T argument) {

    }
}
  

Но, очевидно, это не останавливает людей от вызовов new GenericClass<>(1) .

В качестве альтернативы вы можете написать фабричный метод ofString :

 public static GenericClass<String> ofString(String s) {
    GenericClass<String> gc = new GenericClass<>();
    // do stuff to gc
    return gc;
}
  

Комментарии:

1. Тогда все подклассы GenericClass должны реализовать такой фабричный метод и дублировать поведение родительского фабричного метода вместо простого его вызова?

2. @neXus вы правы. Если у вас есть подклассы, то этот метод создаст много шаблонного кода.

3. Я могу создать конструктор protected и ожидать, что подклассы будут учитывать тот факт, что его следует использовать только для создания экземпляров, для которых <T> тип String . Затем каждому подклассу нужен простой фабричный метод, который просто делегирует конструктору.