Table of Contents

Breviar 8

Clase incluse

1. Clase interne

1.1 Introducere

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:

Unul din avantajele majore al claselor interne este reprezentat de comportamentul acestora ca un membru al clasei. Astfel, o clasă internă poate avea acces la toți membrii clasei în care este definită (outer class), inclusiv la cei declarați folosind modificatorul private.

O clasă internă poate avea modificatorii specifici claselor (abstract, final) și o serie de modificatori specifici membrilor unei clase (public, protected, private, static). Acești modificatori atribuie clasei interne aceleași proprietăți ca în cazul oricărui membru al unei clase.

1.2 Clase interne normale (regular inner classes)

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:

De asemenea, se poate observa în codul de mai sus că putem accesa referința la clasa externă folosind numele acesteia și cuvântul cheie this.

1.3 Clase interne statice (static nested classes)

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();
1.4 Clase interne metodelor (method-local inner classes)

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"
    }
}

Clasele interne declarate în cadrul metodelor nu pot accesa direct variabilele locale sau parametrii metodei, deoarece aceștia există doar pe stivă, pe durata execuției metodei. Pentru a fi accesibile, variabilele respective trebuie să fie constante.

Î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.

O variabilă locală sau un parametru este effectively final dacă valoarea lui nu se schimbă după inițializare, chiar dacă nu a fost declarat explicit final.

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
        }
    }
}
1.5 Clase interne anonime (anonymous inner classes)

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();
    }
}

În mod similar, o clasă anonimă poate implementa o interfață, iar pentru înțelegerea acestui aspect se recomandă analizarea codului de mai jos.

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);
    }
}
1.6 Clase interne în blocuri

La compilare, clasa va fi creată, indiferent dacă la rulare nu se execută blocul în care este definită.

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
        }
    }
}

2. Moștenirea claselor interne

Deoarece pentru a instanția un obiect al cărui tip este reprezentat de o clasă internă este nevoie de o instanță a clasei externe, în care este definită clasa internă, pentru a se atașa constructorul clasei interne, moștenirea unei clase interne nu este tocmai intuitivă, fiind considerată mai complicată decât cea obișnuită.

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
    }
}

3. Poate fi o clasă internă suprascrisă?

Î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ă.