Published by

Il y a 11 ans -

Temps de lecture 8 minutes

Back to Basics : Bien maîtriser les classes internes en Java

Les classes internes en Java sont apparues dans la version 1.1 du langage. Depuis, des discussions se sont multipliées au sein de la communauté qui s’est divisée en deux clans : les pour et les contres. Mis à part les points de vue, les classes internes représentent un mécanisme très puissant lorsqu’elles sont employées correctement. Cet article propose un retour rapide aux bases de Java pour revoir les concepts et les cas d’utilisation de cette fonctionnalité du langage.

Une classe interne n’est ni plus ni moins qu’une classe définie dans une autre classe. Il existe différents types de classes internes. Chaque type étant adapté à une situation spécifique. Voici les 4 types de classes internes en Java :

  • Les classes internes simples
  • Les classes internes anonymes
  • Les classes internes aux méthodes
  • Les classes statiques embarquées

Les classes internes simples

Une classe interne simple (inner class), comme le nom l’indique, est une classe définie dans une autre. Voici un exemple de classe interne simple.

public class OuterClass {
    private int index = 0;

    class InnerClass {
        public void printIndex() {
            // Because the inner class is a member of the
            // outer class, it can access its private members
            System.out.println("Index: " + index);
        }
    }
}

Lorsqu’on compile l’exemple ci-dessus, deux fichiers sont produits :

OuterClass.class
OuterClass$InnerClass.class

Chaque classe interne a son propre fichier. Néanmoins, il n’est pas possible d’exécuter une classe interne directement sans la classe qui l’héberge. Comme une classe interne simple ne peut pas contenir des déclarations statiques, il n’est pas possible de créer une méthode main.

Ce type de classe interne est utilisé quand deux classes sont très liées l’une à l’autre au niveau de leur fonctionnement. Dans ce type de situation, le couplage entre les deux classes est renforcé pour faciliter la communication entre leurs membres.

Un exemple typique d’utilisation des classes internes simples est une application de messagerie instantanée. Dans ce type d’application, des listeners ou des handlers sont souvent utilisés à chaque fois qu’un message arrive ou est envoyé. Ces objets sont utilisés uniquement par la classe qui "gère" la session de chat et n’interagissent avec aucun autre composant. L’interaction se fait entre les mêmes objets à chaque fois. Dans ce contexte, l’utilisation d’une classe interne simple se justifie et facilite l’accès aux membres de la classe externe.

L’accès aux membres d’une classe interne se fait au runtime et à l’aide d’une instance de la classe externe. De ce fait, il est possible d’accéder aux membres d’une classe interne dans un contexte statique, mais toujours à travers l’instance de la classe externe.

public class InnerClassAcessor {
    public static void main(String... args) {
        MyOuterClass.MyInnerClass myInnerClass = new MyOuterClass().new MyInnerClass();
        myInnerClass.printIndex(); // will print Index: 0
    }
}

Le mot clé this fait référence à l’instance de la classe qui l’héberge. Dans le cadre d’une classe interne, il fera référence à la classe interne s’il est utilisé à l’intérieur de ses accolades. Pour faire référence à la classe externe à partir de la classe interne, il faut préfixer this par le nom de la classe externe.

public class SecondOuterClass {
    private String classAlias = "outer alias";

    class InnerClass {
        String classAlias = "inner alias";

        public void printAlias() {
            System.out.println("Outer: " + SecondOuterClass.this.classAlias);
            System.out.println("Inner: " + this.classAlias);
        }
    }

    public static void main(String... args) {
        InnerClass innerClass = new SecondOuterClass().new InnerClass();
        innerClass.printAlias();    // will print   Outer: outer alias
                                    //              Inner: inner alias
    }
}

Les classes internes anonymes

Les classes internes anonymes (anonymous inner classes) sont ainsi appelées car elles n’ont simplement pas de nom. Voici un exemple :

abstract class Bird {
    abstract void fly();
}

public class AnonymousInnerClass {
    public static void main(String... args) {
        Bird bird = new Bird() {
            @Override
            void fly() {
                System.out.println("Flying...");
            }
        };  // Semicolon required!

        bird.fly(); // Will print "Flying..."
    }
}

Les classes internes anonymes sont souvent utilisées dans les méthodes, en tant que variables d’instance ou en tant que paramètre d’une méthode. Dans les deux premiers cas, un point-virgule est nécessaire à la fin de la déclaration, sinon il y a une erreur de compilation.

Lorsque nous devons passer en paramètre un objet qui ne sera utilisé nul part ailleurs, l’utilisation d’une classe anonyme doit être considérée. L’interface Runnable en est un exemple courant. Cette interface est souvent instanciée directement en tant que classe interne anonyme et passée en paramètre de méthode.

Il est utile de rappeler que les nouvelles méthodes créées à l’intérieur d’une classe interne anonyme ne peuvent pas être appelées depuis l’extérieur. Seulement les méthodes surchargées (de l’interface ou de la classe de laquelle la classe anonyme implémente ou étend) sont visibles. Cela est dû au fait que Java est un langage fortement typé.

Quand on utilise l’API Guava de Google on fait une utilisation intense de ce type de classe interne, notamment avec les Predicates et les Functions. Les classes internes anonymes sont à présent le plus proche de la représentation d’une fonction, tel que l’on entend dans la programmation fonctionnelle. Ce lien fourni plus de détails sur l’utilisation des fonctions dans Guava.

Les classes internes aux méthodes

Les classes internes aux méthodes (method-local inner classes) sont des classes internes déclarées à l’intérieur d’une méthode. Il n’est possible d’instancier ce type de classe qu’à l’intérieur de la méthode qui l’héberge. Par exemple :

public class MethodLocalInnerClassExample {
    private int x = 10;

    void printFromInner(final int a) {
        final int y = 10;

        class MethodLocalInnerClass {
            int w = 5;

            public void print(int z) {
                System.out.println("x + y - z + w + a = " + (x+y-z+w+a));
            }
        }

        MethodLocalInnerClass innerClass = new MethodLocalInnerClass(); // must come after the class' definition
        innerClass.print(5);
    }
}

Il y a un point intéressant à remarquer ici. Pour que ce type de classe interne puisse utiliser les variables déclarées à l’intérieur de la méthode ou reçues en paramètre, ces variables doivent être déclarées final. En déclarant une variable final, le compilateur fait une copie séparée de sa valeur dans la classe interne. De cette façon, la classe interne garde son état même si la méthode dans laquelle elle a été déclarée a fini de s’exécuter. Cela s’avère utile quand une instance de la classe interne est utilisée à l’extérieur de la méthode.

Les classes statiques embarquées

Les classes statiques embarquées (static nested classes) sont souvent utilisées quand on a besoin d’accéder aux membres d’une classe à partir d’un contexte statique. Comme la classe est marquée static, ce type de classe interne n’est pas lié à l’instance de la classe externe. Ainsi, classes statiques embarquées ne peuvent accéder qu’aux membres statiques.

class HelperClass {
    static class InnerHelper {
        void print() {
            doPrint();
            int b = a;
        }
    }

    static void doPrint() {
        System.out.println("Printing from HelperClass");
    }

    static int a = 1;
}

public class StaticInnerClassExample {
    static class AnotherHelper {
        void print() {
            System.out.println("Printing from AnotherHelper");
        }
    }

    public static void main(String... args) {
        /* Remark the way we create an instance of a static inner class
         is not the same as for inner classes */
        HelperClass.InnerHelper h1 = new HelperClass.InnerHelper();
        h1.print(); // will print: Printing from HelperClass
        AnotherHelper h2 = new AnotherHelper();
        h2.print(); // will print: Printing from AnotherHelper
    }
}

Dit autrement, une classes statiques embarquée ne fait pas référence à l’instance de la classe qui l’héberge. Ainsi, la classe statique embarquée ne peut pas accéder aux membres non-statiques de la classe externe. De ce fait, l’utilisation de ce type de classe "économise" une référence vers l’instance de la classe externe.

Conclusion

Les classes internes en Java sont un mécanisme puissant pour simplifier le code, limiter la portée d’une classe, exécuter des traitements spécifiques, entre autres. Bien choisir le type de classe interne à utiliser, rendra le code plus simple, plus lisible et plus robuste. Point à remarquer : dans beaucoup de cas, le besoin d’utiliser une classe interne vient du fait que Java ne supporte pas des closures (pas avant Java 8). Si c’est le cas, d’autres solutions sont envisageables, comme, par exemple, choisir un autre langage pour implémenter une partie ou l’intégralité de l’application (polyglot programming) ou utiliser une API "fonctionnelle" comme Guava. Quand le choix du langage ne nous appartient pas, il nous reste la deuxième option. API fonctionnelle ou classes internes, dans les deux cas, bien maîtriser chaque solution permet de choisir celle qui est la mieux adaptée selon le contexte.

Cet article est une traduction libre, faite avec l’autorisation de l’auteur, de "Java: Inner classes" publié par Diego Lemos le 15/07/2012 sur http://diegolemos.net/2012/07/15/java-inner-classes/.

Published by

Publié par Diego Lemos

Diego s’est forgé une solide expérience dans l'écosystème Java. Depuis, longtemps convaincu par l’agilité, Diego a participé à des nombreux projets agiles, d’abord en tant que développeur, puis en tant que scrum master et ensuite coach technique. Diego dispose d’un vaste panel de compétences sur l’ensemble de l’écosystème JVM, et notamment les solutions du monde open-source.
Passionné par l’informatique, il a eu l’occasion de d’intervenir sur des missions techniques très variées, notamment en tant que en tant que développeur frontend et sysadmin. Cela fait de Diego un expert technique full stack.
Il a joué un rôle important dans des projets de grande envergure, en tant que coach technique, notamment autour de la mise en place de pratiques tels que le Continuous Delivery à l’échelle. Aujourd’hui, Diego intervient principalement en tant que formateur, consultant et coach technique sur des sujets tels que le testing, le design du code, le software craftsmanship et le continuous delivery.
Blog personnel : http://diegolemos.net
Twitter : @dlresende.

Commentaire

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.