Давайте поговорим - подстановочные знаки T, E, K, V, ? в дженериках JAVA

Java

предисловие

Универсальные шаблоны 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, не очень полные, только для справки. Если в тексте есть неуместные места, поправьте меня.

Ссылаться на