1980 votes

Classe interne Java et classe statique imbriquée

Quelle est la principale différence entre une classe interne et une classe statique imbriquée en Java ? La conception / l'implémentation joue-t-elle un rôle dans le choix de l'une d'entre elles ?

91 votes

La réponse de Joshua Bloch est dans Java efficace lire item 22 : Favor static member classes over non static

18 votes

Pour mémoire, il s'agit du point 24 de la 3e édition du même livre.

1855voto

Martin Points 8586

De la Tutoriel Java :

Les classes imbriquées sont divisées en deux catégories : statiques et non statiques. Les classes imbriquées qui sont déclarées statiques sont simplement appelées classes statiques imbriquées. Les classes imbriquées non statiques sont appelées classes internes.

Les classes statiques imbriquées sont accessibles en utilisant le nom de la classe englobante :

OuterClass.StaticNestedClass

Par exemple, pour créer un objet pour la classe imbriquée statique, utilisez cette syntaxe :

OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();

Les objets qui sont des instances d'une classe interne existent dans une instance de la classe externe. Considérons les classes suivantes :

class OuterClass {
    ...
    class InnerClass {
        ...
    }
}

Une instance d'InnerClass ne peut exister qu'au sein d'une instance d'OuterClass et a un accès direct aux méthodes et aux champs de son instance englobante.

Pour instancier une classe interne, vous devez d'abord instancier la classe externe. Ensuite, créez l'objet interne dans l'objet externe avec cette syntaxe :

OuterClass outerObject = new OuterClass()
OuterClass.InnerClass innerObject = outerObject.new InnerClass();

voir : Tutoriel Java - Les classes imbriquées

Pour être complet, notez qu'il existe également une chose telle qu'une classe intérieure sans une instance englobante :

class A {
  int t() { return 1; }
  static A a =  new A() { int t() { return 2; } };
}

Ici, new A() { ... } est un classe interne définie dans un contexte statique et n'a pas d'instance englobante.

146 votes

N'oubliez pas que vous pouvez également importer directement une classe statique imbriquée, c'est-à-dire que vous pourriez faire (en haut du fichier) : import OuterClass.StaticNestedClass; puis faire référence à la classe juste comme OuterClass.

656voto

Jegschemesch Points 4093

El Tutoriel Java dit :

Terminologie : Les classes imbriquées sont divisées en deux catégories : statiques et non statiques. Les classes imbriquées qui déclarées statiques sont simplement appelées classes imbriquées statiques. Les classes imbriquées non statiques non statiques sont appelées inner classes.

Dans le langage courant, les termes "imbriqué" et "interne" sont utilisés de manière interchangeable par la plupart des programmeurs, mais j'utiliserai le terme correct de "classe imbriquée" qui couvre à la fois l'interne et le statique.

Les classes peuvent être imbriquées à l'infini Par exemple, la classe A peut contenir la classe B qui contient la classe C qui contient la classe D, etc. Toutefois, il est rare que les classes soient imbriquées sur plus d'un niveau, car elles sont généralement mal conçues.

Il y a trois raisons pour lesquelles vous pouvez créer une classe imbriquée :

  • organisation : parfois, il semble plus judicieux de classer une classe dans l'espace de nom d'une autre classe, surtout lorsqu'elle ne sera pas utilisée dans un autre contexte.
  • accès : les classes imbriquées ont un accès spécial aux variables/champs de leurs classes contenantes (précisément quelles variables/champs dépendent du type de classe imbriquée, interne ou statique).
  • commodité : le fait de devoir créer un nouveau fichier pour chaque nouveau type est à nouveau gênant, surtout lorsque le type ne sera utilisé que dans un seul contexte.

Il y a quatre types de classes imbriquées en Java . En bref, il s'agit de :

  • classe statique déclaré comme membre statique d'une autre classe
  • classe intérieure : déclarée comme membre d'une autre classe
  • classe interne locale déclar déclar déclar déclar dans une méthode d'instance d'une autre classe
  • classe intérieure anonyme : comme une classe interne locale, mais écrite comme une expression qui renvoie un objet unique.

Permettez-moi de vous donner plus de détails.

Classes statiques

Les classes statiques sont les plus faciles à comprendre car elles n'ont rien à voir avec les instances de la classe qui les contient.

Une classe statique est une classe déclarée comme membre statique d'une autre classe. Tout comme les autres membres statiques, une telle classe n'est en fait qu'un accessoire qui utilise la classe qui la contient comme espace de nom, par exemple la classe Chèvre déclaré comme membre statique de la classe Rhino dans le paquet pizza est connu sous le nom de pizza.rhino.chèvre .

package pizza;

public class Rhino {

    ...

    public static class Goat {
        ...
    }
}

Franchement, les classes statiques sont une fonctionnalité plutôt inutile car les classes sont déjà divisées en espaces de noms par les paquets. La seule raison réellement concevable de créer une classe statique est qu'une telle classe a accès aux membres statiques privés de la classe qui la contient, mais je trouve que c'est une justification plutôt boiteuse pour l'existence de la fonctionnalité de classe statique.

Classes intérieures

Une classe interne est une classe déclarée comme membre non statique d'une autre classe :

package pizza;

public class Rhino {

    public class Goat {
        ...
    }

    private void jerry() {
        Goat g = new Goat();
    }
}

Comme pour une classe statique, la classe interne est connue sous le nom de la classe qui la contient, pizza.rhino.chèvre mais dans la classe qui le contient, il peut être connu par son simple nom. Cependant, chaque instance d'une classe interne est liée à une instance particulière de sa classe contenante : ci-dessus, la classe Chèvre créé en jerry est implicitement liée à l Rhino instance este sur jerry . Sinon, nous faisons le Rhino explicite lorsque nous instancions Chèvre :

Rhino rhino = new Rhino();
Rhino.Goat goat = rhino.new Goat();

(Remarquez que vous vous référez au type interne comme juste Chèvre dans le bizarre nouveau syntaxe : Java déduit le type de contenu à partir du rhino partie. Et, oui nouveau rhino.Goat() aurait été plus logique pour moi aussi).

Alors, qu'est-ce que cela nous apporte ? Eh bien, l'instance de classe interne a accès aux membres d'instance de l'instance de classe contenante. Ces membres de l'instance englobante sont désignés à l'intérieur de la classe interne par les termes suivants via juste leurs simples noms, pas via este ( este dans la classe interne fait référence à l'instance de la classe interne, et non à l'instance de la classe contenante associée) :

public class Rhino {

    private String barry;

    public class Goat {
        public void colin() {
            System.out.println(barry);
        }
    }
}

Dans la classe interne, vous pouvez vous référer à este de la classe qui le contient comme Rhino.this et vous pouvez utiliser este pour désigner ses membres, par exemple, Rhino.this.barry .

Classes internes locales

Une classe interne locale est une classe déclarée dans le corps d'une méthode. Une telle classe n'est connue que dans la méthode qui la contient, elle ne peut donc être instanciée et ses membres ne sont accessibles que dans cette méthode. L'avantage est que l'instance d'une classe interne locale est liée aux variables locales finales de la méthode qui la contient et peut y accéder. Lorsque l'instance utilise une variable locale finale de la méthode qui la contient, la variable conserve la valeur qu'elle avait au moment de la création de l'instance, même si la variable est sortie de son champ d'application (il s'agit en fait de la version grossière et limitée des fermetures de Java).

Parce qu'une classe interne locale n'est pas membre d'une classe ou d'un package, elle n'est pas déclarée avec un niveau d'accès. (Il est clair, cependant, que ses propres membres ont des niveaux d'accès comme dans une classe normale).

Si une classe interne locale est déclarée dans une méthode d'instance, l'instanciation de la classe interne est liée à l'instance détenue par l'attribut este au moment de la création de l'instance, et donc les membres d'instance de la classe contenante sont accessibles comme dans une classe interne d'instance. Une classe interne locale est instanciée simplement via son nom, par exemple classe interne locale Chat est instancié en tant que nouveau Chat() et non pas new this.Cat() comme on pourrait s'y attendre.

Classes internes anonymes

Une classe interne anonyme est un moyen syntaxiquement pratique d'écrire une classe interne locale. Le plus souvent, une classe interne locale est instanciée au maximum une seule fois à chaque fois que la méthode qui la contient est exécutée. Il serait donc agréable de pouvoir combiner la définition de la classe interne locale et son instanciation unique en une seule forme syntaxique pratique, et il serait également agréable de ne pas avoir à trouver un nom pour la classe (moins votre code contient de noms inutiles, mieux c'est). Une classe interne anonyme permet ces deux choses :

new *ParentClassName*(*constructorArgs*) {*members*}

Il s'agit d'une expression renvoyant une nouvelle instance d'une classe non nommée qui prolonge Nom de la classe d'origine . Vous ne pouvez pas fournir votre propre constructeur ; au contraire, un constructeur est implicitement fourni qui appelle simplement le super constructeur, donc les arguments fournis doivent correspondre au super constructeur. (Si le parent contient plusieurs constructeurs, le plus "simple" est appelé, "le plus simple" étant déterminé par un ensemble de règles plutôt complexes qu'il ne vaut pas la peine d'apprendre en détail - faites simplement attention à ce que NetBeans ou Eclipse vous disent).

Vous pouvez également spécifier une interface à mettre en œuvre :

new *InterfaceName*() {*members*}

Une telle déclaration crée une nouvelle instance d'une classe non nommée qui étend Object et implémente Nom de l'interface . Encore une fois, vous ne pouvez pas fournir votre propre constructeur ; dans ce cas, Java fournit implicitement un constructeur sans argument et sans action (il n'y aura donc jamais d'arguments de constructeur dans ce cas).

Même si vous ne pouvez pas donner un constructeur à une classe interne anonyme, vous pouvez toujours faire tout ce que vous voulez en utilisant un bloc d'initialisation (un bloc {} placé en dehors de toute méthode).

Il est clair qu'une classe interne anonyme est simplement un moyen moins flexible de créer une classe interne locale avec une seule instance. Si vous voulez une classe interne locale qui implémente plusieurs interfaces ou qui implémente des interfaces tout en étendant une classe autre que la classe Objet ou qui spécifie son propre constructeur, vous êtes coincé en créant une classe interne locale nommée ordinaire.

45 votes

Superbe histoire, merci. Il y a une erreur cependant. Vous pouvez accéder aux champs d'une classe externe à partir d'une instance de classe interne par Rhino.this.variableName .

2 votes

Bien que vous ne puissiez pas fournir votre propre constructeur pour les classes internes anonymes, vous pouvez utiliser l'initialisation par double accolade. c2.com/cgi/wiki?DoubleBraceInitialization

0 votes

Vous n'avez pas besoin d'une variable d'instance pour la classe contenante, Rhino.this fonctionne ! J'aimerais vraiment avoir assez d'expérience pour corriger ces erreurs car sinon, c'est une excellente réponse.

165voto

jrudolph Points 3726

Je ne pense pas que la véritable différence soit apparue clairement dans les réponses ci-dessus.

Il faut d'abord que les termes soient corrects :

  • Une classe imbriquée est une classe qui est contenue dans une autre classe au niveau du code source.
  • Il est statique si vous le déclarez avec la balise statique modificateur.
  • Une classe imbriquée non statique est appelée classe interne. (Je reste avec la classe imbriquée non-statique).

La réponse de Martin est bonne jusqu'à présent. Cependant, la vraie question est la suivante : quel est le but de déclarer une classe imbriquée statique ou non ?

Vous utilisez classes statiques imbriquées si vous voulez simplement garder vos classes ensemble si elles appartiennent au même thème ou si la classe imbriquée est exclusivement utilisée dans la classe englobante. Il n'y a pas de différence sémantique entre une classe imbriquée statique et toute autre classe.

Classes imbriquées non statiques sont une bête différente. Comme les classes internes anonymes, ces classes imbriquées sont en fait des fermetures. Cela signifie qu'elles capturent leur portée environnante et leur instance englobante et les rendent accessibles. Un exemple permettra peut-être de clarifier ce point. Voyez ce stub d'un conteneur :

public class Container {
    public class Item{
        Object data;
        public Container getContainer(){
            return Container.this;
        }
        public Item(Object data) {
            super();
            this.data = data;
        }

    }

    public static Item create(Object data){
        // does not compile since no instance of Container is available
        return new Item(data);
    }
    public Item createSubItem(Object data){
        // compiles, since 'this' Container is available
        return new Item(data);
    }
}

Dans ce cas, vous voulez avoir une référence d'un élément enfant au conteneur parent. En utilisant une classe imbriquée non statique, cela fonctionne sans trop de travail. Vous pouvez accéder à l'instance englobante de Container avec la syntaxe suivante Container.this .

Des explications plus poussées suivront :

Si vous regardez les bytecodes Java que le compilateur génère pour une classe imbriquée (non statique), cela peut devenir encore plus clair :

// class version 49.0 (49)
// access flags 33
public class Container$Item {

  // compiled from: Container.java
  // access flags 1
  public INNERCLASS Container$Item Container Item

  // access flags 0
  Object data

  // access flags 4112
  final Container this$0

  // access flags 1
  public getContainer() : Container
   L0
    LINENUMBER 7 L0
    ALOAD 0: this
    GETFIELD Container$Item.this$0 : Container
    ARETURN
   L1
    LOCALVARIABLE this Container$Item L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 1
  public <init>(Container,Object) : void
   L0
    LINENUMBER 12 L0
    ALOAD 0: this
    ALOAD 1
    PUTFIELD Container$Item.this$0 : Container
   L1
    LINENUMBER 10 L1
    ALOAD 0: this
    INVOKESPECIAL Object.<init>() : void
   L2
    LINENUMBER 11 L2
    ALOAD 0: this
    ALOAD 2: data
    PUTFIELD Container$Item.data : Object
    RETURN
   L3
    LOCALVARIABLE this Container$Item L0 L3 0
    LOCALVARIABLE data Object L0 L3 2
    MAXSTACK = 2
    MAXLOCALS = 3
}

Comme vous pouvez le voir, le compilateur crée un champ caché Container this$0 . Elle est définie dans le constructeur qui possède un paramètre supplémentaire de type Container pour spécifier l'instance englobante. Vous ne pouvez pas voir ce paramètre dans la source mais le compilateur le génère implicitement pour une classe imbriquée.

L'exemple de Martin

OuterClass.InnerClass innerObject = outerObject.new InnerClass();

serait donc compilé en un appel de quelque chose comme (en bytecodes)

new InnerClass(outerObject)

Dans un souci d'exhaustivité :

Une classe anonyme est un parfait exemple de classe imbriquée non statique qui n'a simplement pas de nom associé et ne peut pas être référencée ultérieurement.

20 votes

"Il n'y a pas de différence sémantique entre une classe statique imbriquée et toute autre classe." Sauf que la classe imbriquée peut voir les champs/méthodes privés du parent et que la classe parent peut voir les champs/méthodes privés de la classe imbriquée.

0 votes

La classe interne non statique ne risque-t-elle pas de provoquer des fuites de mémoire massives ? Comme dans, chaque fois que vous créez un écouteur, vous créez une fuite ?

3 votes

@G_V il y a définitivement un potentiel de fuites de mémoire car une instance de la classe interne garde une référence à la classe externe. La question de savoir s'il s'agit d'un problème réel dépend de l'endroit et de la manière dont les références aux instances des classes externe et interne sont conservées.

104voto

aleroot Points 30853

Je pense qu'aucune des réponses ci-dessus ne vous explique la différence réelle entre une classe imbriquée et une classe statique imbriquée en termes de conception d'application :

Vue d'ensemble

Une classe imbriquée peuvent être non statiques ou statiques et dans chaque cas est une classe définie à l'intérieur d'une autre classe . Une classe imbriquée ne doit exister que pour servir la classe qui l'englobe. Si une classe imbriquée est utile à d'autres classes (pas seulement à celle qui l'englobe), elle doit être déclarée comme une classe de niveau supérieur.

Différence

Classe imbriquée non statique est implicitement associée à l'instance englobante de la classe contenante, ce qui signifie qu'il est possible d'invoquer des méthodes et d'accéder aux variables de l'instance englobante. Une utilisation courante d'une classe imbriquée non statique consiste à définir une classe d'adaptateur.

Classe statique imbriquée Elle ne peut pas accéder à l'instance de la classe englobante et invoquer des méthodes sur celle-ci. Elle doit donc être utilisée lorsque la classe imbriquée ne nécessite pas l'accès à une instance de la classe englobante. Une utilisation courante de la classe imbriquée statique est d'implémenter un composant de l'objet externe.

Conclusion

La principale différence entre les deux du point de vue de la conception est donc : une classe imbriquée non statique peut accéder à l'instance de la classe conteneur, alors que les classes statiques ne le peuvent pas. .

0 votes

D'après votre conclusion "alors que les statiques ne peuvent pas", même pas les instances statiques du conteneur ? Vous êtes sûr ?

0 votes

Une utilisation courante de la classe statique imbriquée est le modèle de conception ViewHolder dans RecyclerView et ListView.

2 votes

Dans de nombreux cas, la réponse courte est plus claire et meilleure. En voici un exemple.

26voto

sactiw Points 7717

Je pense que la convention qui est généralement suivie est la suivante :

  • classe statique au sein d'une classe de premier niveau est une classe emboîtée
  • classe non statique au sein d'une classe de premier niveau est une classe intérieure qui, en outre a deux autres formes :
    • classe locale - les classes nommées déclarées à l'intérieur d'un bloc comme le corps d'une méthode ou d'un constructeur
    • classe anonyme - des classes sans nom dont les instances sont créées dans des expressions et des déclarations

Cependant, peu d'autres pointe vers les souvenirs sont :

  • Les classes de niveau supérieur et les classes statiques imbriquées sont sémantiquement identiques, sauf que dans le cas d'une classe statique imbriquée, elle peut faire une référence statique aux champs/méthodes statiques privés de sa classe extérieure [parent] et vice versa.

  • Les classes internes ont accès aux variables d'instance de l'instance englobante de la classe externe [parent]. Cependant, toutes les classes internes n'ont pas d'instances englobantes, par exemple les classes internes dans des contextes statiques, comme une classe anonyme utilisée dans un bloc d'initialisation statique, n'en ont pas.

  • Une classe anonyme étend par défaut la classe parente ou implémente l'interface parente et il n'y a pas de clause supplémentaire pour étendre une autre classe ou implémenter d'autres interfaces. Donc,

    • new YourClass(){}; signifie class [Anonymous] extends YourClass {}
    • new YourInterface(){}; signifie class [Anonymous] implements YourInterface {}

Je pense que la question la plus importante qui reste ouverte est de savoir lequel utiliser et quand ? Cela dépend surtout du scénario auquel vous êtes confronté, mais la lecture de la réponse donnée par @jrudolph peut vous aider à prendre une décision.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X