Verschachtelte Klassen und Verwandtes

in Arbeit ...

1. Verschachtelte Klassen

1.1. Überblick

Verschachtelte Klassen (nested classes) ermöglichen in Java das Einbetten einer Klasse in einer anderen.

Verwendung praktisch nie zwingend, helfen aber oft, kompakteren, lesbareren, logischer strukturierten Code mit präziseren Sichtbarkeitsregeln zu schreiben.

  • Häufig für logisch stark gekoppelte Hilfsklassen (Comparator, Event-Handler in GUIs, …​)

  • Hilfsklassen oder Datenklassen nur für internen Gebrauch der äußeren Klasse

  • Oft nur einiges Objekt an einziger Stelle benötigt (Comparator etc.).

In Java gibt mittlerweile 6 Arten verschachtelter Klassen oder konzeptuell verwandter Strukturen (aktuell für uns wichtig nur 3 davon):

  • (wichtig) Statische verschachtelte Klasse (Static Nested Class) …​ logisch äquivalent zu normaler Klasse, nur syntaktisch eingebettet

  • Innere Klasse (Inner Class) …​ jede Instanz der inneren Klasse ist an eine Instanz der äußeren Klasse gebunden

    • "Normale" innere Klasse …​ Deklaration auf oberster Ebene (direkt innerhalb des Klassen-Rumpfes – "neben" Attributen und Methoden)

    • Lokale Klasse (Local Class) …​ Spezialfall Innere Klasse: "weiter innen" - z.B. innerhalb einer Methode.

    • (wichtig) Anonyme innere Klasse (anonymous inner class) …​ Lokale Klasse, wenn nur eine einzige Instanz an einer einzigen Stelle benötigt wird. In diesem Fall ist keine Name nötig. Beispiel: Comparator-Klasse

  • (wichtig) Lambda-Ausdruck …​ Weitere syntaktische Vereinfachung anonymer innerer Klasse (aber auch logische Adaptionen), ermöglicht extrem kompakte und (meist) übersichtlichere Formulierung z.B. der Logik einer compare-Methode.

  • Methoden-Referenzen …​ Nochmals syntaktische Vereinfachung, wenn im Lambda-Ausdruck lediglich ein einziger Aufruf einer bereits bestehenden Methode erfolgen würde.

Ein Beispiel für alle angeführten Fälle findet sich unten.

1.2. Statische verschachtelte Klasse

(für uns wichtig)
Diese wird beim Kompilieren zu einer eigenständigen Class-Datei (EnclosingClass$NestedClass.class). Sie hat keinen speziellen Zugriff auf Elemente der umschließenden Klasse, verhält sich also bis auf den Namen wie jede andere Klasse auch.

1.3. Innere Klasse

(für uns derzeit nicht Thema)
Im Gegensatz zu obigen statisch verschachtelten Klassen sind Objekte der inneren Klasse jeweils an ein Objekt der umschließenden Klasse gebunden und kann auch auf dessen Attribute zugreifen! Im Gegenzug ist also jeweils davor ein Objekt der umschließenden Klasse nötig. Dann muss die Instanzierung so erfolgen: InnerClass icObj = enclosingClassObject.new InnerClass(); Markant ist. dass new mit '.' an ein äußeres Objekt geknüpft wird.

1.4. Lokale (innere) Klasse

1.5. Anonyme (innere) Klasse

(für uns wichtig)
Wenn ein einziges Objekt einer Klasse nur an einer Stelle benötigt wird, gibt es eine syntaktisch sehr kompakte Möglichkeit, die Klasse gleich "vor Ort" zu definieren und sofort ein Objekt daraus zu erzeugen. Die Klasse benötigt keinen (und hat auch keinen) Namen.

Anwendungsfälle sind z.B. das Sortieren oder (in Kürze) für JavaFX Ereignis-Reaktionen (Button-Klick etc.). Siehe Beispiel unten.

1.7. Methoden-Referenzen

(für uns derzeit nicht Thema)
Siehe auch Method References (The Java™ Tutorials > Learning the Java Language > Classes and Objects) …​ 9.1.2022, 18:39:47

1.8. Beispiel-Klasse für alle 6 Konzepte

TODO: Beispiel Methodenreferenz fehlt noch!
package my.scribble2021q4a;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class EnclosingClass {
    private String[] tokens = { "fff", "u", "bbb", "bb", "zzzz", "zz", "ff", "aaaa", "a" };

    public EnclosingClass() {
        System.out.println("now in constructor for object of DemoNestingOuterClasses ...");
    }

    public static void main(String[] args) {
        EnclosingClass dnocObj = new EnclosingClass();
        dnocObj.someMethod();
        //
        System.out.println("----- static nested classes -----");
        StaticNestedClass dnocSncObj1 = new StaticNestedClass("dnocSncObj1"); // shorter, ok within nesting class
        //From another class use EnclosingClass.StaticNestedClass
        //
        System.out.println("----- (ordinary) inner classes -----");
        InnerClass icObj1 = dnocObj.new InnerClass("icObj1");

        System.out.println("----- Method containing local classes -----");
        dnocObj.methodWithLocalClass();
        dnocObj.methodWithLambda();
    }

    public void someMethod() {
        System.out.println("now in someMethod() ...");
    }

    public /* important--> */ static /* <-- */ class StaticNestedClass {  // used in main(..)

        private String title;

        public StaticNestedClass(String title) {
            System.out.format("now in constructor of StaticNestedClass-object '%s' ...%n", title);
        }
    }

    public class InnerClass {  // used in main(..)
        public InnerClass(String title) {
            System.out.format("now in constructor of InnerClass-object '%s' ...%n", title);
        }
    }

    public void methodWithLocalClass() {
        System.out.println("now in method 'methodWithLocalClass()'");

        class LocalClass {   // Start Implementation
            public LocalClass() {
                System.out.println("now in constructor of 'LocalClass'");
            }

            public void demoMethod1() {
                System.out.println("now in method 'LocalClass#demoMethod1()'");
            }
        }  // End Implementation

        LocalClass lcObj1 = new LocalClass();  // Instantiate LocalClass
        lcObj1.demoMethod1();  // use object of LocalClass (ALL WITHIN this single method!)
    }

    public void methodWithAnonymousInnerClass() {
        System.out.println("now in method 'methodWithAnonymousInnerClass()'");

        List<String> names = new ArrayList<>(Arrays.asList(tokens));

        names.sort(  // Start AnonymousInnerClass
                new Comparator<String>() {   // can extend class or implement interface

                    @Override
                    public int compare(String s1, String s2) {
                        int cmp = s1.length() - s2.length();  // first by length
                        if (cmp == 0) {
                            cmp = s1.compareTo(s2);   // if same length: by ordinary string-compare
                        }
                        return cmp;
                    }
                }  // End AnonymousInnerClass
        );
        // the single param object is directly created with the 'new' before the implementation!
    }

    public void methodWithLambda() {
        System.out.println("now in method 'methodWithLambda()'");

        List<String> names = new ArrayList<>(Arrays.asList(tokens));
        System.out.println("unsorted list: " + names);
        names.sort(  // Start LambdaExpression
                (s1, s2) -> {
                    int cmp = s1.length() - s2.length();  // first by length
                    if (cmp == 0) {
                        cmp = s1.compareTo(s2);   // if same length: by ordinary string-compare
                    }
                    return cmp;
                }  // End LambdaExpression
        );
        System.out.println("list sorted (length, then alphabetical): " + names);
    }


    //TODO: Method reference example
}

2. Mehrere Klassen in der selben Java-Datei

Eine andere Art Verschachtelung bildet die Möglichkeit, innerhalb einer einzigen Klassendatei weitere Klassen zu implementieren.

Im Prinzip können mehrere Klassen innerhalb der selben Datei enthalten sein, allerdings darf nur eine einzige Sichtbarkeit public haben (Dateiname muss Klassenname mit Endung .java sein). Alle anderen müssen die "default visibility" haben (kein public, protected, private Modifier) und sind damit nur für Klassen im selben Package sichtbar.

Üblicherweise wird dies bis auf nachfolgend beschriebene Single-File-Programs und extreme Sonderfälle vermieden.

3. Kurz-Info Single-File-Programs

Seit Java 11 ist es möglich, Java-Dateien ohne vorheriges Kompilieren laufen zu lassen.

Das ist sehr praktisch, wenn man einfache, schnelle Aufgaben lösen muss (was man sonst mit Shell-Scripts o.ä. täte) oder für Probieren, Demo, Schulung etc. .

TODO: fertigstellen

Links:

https://dev.java/learn/single-file-program/ [Launching Single-File Source-Code Programs - Dev.java] …​ 2023-03-05

https://openjdk.org/jeps/330 [JEP 330: Launch Single-File Source-Code Programs] …​ 2023-03-05

https://developer.ibm.com/tutorials/java-theory-and-practice-2/ [Java theory and practice: Run single-file source code programs without compilation - IBM Developer] …​ 2023-03-05

https://www.digitalocean.com/community/tutorials/compile-run-java-program-another-java-program [How to Compile and Run Java Program from another Java Program | DigitalOcean] …​ 2023-03-05

https://www.javacodegeeks.com/2019/01/running-single-programs-shebang-scripts.html [Java 11: Running single-file programs and "shebang" scripts - Java Code Geeks - 2023] …​ 2023-03-05

https://medium.com/the-java-report/new-java-11-feature-launch-single-file-source-code-programs-fadd698abf54 [New Java 11 Feature: Launch Single-File Source-Code Programs | by Adrian D. Finlay | The Java Report | Medium] …​ 2023-03-05

https://foojay.io/today/running-single-file-java-source-code-without-compiling-part-1/ [Running Single-File Java Source Code Without Compiling (Part 1)] …​ 2023-03-05 https://foojay.io/today/running-single-file-java-source-code-without-compiling-part-2/ [Running Single-File Java Source Code Without Compiling (Part 2) | foojay] …​ 2023-03-05

https://www.cs.swarthmore.edu/~newhall/unixhelp/debuggingtips_Java.html [Compiling, Running, Debugging Java Programs on CmdLine] …​ 2023-03-05