Definiția unei clase poate conține definiția unei alte clase, iar aceste clase, definite în interiorul unei alte clase, se numesc imbricate sau interne. Tipul acesta de clase reprezintă o funcționalitate importantă a limbajului Java, deoarece, prin acest mod, este permisă gruparea claselor care sunt legate logic și există un control al vizibilității bine definit.
Clasele interne sunt de mai multe tipuri, în funcție de modul în care se instanțiază și de relația lor cu clasa exterioară care le conține, ele se împart în:
La compilarea unei clase care conține declarații de clase, se vor obține fișiere .class pentru fiecare clasă, în cazul clasei interne numele unui astfel de fișier fiind de forma NumeClasaExterna$NumeClasaInterna.class.
O clasă internă poate fi accesată doar printr-o instanță a clasei externe în care este definită (precum metodele și variabile nestatice ale unei clase).
În continuare, se va prezenta un exemplu care conține o instanțiere a unei clase interne.
class Outer { private int x; class Inner { private int y; public Inner(int x, int y) { Outer.this.x = x; this.y = y; } public int getX() { return Outer.this.x; } public int getY() { return this.y; } } public Inner getInstance(int x, int y) { Inner obj = new Inner(x, y); return obj; } } class Test { public static void main(String args[]) { Outer out = new Outer(); Outer.Inner in1 = out.new Inner(10, 20); System.out.println(in1.getX() + " " + in1.getY()); Outer.Inner in2 = out.getInstance(20, 30); System.out.println(in2.getX() + " " + in2.getY()); } }
În codul de mai sus, sunt utilizate două modalități de a obține o instanță a clasei Inner:
O clasă internă statică este definită cu modificatorul static și, spre deosebire de o clasă internă normală, nu are nevoie de o instanță a clasei externe pentru a fi instanțiată.
Totuși, ea nu poate accesa direct membrii non-statici ai clasei externe, ci doar membrii statici.
Pentru a crea o instanță de clasă statică se utilizează sintaxa:
Outer.Nested instance = new Outer.Nested();
Dacă dorim ca vizibilitatea unei clase să se restrângă doar la nivelul unei singure metode, putem opta pentru definirea acestei clase în cadrul metodei. Singurii modificatori care pot fi aplicați acestor clase sunt abstract sau final.
class Outer { public void printText() { class Local { // putem defini câmpuri și metode aici void message() { System.out.println("Salut din clasa locală!"); } } Local local = new Local(); local.message(); // apelăm metoda din clasa locală } } interface Number<T> { public T getValue(); } class Outer<T> { public Number<T> getInnerInstance() { class Numar implements Number<T> { T value; @Override public T getValue() { return value; // va fi null în acest exemplu } } return new Numar(); } } class Test { public static void main(String[] args) { Outer<Integer> out = new Outer<Integer>(); // Eroare la compilare: Numar este clasă locală în metoda getInnerInstance() Outer.Numar in1 = out.getInnerInstance(); Number<Integer> in2 = out.getInnerInstance(); System.out.println(in2.getValue()); // printează "null" } }
În versiunile mai vechi de Java (≤7) era obligatoriu ca variabilele să fie declarate explicit final.
Începând cu Java 8, este suficient ca acestea să fie effectively final, adică să nu-și mai schimbe valoarea după inițializare.
Astfel, compilatorul permite accesul la ele, făcând o copie în câmpurile clasei interne.
public void metoda() { int x = 10; // effectively final (nu se modifică) class Local { void print() { System.out.println(x); // permis } } new Local().print(); }
Dacă variabila este modificată ulterior, nu mai este effectively final și codul nu compilează:
public void metoda() { int x = 10; x = 20; // variabila nu mai e effectively final class Local { void print() { System.out.println(x); // EROARE } } }
Clasele interne anonime sunt clase interne dar care nu au un nume. În mod uzual aceste tipuri de clase sunt definite ca și subclase ale unei clase sau ca și implementări ale unor interfețe.
class SuperClass { public void doIt() { System.out.println("SuperClass doIt()"); } } public class AnonymousTest { public static void main(String[] args) { SuperClass instance = new SuperClass() { @Override public void doIt() { System.out.println("Anonymous class doIt()"); } }; instance.doIt(); } }
class Test { public static void main(String[] args) { TreeSet<String> set = new TreeSet<String>(new Comparator<String>() { @Override public int compare(String s1, String s2) { int cmp = Integer.compare(s1.length(), s2.length()); if (cmp == 0) { return s1.compareTo(s2); } return cmp; } }); set.add("Clase interne"); set.add("Clase anonime"); set.add("Laborator"); System.out.println(set); } }
class Outer { public Comparator<Integer> getInnerInstance(int nr) { if (nr % 2 == 0) { // Clasă locală definită într-un bloc if class MyComparator implements Comparator<Integer> { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1, o2); } } return new MyComparator(); } else { return null; } } } public class Test { public static void main(String[] args) { Outer out = new Outer(); Comparator<Integer> comp = out.getInnerInstance(4); if (comp != null) { System.out.println(comp.compare(10, 20)); // -1 } } }
Astfel, pentru înțelegerea acesteia, se propune analizarea următorului exemplu.
class Outer { class Inner { public void show() { System.out.println("Metodă din Inner"); } } } // Clasa SubInner moștenește Outer.Inner class SubInner extends Outer.Inner { // Ca să extinzi Inner, ai nevoie de o instanță Outer în constructor SubInner(Outer o) { o.super(); // apelăm constructorul clasei interne } @Override public void show() { System.out.println("Metodă suprascrisă în SubInner"); } } public class Test { public static void main(String[] args) { Outer outer = new Outer(); SubInner sub = new SubInner(outer); sub.show(); // afișează: Metodă suprascrisă în SubInner } }
În această secțiune vom analiza următorul scenariu: avem o clasă care conține definiția unei clase interne și o altă clasă care moștenește clasa inițială și conține definiția unei clase interne având același nume cu cea definită în superclasă. Ne dorim să aflăm dacă se întâmplă același lucru ca în cazul metodelor care pot fi suprascrise, iar pentru a afla răspunsul se va propune analizarea următorului exemplu.
class Egg { private Yolk y; protected class Yolk { public Yolk() { System.out.println("Egg.Yolk()"); } } public Egg() { System.out.println("New Egg()"); y = new Yolk(); // => creează Egg.Yolk } } class BigEgg2 extends Egg { public class Yolk { public Yolk() { System.out.println("BigEgg2.Yolk()"); } } public static void main(String[] args) { new BigEgg2(); } }
Așadar, o clasă internă dintr-o subclasă nu suprascrie clasa internă din superclasă, ci doar coexistă cu ea ca tip separat. Pentru a folosi o versiune diferită, trebuie explicit creată instanța corespunzătoare din subclasă.