Обобщения
– это способность класса/интерфейса/метода быть универсальными в определении типов данных, с которыми они работают. Пользователи, в свою очередь, могут быть конкретными в типах данных во время создания объекта или вызова метода.
Появление обобщений привнесло свои преимущества в код:
Безопасность типа
- обобщения обеспечивают безопасность типа во время компиляции. Это возможно, так как ошибки, связанные с использованием типов данных, и ошибки, которые могли возникнуть во время исполнения, теперь выявляются на этапе компиляции.Ненужность приведения типов
- обобщения устраняют необходимость явного приведения типов между объектами, поскольку компилятор уже информирован, с какими типами данных производится работа.Однородные коллекции
- обобщения обеспечивают возможность создания проверяемых компилятором однородных коллекций. Другими словами, обобщения гарантируют, что все операции с элементами коллекции являются корректными, так как компилятор информирован о типах элементов коллекции и может выполнить проверки.
[доступ] class/interface имя класса/интерфейса<T1, T2, ..., Tn>
<T1, T2, ..., Tn> - Компонент “Параметр типа” всегда указывается в угловых скобках (<>)
T - Аргумент типа может быть любым ссылочным типом: класс или интерфейс.
Примитивный тип не может использоваться в качестве аргумента типа.
Существуют соглашения по обозначению параметра типа
.
Наиболее часто используемые обозначения параметров типов:
Е
– Element: для элементов коллекцийT
– Type: для любого типаK
– Key: для ключаV
– Value: для значенияN
– Number: для числовых данныхS
,U
,V
и так далее - для 2, 3, 4 параметров типа.
- Обобщение — это универсальность класса/интерфейса/метода в определении типов данных, с которыми они работают. Оно позволяет пользователю использовать конкретные типы данных при вызове метода или во время создания объекта.
- Обобщения делают код более стабильным, так как позволяют компилятору обнаруживать большинство ошибок.
- Пустой набор параметров типа (<>) может заменить аргументы типа, необходимые для вызова конструктора универсального класса. Такая пара скобок называется бриллиантовым (алмазным) оператором.
- Сырой (raw) тип — это название универсального класса или интерфейса без каких-либо аргументов типа.
Обычно параметризованный/обобщенный метод
определяет базовый набор операций, которые будут применяться к разным типам данных, получаемых методом в качестве параметра. Обобщенными могут быть описаны:
- методы класса (статические)
- методы экземпляра (не статические)
- конструкторы.
При описании обобщенного метода местоположение параметра типа всегда указывается после модификатора доступа и перед типом возвращаемого значения.
[доступ] <T> тип_возвращаемого_значения имя_метода(T arg) {}
Пример
class GenericMethod {
public static ‹T› byte asByte(T num) {
if (num instanceof Number) {
return ((Number) num).byteValue();
}
return 0;
}
}
Обобщенный метод экземпляра:
ссылка_на_экземпляр.<Type>имя_метода(перечень аргументов, один из которых обязательно относится к типу Type);
Обобщенный метод класса (статический):
имя_класса.<Type>имя_метода(перечень аргументов, один из которых обязательно относится к типу Type);
Пример
class Box‹T› {
private T value;
public Box(T value) {
this.value = value;
}
public static ‹V› Box‹V› box(V value) {
return new Box‹›(value);
}
}
class BoxProvider {
public ‹T› Box‹T› box(T value) {
return new Box‹›(value);
}
}
class Main {
public static void main(String[] args) {
Box‹String› box1 = Box.‹String›box("1"); // a box with String
Box‹Integer› box2 = new BoxProvider().‹Integer>box(2); // a box with Integer
}
}
В следующем примере описывается класс Main, в котором вызывается статический обобщенный метод asByte() с аргументами типа Integer, Float и Character.
public class Main {
public static void main(String[] args) {
System.out.println(GenericMethod.asByte(Integer.valueOf(7)));
System.out.println(GenericMethod.asByte(Float.valueOf(7.0F)));
System.out.println(GenericMethod.asByte(Character.valueOf('7')));
}
}
- Обобщенный метод определяет базовый набор операций с разными типами данных, получаемых методом в качестве параметра.
- Методы класса (статические), методы экземпляра (не статические) и конструкторы могут быть описаны как обобщенные.
Иногда при описании обобщенного типа или метода известно, что он не предполагается для работы с любым типом данных. Иными словами, не все типы данных можно использовать с обобщенным классом или методом. Например, обобщенный класс описывается для работы с числовыми типами данных и использование типов, не поддерживающих операции над числовыми данными, приведет к ошибке.
Во избежание таких ошибок и дополнительных проверок на параметр типа можно установить границы. Они могут быть двух видов:
- верхняя граница - описывается как и означает, что T может быть только типом Type или его подтипом
- нижняя граница - описывается как и означает, что T может быть только типом Type или его супертипом
Подстановочный тип (wildcard) означает неизвестный/любой тип при описании ссылки. Иными словами, это способ выражения ограничений, накладываемых на тип, в терминах неизвестного типа.
Подстановочный тип обозначается через символ знака вопроса ("?
").
Подстановочный тип полезен в различных ситуациях. Например:
- Для метода, который может быть реализован с помощью функциональных возможностей, предоставляемых классом
Object
. - Когда код использует методы обобщенного класса, которые не зависят от параметра типа.
Подстановочный тип можно использовать с границами, чтобы ослабить ограничения на переменную и очертить группу типов, которые могут использоваться:
<? extends SuperClass>
– верхняя граница: означает, что может быть любой тип, но из подтипа SuperClass или он сам<? super SubClass>
– нижняя граница: означает, что может быть любой тип, но из супертипов SubClass или он сам.
Например, DynamicArray <Number>
является более ограничительным, чем DynamicArray <? extends Number>
, поскольку первый совпадает только с набором данных типа Number
, в то время как второй – с набором данных типа Number или любого из его подклассов (Integer
, Double
, Float
и так далее).