Что делает `someObject.new` в Java?

В Java я только что узнал, что следующий код является законным:

KnockKnockServer newServer = new KnockKnockServer();                    
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket);

FYI, приемник - это только класс-помощник со следующей сигнатурой:

public class receiver extends Thread {  /* code_inside */  }

Я никогда раньше не видел нотацию XYZ.new. Как это работает? Есть ли способ кодировать это более условно?

Ответ 1

Это способ создания нестатического внутреннего класса извне содержащего тело класса, как описано в Oracle docs.

Каждый внутренний экземпляр класса связан с экземпляром его содержащего класса. Когда вы new внутренний класс изнутри его содержащего класса, он использует экземпляр this контейнера по умолчанию:

public class Foo {
  int val;
  public Foo(int v) { val = v; }

  class Bar {
    public void printVal() {
      // this is the val belonging to our containing instance
      System.out.println(val);
    }
  }

  public Bar createBar() {
    return new Bar(); // equivalent of this.new Bar()
  }
}

Но если вы хотите создать экземпляр Bar вне Foo или связать новый экземпляр с содержащим экземпляром, отличным от this, тогда вы должны использовать префиксную нотацию.

Foo f = new Foo(5);
Foo.Bar b = f.new Bar();
b.printVal(); // prints 5

Ответ 2

Взгляните на этот пример:

public class Test {

    class TestInner{

    }

    public TestInner method(){
        return new TestInner();
    }

    public static void main(String[] args) throws Exception{
        Test t = new Test();
        Test.TestInner ti = t.new TestInner();
    }
}

Используя javap, мы можем просмотреть инструкции, сгенерированные для этого кода

Основной метод:

public static void main(java.lang.String[])   throws java.lang.Exception;
  Code:
   0:   new     #2; //class Test
   3:   dup
   4:   invokespecial   #3; //Method "<init>":()V
   7:   astore_1
   8:   new     #4; //class Test$TestInner
   11:  dup
   12:  aload_1
   13:  dup
   14:  invokevirtual   #5; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   17:  pop
   18:  invokespecial   #6; //Method Test$TestInner."<init>":(LTest;)V
   21:  astore_2
   22:  return
}

Внутренний конструктор классов:

Test$TestInner(Test);
  Code:
   0:   aload_0
   1:   aload_1
   2:   putfield        #1; //Field this$0:LTest;
   5:   aload_0
   6:   invokespecial   #2; //Method java/lang/Object."<init>":()V
   9:   return

}

Все просто: при вызове конструктора TestInner java передает тестовый экземпляр в качестве первого аргумента main: 12. Не смотря на то, что TestInner не должен иметь конструктора аргументов. TestInner, в свою очередь, просто сохраняет ссылку на родительский объект, Test $TestInner: 2. Когда вы вызываете внутренний конструктор классов из метода экземпляра, ссылка на родительский объект проходит автоматически, поэтому вам не нужно указывать его. Фактически его проходит каждый раз, но при вызове извне он должен быть явно передан.

t.new TestInner(); - это просто способ указать первый скрытый аргумент для конструктора TestInner, а не тип

метод() равен:

public TestInner method(){
    return this.new TestInner();
}

TestInner равен:

class TestInner{
    private Test this$0;

    TestInner(Test parent){
        this.this$0 = parent;
    }
}

Ответ 3

Когда внутренние классы были добавлены в Java в версии 1.1 языка, они были первоначально определены как преобразование в 1.0 совместимый код. Если вы посмотрите на пример этого преобразования, я думаю, что он значительно упростит работу внутреннего класса.

Рассмотрим код ответа Яна Робертса:

public class Foo {
  int val;
  public Foo(int v) { val = v; }

  class Bar {
    public void printVal() {
      System.out.println(val);
    }
  }

  public Bar createBar() {
    return new Bar();
  }
}

При преобразовании в 1.0 совместимый код этот внутренний класс Bar станет примерно таким:

class Foo$Bar {
  private Foo this$0;

  Foo$Bar(Foo outerThis) {
    this.this$0 = outerThis;
  }

  public void printVal() {
    System.out.println(this$0.val);
  }
}

Внутреннее имя класса имеет префикс с именем внешнего класса, чтобы сделать его уникальным. Добавляется скрытый закрытый член this$0, который содержит копию внешнего this. И для инициализации этого элемента создается скрытый конструктор.

И если вы посмотрите на метод createBar, он будет преобразован в нечто вроде этого:

public Foo$Bar createBar() {
  return new Foo$Bar(this);
}

Итак, посмотрим, что произойдет, когда вы выполните следующий код.

Foo f = new Foo(5);
Foo.Bar b = f.createBar();                               
b.printVal();

Сначала мы создаем экземпляр Foo и вводим член val в 5 (т.е. f.val = 5).

Далее мы вызываем f.createBar(), который создает экземпляр Foo$Bar и инициализирует член this$0 значением this, переданным из createBar (т.е. b.this$0 = f).

Наконец, мы вызываем b.printVal(), который пытается напечатать b.this$0.val, который равен f.val, который равен 5.

Теперь это было регулярное создание внутреннего класса. Посмотрите, что происходит при создании Bar извне Foo.

Foo f = new Foo(5);
Foo.Bar b = f.new Bar();
b.printVal();

Снова применив наше преобразование 1.0, эта вторая строка станет примерно такой:

Foo$Bar b = new Foo$Bar(f);

Это почти идентично вызову f.createBar(). Снова мы создаем экземпляр Foo$Bar и инициализируем член this$0 равным f. Итак, снова b.this$0 = f.

И снова, когда вы вызываете b.printVal(), вы печатаете b.thi$0.val, который равен f.val, который равен 5.

Главное помнить, что внутренний класс имеет скрытый элемент, содержащий копию this из внешнего класса. Когда вы создаете внутренний класс из внешнего класса, он неявно инициализируется текущим значением this. Когда вы создаете экземпляр внутреннего класса извне внешнего класса, вы явно указываете, какой экземпляр внешнего класса использовать, используя префикс в ключе new.

Ответ 4

Подумайте о new receiver как о единственном токене. Вид вроде имени функции с пробелом в ней.

Конечно, класс KnockKnockServer буквально не имеет функции с именем new receiver, но я предполагаю, что синтаксис предполагает это. Это означало, что вы вызываете функцию, которая создает новый экземпляр KnockKnockServer.receiver, используя конкретный экземпляр KnockKnockServer для любых обращений к окружающему классу.

Ответ 5

Затенение

Если объявление типа (например, переменная-член или имя параметра) в определенной области (например, внутренний класс или определение метода) имеет то же имя, что и другое объявление в охватывающей области, то декларация затеняет объявление вложенной области. Вы не можете ссылаться на теневую декларацию только по имени. Следующий пример, ShadowTest, демонстрирует это:

public class ShadowTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
        }
    }

    public static void main(String... args) {
        ShadowTest st = new ShadowTest();
        ShadowTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

Ниже приведен результат этого примера:

x = 23
this.x = 1
ShadowTest.this.x = 0

В этом примере определяются три переменные с именем x: переменная-член класса ShadowTest, переменная-член внутреннего класса FirstLevel и параметр метода методаInFirstLevel. Переменная x, определяемая как параметр метода methodInFirstLevel, изменяет переменную внутреннего класса FirstLevel. Следовательно, когда вы используете переменную x в методе методаInFirstLevel, она ссылается на параметр метода. Чтобы ссылаться на переменную-член внутреннего класса FirstLevel, используйте ключевое слово this, чтобы представить охватывающую область:

System.out.println("this.x = " + this.x);

Обратитесь к переменным-членам, которые заключают в себя большие области действия по имени класса, к которому они принадлежат. Например, следующая инструкция обращается к переменной-члену класса ShadowTest из метода methodInFirstLevel:

System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);

Обратитесь к документам