Какова концепция стирания в дженериках в Java?

java generics

40881 просмотра

7 ответа

Какова концепция стирания в дженериках в Java?

Автор: Tushu Источник Размещён: 12.11.2019 09:09

Ответы (7)


194 плюса

Решение

Это в основном способ, которым дженерики реализуются в Java с помощью хитрости компилятора. Скомпилированный универсальный код фактически использует java.lang.Objectтолько то, о чем вы говорите T(или какой-либо другой параметр типа) - и есть некоторые метаданные, чтобы сообщить компилятору, что это действительно универсальный тип.

Когда вы компилируете некоторый код для универсального типа или метода, компилятор решает, что вы на самом деле имеете в виду (то есть, для чего нужен аргумент типа T), и проверяет во время компиляции, что вы делаете правильные вещи, но выданный код снова просто говорит с точки зрения java.lang.Object- компилятор генерирует дополнительные приведения, где это необходимо. Во время выполнения a List<String> и a List<Date>абсолютно одинаковы; дополнительная информация о типе была стерта компилятором.

Сравните это, скажем, с C #, где информация сохраняется во время выполнения, позволяя коду содержать выражения, например typeof(T)эквивалентные T.class- за исключением того, что последний является недействительным. (Заметим, что между обобщениями .NET и обобщениями Java есть и другие различия.) Стирание типов является источником многих «странных» предупреждений / сообщений об ошибках при работе с обобщениями Java.

Другие источники:

Автор: Jon Skeet Размещён: 24.11.2008 07:25

40 плюса

Как примечание, это интересное упражнение, чтобы на самом деле увидеть, что делает компилятор, когда он выполняет стирание - делает концепцию немного легче понять. Существует специальный флаг, который вы можете передать компилятору для вывода java-файлов, у которых были удалены общие и вставленные приведения. Пример:

javac -XD-printflat -d output_dir SomeFile.java

Это -printflatфлаг, который передается компилятору, который генерирует файлы. (Это -XDто, что говорит javacпередать его исполняемому jar-файлу, который на самом деле выполняет компиляцию, а не просто javac, но я отступаю ...) Это -d output_dirнеобходимо, потому что компилятору нужно место для размещения новых файлов .java.

Это, конечно, больше, чем просто стирание; все автоматические вещи, которые делает компилятор, выполняются здесь. Например, конструкторы по умолчанию также вставляются, новые forциклы в стиле foreach расширяются до обычных forциклов и т. Д. Приятно видеть мелочи, которые происходят автоматически.

Автор: jigawot Размещён: 05.06.2009 07:01

28 плюса

Стирание буквально означает, что информация о типе, которая присутствует в исходном коде, стирается из скомпилированного байт-кода. Позвольте нам понять это с помощью некоторого кода.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class GenericsErasure {
    public static void main(String args[]) {
        List<String> list = new ArrayList<String>();
        list.add("Hello");
        Iterator<String> iter = list.iterator();
        while(iter.hasNext()) {
            String s = iter.next();
            System.out.println(s);
        }
    }
}

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

import java.io.PrintStream;
import java.util.*;

public class GenericsErasure
{

    public GenericsErasure()
    {
    }

    public static void main(String args[])
    {
        List list = new ArrayList();
        list.add("Hello");
        String s;
        for(Iterator iter = list.iterator(); iter.hasNext(); System.out.println(s))
            s = (String)iter.next();

    }
} 
Автор: Parag Размещён: 13.12.2011 09:32

25 плюса

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

Первоначально представленный на EclipseCon 2007 (больше не доступен), совместимость включала в себя следующие пункты:

  • Совместимость с исходным кодом (приятно иметь ...)
  • Бинарная совместимость (должна быть!)
  • Миграционная совместимость
    • Существующие программы должны продолжать работать
    • Существующие библиотеки должны быть в состоянии использовать универсальные типы
    • Должны быть!

Оригинальный ответ:

Следовательно:

new ArrayList<String>() => new ArrayList()

Есть предложения для большего овеществления . Reify - «рассматривайте абстрактное понятие как реальное», где языковые конструкции должны быть понятиями, а не просто синтаксическим сахаром.

Следует также упомянуть checkCollectionметод Java 6, который возвращает динамически безопасное для типов представление указанной коллекции. Любая попытка вставить элемент неправильного типа приведет к немедленной ClassCastException.

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

Обычно это не проблема, так как компилятор выдает предупреждения обо всех таких непроверенных операциях.

Однако бывают случаи, когда одной статической проверки типов недостаточно, например:

  • когда коллекция передается в стороннюю библиотеку, и обязательно, чтобы код библиотеки не повредил коллекцию, вставив элемент неправильного типа.
  • программа завершается с ошибкой ClassCastException, указывая, что неправильно заданный элемент был помещен в параметризованную коллекцию. К сожалению, исключение может произойти в любое время после вставки ошибочного элемента, поэтому оно обычно предоставляет мало информации или вообще не содержит информации о реальном источнике проблемы.

Обновление июль 2012, почти четыре года спустя:

Теперь (2012) подробно описано в « Правилах совместимости миграции API (Проверка подписи) »

Язык программирования Java реализует дженерики, используя стирание, что гарантирует, что унаследованные и дженерические версии обычно генерируют идентичные файлы классов, за исключением некоторой вспомогательной информации о типах. Бинарная совместимость не нарушена, поскольку можно заменить устаревший файл класса универсальным файлом класса без изменения или перекомпиляции любого клиентского кода.

Чтобы облегчить взаимодействие с неуниверсальным унаследованным кодом, также можно использовать стирание параметризованного типа в качестве типа. Такой тип называется необработанным типом ( спецификация языка Java 3 / 4.8 ). Разрешение необработанного типа также обеспечивает обратную совместимость исходного кода.

В соответствии с этим следующие версии java.util.Iteratorкласса являются двоичными и обратно совместимыми с исходным кодом:

Class java.util.Iterator as it is defined in Java SE version 1.4:

public interface Iterator {
    boolean hasNext();
    Object next();
    void remove();
}

Class java.util.Iterator as it is defined in Java SE version 5.0:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}
Автор: VonC Размещён: 24.11.2008 07:38

8 плюса

Дополняя уже дополненный ответ Джона Скита ...

Было упомянуто, что реализация дженериков посредством стирания приводит к некоторым раздражающим ограничениям (например, нет new T[42]). Также было упомянуто, что основной причиной таких действий была обратная совместимость в байт-коде. Это также (в основном) правда. Сгенерированный байт-код -target 1.5 несколько отличается от просто обессахаренного приведения -target 1.4. Технически, даже возможно (посредством огромного обмана) получить доступ к экземплярам универсального типа во время выполнения , доказывая, что в байт-коде действительно что-то есть.

Более интересным моментом (который не был поднят) является то, что реализация обобщений с использованием стирания предлагает гораздо больше гибкости в том, что может достичь система типов высокого уровня. Хорошим примером этого может быть реализация JVM в Scala против CLR. На JVM возможно реализовать более высокие виды напрямую, потому что сама JVM не накладывает ограничений на универсальные типы (так как эти "типы" фактически отсутствуют). Это контрастирует с CLR, который знает во время выполнения об экземплярах параметров. Из-за этого у CLR должна быть некоторая концепция того, как следует использовать дженерики, сводя на нет попытки расширить систему непредвиденными правилами. В результате высшие виды Scala в CLR реализованы с использованием странной формы стирания, эмулируемой внутри самого компилятора,

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

Автор: Daniel Spiewak Размещён: 24.11.2008 08:29

5 плюса

Насколько я понимаю (будучи парнем в .NET ), JVM не имеет понятия обобщений, поэтому компилятор заменяет параметры типа на Object и выполняет все приведение типов за вас.

Это означает, что универсальные шаблоны Java являются не чем иным, как синтаксическим сахаром и не обеспечивают какого-либо улучшения производительности для типов значений, которые требуют упаковки / распаковки при передаче по ссылке.

Автор: Andrew Kennan Размещён: 24.11.2008 07:25

2 плюса

Есть хорошие объяснения. Я только добавляю пример, чтобы показать, как стирание типов работает с декомпилятором.

Оригинальный класс,

import java.util.ArrayList;
import java.util.List;


public class S<T> {

    T obj; 

    S(T o) {
        obj = o;
    }

    T getob() {
        return obj;
    }

    public static void main(String args[]) {
        List<String> list = new ArrayList<>();
        list.add("Hello");

        // for-each
        for(String s : list) {
            String temp = s;
            System.out.println(temp);
        }

        // stream
        list.forEach(System.out::println);
    }
}

Декомпилированный код из его байт-кода,

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.Consumer;

public class S {

   Object obj;


   S(Object var1) {
      this.obj = var1;
   }

   Object getob() {
      return this.obj;
   }

   public static void main(String[] var0) {

   ArrayList var1 = new ArrayList();
   var1.add("Hello");


   // for-each
   Iterator iterator = var1.iterator();

   while (iterator.hasNext()) {
         String string;
         String string2 = string = (String)iterator.next();
         System.out.println(string2);
   }


   // stream
   PrintStream printStream = System.out;
   Objects.requireNonNull(printStream);
   var1.forEach(printStream::println);


   }
}
Автор: snr Размещён: 08.03.2018 09:23
Вопросы из категории :
32x32