Стирание типа Java и множественные границы

Я знаю, что в дженериках Java при использовании параметра типа с несколькими границами компилятор стирает информацию о типе до «крайней левой границы» (т.е. первого класса/перечисления или интерфейса в списке). Так почему же следующий код компилируется без проблем?

public class Generic<T extends Object & Appendable & AutoCloseable> {

  T t;

  T method() throws Exception {
    t.close();
    char c='\u0000';
    t.append(c);
    return t;
  }

  public <T> T method2(T t) {
    return t;
  }  

}

разве параметр типа T не должен рассматриваться как объект ?? (таким образом запрещая мне вызывать close() или append())??


person Luca    schedule 26.12.2015    source источник
comment
Какую часть вы не понимаете?method?   -  person SMA    schedule 26.12.2015
comment
@SMA переменная, тип которой является параметром типа, должна иметь возможность использовать только элементы типа стирания параметра (который, как говорят, является крайней левой границей в списке). В моем коде это не так.   -  person Luca    schedule 26.12.2015
comment
У компилятора с этим проблем нет. Не лучше ли спросить, почему среда выполнения может вызывать t.close() несмотря на стирание типа?   -  person wero    schedule 26.12.2015
comment
@wero с чего бы это? этот тип стирается до AutoCloseable, и OP ожидает, что он будет стерт до Object. также см. мой ответ (это еще не ответ)   -  person Eugene    schedule 17.05.2018


Ответы (4)


Вы должны прочитать о методах моста здесь

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

person Ruslan Neruka    schedule 26.12.2015
comment
Я знаю о них. Это не тот вопрос, который я задавал, я не расширяю универсальный класс и не реализую универсальный интерфейс в своем коде. - person Luca; 26.12.2015
comment
хорошо, поэтому я использую jd-gui, чтобы заглянуть внутрь файла .class и нашел такой код T method() throws Exception { ((AutoCloseable)this.t).close(); char c = '\000'; ((Appendable)this.t).append(c); return (T)this.t; }. Извините за многократное редактирование - person Ruslan Neruka; 26.12.2015

Ваш случай содержит множественное связывание.

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

Поэтому вызов метода close(), содержащегося в интерфейсе AutoCloseable, и append(), содержащегося в интерфейсе Appendable, синтаксически допустим.

person Laszlo Hirdi    schedule 26.12.2015
comment
это действительно подтип (поскольку компилятор не позволяет передавать аргумент типа, который не является подтипом всех типов в списке), но поскольку параметр типа стирается до типа (компилятор не может заменить несколько типов в байт-коде) Я должен иметь возможность вызывать только членов типа стирания. - person Luca; 26.12.2015
comment
Вся информация о типах доступна для компилятора во время компиляции. Вот почему он компилируется. Информация о типе теряется только в байтовом коде. Интересный вопрос: что если вы повторно используете скомпилированный двоичный класс в качестве зависимости в другой единице компиляции. - person Laszlo Hirdi; 26.12.2015

Это дизассемблированный ваш код. Переменная t — это тип объекта. Инструкция checkcast генерируется перед вызовом метода интерфейса. Если значение t не реализует интерфейс, будет выброшено исключение ClassCastException.

Compiled from "Generic.java"
public class Generic<T extends java.lang.Appendable & java.lang.AutoCloseable> {
  T t;

  public Generic();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  T method() throws java.lang.Exception;
    Code:
       0: aload_0
       1: getfield      #2                  // Field t:Ljava/lang/Object;
       4: checkcast     #3                  // class java/lang/AutoCloseable
       7: invokeinterface #4,  1            // InterfaceMethod java/lang/AutoCloseable.close:()V
      12: iconst_0
      13: istore_1
      14: aload_0
      15: getfield      #2                  // Field t:Ljava/lang/Object;
      18: checkcast     #5                  // class java/lang/Appendable
      21: iload_1
      22: invokeinterface #6,  2            // InterfaceMethod java/lang/Appendable.append:(C)Ljava/lang/Appendable;
      27: pop
      28: aload_0
      29: getfield      #2                  // Field t:Ljava/lang/Object;
      32: areturn

  public <T> T method2(T);
    Code:
       0: aload_1
       1: areturn
}
person saka1029    schedule 26.12.2015
comment
разве это не должно быть java.lang.Appendable? Я понимаю приведения, но иначе, что означает стирание до крайней левой границы?? - person Luca; 26.12.2015

Это не ответ как таковой, просто это крайнее левое правило не совсем актуально - я все еще пытаюсь выяснить правильные JLS части для этого (не так просто, как я надеюсь) , но отсюда видно, что тип не Object:

<T extends Object & Appendable & AutoCloseable> void whatType(T... args) {
    System.out.println(args.getClass().getComponentType().getSimpleName());
}


new DeleteMe().whatType(); // prints AutoCloseable
person Eugene    schedule 17.05.2018
comment
То, что вы показываете, — это решение компилятора для фактического типа массива на сайте вызова. Тип аргумента стертого метода по-прежнему Object[]. Разные вызывающие объекты могут использовать разные типы массивов; вы можете добавить другого вызывающего абонента whatType(new StringWriter()), и этот вызывающий абонент будет использовать StringWriter[]. И правило, лежащее в основе этого решения, — это совершенно другой вопрос, поскольку ОП прав, предполагая, что T будет стерт до Object, хотя я не знаю, почему ОП подумал, что это имеет какое-то значение. - person Holger; 18.05.2018
comment
@Holger, спасибо, поэтому он всегда стирается до Object.. но почему <T extends AutoCloseable & Runnable> для этого используется тип AutoCloseable, а для <T extends AutoCloseable & Serializable> - Serializable? Где здесь находится крайняя левая черта? - person Eugene; 18.05.2018
comment
Я не знаю, какую стратегию использует здесь javac, и спецификация, похоже, не подтверждает это. Но очень интересно, что такое поведение приводит к тому, что несовместимые назначения массивов (Serializable[] передаются формальным параметрам типа AutoCloseable[]) остаются незамеченными JVM, в то время как явное приведение к тому, что уже кажется, не удастся. Но, как уже было сказано, это совершенно актуальный вопрос (и, похоже, он открывает действительно большую кроличью нору). - person Holger; 18.05.2018
comment
@Holger Я не хочу брать на себя ответственность здесь ... вопрос изначально был размещен здесь stackoverflow.com/questions/50177313/ и пока там это ответ с большим количеством голосов, мне трудно его понять - person Eugene; 18.05.2018