Sök…


Introduktion

Med Java har utvecklare möjlighet att definiera en klass inom en annan klass. En sådan klass kallas en kapslad klass . Häckade klasser kallas Inner Classes om de förklarades som icke-statiska, om inte, de kallas helt enkelt Static Nested Classes. Denna sida ska dokumentera och ge detaljer med exempel på hur man använder Java Nested och Inner Classes.

Syntax

  • public class OuterClass {public class InnerClass {}} // Innerklasser kan också vara privata
  • public class OuterClass {public static class StaticNestedClass {}} // Statiska kapslade klasser kan också vara privata
  • public void method () {privat klass LocalClass {}} // Lokala klasser är alltid privata
  • SomeClass anonymousClassInstance = new SomeClass () {}; // Anonyma inre klasser kan inte namnges, varför åtkomst är en del. Om 'SomeClass ()' är abstrakt måste kroppen implementera alla abstrakta metoder.
  • SomeInterface anonymousClassInstance = new SomeInterface () {}; // Kroppen måste implementera alla gränssnittsmetoder.

Anmärkningar

Terminologi och klassificering

Java Language Specification (JLS) klassificerar olika typer av Java-klass enligt följande:

En klass på högsta nivå är en klass som inte är en kapslad klass.

En kapslad klass är varje klass vars deklaration inträffar i kroppen av en annan klass eller gränssnitt.

En inre klass är en kapslad klass som inte uttryckligen eller implicit förklaras statisk.

En inre klass kan vara en icke-statisk medlemsklass , en lokal klass eller en anonym klass . En medlemsklass i ett gränssnitt är implicit statisk så det anses aldrig vara en inre klass.

I praktiken refererar programmerare till en klass på högsta nivå som innehåller en inre klass som "ytterklassen". Det finns också en tendens att använda "kapslad klass" för att endast hänvisa till (uttryckligen eller implicit) statiska kapslade klasser.

Observera att det finns en nära relation mellan anonyma inre klasser och lambdas, men lambdas är klasser.

Semantiska skillnader

  • Toppnivåklasser är "basfallet". De är synliga för andra delar av ett program enligt normala synbarhetsregler baserade på åtkomstmodifieringssemantik. Om de inte är abstrakta kan de inställas med vilken kod som helst där de relevanta konstruktörerna är synliga baserade på åtkomstmodifierarna.

  • Statiska kapslade klasser följer samma åtkomst- och inställningsregler som klasser på toppnivå, med två undantag:

    • En kapslad klass kan förklaras som private , vilket gör den otillgänglig utanför dess omslutande toppnivåklass.
    • En kapslad klass har tillgång till de private medlemmarna i den slutna toppklass och alla dess testade klasser.

    Detta gör statiska kapslade klasser användbara när du behöver representera flera "entitetstyper" inom en snäv abstraktionsgräns; t.ex. när de kapslade klasserna används för att dölja "implementeringsdetaljer".

  • Inre klasser lägger till möjligheten att få åtkomst till icke-statiska variabler som deklareras i omslutande räckvidd:

    • En icke-statisk medlemsklass kan referera till instansvariabler.
    • En lokal klass (deklarerad inom en metod) kan också hänvisa till metodens lokala variabler, förutsatt att de är final . (För Java 8 och senare kan de vara effektiva .)
    • En anonym inre klass kan deklareras inom antingen en klass eller en metod, och har åtkomst till variabler enligt samma regler.

    Det faktum att en inre klassinstans kan hänvisa till variabler i en avslutande klassinstans har konsekvenser för inställning. Specifikt måste en tillslutande instans tillhandahållas, antingen implicit eller uttryckligen, när en instans av en inre klass skapas.

En enkel stack med en kapslad klass

public class IntStack {

    private IntStackNode head;

    // IntStackNode is the inner class of the class IntStack
    // Each instance of this inner class functions as one link in the
    // Overall stack that it helps to represent
    private static class IntStackNode {

        private int val;
        private IntStackNode next;

        private IntStackNode(int v, IntStackNode n) {
            val = v;
            next = n;
        }
    }

    public IntStack push(int v) {
        head = new IntStackNode(v, head);
        return this;
    }

    public int pop() {
        int x = head.val;
        head = head.next;
        return x;
    }
}

Och användningen av detta, som (särskilt) inte alls erkänner existensen av den kapslade klassen.

public class Main {
    public static void main(String[] args) {
 
        IntStack s = new IntStack();
        s.push(4).push(3).push(2).push(1).push(0);

        //prints: 0, 1, 2, 3, 4, 
        for(int i = 0; i < 5; i++) {
            System.out.print(s.pop() + ", ");
        }            
    }
}

Statiska vs icke-statiska kapslade klasser

När du skapar en kapslad klass står du inför ett val av att ha den kapslade klassen statisk:

public class OuterClass1 {

    private static class StaticNestedClass {

    }

}

Eller icke-statisk:

public class OuterClass2 {

    private class NestedClass {

    }

}

I grunden har statiska kapslade klasser inte ett omgivande exempel på den yttre klassen, medan icke-statiska kapslade klasser gör det. Detta påverkar både var / när man får anställa en kapslad klass, och vilka instanser av de kapslade klasserna får åtkomst. Lägg till ovanstående exempel:

public class OuterClass1 {

    private int aField;
    public void aMethod(){}

    private static class StaticNestedClass {
        private int innerField;

        private StaticNestedClass() {
             innerField = aField; //Illegal, can't access aField from static context 
             aMethod();           //Illegal, can't call aMethod from static context 
        }

        private StaticNestedClass(OuterClass1 instance) {
             innerField = instance.aField; //Legal
        }

    }

    public static void aStaticMethod() {
        StaticNestedClass s = new StaticNestedClass(); //Legal, able to construct in static context
        //Do stuff involving s...
    }

}

public class OuterClass2 {

    private int aField;

    public void aMethod() {}

    private class NestedClass {
        private int innerField;

        private NestedClass() {
             innerField = aField; //Legal   
             aMethod(); //Legal
        }
    }

    public void aNonStaticMethod() {
        NestedClass s = new NestedClass(); //Legal
    }

    public static void aStaticMethod() {
        NestedClass s = new NestedClass(); //Illegal. Can't construct without surrounding OuterClass2 instance.
                                         //As this is a static context, there is no surrounding OuterClass2 instance
    }
}

Därför beror ditt beslut på statisk kontra icke-statisk huvudsakligen på om du behöver kunna direkt komma åt fält och metoder för den yttre klassen, även om det också har konsekvenser för när och var du kan konstruera den kapslade klassen.

Som tumregel, gör dina kapslade klasser statiska såvida du inte behöver komma åt fält och metoder för den yttre klassen. På samma sätt som att göra dina fält privata såvida du inte behöver dem offentliga, minskar detta synligheten för den kapslade klassen (genom att inte tillåta åtkomst till en yttre instans), vilket minskar risken för fel.

Tillgångsmodifierare för inre klasser

En fullständig förklaring av Access Modifiers i Java kan hittas här . Men hur interagerar de med inre klasser?

public som vanligt obegränsad tillgång till alla omfattningar som kan komma åt typen.

public class OuterClass {

    public class InnerClass {

        public int x = 5;

    }

    public InnerClass createInner() {
        return new InnerClass();
    }
}

public class SomeOtherClass {

    public static void main(String[] args) {
        int x = new OuterClass().createInner().x; //Direct field access is legal
    }
}

både protected och standardmodifieraren (av ingenting) beter sig som förväntat, samma som för icke-kapslade klasser.

private , intressant nog, begränsar inte till den klass den tillhör. Snarare begränsar det kompilationsenheten - .java-filen. Detta innebär att yttre klasser har full tillgång till inre klassfält och metoder, även om de är markerade som private .

public class OuterClass {

    public class InnerClass {

        private int x;
        private void anInnerMethod() {}
    }

    public InnerClass aMethod() {
        InnerClass a = new InnerClass();
        a.x = 5; //Legal
        a.anInnerMethod(); //Legal
        return a;
    }
}

Själva den inre klassen kan ha en annan synlighet än public . Genom att markera den private eller en annan begränsad åtkomstmodifierare får andra (externa) klasser inte tillåta att importera och tilldela typen. De kan dock fortfarande få referenser till objekt av den typen.

public class OuterClass {

    private class InnerClass{}

    public InnerClass makeInnerClass() {
        return new InnerClass();
    }
}

public class AnotherClass {

    public static void main(String[] args) {
        OuterClass o = new OuterClass();
     
        InnerClass x = o.makeInnerClass(); //Illegal, can't find type
        OuterClass.InnerClass x = o.makeInnerClass(); //Illegal, InnerClass has visibility private
        Object x = o.makeInnerClass(); //Legal
    }
}

Anonyma inre klasser

En anonym innerklass är en form av inre klass som förklaras och instanseras med ett enda uttalande. Som en konsekvens finns det inget namn på klassen som kan användas någon annanstans i programmet; dvs det är anonymt.

Anonyma klasser används vanligtvis i situationer där du behöver kunna skapa en lättklass som ska ges som parameter. Detta görs vanligtvis med ett gränssnitt. Till exempel:

public static Comparator<String> CASE_INSENSITIVE =
        new Comparator<String>() {
            @Override
            public int compare(String string1, String string2) {
                return string1.toUpperCase().compareTo(string2.toUpperCase());
            }
        };

Denna anonyma klass definierar ett Comparator<String> -objekt ( CASE_INSENSITIVE ) som jämför två strängar som ignorerar skillnader i fall.

Andra gränssnitt som ofta implementeras och instanseras med anonyma klasser är Runnable och Callable . Till exempel:

// An anonymous Runnable class is used to provide an instance that the Thread
// will run when started.
Thread t = new Thread(new Runnable() {
        @Override 
        public void run() {
              System.out.println("Hello world");
        }
    });
t.start();  // Prints "Hello world"

Anonyma inre klasser kan också baseras på klasser. I detta fall extends den anonyma klassen implicit den befintliga klassen. Om klassen som utökas är abstrakt måste den anonyma klassen implementera alla abstrakta metoder. Det kan också åsidosätta metoder som inte är abstrakta.

konstruktörer

En anonym klass kan inte ha en tydlig konstruktör. Istället definieras en implicit konstruktör som använder super(...) att överföra alla parametrar till en konstruktör i klassen som utökas. Till exempel:

SomeClass anon = new SomeClass(1, "happiness") {
            @Override
            public int someMethod(int arg) {
                // do something
            }
        };

Den implicita konstruktören för vår anonyma underklass av SomeClass kommer att ringa en konstruktör av SomeClass som matchar samtalssignaturen SomeClass(int, String) . Om ingen konstruktör är tillgänglig får du ett sammanställningsfel. Alla undantag som kastas av den matchade konstruktören kastas också av den implicita konstruktören.

Detta fungerar naturligtvis inte när ett gränssnitt utökas. När du skapar en anonym klass från ett gränssnitt är klasserna superklassen java.lang.Object som bara har en no-args-konstruktör.

Metod Lokala inre klasser

En klass skriven inom en metod som kallas metod lokal inre klass . I så fall begränsas omfattningen av den inre klassen inom metoden.

En metodlokal inre klass kan endast instanseras inom metoden där den inre klassen definieras.

Exemplet med att använda metoden lokal inre klass:

public class OuterClass {
    private void outerMethod() {
       final int outerInt = 1;
        // Method Local Inner Class
        class MethodLocalInnerClass {
            private void print() {
                System.out.println("Method local inner class " + outerInt);
            }
        }
        // Accessing the inner class
        MethodLocalInnerClass inner = new MethodLocalInnerClass();
        inner.print();
    }

    public static void main(String args[]) {
        OuterClass outer = new OuterClass();
        outer.outerMethod();
    }
}

Exekvering ger en utgång: Method local inner class 1 .

Tillgång till den yttre klassen från en icke-statisk inre klass

Hänvisningen till den yttre klassen använder klassnamnet och this

public class OuterClass {
    public class InnerClass {
        public void method() {
            System.out.println("I can access my enclosing class: " + OuterClass.this);
        }
    }
}

Du kan komma åt fält och metoder i den yttre klassen direkt.

public class OuterClass {
    private int counter;

    public class InnerClass {
        public void method() {
            System.out.println("I can access " + counter);
        }
    }
}

Men vid namnkollision kan du använda den yttre klassreferensen.

public class OuterClass {
    private int counter;

    public class InnerClass {
        private int counter;
        
        public void method() {
            System.out.println("My counter: " + counter);
            System.out.println("Outer counter: " + OuterClass.this.counter);
            
            // updating my counter
            counter = OuterClass.this.counter;
        }
    }
}

Skapa instans av icke-statisk inre klass utanför

En inre klass som är synlig för alla utanför klass kan också skapas från denna klass.

Den inre klassen beror på den yttre klassen och kräver en hänvisning till en instans av den. För att skapa en instans av den inre klassen behöver den new operatören bara kallas till en instans av den yttre klassen.

class OuterClass {

    class InnerClass {
    }
}

class OutsideClass {

    OuterClass outer = new OuterClass();
    
    OuterClass.InnerClass createInner() {
        return outer.new InnerClass();
    }
}

Notera användningen som outer.new .



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow