Почему анонимный внутренний класс не содержит ничего из этого кода?

package com.test;

public class OuterClass {
    public class InnerClass {
        public class InnerInnerClass {

        }
    }

    public class InnerClass2 {

    }

    //this class should not exist in OuterClass after dummifying
    private class PrivateInnerClass {
        private String getString() {
            return "hello PrivateInnerClass";
        }
    }

    public String getStringFromPrivateInner() {
        return new PrivateInnerClass().getString();
    }
}

При запуске javac в командной строке с Sun JVM 1.6.0_20 этот код создает 6 файлов .class:

OuterClass.class
OuterClass $1.class
OuterClass $InnerClass.class
OuterClass $InnerClass2.class
OuterClass $InnerClass $InnerInnerClass.class
OuterClass $PrivateInnerClass.class

При запуске через JDT в eclipse он производит только 5 классов.

OuterClass.class
OuterClass $1.classдел >
OuterClass $InnerClass.class
OuterClass $InnerClass2.class
OuterClass $InnerClass $InnerInnerClass.class
OuterClass $PrivateInnerClass.class

При декомпиляции OuterClass$1.class ничего не содержит. Откуда этот дополнительный класс и почему он создан?

Ответ 1

У меня нет ответа, но я могу подтвердить это и уменьшить фрагмент к следующему:

public class OuterClass {
    private class PrivateInnerClass {
    }
    public void instantiate() {
        new PrivateInnerClass();
    }
}

Это создает OuterClass$1.class

Compiled from "OuterClass.java"
class OuterClass$1 extends java.lang.Object{
}

И здесь javap -c для OuterClass.class:

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

public void instantiate();
  Code:
   0:   new     #2; //class OuterClass$PrivateInnerClass
   3:   dup
   4:   aload_0
   5:   aconst_null
   6:   invokespecial #3; //Method OuterClass$PrivateInnerClass."<init>":
                          //(LOuterClass;LOuterClass$1;)V
   9:   pop
   10:  return

}

И для OuterClass$PrivateInnerClass:

Compiled from "OuterClass.java"
class OuterClass$PrivateInnerClass extends java.lang.Object{
final OuterClass this$0;

OuterClass$PrivateInnerClass(OuterClass, OuterClass$1);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokespecial   #1; //Method "<init>":(LOuterClass;)V
   5:   return

}

Как вы можете видеть, синтезированный конструктор принимает аргумент OuterClass$1.

Итак, javac создает конструктор по умолчанию для принятия дополнительного аргумента типа $1, а значение этого аргумента по умолчанию - 5: aconst_null.


Я обнаружил, что $1 не создается, если выполняется одно из следующих условий:

  • Вы делаете public class PrivateInnerClass
  • Вы объявляете конструктор с нулевым значением для PrivateInnerClass
  • Или вы не вызываете new на нем
  • Возможно, другие вещи (например, static вложенные и т.д.).

Возможно связанный

Создайте следующий источник в каталоге с именем test:

package test;
public class testClass
{
    private class Inner
    {
    }
    public testClass()
    {
        Inner in = new Inner();
    }
}

Скомпилируйте файл из родительского каталога javac test/testClass.java

Обратите внимание, что файл testClass$1.class создается в текущем каталоге. Не уверен, почему этот файл даже создан, так как есть также созданный test/testClass$Inner.class.

ОЦЕНКА

Файл testClass$1.class предназначен для фиктивного класса, необходимого для доступа конструктор "для частного конструктора частного внутреннего класса testClass$Inner. Разборки показывают, что полное имя этот класс правильно указан, поэтому непонятно, почему файл класса заканчивается в неправильном каталоге.

Ответ 2

Я использую меньшие фрагменты из полигеновых смазочных материалов.

Помните, что нет понятия вложенных классов в байт-коде; однако байт-код знает о модификаторах доступа. Проблема, которую компилятор пытается обойти здесь, заключается в том, что метод instantiate() должен создать новый экземпляр PrivateInnerClass. Однако OuterClass не имеет доступа к конструктору PrivateInnerClass (OuterClass$PrivateInnerClass будет сгенерирован как класс, защищенный пакетом, без публичного конструктора).

Так что же может сделать компилятор? Очевидным решением является изменение PrivateInnerClass на конструктор, защищенный пакетом. Проблема в том, что это позволит любому другому коду, который взаимодействует с классом, создать новый экземпляр PrivateInnerClass, хотя он явно объявлен как закрытый!

Чтобы попытаться предотвратить это, компилятор javac делает небольшой трюк: вместо того, чтобы сделать регулярный конструктор PrivateInnerClass видимым из других классов, он оставляет его скрытым (на самом деле он вообще не определяет его, но тот же предмет из-за границы). Вместо этого он создает новый конструктор, который получает дополнительный параметр специального типа OuterClass$1.

Теперь, если вы посмотрите на instantiate(), он вызывает этот новый конструктор. Он фактически отправляет null в качестве второго параметра (типа OuterClass$1) - этот параметр используется только для указания того, что этот конструктор является тем, который должен быть вызван.

Итак, зачем создавать новый тип для второго параметра? Почему бы не использовать, скажем, Object? Он используется только для того, чтобы отличать его от обычного конструктора и null передается в любом случае! И ответ заключается в том, что поскольку OuterClass$1 является закрытым для OuterClass, правовой компилятор никогда не позволит пользователю вызывать специальный конструктор OuterClass$PrivateInnerClass, поскольку один из необходимых типов параметров OuterClass$1 скрыт.

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

Ответ 3

Основываясь на ответе полигенных смазочных материалов, я бы предположил, что этот таинственный класс не позволяет кому-либо другому (т.е. вне OuterClass) создавать экземпляр OuterClass$PrivateInnerClass, потому что у них нет доступа к OuterClass$1.

Ответ 4

После поиска я нашел эту ссылку. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6378717

Комментарий ссылается на исходный код, доступный в данной ссылке.

Это не ошибка.

Компилятор пытается решить проблему с доступом. Поскольку внутренняя class Test.Request является частным, его конструктор является закрытым. Это может быть если вы используете -private для javap:

$javap -private Test\$Запрос, скомпилированный из окончательного класса Test.java Test $Request extends java.lang.Object {     final Test this $0;     private Test $Request (Test);     Test $Request (Test, Test $1); }

Однако JVM не позволит анонимному подклассу Coucou (Test $1) доступ к этому частному конструктору. Это фундаментальный разница между JVM и языком программирования Java, когда он приходит к вложенным классам. Язык позволяет вложенным классам получать доступ частные члены окружающего класса.

Первоначально, когда вложенные классы были добавлены на язык, Решение этой проблемы заключалось в том, чтобы сделать пакет конструктора приватным и выглядел бы так:

$javap -private Test\$Запрос, скомпилированный из окончательного класса Test.java Test $Request extends java.lang.Object {     final Test this $0;     Тест $Request (Test); }

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

private Test$Request(Test);

Однако другим вложенным классам должно быть позволено называть это конструктор. Поэтому должен быть предоставлен конструктор доступа. Однако это конструктор доступа должен отличаться от "реального" конструктора. к решить эту проблему, компилятор добавляет дополнительный параметр для доступа конструктор. Тип этого дополнительного параметра должен быть чем-то уникальный, который не конфликтует ни с чем, что может быть у пользователя написано. Таким образом, очевидным решением является добавление анонимного класса и использование что как тип второго параметра:

Test$Request(Test, Test$1);

Однако компилятор умный и повторно использует любой анонимный класс, если один существует. Если вы измените пример, чтобы не включать анонимный класс, вы увидите, что компилятор создаст один:

открытый абстрактный класс Test {     private final class Request {}     закрытый конечный класс OtherRequest {Request test() {return new Request(); }     }}

Если доступ к частному конструктору отсутствует, компилятор не выполняет необходимо создать любой конструктор доступа, который объясняет поведение этот пример:

открытый абстрактный класс Test {     private final class Request {}}

Ответ 5

Еще одно место - если OuterClass$1 уже объявлено пользователем, OuterClass$PrivateInnerClass будет иметь его как аргумент конструктора в любом случае:

public class OuterClass { 

    ... 

    public String getStringFromPrivateInner() { 
        PrivateInnerClass c = new PrivateInnerClass();
        Object o = new Object() {};
        return null;
    }
}

-

public java.lang.String getStringFromPrivateInner();
  Code:
   0:   new     #2; //class OuterClass$PrivateInnerClass
   3:   dup
   4:   aload_0
   5:   aconst_null
   6:   invokespecial   #3; //Method OuterClass$PrivateInnerClass."":
(LOuterClass;LOuterClass$1;)V
   9:   astore_1
   10:  new     #4; //class OuterClass$1
   13:  dup
   14:  aload_0
   15:  invokespecial   #5; //Method OuterClass$1."":(LOuterClass;)V
   18:  astore_2
   19:  aconst_null
   20:  areturn