Почему анонимный внутренний класс не содержит ничего, сгенерированного из этого кода? - PullRequest
33 голосов
/ 21 мая 2010
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 ничего не содержит. Откуда взялся этот дополнительный класс и почему он создан?

Ответы [ 5 ]

26 голосов
/ 21 мая 2010

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

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

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

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

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

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

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

12 голосов
/ 21 мая 2010

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

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. Разборка показывает, что полное имя этот класс правильно отмечен, поэтому неясно, почему заканчивается файл класса в неправильном каталоге.

7 голосов
/ 21 мая 2010

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

4 голосов
/ 09 июня 2016

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

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

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

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

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

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

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

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

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

private Test$Request(Test);

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

Test$Request(Test, Test$1);

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

публичный абстрактный класс Test { закрытый финал Класс Запрос {} закрытый финальный класс OtherRequest {Request test () {return new Request (); } }}

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

публичный абстрактный класс Test { закрытый выпускной запрос {}}

0 голосов
/ 21 мая 2010

Еще одна точка - если 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;L<b>OuterClass$1</b>;)V
   9:   astore_1
   10:  new     #4; //class <b>OuterClass$1</b>
   13:  dup
   14:  aload_0
   15:  invokespecial   #5; //Method OuterClass$1."":(LOuterClass;)V
   18:  astore_2
   19:  aconst_null
   20:  areturn
...