En Java, acabo de descubrir que el siguiente código es legal:
KnockKnockServer newServer = new KnockKnockServer();
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket);
FYI, el receptor es solo una clase auxiliar con la siguiente firma:
public class receiver extends Thread { /* code_inside */ }
Nunca antes había visto la XYZ.new
notación. ¿Cómo funciona? ¿Hay alguna forma de codificar eso de manera más convencional?
new
era un operador en muchos idiomas. (¿Pensé que también podrías sobrecargarnew
en C ++?) Sin embargo, la clase interna de Java es un poco extraña para mí.Respuestas:
Es la forma de crear una instancia de una clase interna no estática desde fuera del cuerpo de la clase contenedora, como se describe en los documentos de Oracle .
Cada instancia de clase interna está asociada con una instancia de su clase contenedora. Cuando tiene
new
una clase interna desde dentro de su clase contenedora, usa lathis
instancia del contenedor de forma predeterminada:Pero si desea crear una instancia de Bar fuera de Foo, o asociar una nueva instancia con una instancia contenedora que no sea
this
entonces, debe usar la notación de prefijo.fuente
f.new
.public
el nivel de acceso en , sería imposible crear una instancia de esta manera, ¿verdad? Para extender el comentario de @EricJablow, las clases internas generalmente siempre deben tener un nivel de acceso predeterminado .KnockKnockServer.receiver
private
private
receiver
clase desde fuera. Si lo estuviera diseñando, probablemente tendría la clase public pero su constructor protegido o package-private, y tendría un métodoKnockKnockServer
para crear instancias de receptor.x.new
.Eche un vistazo a este ejemplo:
Usando javap podemos ver las instrucciones generadas para este código
Método principal:
Constructor de clases internas:
Todo es simple: al invocar el constructor TestInner, java pasa la instancia de prueba como primer argumento principal: 12 . Sin mirar ese TestInner debería tener un constructor sin argumentos. TestInner a su vez solo guarda la referencia al objeto principal, Test $ TestInner: 2 . Cuando invoca el constructor de clase interna desde un método de instancia, la referencia al objeto principal se pasa automáticamente, por lo que no tiene que especificarla. En realidad, pasa cada vez, pero cuando se invoca desde fuera debe pasarse explícitamente.
t.new TestInner();
- es solo una forma de especificar el primer argumento oculto para el constructor TestInner, no un tipométodo () es igual a:
TestInner es igual a:
fuente
Cuando se agregaron clases internas a Java en la versión 1.1 del lenguaje, originalmente se definieron como una transformación a un código compatible con 1.0. Si miras un ejemplo de esta transformación, creo que aclarará mucho cómo funciona realmente una clase interna.
Considere el código de la respuesta de Ian Roberts:
Cuando se transforma a un código compatible con 1.0, esa clase interna
Bar
se convertiría en algo como esto:El nombre de la clase interna tiene como prefijo el nombre de la clase externa para que sea único. Se
this$0
agrega un miembro privado oculto que contiene una copia del externothis
. Y se crea un constructor oculto para inicializar ese miembro.Y si miras el
createBar
método, se transformaría en algo como esto:Entonces, veamos qué sucede cuando ejecuta el siguiente código.
Primero instanciamos una instancia de
Foo
e inicializamos elval
miembro en 5 (es decirf.val = 5
).A continuación, llamamos
f.createBar()
, que instancia una instancia deFoo$Bar
e inicializa elthis$0
miembro con el valor dethis
pasado decreateBar
(es decirb.this$0 = f
).Finalmente llamamos a
b.printVal()
cuál intenta imprimirb.this$0.val
cuál esf.val
cuál es 5.Ahora que era una instanciación regular de una clase interna. Veamos qué sucede cuando se crea una instancia
Bar
desde el exteriorFoo
.Aplicando nuestra transformación 1.0 nuevamente, esa segunda línea se convertiría en algo como esto:
Esto es casi idéntico a la
f.createBar()
llamada. Nuevamente, estamos creando una instancia deFoo$Bar
e inicializando elthis$0
miembro en f. Así que de nuevob.this$0 = f
.Y nuevamente cuando llamas
b.printVal()
, estás imprimiendob.thi$0.val
cuál esf.val
cuál es 5.La clave para recordar es que la clase interna tiene un miembro oculto que tiene una copia de
this
la clase externa. Cuando crea una instancia de una clase interna desde dentro de la clase externa, se inicializa implícitamente con el valor actual dethis
. Cuando crea una instancia de la clase interna desde fuera de la clase externa, especifica explícitamente qué instancia de la clase externa usar, a través del prefijo de lanew
palabra clave.fuente
Piense
new receiver
en una sola ficha. Algo así como el nombre de una función con un espacio en él.Por supuesto, la clase
KnockKnockServer
no tiene literalmente una función nombradanew receiver
, pero supongo que la sintaxis debe sugerir eso. Está destinado a que parezca que estás llamando a una función que crea una nueva instancia deKnockKnockServer.receiver
uso de una instancia particular deKnockKnockServer
para cualquier acceso a la clase adjunta.fuente
new receiver
en una sola ficha. ¡muchas gracias!Sombreado
Si una declaración de un tipo (como una variable miembro o un nombre de parámetro) en un ámbito particular (como una clase interna o una definición de método) tiene el mismo nombre que otra declaración en el ámbito adjunto, entonces la declaración ensombrece la declaración del alcance adjunto. No puede referirse a una declaración sombreada solo por su nombre. El siguiente ejemplo, ShadowTest, demuestra esto:
El siguiente es el resultado de este ejemplo:
Este ejemplo define tres variables llamadas x: la variable miembro de la clase ShadowTest, la variable miembro de la clase interna FirstLevel y el parámetro en el método methodInFirstLevel. La variable x definida como parámetro del método methodInFirstLevel sombrea la variable de la clase interna FirstLevel. En consecuencia, cuando usa la variable x en el método methodInFirstLevel, se refiere al parámetro del método. Para hacer referencia a la variable miembro de la clase interna FirstLevel, use la palabra clave this para representar el ámbito adjunto:
Consulte las variables miembro que encierran ámbitos más grandes por el nombre de clase a la que pertenecen. Por ejemplo, la siguiente instrucción accede a la variable miembro de la clase ShadowTest desde el método methodInFirstLevel:
Consulte los documentos
fuente