En Java, je viens de découvrir que le code suivant est légal:
KnockKnockServer newServer = new KnockKnockServer();
KnockKnockServer.receiver receive = newServer.new receiver(clientSocket);
Pour info, le receveur est juste une classe d'assistance avec la signature suivante:
public class receiver extends Thread { /* code_inside */ }
Je n'ai jamais vu la XYZ.new
notation auparavant. Comment ça marche? Existe-t-il un moyen de coder cela de manière plus conventionnelle?
new
un opérateur dans de nombreuses langues. (Je pensais que vous pourriez également surchargernew
en C ++?) La classe interne de Java est un peu étrange pour moi, cependant.Réponses:
C'est le moyen d'instancier une classe interne non statique depuis l'extérieur du corps de la classe conteneur, comme décrit dans la documentation Oracle .
Chaque instance de classe interne est associée à une instance de sa classe contenante. Lorsque vous
new
créez une classe interne à partir de sa classethis
conteneur, elle utilise l' instance du conteneur par défaut:Mais si vous souhaitez créer une instance de Bar en dehors de Foo, ou associer une nouvelle instance à une instance contenant autre que celle-ci,
this
vous devez utiliser la notation de préfixe.la source
f.new
.public
niveau d'accèsKnockKnockServer.receiver
était défini,private
il serait impossible de l'instancier de cette manière, non? Pour étendre le commentaire de @EricJablow, les classes internes doivent généralement toujours utiliser par défaut unprivate
niveau d'accès.receiver
classe de l'extérieur. Si je le concevais, j'aurais probablement la classe publique mais son constructeur protégé ou package-private, et j'aurais une méthodeKnockKnockServer
pour créer des instances de récepteur.x.new
.Jetez un œil à cet exemple:
En utilisant javap, nous pouvons afficher les instructions générées pour ce code
Méthode principale:
Constructeur de classe interne:
Tout est simple - lors de l'appel du constructeur TestInner, java passe l'instance de test comme premier argument main: 12 . Ne pas regarder cela TestInner devrait avoir un constructeur sans argument. TestInner à son tour enregistre simplement la référence à l'objet parent, Test $ TestInner: 2 . Lorsque vous appelez le constructeur de classe interne à partir d'une méthode d'instance, la référence à l'objet parent est transmise automatiquement, vous n'avez donc pas à le spécifier. En fait, cela passe à chaque fois, mais lors de l'invocation de l'extérieur, il doit être passé explicitement.
t.new TestInner();
- est juste un moyen de spécifier le premier argument caché du constructeur TestInner, pas un typemethod () est égal à:
TestInner est égal à:
la source
Lorsque des classes internes ont été ajoutées à Java dans la version 1.1 du langage, elles ont été initialement définies comme une transformation en code compatible 1.0. Si vous regardez un exemple de cette transformation, je pense que cela rendra beaucoup plus clair comment une classe interne fonctionne réellement.
Considérez le code de la réponse d'Ian Roberts:
Une fois transformée en code compatible 1.0, cette classe interne
Bar
deviendrait quelque chose comme ceci:Le nom de la classe interne est précédé du nom de la classe externe afin de le rendre unique. Un
this$0
membre privé masqué est ajouté qui contient une copie de l'externethis
. Et un constructeur caché est créé pour initialiser ce membre.Et si vous regardez la
createBar
méthode, elle serait transformée en quelque chose comme ceci:Voyons donc ce qui se passe lorsque vous exécutez le code suivant.
Tout d'abord, nous instancions une instance de
Foo
et initialisons leval
membre à 5 (ief.val = 5
).Ensuite, nous appelons
f.createBar()
, qui instancie une instance deFoo$Bar
et initialise lethis$0
membre à la valeur dethis
passé decreateBar
(ieb.this$0 = f
).Enfin, nous appelons
b.printVal()
qui essaie d'imprimerb.this$0.val
cef.val
qui vaut 5.Maintenant, c'était une instanciation régulière d'une classe interne. Regardons ce qui se passe lors de l'instanciation
Bar
de l'extérieurFoo
.En appliquant à nouveau notre transformation 1.0, cette deuxième ligne deviendrait quelque chose comme ceci:
C'est presque identique à l'
f.createBar()
appel. Encore une fois, nous instancions une instance deFoo$Bar
et initialisons lethis$0
membre à f. Encore une fois,b.this$0 = f
.Et encore une fois, lorsque vous appelez
b.printVal()
, vous imprimezb.thi$0.val
cef.val
qui est de 5.La chose clé à retenir est que la classe interne a un membre masqué contenant une copie de
this
de la classe externe. Lorsque vous instanciez une classe interne à partir de la classe externe, elle s'est implicitement initialisée avec la valeur actuelle dethis
. Lorsque vous instanciez la classe interne depuis l'extérieur de la classe externe, vous spécifiez explicitement l'instance de la classe externe à utiliser, via le préfixe dunew
mot - clé.la source
Pensez
new receiver
à un seul jeton. Un peu comme un nom de fonction avec un espace dedans.Bien sûr, la classe
KnockKnockServer
n'a pas littéralement une fonction nomméenew receiver
, mais je suppose que la syntaxe est censée le suggérer. Cela donne l'impression que vous appelez une fonction qui crée une nouvelle instance d'KnockKnockServer.receiver
utilisation d'une instance particulière deKnockKnockServer
pour tout accès à la classe englobante.la source
new receiver
comme un jeton! Merci beaucoup!Ombrage
Si une déclaration d'un type (telle qu'une variable membre ou un nom de paramètre) dans une portée particulière (telle qu'une classe interne ou une définition de méthode) a le même nom qu'une autre déclaration dans la portée englobante, alors la déclaration masque la déclaration de la portée englobante. Vous ne pouvez pas faire référence à une déclaration masquée par son seul nom. L'exemple suivant, ShadowTest, illustre ceci:
Voici la sortie de cet exemple:
Cet exemple définit trois variables nommées x: la variable membre de la classe ShadowTest, la variable membre de la classe interne FirstLevel et le paramètre de la méthode methodInFirstLevel. La variable x définie comme paramètre de la méthode methodInFirstLevel masque la variable de la classe interne FirstLevel. Par conséquent, lorsque vous utilisez la variable x dans la méthode methodInFirstLevel, elle fait référence au paramètre de méthode. Pour faire référence à la variable membre de la classe interne FirstLevel, utilisez le mot clé this pour représenter la portée englobante:
Faites référence aux variables membres qui entourent de plus grandes étendues par le nom de la classe à laquelle elles appartiennent. Par exemple, l'instruction suivante accède à la variable membre de la classe ShadowTest à partir de la méthode methodInFirstLevel:
Reportez-vous à la documentation
la source