В Java я только что обнаружил, что следующий код является допустимым:
KnockKnockServer newServer = new KnockKnockServer();
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket);
К вашему сведению, получатель - это просто вспомогательный класс со следующей подписью:
public class receiver extends Thread { /* code_inside */ }
Я никогда раньше не видел XYZ.new
обозначений. Как это работает? Есть ли способ написать более традиционный код?
new
это оператор на многих языках. (Я думал, вы также можете перегрузитьnew
в C ++?) Однако внутренний класс Java для меня немного странный.Ответы:
Это способ создания нестатического внутреннего класса вне тела содержащего класса, как описано в документации Oracle .
Каждый экземпляр внутреннего класса связан с экземпляром содержащего его класса. Когда
new
внутренний класс из внутри содержащего его класса он используетthis
экземпляр контейнера по умолчанию:Но если вы хотите создать экземпляр Bar вне Foo или связать новый экземпляр с содержащим его экземпляром, отличным от
this
этого, вам необходимо использовать префиксную нотацию.источник
f.new
.public
уровень доступаKnockKnockServer.receiver
был включен,private
то было бы невозможно создать экземпляр таким образом, верно? Чтобы расширить комментарий @EricJablow, внутренние классы обычно всегда должны по умолчанию использоватьprivate
уровень доступа.receiver
класс извне. Если бы я его разрабатывал, у меня, вероятно, был бы класс public, но его конструктор был бы защищенным или закрытым для пакета, и имел бы методKnockKnockServer
для создания экземпляров получателя.x.new
.Взгляните на этот пример:
Используя javap, мы можем просматривать инструкции, созданные для этого кода
Основной метод:
Конструктор внутреннего класса:
Все просто - при вызове конструктора TestInner java передает экземпляр Test в качестве первого аргумента main: 12 . Не смотря на то, что TestInner не должен иметь конструктора аргументов. TestInner, в свою очередь, просто сохраняет ссылку на родительский объект Test $ TestInner: 2 . Когда вы вызываете конструктор внутреннего класса из метода экземпляра, ссылка на родительский объект передается автоматически, поэтому вам не нужно указывать его. На самом деле он проходит каждый раз, но при вызове извне его следует передавать явно.
t.new TestInner();
- это просто способ указать первый скрытый аргумент конструктора TestInner, а не типmethod () равен:
TestInner невероятно похож на:
источник
Когда внутренние классы были добавлены в Java в версии 1.1 языка, они изначально были определены как преобразование в код, совместимый с 1.0. Если вы посмотрите на пример этого преобразования, я думаю, он сделает более понятным, как на самом деле работает внутренний класс.
Рассмотрим код из ответа Яна Робертса:
При преобразовании в код, совместимый с 1.0, этот внутренний класс
Bar
станет примерно таким:Имя внутреннего класса имеет префикс с именем внешнего класса, чтобы сделать его уникальным.
this$0
Добавляется скрытый частный член, который содержит копию внешнегоthis
. И создается скрытый конструктор для инициализации этого члена.А если посмотреть на
createBar
метод, то он трансформируется примерно в это:Итак, давайте посмотрим, что произойдет, когда вы выполните следующий код.
Сначала мы создаем экземпляр
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
.Снова применив наше преобразование 1.0, эта вторая строка станет примерно такой:
Это почти идентично
f.createBar()
звонку. Мы снова создаем экземплярFoo$Bar
и инициализируемthis$0
член для f. Итак, сноваb.this$0 = f
.И еще раз , когда вы звоните
b.printVal()
, вы печатаете ,b.thi$0.val
который ,f.val
который является 5.Важно помнить, что у внутреннего класса есть скрытый член, содержащий копию
this
внешнего класса. Когда вы создаете экземпляр внутреннего класса из внешнего класса, он неявно инициализируется текущим значениемthis
. Когда вы создаете экземпляр внутреннего класса вне внешнего класса, вы явно указываете, какой экземпляр внешнего класса использовать, через префиксnew
ключевого слова.источник
Думайте об
new receiver
одном токене. Вроде как имя функции с пробелом.Конечно, у класса
KnockKnockServer
буквально нет названной функцииnew receiver
, но я предполагаю, что синтаксис предполагает это. Это должно выглядеть так, как будто вы вызываете функцию, которая создает новый экземплярKnockKnockServer.receiver
использования определенного экземпляраKnockKnockServer
для любого доступа к включающему классу.источник
new receiver
одном токене! Огромное спасибо!Затенение
Если объявление типа (например, переменная-член или имя параметра) в определенной области (например, во внутреннем классе или определении метода) имеет то же имя, что и другое объявление во включающей области, то объявление затеняет объявление охватывающей области. Вы не можете ссылаться на затененное объявление только по его имени. Следующий пример, ShadowTest, демонстрирует это:
Ниже приводится результат этого примера:
В этом примере определяются три переменные с именем x: переменная-член класса ShadowTest, переменная-член внутреннего класса FirstLevel и параметр в методе methodInFirstLevel. Переменная x, определенная как параметр метода methodInFirstLevel, затеняет переменную внутреннего класса FirstLevel. Следовательно, когда вы используете переменную x в методе methodInFirstLevel, она ссылается на параметр метода. Чтобы обратиться к переменной-члену внутреннего класса FirstLevel, используйте ключевое слово this для представления охватывающей области:
Обращайтесь к переменным-членам, охватывающим более крупные области, по имени класса, к которому они принадлежат. Например, следующий оператор обращается к переменной-члену класса ShadowTest из метода methodInFirstLevel:
Обратитесь к документации
источник