Що робить `someObject.new` на Java?


100

У Java я щойно з’ясував, що наступний код є законним:

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

FYI, приймач - це лише допоміжний клас із таким підписом:

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

Я ніколи раніше не бачив XYZ.newпозначень. Як це працює? Чи є спосіб кодувати це більш умовно?



1
Крім того, я вважав, що newце оператор на багатьох мовах. (Я думав, що ви також можете перевантажуватися newC ++?) Внутрішній клас Java для мене дещо дивний.
Елвін Вонг

5
На StackOverflow немає дурних питань!
Ісаак Рабінович

2
@IsaacRabinovitch - Дурних питань немає. Однак дурних багато. (І також епізодична дурна відповідь.)
Гарячий лизає

2
@HotLicks А яке твоє визначення дурного питання? Думаю, такого, якого ти занадто розумний, щоб його вимагати. Добре, що у вас така велика самооцінка.
Ісаак Рабінович

Відповіді:


120

Це спосіб інсталювати нестатичний внутрішній клас ззовні тіла, що містить клас, як описано в документах Oracle .

Кожен екземпляр внутрішнього класу асоціюється з екземпляром класу, що містить його. Коли 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

18
І, як ви можете сказати, це може бути неймовірно заплутаним. В ідеалі внутрішні класи повинні бути деталями реалізації зовнішнього класу та не піддаватися впливу навколишнього світу.
Ерік Яблов

10
@EricJablow дійсно, це один з тих біт синтаксису, який повинен існувати, щоб зберегти специфікацію послідовною, але 99,9999% часу фактично не потрібно її використовувати. Якщо стороннім особам справді потрібно створити екземпляри Bar, тоді я б запропонував заводський метод на Foo, а не їх використовувати f.new.
Ян Робертс

Виправте мене, якщо я помиляюся, але якби рівень publicдоступу KnockKnockServer.receiverбув зроблений, privateнеможливо таким чином створити інстанцію, правда? Щоб розширити коментар @EricJablow, внутрішні класи, як правило, завжди мають типовий privateрівень доступу.
Ендрю Бісселл

1
@AndrewBissell так, але це також було б неможливо receiverвзагалі звернутися до класу. Якби я його розробляв, я, мабуть, мав би клас class public, але його конструктор захищений або пакет-приватний, і я мав би метод KnockKnockServerстворити екземпляри приймача.
Ян Робертс

1
@emory Я не кажу про це, я знаю, що можуть бути цілком обґрунтовані причини, щоб хотіти зробити внутрішній клас загальнодоступним і повернути екземпляри внутрішнього класу з методів зовнішнього, але я схильний створити свій код таким, щоб " стороннім особам "не потрібно безпосередньо створювати екземпляри внутрішнього класу, використовуючи x.new.
Ян Робертс

18

Подивіться на цей приклад:

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 передає тестовий екземпляр як основний аргумент : 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;
    }
}

7

Коли внутрішні класи були додані до 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для будь-якого доступу до класу, що додає.


Дякую, так - це допомагає мені зараз вважати new receiverце одним символом! дуже дякую!
Кава

1

Тінізація

Якщо декларація типу (наприклад, змінної члена або імені параметра) у певній області (наприклад, внутрішній клас чи визначення методу) має те саме ім'я, що й інше оголошення в області, що додається, то декларація затінює декларацію додаткового обсягу. Ви не можете посилатися на тіньову декларацію лише за її іменем. Наступний приклад, 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 та параметр у методі methodInFirstLevel. Змінна x, визначена як параметр методу methodInFirstLevel, затінює змінну внутрішнього класу FirstLevel. Отже, коли ви використовуєте змінну x у методі methodInFirstLevel, вона посилається на параметр методу. Щоб звернутися до змінної члена внутрішнього класу FirstLevel, використовуйте ключове слово this, щоб представити область, що додається:

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

Зверніться до змінних членів, які додають великі області до назви класу, якому вони належать. Наприклад, наступне твердження звертається до змінної члена класу ShadowTest з методу methodInFirstLevel:

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

Зверніться до документів

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.