#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
. Затем каждому подклассу нужен простой фабричный метод, который просто делегирует конструктору.