Основы Java — подробное объяснение обобщений

Java задняя часть переводчик Безопасность

мой блогПожалуйста, указывайте первоисточник при перепечатке.

последовательность

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

В то время мои мысли были такими:

Поэтому я быстро просмотрел его и записал. Фундамент непрочен, и исходный код понять невозможно.

Введение в дженерики

Универсальные шаблоны Java (дженерики) — это новая функция, представленная в JDK 5. Универсальные шаблоны предоставляют механизм безопасного обнаружения типов во время компиляции, который позволяет программистам обнаруживать недопустимые типы во время компиляции. Суть дженериков заключается в параметризованном типе, что означает, что тип данных, с которым нужно манипулировать, указывается в качестве параметра, что широко используется в структуре коллекций Java.

Смысл определения в том, чтобы обеспечить механизм обнаружения безопасности типа во время компиляции. Например, есть такой общий класс:

public class Generics <T> {

    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

Затем напишите такой класс:

public class Generics  {

    private Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

Они также могут хранить все значения, но универсальные классы имеют проверки безопасности типа во время компиляции:

универсальный класс

Класс, который определяет одну или несколько переменных типа, является универсальным классом. Синтаксис заключен в угловые скобки после имени класса, а переменные типа записываются внутри через запятую. Затем вы можете использовать переменные типа в возвращаемом типе, параметрах и полях метода, а также в локальных переменных, но не вstaticМодификатор используется в измененном методе или поле. пример:

类定义参考上文例子
使用形式
Generics<String> generics = new Generics<String>();
后面尖括号内的内容在Jdk7以后可以省略
Generics<String> generics = new Generics<>();

общий метод

Метод, который определяет одну или несколько переменных типа, является универсальным методом. Синтаксис заключен в угловые скобки после модификатора метода и перед возвращаемым типом, а переменные типа записываются внутри и разделяются запятыми. Универсальные методы могут быть определены в обычных классах и универсальных классах, а универсальные методы могут бытьstaticмодификатор модификатор. пример:

    private <U> void out(U u) {
        System.out.println(u);
    }
    
    调用形式,
    Test.<String>out("test");
    大部分情况下<String>都可以省略,编译器可以推断出来类型
    Test.out("test");
    

Ограничения на переменные типа

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

public class Generics <T extends List> {

    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public void add(Object u) {
        value.add(u);
    }

    public static void main(String[] args) {
        Generics<List> generics = new Generics<>();
        generics.setValue(new ArrayList<>());
        generics.add("ss");
        System.out.println(generics.getValue());
    }
    
}

Квалифицированный синтаксис должен добавить переменную типа послеextendsключевое слово, а затем добавьте квалифицированный тип, следует использовать несколько квалифицированных типов.&разделены. Переменные типа и ограниченные типы могут быть классами или интерфейсами, потому что класс в Java может наследовать только один класс, поэтому, если ограниченный тип является классом, он должен быть первым в ограниченном списке.

стирание типа

Стирание типа создается для совместимости, в том смысле, что в виртуальной машине нет универсального типа, а универсальный тип существует только во время компиляции. Переменные универсального типа стираются после компиляции и заменяются первым квалифицированным типом (используется неквалифицированный тип).Objectзаменять). вышесказанноеGenerics <T>После стирания универсального класса создается соответствующий примитивный тип:

public class Generics {

    private Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

}

Причина, по которой мы можем установить и вернуть правильный тип, заключается в том, что компилятор автоматически вставляет инструкции по преобразованию типов.

    public static void main(String[] args) {
        Generics<String> generics = new Generics<>();
        generics.setValue("ss");
        System.out.println(generics.getValue());
    }
    
    javac Generics.java
    javap -c Generics
    编译后的代码
     public static void main(java.lang.String[]);
        Code:
       0: new           #3                  // class generics/Generics
       3: dup
       4: invokespecial #4                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #5                  // String ss
      11: invokevirtual #6                  // Method setValue:(Ljava/lang/Object;)V
      14: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      17: aload_1
      18: invokevirtual #8                  // Method getValue:()Ljava/lang/Object;
      21: checkcast     #9                  // class java/lang/String
      24: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: return

Мы видим, что инструкция преобразования типа вставлена ​​в строку 21.

Стирание типа также приносит еще одну проблему, если у нас есть класс, который наследует универсальный класс и переопределяет метод родительского класса:

public class SubGenerics extends Generics<String> {

    @Override
    public void setValue(String value) {
        System.out.println(value);
    }

    public static void main(String[] args) {
        Generics<String> generics = new SubGenerics();
        generics.setValue("ss");
    }

}

Из-за стирания типаSubGenericsНа самом деле их дваsetValueметод,SubGenericsмой собственныйsetValue(String value)метод и изGenericsунаследовалsetValue(Object value)метод. в примереgenericsЦитируетсяSubGenericsобъект, поэтому то, что мы хотим назвать,SubGenerics.setValue. Для обеспечения правильного полиморфизма компиляторSubGenericsкласс сгенерировал桥方法:

public void setValue(Object value) {
    setValue((String) value);
}

Мы можем скомпилировать и проверить:

Compiled from "SubGenerics.java"
public class generics.SubGenerics extends generics.Generics<java.lang.String> {
  public generics.SubGenerics();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method generics/Generics."<init>":()V
       4: return

  public void setValue(java.lang.String);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_1
       4: invokevirtual #3                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       7: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #4                  // class generics/SubGenerics
       3: dup
       4: invokespecial #5                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #6                  // String ss
      11: invokevirtual #7                  // Method generics/Generics.setValue:(Ljava/lang/Object;)V
      14: return

  public void setValue(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #8                  // class java/lang/String
       5: invokevirtual #9                  // Method setValue:(Ljava/lang/String;)V
       8: return
}

Цитата из "Java Core Technology Volume 1"

В заключение необходимо помнить о фактах об общих преобразованиях Java: 1. В виртуальной машине нет дженериков, только обычные классы и методы. 2. Все параметры типов заменяются их уточненными типами. 3. Метод моста синтезируется для сохранения полиморфизма. 4. Чтобы обеспечить безопасность типов, интерполируйте приведения, когда это необходимо.

Ограничения и ограничения

  • Переменные типа не могут быть примитивными переменными, такими какint,doubleЖдать. следует использовать их класс-оболочкуInteger,Double.
  • В виртуальной машине нет дженериков, поэтому вы не можете использовать запросы типа времени выполнения, такие какif (generics instanceof Generics<String>) // Error
  • Массивы общих классов не могут быть созданы из-за стирания типа, напримерGenerics<String>[] generics = new Generics<String>[10]; // Error
  • Переменные типа не могут быть созданы, напримерnew T(...) new T[...] 或 T.class

подстановочный знак

Тип подстановочного знака чем-то похож на ограничение переменной типа выше, разница в том, что тип подстановочного знака используется во время объявления, а ограничение переменной типа — во время определения. такие как подстановочные знакиGenerics<? extends List>представляет любой общийGenericsТиповая переменная типаListа такжеListподкласс .

Generics<? extends List> generics = new Generics<ArrayList>();

Но после этого заявленияGenericsМетод также изменился, став

Это делает невозможным вызовsetValueметод

а такжеgetValueметод нормальный

квалификация супертипа

Квалификация с подстановочными знаками также может квалифицировать суперклассы, например типы с подстановочными знаками.Generics<? super ArrayList>представляет любой общийGenericsТиповая переменная типаArrayListа такжеArrayListсуперкласс.

Generics<? super ArrayList> generics = new Generics<List>();

такой же,GenericsМетод также изменился, став

перечислитьgetValueметоды могут быть назначены толькоObjectПеременная

перечислитьsetValueметод может быть передан только вArrayListа такжеArrayListподкласс, суперклассList,Objectне могу дождаться

отражение и дженерики

Хотя в виртуальной машине нет дженериков из-за стирания типов. Однако стертый класс по-прежнему сохраняет некоторую информацию о дженериках и может использовать связанные с отражениемApiчтобы получить.

Точно так же взгляните на общие методы

public static <T extends Comparable<? super T>> T min(T[] a)

это после стирания

public static Comparable min(Coniparable[] a)

API отражения можно использовать для определения:

  • Этот общий метод имеет метод, называемыйTпараметр типа.
  • Этот параметр типа имеет квалификацию подтипа и сам является универсальным типом.
  • Этот квалифицированный тип имеет подстановочный параметр.
  • Этот подстановочный знак имеет квалификацию супертипа.
  • Этот общий метод имеет общий параметр массива.

постскриптум

Черновик был создан в понедельник, а писался только в воскресенье, а некоторые подразделы были удалены.Боюсь, что прокрастинация запоздала... Но это еще и потому, что общего содержания достаточно, хотя в повседневных делах его мало Пишите код, связанный с дженериками, сами, но если вы не понимаете дженериков при чтении исходного кода библиотеки классов, вам будет сложно что-либо сделать, особенно связанное с коллекциями. Большая часть контента на этот раз находится в "Java Core Technology Volume 1", который посвященJavaХорошая книга для основ. Однако это все еще старое правило, его недостаточно просто прочитать, нужно еще записать на своем родном языке. Как мы все знаем, сущность человека — это повторение. Повторение содержания хорошей книги означает, что я также несу ответственность!