Почему анонимный внутренний класс не содержит ничего из этого кода?
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