- Si un type n'est utilisé que dans une petite partie du code, faire un type interne permet de renforcer l'encapsulation (par exemple
Pile4
dans TP3).
- Si un type a besoin de manipuler l'état interne de son type englobant, le type interne accède aux données privées de son type englobant (de la même manière que les méthodes d'une classe).
Un type imbriqué n'a pas d'existence indépendante, existe au sein de son type englobant.
Souvent appelés "classes internes" (désignation imprécise, mais syntaxe java précise).
Important : bien distinguer inheritance hierarchy et containment hierarchy.
Membres static
Exemple dans TP3, question 2 : la classeMaillon
est un membre static de Pile4
.
- Les classes internes static doivent utiliser le mot-clé static.
- Comme les méthodes statiques, ont accès aux membres statiques du type englobant (y compris les autres types imbriqués static).
- Mais n'ont pas accès aux membres d'instance (donc mot-clé
this
pas accessible). - Ont accès aux membres privés du type englobant, et inversement.
- Peuvent accéder aux membres du type englobant sans préfixer par le nom de la classe (sauf en cas de conflit de nom).
- Un type imbriqué dans une interface ou une annotation est implicitement static (ce qui est normal).
- Interfaces, enums, annotations sont toujours static (même si déclarés sans mot-clé static).
- Un type imbriqué ne peut pas avoir le même nom qu'une classe englobante.
- java supporte un nombre arbitraire de niveaux d'imbrication.
class OuterClass { ... static class StaticNestedClass { ... } }On utilise le nom de la classe englobante pour y accéder, par ex :
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();Les membres static d'accès public ou package peuvent être utilisés ailleurs :
import package1.Class1.ClasseInterne; import package1.Class1.*;
Membre d'instance
(Uniquement pour les classes, pas les interfaces ou enums)- Toujours associé à une instance de la classe englobante.
- A accès aux membres static et d'instance de la classe englobante, même s'ils sont déclarés
private
. - Comme est associé à une instance de sa classe englobante, ne peut pas déclarer de champs static.
class OuterClass { ... class InnerClass { ... } }Pour instancier une inner class :
OuterClass outerObject = new OuterClass(); OuterClass.InnerClass innerObject = outerObject.new InnerClass();
Shadowing
Si une variable porte le même nom qu'une autre variable dans un contexte englobant, elle cache le nom (shadowing), donc syntaxe spéciale.public class ShadowTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) { System.out.println("x = " + x); System.out.println("this.x = " + this.x); System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); // syntaxe spéciale } } public static void main(String... args) { ShadowTest st = new ShadowTest(); ShadowTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); } }(code dans ShadowTest.java).
java ShadowTest
x = 23 this.x = 1 ShadowTest.this.x = 0
Le code suivant récapitule les ambiguités qu'on peut recontrer ; code dans exemples/java/nested/tests.
import static java.lang.System.out; class Class1{ // ***** static ***** private static int a = 1; private static int b = 2; public static void printStatic(){ out.println("Class1.printStatic(), a = " + a); // 1 } public static class ClasseInterneStatic{ private static int a = 3; public static void printStatic(){ out.println("ClasseInterneStatic.printStatic(), a = " + a); // 3 out.println("ClasseInterneStatic.printStatic(), Class1.a = " + Class1.a); // 1 out.println("ClasseInterneStatic.printStatic(), b = " + b); // 2 out.println("ClasseInterneStatic.printStatic(), Class1.b = " + Class1.b); // 2 } } // ***** instance ***** private int c = 4; private int d = 5; public void printInstance(){ out.println("Class1.printInstance(), c = " + c); // 4 out.println("Class1.printInstance(), this.c = " + this.c); // 4 } public class ClasseInterne{ private int c = 6; public void printInstance(){ out.println("ClasseInterne.printInstance(), c = " + c); // 6 out.println("ClasseInterne.printInstance(), this.c = " + this.c); // 6 out.println("ClasseInterne.printInstance(), Class1.this.c = " + Class1.this.c); // 6 out.println("ClasseInterne.printInstance(), d = " + d); // 5 } // Les deux lignes suivantes ne passent pas à la compilation : // on ne peut pas définir de membres static dans une classe qui est un membre d'instance // public static int e = 7; // IMPOSSIBLE // public static void printStatic(){ } // IMPOSSIBLE } }
import static java.lang.System.out; class Main{ public static void main(String[] args){ out.println("==== tests static ===="); Class1.printStatic(); Class1.ClasseInterneStatic.printStatic(); out.println("\n==== tests instance ===="); Class1 c1 = new Class1(); Class1.ClasseInterne interne1 = c1.new ClasseInterne(); c1.printInstance(); interne1.printInstance(); } }
java Main ==== tests static ==== Class1.printStatic(), a = 1 ClasseInterneStatic.printStatic(), a = 3 ClasseInterneStatic.printStatic(), Class1.a = 1 ClasseInterneStatic.printStatic(), b = 2 ClasseInterneStatic.printStatic(), Class1.b = 2 ==== tests instance ==== Class1.printInstance(), c = 4 ClasseInterne.printInstance(), c = 6 ClasseInterne.printInstance(), this.c = 6 ClasseInterne.printInstance(), Class1.this.c = 4 ClasseInterne.printInstance(), d = 5
Exercice
Allez voir l'API de
- Quelle classe imbriquée statique (static nested class) est définie dans
- Quelle classe interne (inner class) est définie dans
- Quelle est la supeclasse de cette classe interne ?
- Quelle classe interne de
- Quel code permet de créer une instance de la classe interne
Allez voir l'API de
javax.swing.Box
.
- Quelle classe imbriquée statique (static nested class) est définie dans
Box
?
- Quelle classe interne (inner class) est définie dans
Box
?
- Quelle est la supeclasse de cette classe interne ?
- Quelle classe interne de
Box
peut on utiliser depuis n'importe quelle classe ?
- Quel code permet de créer une instance de la classe interne
Filler
?
Classes locales
On peut définir une classe interne dans n'importe quel bloc, par ex dans une méthode, un initialisateur statique ou d'instance, une boucle for, dans un if.Une classe locale a accès aux champs de sa classe environnante et aux variables locales de son bloc.
Ne peut pas être définie
public
, protected
, private
ou static
.
Classes anonymes
Classes utilisées à un seul endroit ; n'ont même pas de nom.Exemple : on cherche à afficher les fichiers ".java" d'un répertoire.
(code des exemples dans exemples/java/nested/filter).
De manière classique :
import java.io.File; public class TestFilter1{ public static void main(String[] args){ File dir = new File(args[0]); String[] files = dir.list(); for(String file : files){ if(file.endsWith(".java")){ System.out.println(file); } } } }On a utilisé
java.io.File.list()
:
package java.io; public class File extends Object implements Serializable, Comparable<File>{ ... /** Returns an array of strings naming the files and directories in the directory denoted by this abstract pathname. **/ public String[] list(){ ... } /** Returns an array of strings naming the files and directories in the directory denoted by this abstract pathname that satisfy the specified filter. **/ public String[] list(FilenameFilter filter){ ... } ... }Mais on peut aussi utiliser
File.list(FilenameFilter filter)
, qui prend en paramètre un objet d'une classe implémentant java.io.FilenameFilter
.
@FunctionalInterface public interface FilenameFilter{ boolean accept(File dir, String name); }
Utilisation classique
import java.io.File; import java.io.FilenameFilter; public class TestFilter2{ public static void main(String[] args){ File dir = new File(args[0]); String[] files = dir.list(new JavaFilenameFilter()); for(String file : files){ System.out.println(file); } } } class JavaFilenameFilter implements FilenameFilter{ public boolean accept(File f, String s) { return s.endsWith(".java"); } }
Avec classe anonyme
import java.io.File; import java.io.FilenameFilter; public class TestFilter3{ public static void main(String[] args){ File dir = new File(args[0]); String[] files = dir.list(new FilenameFilter() { public boolean accept(File f, String s) { return s.endsWith(".java"); } }); for(String file : files){ System.out.println(file); } } }
f
et s
?
La réponse est dans l'implémentation de
java.io.File.list(FilenameFilter filter)
(voir le code source) :
public String[] list(FilenameFilter filter) { String names[] = list(); if ((names == null) || (filter == null)) { return names; } List<String> v = new ArrayList<>(); for (int i = 0 ; i < names.length ; i++) { if (filter.accept(this, names[i])) { v.add(names[i]); } } return v.toArray(new String[v.size()]); }Donc dans le code
File dir = new File(args[0]); String[] files = dir.list(new FilenameFilter() { public boolean accept(File f, String s) { return s.endsWith(".java"); } });La variable
f
fait donc référence à dir
(puisque list()
est une méthode de l'instance dir
).
L'utilisation d'une classe anonyme peut être ici rendue plus compacte en utilisant une lambda expression.