Является ли «T.super» допустимым выражением согласно JLS?

Рассмотрим следующий набор выражений:

class T {{
/*1*/   Object o = T.super; // error: '.' expected
/*2*/   o.toString();
}}

Попытка скомпилировать это будет неудачной в строке /*1*/ с ошибкой:

error: '.' expected
    o = T.super;
               ^

как при использовании OpenJDK 1.8.0 (Ubuntu), так и Oracle JDK 1.8 (Windows).

Однако Eclipse 4.5.0 (Mars) компилирует это без ошибок, и в результате получается:

class T {
    T();
     0  aload_0 [this]
     1  invokespecial java.lang.Object() [8] // super()
     4  aload_0 [this]
     5  astore_1 [o]  // o = T.super
     7  invokevirtual java.lang.Object.toString() : java.lang.String [10]
    10  pop           // ^-- o.toString()
    11  return
}

Из этого вы можете видеть, что строка /*1*/ кода Java (строка 5 результата) правильно сохраняет this, приведенный как Object (понимание Eclipse T.super), в локальную переменную o. Когда код выполняется, он завершается нормально, и строка /*2*/ дает правильный результат.

Пока мне не удалось найти ничего, относящегося к o = T.super;, в Спецификации языка Java 8, то есть законно это или нет. Поскольку в нем прямо не указано, что это допустимое выражение, я предполагаю, что это означает, что оно незаконно. Но тогда почему Eclipse считает это законным? Отсюда мой вопрос:

Является ли T.super допустимым выражением согласно JLS?


Редактировать: упростили код, удалив внутренний класс-оболочку.


person charlie    schedule 07.01.2016    source источник
comment
Eclise Mars 4.5.1, по-видимому, тоже отлично компилирует. Но javac 1.8.0_66 действительно терпит неудачу.   -  person Tunaki    schedule 07.01.2016
comment
Я не могу говорить за JLS, но скомпилированный код должен по-прежнему печатать toString для экземпляра T, если toString переопределен.   -  person Sam Sun    schedule 07.01.2016
comment
JLS 15.11.2 определяет только super.identifier и T.super.identifier. Для меня это означает, что T.super не определен (и super не может быть идентификатором, поскольку это ключевое слово). Поэтому я бы рассматривал T.super как неопределенный (и, следовательно, как ошибку времени компиляции).   -  person Joachim Sauer    schedule 07.01.2016
comment
Для чего нужны двойные скобки? Я что-то упускаю?   -  person Fund Monica's Lawsuit    schedule 07.01.2016
comment
@QPaysTaxes, это инициализатор. Иногда брекеты размещаются таким образом, поэтому их трудно распознать, если вы этого не видели.   -  person Sergei Tachenov    schedule 07.01.2016
comment
Кстати, @QPaysTaxes часто используется для заполнения коллекции: new ArrayList<>() {{add(something);}}. Не очень элегантный способ, поскольку он создает ненужный анонимный класс, но все же.   -  person Sergei Tachenov    schedule 07.01.2016


Ответы (4)


Нет, это не так. См. главу 19. Поиск по ключевому слову super дает следующие конструкции:

  • границы подстановочных знаков: extends T / super T;
  • явный вызов конструктора: super(args);
  • доступ к полю: [Typename.]super.field;
  • вызов метода: [Typename.]super.method();
  • ссылка на метод: super::method.

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

person Sergei Tachenov    schedule 07.01.2016
comment
Да, я провел поиск по всему JLS и также не нашел подходящего синтаксиса. Но значит ли это, что это незаконно или просто не указано? - person charlie; 07.01.2016
comment
@charlie, глава 19 - это полная грамматика. Если его там нет, это недопустимый синтаксис. Если бы это было технически разрешено грамматикой, но не было описано в других главах, то можно было бы сказать, что оно не определено. Но как бы то ни было, это просто недопустимая языковая конструкция. - person Sergei Tachenov; 07.01.2016
comment
Расширение языка является ошибкой. Особенно в этом случае, так как это не дает никакой пользы. В любом сценарии, где компилируется WhateverType x=Outer.super;, соответствующий стандарту WhateverType x=Outer.this; будет делать то же самое. - person Holger; 07.01.2016
comment
@ Хольгер, согласен. И даже если бы он делал что-то вроде того, чтобы x действовал позже, как super (то есть действительно влиял на разрешение метода), то это было бы еще более опасно и запутанно. Поэтому, когда я говорю об ошибке или языковом расширении, я, по сути, имею в виду, что это разные названия одного и того же. Однако лучше отредактировать, чтобы уточнить это. - person Sergei Tachenov; 07.01.2016
comment
Ну, а есть ли официальное заявление о необходимости строгого соблюдения синтаксиса, например: A compliant compiler must fail on syntax extensions. Пожалуйста, дайте ссылку, если можно. Тогда пришло время сообщить об ошибке в Eclipse (предположительно). - person charlie; 07.01.2016
comment
@charlie, разве это не очевидно? Я не думаю, что есть какая-то причина размещать подобное заявление где-либо. Вы можете придумать множество совершенно сумасшедших конструкций и реализовать их в своем компиляторе, будет ли это по-прежнему Java? Этот конкретный выглядит достаточно безобидным, но, поскольку в JLS нет определения безвредности, где будет граница? - person Sergei Tachenov; 08.01.2016
comment
@SergeyTachenov: Хорошо, я буду придерживаться этого. - person charlie; 11.01.2016

T.super недопустимое выражение, потому что оно не имеет смысла. Синтаксис super (с явным типом или без него) используется только для вызова методов из суперкласса. T.super не вызывает никакого метода (если бы это был допустимый синтаксис), он только ссылается на экземпляр класса. В вашем случае он ссылается на внешний экземпляр T. Правильным синтаксисом будет T.this, который указывает на внешний this.

Ваше определение класса можно просмотреть следующим образом (с именованным внутренним классом):

class Outer
{
    // Constructor Body
    {
        class Inner
        {{
            /*1*/   Outer o = Outer.super; // error: '.' expected
            /*2*/   o.toString();
        }};
        new Inner();
    }
}

Правильным способом ссылки на внешний класс будет

Outer o = Outer.this; // valid
person Clashsoft    schedule 07.01.2016
comment
Не совсем так, хотя Inner.super и Outer.this (в псевдокоде) относятся к типу Outer, Outer.super в вашем примере относится к типу Object, т. е. к суперклассу Outer. - person charlie; 07.01.2016
comment
Почему Inner.super будет Outer? Inner не расширяет Outer. Кроме того, в моем примере переменная o имеет тип Object, а не выражение, которое ей присвоено. - person Clashsoft; 07.01.2016
comment
Извините, я пропустил тот факт, что Inner не наследуется от Outer. Тем не менее, вторая часть верна, Outer.this относится к типу Outer, а Outer.super относится к типу Object (или суперклассу, если он есть). Итак, ваш пример после того, как вы изменили Object o на Outer o, больше не компилируется в Eclipse. - person charlie; 07.01.2016
comment
Кроме того, Outer.super.toString() является допустимым и отличается от Outer.this.toString(), так как первое разрешается в Object.toString(), а второе в Outer.toString(), хотя оба применяются к внешнему экземпляру (возможно, лучшим примером будет статический метод). И оба отличаются от super.toString(), так как это применяется к внутреннему экземпляру. - person charlie; 07.01.2016
comment
@charlie: это обсуждение - пустая трата ресурсов. Поскольку Outer.super не является допустимым выражением, нет смысла говорить, что оно имеет тип Object. Конструкцию Outer.super.identifier можно использовать для выбора членов супертипа Outer, но доступ к ним по-прежнему осуществляется в экземпляре типа Outer. Расширяющее преобразование не происходит. - person Holger; 07.01.2016
comment
@Holger: Я согласен, моя точка зрения заключалась в том, что я не могу просто заменить его на Outer.this, поскольку Outer.super ведет себя как приведение (Object) Outer.this. И суть вопроса заключалась не в том, чтобы найти подходящую замену, а в том, незаконна ли она или просто не определена, когда Eclipse вполне с ней справляется. - person charlie; 07.01.2016
comment
@charlie, нет причин полагать, что это ведет себя как расширяющийся состав. Потому что, если вы сделаете Object x = Outer.this, расширяющая конверсия все равно произойдет. Object можно назначить что угодно без приведения. Кроме того, такое приведение все равно не дало бы эффекта, поскольку все функции в Java и так виртуальные. - person Sergei Tachenov; 08.01.2016
comment
@SergeyTachenov: Дело не в назначении, Outer.super просто не позволяет ссылаться ни на что из Outer, только на родителя. Outer o = Outer.super; недействителен даже в Eclipse. - person charlie; 08.01.2016
comment
@Чарли, понятно. И все же String s = Outer.super.toString() и Object x = Outer.super; String s = x.toString(); не не одно и то же, не так ли, даже в Eclipse? Это еще одна причина считать это ошибкой. - person Sergei Tachenov; 08.01.2016
comment
@SergeyTachenov: Это одно и то же с одинаковым результатом. Первый по какой-то причине использует синтетический метод доступа, поэтому он преобразуется в Outer.access$0(Outer.this), где String access$0(Outer arg 0) {return arg0.toString();}. Последнее переводится как (x = (Object) Outer.this).toString(). - person charlie; 08.01.2016
comment
@charlie, в этом конкретном примере они могут быть одинаковыми, но я говорю об общем значении. Предположим, что Outer переопределяет toString(). Тогда System.out.println(Outer.super.toString()) проигнорирует переопределение и напечатает что-то вроде package.Outer@12345, в то время как Object x = Outer.super; System.out.println(x.toString());, вероятно, напечатает все, что возвращает метод переопределения. - person Sergei Tachenov; 08.01.2016
comment
@Сергей Таченов: верно. Конструкция Outer.super.toString() позволяет игнорировать реализацию toString(), содержащуюся в классе Outer. Вот почему синтетический метод доступа был сгенерирован в классе Outer, так как только сам объявляющий класс может игнорировать переопределяющий метод, поэтому внутреннему классу нужна эта помощь, чтобы сделать это. Напротив, не имеет значения, имеет ли переменная x тип Outer или Object, вызов x.toString() всегда будет вызывать наиболее конкретный метод, т. е. метод в Outer, если он есть. - person Holger; 08.01.2016
comment
@Holger: А, понятно, вот почему синтетический метод доступа использует invokespecial на Object::toString с Outer.this в качестве цели, а x.toString() использует invokevirtual, хотя снова на Object::toString с Outer.this в качестве цели. Спасибо, это отвечает на связанный вопрос который я только что выложил. ;о) - person charlie; 08.01.2016
comment
Ну, нет, похоже, это не отвечает на другой вопрос, поскольку все вызовы super находятся во внешнем (объявляющем) классе. Там должна быть другая причина... - person charlie; 08.01.2016

Принятие этого синтаксиса было давней ошибкой в ​​Eclipse, которая была исправлена ​​на этапе 5 до Eclipse 4.6.

person Stephan Herrmann    schedule 10.01.2016
comment
Спасибо за исправление! Просто короткий вопрос: в отчете об ошибке указано: problem is specific to field initialization (in an initializer or ctor). Но, по крайней мере, в 4.5.0M3 это было законным назначением в любом месте кода, например. public String toString() {Object o = ""; return (o = T.super).toString();}. Итак, чтобы быть уверенным, применимо ли исправление ко всем таким незаконным случаям? - person charlie; 11.01.2016
comment
@charlie, заявление об инициализации поля было отвлекающим маневром. С исправлением T.super может упоминаться только как получатель поля, вызова метода или ссылки на метод, поэтому я думаю, что это решение завершено (я только что проверил ваш пример toString(), и он правильно отклонен в HEAD). - person Stephan Herrmann; 12.01.2016

Это правильно согласно JLS. Спецификация JLS «Форма super.Identifier относится к полю с именем Identifier текущего объекта, но с текущим объектом, рассматриваемым как экземпляр суперкласса текущего класса».

На основе этого супера является экземпляром суперкласса.

Также JLS заявляет, что «это ошибка времени компиляции, если текущий класс не является внутренним классом класса T или самого T».

В вашем случае это явно внутренний класс. Можете ли вы узнать версию Java, используемую в eclipse.

person Ashraff Ali Wahab    schedule 07.01.2016
comment
Вопрос не в super.Identifier, а в форме без идентификатора, что не правильно. - person Sergei Tachenov; 07.01.2016
comment
Верный. Но если вы хотите получить идентификатор из super, super должен неявно содержать экземпляр суперкласса. Ты согласен? - person Ashraff Ali Wahab; 07.01.2016
comment
Нет. Потому что super.Identifier и Primary.Identifier синтаксически разные конструкции. Последний на самом деле оценивает что-то, и вы получаете доступ к члену этого чего-то. Первый — особенный. В нем super фактически относится к this, отличается только алгоритм выбора участников. - person Sergei Tachenov; 07.01.2016
comment
@AshraffAliWahab super не должен содержать экземпляр суперкласса. super не содержит экземпляра суперкласса, поскольку существует только один экземпляр (this один). super — это ключевое слово, которое просто указывает компилятору использовать код из суперкласса (в зависимости от контекста, в котором он используется). - person Pshemo; 07.01.2016
comment
Возможно, я понял это по-другому. Форма super.Identifier относится к полю с именем Identifier текущего объекта, но при этом текущий объект рассматривается как экземпляр суперкласса текущего класса. Заявление от JLS. Если вы посмотрите на комментарии @Sam Sun в вопросе, он правильно напечатал экземпляр T. - person Ashraff Ali Wahab; 07.01.2016
comment
Это просто означает, что объект this интерпретируется так, как будто он на самом деле является экземпляром суперкласса, без учета переопределений. Но слова текущий объект относятся к this, а не к ключевому слову super, которое является ключевым словом, а не какой-либо специальной переменной. - person Sergei Tachenov; 07.01.2016
comment
Другими словами, он действительно определяет как просматривается текущий объект, но это не означает, что он оценивается как текущий объект. - person Sergei Tachenov; 07.01.2016
comment
Но форма T.super.Identifier на самом деле относится к другому экземпляру — экземпляру окружающего класса, который не обязательно является суперклассом this. И событие, если это суперкласс, это экземпляр, отличный от super. - person charlie; 07.01.2016
comment
Другими словами, если у вас есть класс Outer и внутренний класс Inner, который не наследуется от Outer, то Outher.this != super. Я обновлю вопрос, чтобы было ясно, что наследование (как в случае анонимного класса) не имеет решающего значения. - person charlie; 07.01.2016
comment
@charlie, мы обсуждали здесь форму super.Identifier, поэтому Outer не совсем относится к этому конкретному обсуждению. Это только означает, что этот ответ еще менее актуален. - person Sergei Tachenov; 07.01.2016
comment
@SergeyTachenov: Да, это была первоначальная путаница T.super и super в этом ответе, которую я пытался подчеркнуть. Первый идет вне к окружающим классам, последний вверх к суперклассам в иерархии наследования. Возможно, использование имен классов из другого ответа было не лучшей идеей. - person charlie; 07.01.2016