Les classes internes simples sont définies au sein des classes. Elles constituent des membres à part entière des classes d'inclusions au même titre que des variables ou des méthodes.

class ClasseParente {
  ...
  modificateur class ClasseInterne {
    // instructions...
  }
  ...
}

Une classe interne peut être déclarée avec n'importe lequel des modificateurs d'accès (public, protected, par défaut ou private) et les modificateurs spéciaux abstract, final ou static. Les classes possèdant le modificateur static deviennent des classes internes statiques.

Une classe interne déclarée avec le modificateur d'accès private implique que cette classe ne pourra être utilisée que dans sa classe parente.

Les classes internes ne peuvent pas être déclarées à l'intérieur d'initialisateurs statiques ou de membres d'interface.

Les classes internes ne doivent pas déclarer de membres statiques, hormis s'ils comportent le modificateur final, dans le cas contraire, une erreur de compilation se produit.

class Classeinterne {
  static final x = 10; // constante statique
  static int y = 12; // erreur de compilation
  ...
}

Toutefois, les membres statiques de la classe externe peuvent être hérités sans problème par la classe interne.

Les classes imbriquées sont capables d'accéder à toutes les variables et méthodes de la classe parente, y compris celles déclarées avec un modificateur private.

class ClasseParente {
  int x = 10;
  int y = 12;
  private int addition(){
    return (x + y);
  }
  class ClasseInterne {
    void verification(){
      if((x + y)== addition())
        System.out.println("La classe interne a bien accédé "
                          + "aux membres de sa classe parente.");
    }
  }
  public static void main(String[] args){
    ClassParente obj_out = new ClasseParente();
    ClasseInterne obj_in = obj_out.new ClasseInterne();
    obj_in.verification();
  }
}

Cette notation particulière spécifie que l'objet créé est une instance de la classe interne associée à l'objet résultant de l'instanciation d'une classe de plus haut niveau.

L'instanciation de la classe interne passe obligatoirement par une instance préalable de la classe d'inclusion.
La classe parente est d'abord instanciée, puis c'est au tour de la classe interne de l'être par l'intermédiaire de l'objet résultant de la première instance.

ClassParente obj_out = new ClasseParente();
ClasseInterne obj_in = obj_out.new ClasseInterne();
// est équivalent à
ClasseInterne obj_in = (new ClassParente()).new ClasseInterne();

Il est possible d'utiliser une méthode de la classe parente pour créer directement une instance de la classe interne. Toutefois, lors de l'appel de la méthode, il sera nécessaire de créer une instance de la classe d'inclusion.

class ClasseParente {
  int x = 10;
  int y = 12;
  private int addition(){
    return (x + y);
  }
  class ClasseInterne {
    public void verification(){
      if((x+y)== addition())
        System.out.println("La classe interne a bien accédé "
                          + "aux membres de sa classe parente.");
    }
  }
  void rendDisponible(){
    // Création d'une instance de la classe interne
    ClasseInterne obj_in = new ClasseInterne();
    // Appel d'une méthode de la classe interne
    obj_in.verification();
  }
  public static void main(String[] args){
    // Création préalable d'une instance de la classe parente
    ClasseParente obj_out = new ClasseParente();
    // Appel de la méthode instanciant la classe interne
    obj_out.rendDisponible();
  }
}

Cette manière de créer une instance de la classe interne est possible grâce à la référence d'objet this désignant une instance de la classe parente.

ClasseInterne obj_in = new ClasseInterne();
// est équivalent à
this.ClasseInterne obj_in = this.new ClasseInterne();

L'utilisation implicite du mot-clé this permet au constructeur de la classe parente de créer une instance de la classe interne. Le constructeur de cette dernière pourra alors exécuter ses propres instructions.

class ClasseParente {
  int x = 10;
  int y = 12;
  // constructeur externe
  ClasseParente(){
    // Création d'une instance de la classe interne
    new ClasseInterne();
  }
  public static void main(String[] args){
    // Création préalable d'une instance de la classe parente
    new ClasseParente();
  }
  private int addition(){
    return (x + y);
  }
  class ClasseInterne {
    // constructeur interne
    ClasseInterne(){
      if((x + y)== addition())
        System.out.println("La classe interne a bien accédé "
                          + "aux membres de sa classe parente.");
    }
  }
}

Il est possible d'imbriquer des classes sur plusieurs niveaux. Une classe normale peut contenir une à plusieurs classes internes et ces dernières peuvent contenir également une à plusieurs autres classes intérieures.

class ClasseParente {
  int x = 10;
  int y = 12;

  public static void main(String[] args){
  ClasseInterne obj_in = 
                      (new ClasseParente()).new ClasseInterne();
    obj_in.verification();
  }

  private int addition(){
    return (x + y);
  }

  class ClasseInterne {
    public void verification(){
      if((x + y)== addition())
        System.out.println("La classe interne a bien accédé "
                          + "aux membres de sa classe parente.");
        ClasseInterneInterieure obj_inin = 
                        new ClasseInterneInterieure();
        System.out.println(obj_inin.ajouter());
    }
    class ClasseInterneInterieure {
      int z = 10;
      int ajouter(){
        return (z + x);}
    }
  }
}

La classe doublement imbriquée ne peut être instanciée que dans sa classe parente directe.

Les méthodes et les variables d'instance d'une classe interne ne deviennent disponibles dans la classe parente qu'après l'instanciation de cette classe imbriquée.

Parfois, il peut être nécessaire de distinguer les variables situées dans les classes interne et externe.

class Externe {
  int x = 10;
  int y = 12;
  Externe(){
    new Interne();
  }
  public static void main(String[] args){
    new Externe();
  }
  class Interne {
    int x = 8;
    int y = 14;
    Interne(){
      if(this.x + this.y == Externe.this.x + Externe.this.y)
        System.out.println("La classe interne a bien "
                          + "accédé à l'ensemble des membres "
                          + "des classes imbriquées.");
    }
  }
}

Le mot-clé this permet d'accéder à un membre de la classe en cours, c'est-pourquoi this.variable accède au membre de la classe interne et Externe.this.variable à celui de la classe parente spécifiée.