Em Java, acabei de descobrir que o seguinte código é válido:
KnockKnockServer newServer = new KnockKnockServer();
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket);
Para sua informação, o receptor é apenas uma classe auxiliar com a seguinte assinatura:
public class receiver extends Thread { /* code_inside */ }
Eu nunca vi a XYZ.new
notação antes. Como isso funciona? Existe alguma maneira de codificar isso de forma mais convencional?
new
era um operador em vários idiomas. (Achei que você também pode sobrecarregarnew
em C ++?) A classe interna de Java é um pouco estranha para mim, no entanto.Respostas:
É a maneira de instanciar uma classe interna não estática de fora do corpo da classe, conforme descrito nos documentos do Oracle .
Cada instância de classe interna está associada a uma instância de sua classe contida. Quando você usa
new
uma classe interna de dentro da classe que a contém, ela usa athis
instância do contêiner por padrão:Mas se você deseja criar uma instância de Bar fora de Foo, ou associar uma nova instância a uma instância contida diferente
this
, você deve usar a notação de prefixo.fonte
f.new
.public
nível de acesso ativadoKnockKnockServer.receiver
for feitoprivate
, seria impossível instanciar dessa forma, certo? Para estender o comentário de @EricJablow, as classes internas geralmente devem sempre usar como padrão umprivate
nível de acesso.receiver
classe de fora. Se eu o estivesse projetando, provavelmente teria a classe pública, mas seu construtor protegido ou privado de pacote, e teria um métodoKnockKnockServer
para criar instâncias de receptor.x.new
.Dê uma olhada neste exemplo:
Usando javap, podemos ver as instruções geradas para este código
Método principal:
Construtor de classe interna:
Tudo é simples - ao invocar o construtor TestInner, java passa a instância de teste como um primeiro argumento main: 12 . Não olhar para aquele TestInner deve ter um construtor sem argumento. O TestInner, por sua vez, salva apenas a referência ao objeto pai, Test $ TestInner: 2 . Quando você invoca o construtor de classe interna a partir de um método de instância, a referência ao objeto pai é passada automaticamente, portanto, você não precisa especificá-lo. Na verdade, ele passa todas as vezes, mas ao invocar de fora, deve ser passado explicitamente.
t.new TestInner();
- é apenas uma maneira de especificar o primeiro argumento oculto para o construtor TestInner, não um tipométodo () é igual a:
TestInner é igual a:
fonte
Quando as classes internas foram adicionadas ao Java na versão 1.1 da linguagem, elas foram originalmente definidas como uma transformação para o código compatível com 1.0. Se você olhar um exemplo dessa transformação, acho que ficará muito mais claro como uma classe interna realmente funciona.
Considere o código da resposta de Ian Roberts:
Quando transformada em código compatível com 1.0, essa classe interna
Bar
se tornaria algo assim:O nome da classe interna é prefixado com o nome da classe externa para torná-lo único. Um
this$0
membro privado oculto é adicionado, contendo uma cópia dothis
. E um construtor oculto é criado para inicializar esse membro.E se você olhar para o
createBar
método, ele seria transformado em algo assim:Então, vamos ver o que acontece quando você executa o código a seguir.
Primeiro, instanciamos uma instância de
Foo
e inicializamos oval
membro em 5 (ou sejaf.val = 5
).Em seguida, chamamos
f.createBar()
, que instancia uma instância deFoo$Bar
e inicializa othis$0
membro com o valor dethis
passado decreateBar
(ou seja,b.this$0 = f
).Finalmente chamamos o
b.printVal()
que tenta imprimir ob.this$0.val
que éf.val
que é 5.Essa foi uma instanciação regular de uma classe interna. Vejamos o que acontece ao instanciar
Bar
de foraFoo
.Aplicando nossa transformação 1.0 novamente, a segunda linha se tornaria algo assim:
Isso é quase idêntico à
f.createBar()
chamada. Novamente, estamos instanciando uma instância deFoo$Bar
e inicializando othis$0
membro para f. Então novamente,b.this$0 = f
,.E novamente quando você ligar
b.printVal()
, você está imprimindob.thi$0.val
o que éf.val
que é 5.O principal a ser lembrado é que a classe interna tem um membro oculto que contém uma cópia da
this
classe externa. Quando você instancia uma classe interna de dentro da classe externa, ela é inicializada implicitamente com o valor atual dethis
. Ao instanciar a classe interna de fora da classe externa, você especifica explicitamente qual instância da classe externa usar, por meio do prefixo danew
palavra - chave.fonte
Pense
new receiver
em um único token. É como o nome de uma função com um espaço.Claro, a classe
KnockKnockServer
não tem literalmente uma função chamadanew receiver
, mas estou supondo que a sintaxe pretende sugerir isso. Deve parecer que você está chamando uma função que cria uma nova instância deKnockKnockServer.receiver
uso de uma instância específica deKnockKnockServer
para qualquer acesso à classe envolvente.fonte
new receiver
como um token! muito obrigado!Sombreamento
Se uma declaração de um tipo (como uma variável de membro ou um nome de parâmetro) em um escopo específico (como uma classe interna ou uma definição de método) tem o mesmo nome de outra declaração no escopo envolvente, então a declaração obscurece a declaração do escopo envolvente. Você não pode se referir a uma declaração obscura apenas pelo seu nome. O exemplo a seguir, ShadowTest, demonstra isso:
A seguir está a saída deste exemplo:
Este exemplo define três variáveis denominadas x: A variável de membro da classe ShadowTest, a variável de membro da classe interna FirstLevel e o parâmetro no método methodInFirstLevel. A variável x definida como um parâmetro do método methodInFirstLevel obscurece a variável da classe interna FirstLevel. Conseqüentemente, quando você usa a variável x no método methodInFirstLevel, ela se refere ao parâmetro do método. Para se referir à variável de membro da classe interna FirstLevel, use a palavra-chave this para representar o escopo envolvente:
Consulte as variáveis de membro que abrangem escopos maiores pelo nome da classe à qual pertencem. Por exemplo, a instrução a seguir acessa a variável de membro da classe ShadowTest do método methodInFirstLevel:
Consulte a documentação
fonte