Литье в сыром виде - PullRequest
       30

Литье в сыром виде

0 голосов
/ 26 апреля 2018

Я застрял с кодом, который я не понимаю, почему он работает.Предположим, что я создаю общий интерфейс Foo<T> следующим образом:

interface Foo<T>{
   void set(T item);
}

Затем я создаю класс с именем Bar, который реализует Foo<String> следующим образом:

class Bar implements Foo<String>{ 
   @override
   public void set(String item){
      //useless body
   }
}

На основедля этого мы можем написать следующий код:

Bar bar = new Bar();
bar.set("Some string");
Foo rawFoo = (Foo) bar;
rawFoo.set(new Object()); // ClassCastException: Object cannot be cast to string

Эта последняя строка - та, которую я на самом деле не получаю.Как известно, при использовании необработанных типов универсальные типы параметров преобразуются в Object.В этом случае код компилируется, и мы можем передать Object в метод set().Но как Java определяет, что она должна приводить Object к String во время выполнения?

Ответы [ 4 ]

0 голосов
/ 26 апреля 2018

Оказывается, ответ на мой вопрос - бридж-методы.В настоящее время я читаю книгу Мориса Нафталина и Филиппа Уодлера «Обобщения и коллекции Java».На самом деле я прочитал раздел о методах моста, но, похоже, я не прочитал его внимательно.Я рад, что у всех ответов была общая точка зрения.Теперь я пойду и прочитаю этот раздел снова

0 голосов
/ 26 апреля 2018

Если вы декомпилируете Bar, используя javap:

class Bar implements Foo<java.lang.String> {
  Bar();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void set(java.lang.String);
    Code:
       0: return

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

void set(java.lang.Object) - это метод искусственного моста.Обратите внимание на инструкцию checkcast.Эквивалентный «реальный» код будет выглядеть следующим образом:

public void set(Object object) {
  set((String) object);
}

Компилятор создал этот метод для того, чтобы класс работал при стирании типа;фактический метод, выполняющий работу, - это set(java.lang.String), которому делегируется метод моста.

Именно этот метод моста на самом деле переопределяет метод в базовом классе.Метод, который вы объявляете с помощью аннотации @Override, является просто перегрузкой.

0 голосов
/ 26 апреля 2018

Обобщения в Java - это функция безопасности типа во время компиляции.Поскольку они были представлены в версии 1.5, они должны были быть обратно совместимы с предыдущими версиями.Это позволяет вам использовать необработанные типы - переменную без параметров типа, ссылающуюся на класс с параметрами типа.

Во время выполнения большая часть информации о параметрах типа стирается, но не вся.На этой странице учебника об удалении типа мы узнаем, что компилятор будет вставлять приведения при необходимости.

  • Замените все параметры типа в универсальных типах их границами или Object, еслипараметры типа не ограничены.Поэтому созданный байт-код содержит только обычные классы, интерфейсы и методы.
  • Вставьте приведение типов в случае необходимости, чтобы сохранить безопасность типов.
  • Создайте методы-мосты, чтобы сохранить полиморфизм в расширенных обобщенных типах.

Здесь компилятор создает метод моста для сохранения полиморфизма во время выполнения, который принимает Object, преобразует его параметр в String, затем вызываетнастоящий set(String) метод.

Когда вы вызываете rawFoo.set(new Object());, из-за полиморфизма этот метод моста вызывается в Bar.Это неявное приведение, которое вы видите здесь как ClassCastException.

0 голосов
/ 26 апреля 2018

Прежде всего, вы просто предоставляете ссылку на существующий объект, вы не создаете новый объект.

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

После стирания типа Foo становится:

interface Foo{
   void set(Object item);
}

и ваш класс становится:

class Bar implements Foo{ 
   @override
   public void set(String item){
      //useless body
   }
}

После стирания типа сигнатуры методов не совпадают.

Следовательно, метод Bar set не реализует метод Foo set.

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

public void set(Object item){
    set((String) data);
}

https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...