2013-03-30 3 views
100

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

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

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

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

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

+7

Для справки [внутренний класс] (http://docs.oracle.com/javase /tutorial/java/javaOO/nested.html). –

+1

Кроме того, я считал, что 'new' является оператором на многих языках. (Я думал, вы также можете перегружать 'new' в C++?) Внутренний класс Java для меня немного странный. –

+5

В StackOverflow нет глупых вопросов! –

ответ

120

Это способ создания нестатического внутреннего класса извне содержащего класс класса, как описано в 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() 
    } 
} 

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

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

А теперь выясняется - Спасибо, очень много! – Coffee

+18

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

+10

@ EricJablow действительно, это один из тех битов синтаксиса, который должен существовать, чтобы поддерживать спецификацию, но 99,9999% времени, когда вам действительно не нужно ее использовать. Если посторонним действительно нужно создавать экземпляры Bar, я бы предоставил фабричный метод для Foo, вместо того, чтобы использовать их 'f.new'. –

4

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

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

+0

Спасибо, да - это помогает мне теперь думать о «новом приемнике» как о одном токене! Огромное спасибо! – Coffee

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 передает тестовый экземпляр в качестве первого аргумента 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; 
    } 
} 
+1

Большое спасибо! – Coffee

7

Когда внутренние классы были добавлены в Java в версии sion 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 и intialise на 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.

1

Shadowing

Если объявление типа (например, переменной-члена или имя параметра) в определенной области (например, как внутренний класс или определения метода) имеет такое же имя, как другой декларации в охватывающей области, тогда объявление затеняет объявление области охвата. Вы не можете ссылаться на теневую декларацию только по имени. В следующем примере, 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 

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

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

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

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

Refer to the docs

Смежные вопросы