предисловие
Универсальные шаблоны Java (дженерики) — это новая функция, представленная в JDK 5. Универсальные шаблоны предоставляют механизм безопасного обнаружения типов во время компиляции, который позволяет разработчикам обнаруживать недопустимые типы во время компиляции.
Суть дженериков в том, что они являются параметризованными типами, то есть тип данных, с которым манипулируют, указывается в качестве параметра.
Преимущества дженериков
При отсутствии дженериков "арбитризация" параметров достигается через ссылку на тип Object. Недостаток "арбитризации" в том, что требуется явное преобразование типов, а это преобразование требует от разработчиков. Это делается, когда фактический тип параметра предсказуемо. В случае ошибки принудительного преобразования типа компилятор может не выдать ошибку, а во время выполнения возникает исключение, что само по себе является угрозой безопасности.
Преимущество дженериков в том, что безопасность типов проверяется во время компиляции, а все приведения являются автоматическими и неявными.
public class GlmapperGeneric<T> {
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
public static void main(String[] args) {
// do nothing
}
/**
* 不指定类型
*/
public void noSpecifyType(){
GlmapperGeneric glmapperGeneric = new GlmapperGeneric();
glmapperGeneric.set("test");
// 需要强制类型转换
String test = (String) glmapperGeneric.get();
System.out.println(test);
}
/**
* 指定类型
*/
public void specifyType(){
GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric();
glmapperGeneric.set("test");
// 不需要强制类型转换
String test = glmapperGeneric.get();
System.out.println(test);
}
}
В методе specType в приведенном выше коде приведение опущено, безопасность типов можно проверить во время компиляции, и его можно использовать в классах, методах и интерфейсах.
Подстановочные знаки в дженериках
Когда мы определяем универсальные классы, универсальные методы и универсальные интерфейсы, мы часто сталкиваемся с множеством различных подстановочных знаков, таких как T, E, K, V и т. д. Что означают эти подстановочные знаки?
Часто используемые T, E, K, V, ?
По сути, это все подстановочные знаки, разницы нет, это просто условность при кодировании. Например, T в приведенном выше коде, мы можем заменить на любую букву между A-Z, и это не повлияет на нормальную работу программы, но если мы заменим T на другие буквы, это может быть слабее в читабельности.Обычно T, E, K, V, ? Согласовано следующее:
- ? представляет неопределенный тип java
- T (тип) представляет определенный тип Java
- K V (значение ключа) соответственно представляют значение ключа в значении ключа java.
- E (элемент) означает элемент
?неограниченный подстановочный знак
Начнем с небольшого примера, исходный текст находится вздесь.
У меня есть родительский класс Animal и несколько подклассов, таких как собака, кошка и т. д. Теперь мне нужен список животных, моя первая идея примерно такая:
List<Animal> listAnimals
Но на самом деле начальник думает так:
List<? extends Animal> listAnimals
Зачем использовать подстановочные знаки вместо простых дженериков? Подстановочные знаки действительно бессмысленны при объявлении локальных переменных, но они очень важны, когда вы объявляете параметр для метода.
static int countLegs (List<? extends Animal > animals ) {
int retVal = 0;
for ( Animal animal : animals )
{
retVal += animal.countLegs();
}
return retVal;
}
static int countLegs1 (List< Animal > animals ){
int retVal = 0;
for ( Animal animal : animals )
{
retVal += animal.countLegs();
}
return retVal;
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
// 不会报错
countLegs( dogs );
// 报错
countLegs1(dogs);
}
При вызове countLegs1 он будет мигать красным, а сообщение об ошибке будет следующим:
Таким образом, для типов, которые являются неопределенными или не заботятся о фактической операции, вы можете использовать неограниченное количество подстановочных знаков (вопросительный знак в угловых скобках, т. е. > ), указывающих, что может храниться любой тип. Как и countLegs В методе предыдущий термин ограничен, но ему все равно, какой конкретный тип, поэтому он может поддерживать все подклассы входящего Animal и не сообщит об ошибке. И countLegs1 не работает.
Подстановочный знак верхней границы
Последний сеанс: объявлен с ключевым словом extends, указывающим, что параметризованный тип может быть указанным типом или подклассом этого типа.
Использование extensions в параметре типа указывает, что параметр в этом универсальном должен быть E или подклассом E, что имеет два преимущества:
- Если переданный тип не является E или подклассом E, компиляция завершается ошибкой.
- Метод E можно использовать в дженериках, в противном случае его необходимо привести к E, прежде чем его можно будет использовать.
private <K extends A, E extends B> E test(K arg1, E arg2){
E result = arg2;
arg2.compareTo(arg1);
//.....
return result;
}
Если в списке параметров типа указано более одного верхнего предела параметра типа, разделите их запятыми.
Подстановочный знак Нижнего мира
Нижняя граница: объявлено с супер, что указывает на то, что параметризованный тип может быть указанным типом или супертипом этого типа, вплоть до Object
Использование super в параметре типа означает, что параметр в этом универсальном шаблоне должен быть E или надклассом E .
private <T> void test(List<? super T> dst, List<T> src){
for (T t : src) {
dst.add(t);
}
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = new ArrayList<>();
new Test3().test(animals,dogs);
}
// Dog 是 Animal 的子类
class Dog extends Animal {
}
dst type «больше или равно» типу src, где «больше или равно» означает, что диапазон, представленный dst, больше диапазона src, поэтому контейнер, который может содержать dst, также может содержать src.
? отличие от Т
? и T оба представляют неопределенные типы, разница в том, что мы можем работать с T , но верно? Нет, вот так:
// 可以
T t = operate();
// 不可以
? car = operate();
Кратко резюмируя:
T — это детерминированный тип, обычно используемый в определении универсальных классов и универсальных методов. — это неопределенный тип, обычно используемый для вызова кода и формальных параметров универсальных методов, и его нельзя использовать для определения классов и универсальных методов.
Отличие 1: обеспечить согласованность общих параметров с помощью T
// 通过 T 来 确保 泛型参数的一致性
public <T extends Number> void
test(List<T> dest, List<T> src)
//通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型
public void
test(List<? extends Number> dest, List<? extends Number> src)
Как и в приведенном ниже коде, согласованный T является подклассом Number, но в объявлении используется String, поэтому будет сообщено об ошибке.
Случаи, когда два списка не обязательно имеют одинаковый тип элемента
GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric<>();
List<String> dest = new ArrayList<>();
List<Number> src = new ArrayList<>();
glmapperGeneric.testNon(dest,src);
Приведенный выше код не сообщит об ошибке в компиляторе, но при входе во внутреннюю операцию метода testNon (такую как присваивание) для dest и src по-прежнему требуется преобразование типов.
Отличие 2: параметры типа могут быть уточнены несколько раз, в то время как подстановочные знаки не могут
Используйте символ &, чтобы установить несколько границ, и укажите, что универсальный тип T должен быть общим подтипом MultiLimitInterfaceA и MultiLimitInterface B. В настоящее время переменная t имеет все ограниченные методы и свойства. Для подстановочных знаков, поскольку это не определенный тип, его нельзя квалифицировать несколько раз.
Отличие 3: подстановочные знаки можно квалифицировать с помощью суперклассов, но не параметров типа.
Параметр типа T имеет только одну квалификацию типа:
T extends A
Но подстановочный знак ? можно квалифицировать двумя способами:
? extends A
? super A
Class<T>
а такжеClass<?>
разница
ввели раньше? и T разность, то для,Class<T>
а также<Class<?>
Какая разница?Class<T>
а такжеClass<?>
Наиболее распространенным является использование в сценариях отражения, которое объясняется здесь с помощью фрагмента кода эмиссии.
// 通过反射的方式生成 multiLimit
// 对象,这里比较明显的是,我们需要使用强制类型转换
MultiLimit multiLimit = (MultiLimit)
Class.forName("com.glmapper.bridge.boot.generic.MultiLimit").newInstance();
Для приведенного выше кода во время выполнения, если отраженный тип не является классом MultiLimit, будет сообщено об ошибке java.lang.ClassCastException.
В этом случае вместо этого можно использовать следующий код, чтобы проблему с типом можно было проверить непосредственно во время компиляции:
Class<T>
Во время создания T заменяется конкретным классом.Class<?>
Это подстановочный знак, и ? может представлять любой тип, поэтому он в основном используется для ограничения объявления. Например, мы можем объявить это:
// 可以
public Class<?> clazz;
// 不可以,因为 T 需要指定类型
public Class<T> clazzT;
Таким образом, вы можете определить класс>, когда не знаете, какой тип класса объявить.
Тогда, если вы хотитеpublic Class<T> clazzT;
В этом случае текущий класс также должен указывать T ,
public class Test3<T> {
public Class<?> clazz;
// 不会报错
public Class<T> clazzT;
резюме
В этой статье разобраны некоторые моменты в дженериках JAVA, не очень полные, только для справки. Если в тексте есть неуместные места, поправьте меня.