Recherche…


Introduction

En utilisant Java, les développeurs ont la possibilité de définir une classe dans une autre classe. Une telle classe s'appelle une classe imbriquée . Les classes imbriquées sont appelées classes internes si elles ont été déclarées comme non statiques, sinon elles sont simplement appelées classes imbriquées statiques. Cette page sert à documenter et à fournir des détails sur les exemples d'utilisation des classes imbriquées et internes Java.

Syntaxe

  • Classe publique OuterClass {classe publique InnerClass {}} // Les classes internes peuvent également être privées
  • Classe publique OuterClass {classe statique publique StaticNestedClass {}} // Les classes imbriquées statiques peuvent également être privées
  • public void method () {classe privée LocalClass {}} // Les classes locales sont toujours privées
  • SomeClass anonymousClassInstance = new SomeClass () {}; // Les classes internes anonymes ne peuvent pas être nommées, donc l'accès est sans objet. Si 'SomeClass ()' est abstrait, le corps doit implémenter toutes les méthodes abstraites.
  • SomeInterface anonymousClassInstance = new SomeInterface () {}; // Le corps doit implémenter toutes les méthodes d'interface.

Remarques

Terminologie et classification

La spécification de langage Java (JLS) classe les différents types de classe Java comme suit:

Une classe de niveau supérieur est une classe qui n'est pas une classe imbriquée.

Une classe imbriquée est une classe dont la déclaration se produit dans le corps d'une autre classe ou interface.

Une classe interne est une classe imbriquée qui n'est pas explicitement ou implicitement déclarée statique.

Une classe interne peut être une classe membre non statique , une classe locale ou une classe anonyme . Une classe membre d'une interface est implicitement statique, elle n'est donc jamais considérée comme une classe interne.

En pratique, les programmeurs se réfèrent à une classe de niveau supérieur contenant une classe interne en tant que "classe externe". En outre, il existe une tendance à utiliser "classe imbriquée" pour faire référence uniquement aux classes imbriquées statiques (explicitement ou implicitement).

Notez qu'il existe une relation étroite entre les classes internes anonymes et les lambda, mais les lambda sont des classes.

Différences sémantiques

  • Les classes supérieures sont le "cas de base". Ils sont visibles par d'autres parties d'un programme soumis à des règles de visibilité normales basées sur la sémantique des modificateurs d'accès. S'ils ne sont pas abstraits, ils peuvent être instanciés par tout code où les constructeurs pertinents sont visibles sur la base des modificateurs d'accès.

  • Les classes imbriquées statiques suivent les mêmes règles d'accès et d'instanciation que les classes de niveau supérieur, à deux exceptions près:

    • Une classe imbriquée peut être déclarée comme private , ce qui la rend inaccessible en dehors de sa classe supérieure.
    • Une classe imbriquée a accès aux membres private de la classe de niveau supérieur englobante et de toutes ses classes testées.

    Cela rend les classes imbriquées statiques utiles lorsque vous devez représenter plusieurs "types d'entités" dans une limite d'abstraction étroite; Par exemple, lorsque les classes imbriquées sont utilisées pour masquer les "détails d'implémentation".

  • Les classes internes ajoutent la possibilité d'accéder aux variables non statiques déclarées dans des étendues englobantes:

    • Une classe membre non statique peut faire référence à des variables d'instance.
    • Une classe locale (déclarée dans une méthode) peut également faire référence aux variables locales de la méthode, à condition qu'elles soient final . (Pour Java 8 et versions ultérieures, ils peuvent être effectivement définitifs .)
    • Une classe interne anonyme peut être déclarée dans une classe ou une méthode et peut accéder à des variables selon les mêmes règles.

    Le fait qu'une instance de classe interne puisse faire référence à des variables dans une instance de classe englobante a des implications pour l'instanciation. Plus précisément, une instance englobante doit être fournie, implicitement ou explicitement, lorsqu'une instance d'une classe interne est créée.

Une pile simple utilisant une classe imbriquée

public class IntStack {

    private IntStackNode head;

    // IntStackNode is the inner class of the class IntStack
    // Each instance of this inner class functions as one link in the
    // Overall stack that it helps to represent
    private static class IntStackNode {

        private int val;
        private IntStackNode next;

        private IntStackNode(int v, IntStackNode n) {
            val = v;
            next = n;
        }
    }

    public IntStack push(int v) {
        head = new IntStackNode(v, head);
        return this;
    }

    public int pop() {
        int x = head.val;
        head = head.next;
        return x;
    }
}

Et son utilisation, qui (notamment) ne reconnaît pas du tout l'existence de la classe imbriquée.

public class Main {
    public static void main(String[] args) {
 
        IntStack s = new IntStack();
        s.push(4).push(3).push(2).push(1).push(0);

        //prints: 0, 1, 2, 3, 4, 
        for(int i = 0; i < 5; i++) {
            System.out.print(s.pop() + ", ");
        }            
    }
}

Classes imbriquées statiques et non statiques

Lors de la création d'une classe imbriquée, vous avez le choix entre avoir cette classe imbriquée statique:

public class OuterClass1 {

    private static class StaticNestedClass {

    }

}

Ou non statique:

public class OuterClass2 {

    private class NestedClass {

    }

}

À la base, les classes imbriquées statiques n'ont pas d' instance environnante de la classe externe, contrairement aux classes imbriquées non statiques. Cela affecte à la fois où / quand on est autorisé à instancier une classe imbriquée et quelles instances de ces classes imbriquées sont autorisées à accéder. Ajout à l'exemple ci-dessus:

public class OuterClass1 {

    private int aField;
    public void aMethod(){}

    private static class StaticNestedClass {
        private int innerField;

        private StaticNestedClass() {
             innerField = aField; //Illegal, can't access aField from static context 
             aMethod();           //Illegal, can't call aMethod from static context 
        }

        private StaticNestedClass(OuterClass1 instance) {
             innerField = instance.aField; //Legal
        }

    }

    public static void aStaticMethod() {
        StaticNestedClass s = new StaticNestedClass(); //Legal, able to construct in static context
        //Do stuff involving s...
    }

}

public class OuterClass2 {

    private int aField;

    public void aMethod() {}

    private class NestedClass {
        private int innerField;

        private NestedClass() {
             innerField = aField; //Legal   
             aMethod(); //Legal
        }
    }

    public void aNonStaticMethod() {
        NestedClass s = new NestedClass(); //Legal
    }

    public static void aStaticMethod() {
        NestedClass s = new NestedClass(); //Illegal. Can't construct without surrounding OuterClass2 instance.
                                         //As this is a static context, there is no surrounding OuterClass2 instance
    }
}

Ainsi, votre décision de statique contre non-statique dépend principalement de la nécessité ou non d’accéder directement aux champs et aux méthodes de la classe externe, même si cela a aussi des conséquences sur quand et où vous pouvez construire la classe imbriquée.

En règle générale, faites en sorte que vos classes imbriquées soient statiques sauf si vous devez accéder aux champs et aux méthodes de la classe externe. Comme pour rendre vos champs privés à moins que vous en ayez besoin publiquement, cela réduit la visibilité disponible pour la classe imbriquée (en ne permettant pas l'accès à une instance externe), réduisant ainsi le risque d'erreur.

Modificateurs d'accès pour les classes internes

Une explication complète des modificateurs d'accès en Java peut être trouvée ici . Mais comment interagissent-ils avec les classes internes?

public , comme d'habitude, donne un accès illimité à toute portée pouvant accéder au type.

public class OuterClass {

    public class InnerClass {

        public int x = 5;

    }

    public InnerClass createInner() {
        return new InnerClass();
    }
}

public class SomeOtherClass {

    public static void main(String[] args) {
        int x = new OuterClass().createInner().x; //Direct field access is legal
    }
}

Les deux protected et le modificateur par défaut (de rien) se comportent aussi bien que prévu, comme ils le font pour les classes non imbriquées.

private intéressant, private , ne se limite pas à la classe à laquelle il appartient. Au contraire, il se limite à l'unité de compilation - le fichier .java. Cela signifie que les classes externes ont un accès complet aux champs et méthodes de classe interne, même si elles sont marquées comme private .

public class OuterClass {

    public class InnerClass {

        private int x;
        private void anInnerMethod() {}
    }

    public InnerClass aMethod() {
        InnerClass a = new InnerClass();
        a.x = 5; //Legal
        a.anInnerMethod(); //Legal
        return a;
    }
}

La classe interne elle-même peut avoir une visibilité autre que public . En le marquant private ou un autre modificateur d'accès restreint, les autres classes (externes) ne seront pas autorisées à importer et assigner le type. Cependant, ils peuvent toujours obtenir des références à des objets de ce type.

public class OuterClass {

    private class InnerClass{}

    public InnerClass makeInnerClass() {
        return new InnerClass();
    }
}

public class AnotherClass {

    public static void main(String[] args) {
        OuterClass o = new OuterClass();
     
        InnerClass x = o.makeInnerClass(); //Illegal, can't find type
        OuterClass.InnerClass x = o.makeInnerClass(); //Illegal, InnerClass has visibility private
        Object x = o.makeInnerClass(); //Legal
    }
}

Classes internes anonymes

Une classe interne anonyme est une forme de classe interne déclarée et instanciée avec une seule instruction. En conséquence, il n'y a pas de nom pour la classe qui peut être utilisé ailleurs dans le programme; c'est à dire qu'il est anonyme.

Les classes anonymes sont généralement utilisées dans les situations où vous devez pouvoir créer une classe légère à transmettre en tant que paramètre. Cela se fait généralement avec une interface. Par exemple:

public static Comparator<String> CASE_INSENSITIVE =
        new Comparator<String>() {
            @Override
            public int compare(String string1, String string2) {
                return string1.toUpperCase().compareTo(string2.toUpperCase());
            }
        };

Cette classe anonyme définit un objet Comparator<String> ( CASE_INSENSITIVE ) qui compare deux chaînes en ignorant les différences dans la casse.

Les autres interfaces fréquemment implémentées et instanciées à l'aide de classes anonymes sont Runnable et Callable . Par exemple:

// An anonymous Runnable class is used to provide an instance that the Thread
// will run when started.
Thread t = new Thread(new Runnable() {
        @Override 
        public void run() {
              System.out.println("Hello world");
        }
    });
t.start();  // Prints "Hello world"

Les classes internes anonymes peuvent également être basées sur des classes. Dans ce cas, la classe anonyme extends implicitement la classe existante. Si la classe étendue est abstraite, la classe anonyme doit implémenter toutes les méthodes abstraites. Il peut également remplacer les méthodes non abstraites.

Constructeurs

Une classe anonyme ne peut pas avoir de constructeur explicite. Au lieu de cela, un constructeur implicite est défini qui utilise super(...) pour transmettre tous les paramètres à un constructeur de la classe en cours d'extension. Par exemple:

SomeClass anon = new SomeClass(1, "happiness") {
            @Override
            public int someMethod(int arg) {
                // do something
            }
        };

Le constructeur implicite de notre sous-classe anonyme de SomeClass appellera un constructeur de SomeClass correspondant à la signature d'appel SomeClass(int, String) . Si aucun constructeur n'est disponible, vous obtiendrez une erreur de compilation. Toutes les exceptions lancées par le constructeur correspondant sont également renvoyées par le constructeur implicite.

Naturellement, cela ne fonctionne pas lors de l'extension d'une interface. Lorsque vous créez une classe anonyme à partir d'une interface, la superclasse de classes est java.lang.Object qui ne possède qu'un constructeur no-args.

Méthode Classes internes

Une classe écrite dans une méthode appelée méthode classe interne locale . Dans ce cas, la portée de la classe interne est restreinte dans la méthode.

Une classe interne à la méthode locale ne peut être instanciée que dans la méthode où la classe interne est définie.

L'exemple de l'utilisation de la classe interne locale method:

public class OuterClass {
    private void outerMethod() {
       final int outerInt = 1;
        // Method Local Inner Class
        class MethodLocalInnerClass {
            private void print() {
                System.out.println("Method local inner class " + outerInt);
            }
        }
        // Accessing the inner class
        MethodLocalInnerClass inner = new MethodLocalInnerClass();
        inner.print();
    }

    public static void main(String args[]) {
        OuterClass outer = new OuterClass();
        outer.outerMethod();
    }
}

L'exécution donnera une sortie: Method local inner class 1 .

Accéder à la classe externe à partir d'une classe interne non statique

La référence à la classe externe utilise le nom de la classe et this

public class OuterClass {
    public class InnerClass {
        public void method() {
            System.out.println("I can access my enclosing class: " + OuterClass.this);
        }
    }
}

Vous pouvez accéder directement aux champs et aux méthodes de la classe externe.

public class OuterClass {
    private int counter;

    public class InnerClass {
        public void method() {
            System.out.println("I can access " + counter);
        }
    }
}

Mais en cas de collision de noms, vous pouvez utiliser la référence de classe externe.

public class OuterClass {
    private int counter;

    public class InnerClass {
        private int counter;
        
        public void method() {
            System.out.println("My counter: " + counter);
            System.out.println("Outer counter: " + OuterClass.this.counter);
            
            // updating my counter
            counter = OuterClass.this.counter;
        }
    }
}

Créer une instance de classe interne non statique depuis l'extérieur

Une classe interne visible par toute classe externe peut également être créée à partir de cette classe.

La classe interne dépend de la classe externe et nécessite une référence à une instance de celle-ci. Pour créer une instance de la classe interne, l'opérateur new doit uniquement être appelé sur une instance de la classe externe.

class OuterClass {

    class InnerClass {
    }
}

class OutsideClass {

    OuterClass outer = new OuterClass();
    
    OuterClass.InnerClass createInner() {
        return outer.new InnerClass();
    }
}

Notez l'utilisation en tant que outer.new .



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow