Klassen in Klassen
Kurz gesagt: Eine verschachtelte Klasse ist eine Klasse, die innerhalb einer anderen Klasse deklariert ist, weil sie nur dort Sinn ergibt; Java hat statische und innere verschachtelte Klassen, lokale und anonyme.
Problem
Eine Klasse, die nur an einer Stelle Sinn ergibt
In der Lektion über die verkettete Liste war Node eine eigene Klasse. Jeder im Programm kann Knoten anlegen, aber sie ergeben nur in der Liste Sinn. Genauso ist es mit einer Zeile der Bestellung:
class OrderLine { String product; int quantity; double price;}
class Order { List<OrderLine> lines = new ArrayList<>();}OrderLine ohne Order ergibt keinen Sinn. Der Name ist lang, damit er nicht mit der Zeile einer Rechnung, InvoiceLine, oder mit der Zeile im Lager zusammenstößt. Und nichts sagt, dass die beiden Klassen zusammengehören. Kann die Zeile innerhalb der Bestellung leben?
Die Idee
Statische verschachtelte Klasse
Ja. Eine Klasse, die innerhalb einer anderen Klasse deklariert ist, heißt verschachtelte Klasse (nested class):
class Order { static class Line { String product; int quantity; double price;
Line(String product, int quantity, double price) { this.product = product; this.quantity = quantity; this.price = price; }
double total() { return quantity * price; } }
private List<Line> lines = new ArrayList<>();
void add(Line line) { lines.add(line); }
double total() { double sum = 0; for (Line line : lines) { sum += line.total(); } return sum; }}Order order = new Order();order.add(new Order.Line("Kaffeebohnen", 2, 12.5));order.add(new Order.Line("Tasse", 4, 3.0));System.out.println("Gesamt: " + order.total() + " €");Gesamt: 37.0 €- Von außen heißt sie
Order.Line. Innerhalb vonOrderheißt sie nurLine. staticbedeutet, dass die Zeile an keine bestimmte Bestellung gebunden ist.new Order.Line(...)entsteht ohne einOrder-Objekt, wie bei einer normalen Klasse.- Ist sie
private static class Line, kann niemand außerhalb vonOrdersie benutzen.
So eine Klasse kennst du schon: Map.Entry aus der Lektion über HashMap ist in Map verschachtelt. Und LinkedList in Java hält ihre Knoten in einer private static class Node.
Die Idee
Innere Klasse: Sie sieht das äußere Objekt
Der Warenkorb hat einen Rabatt für alle Produkte darin. Jedes Produkt muss den Rabatt seines eigenen Warenkorbs kennen. Dann hat die Klasse kein static:
class Cart { private int discountPercent; private List<Item> items = new ArrayList<>();
Cart(int discountPercent) { this.discountPercent = discountPercent; }
class Item { String name; double price;
Item(String name, double price) { this.name = name; this.price = price; }
double finalPrice() { return price * (100 - discountPercent) / 100; } }
void add(String name, double price) { items.add(new Item(name, price)); }
void print() { for (Item item : items) { System.out.println(item.name + ": " + item.finalPrice() + " €"); } }}Cart cart = new Cart(10);cart.add("Kopfhörer", 80);cart.add("Hülle", 20);cart.print();Kopfhörer: 72.0 €Hülle: 18.0 €static heißt innere Klasse (inner class). Jedes Item wird von einem bestimmten Warenkorb erzeugt und sieht dessen Felder, sogar die private. finalPrice liest den discountPercent des Warenkorbs, in dem es liegt.Cart cart = new Cart(10);cart.add("Kopfhörer", 80);cart.add("Hülle", 20);cart.print();Schritt für Schritt, in Worten
- Der Warenkorb ist ein Cart-Objekt mit 10% Rabatt.
- add erzeugt ein Item. Neben name und price hält jedes Item einen versteckten Pfeil zu dem Warenkorb, der es erzeugt hat: Cart.this.
- Das zweite Item zeigt auf denselben Warenkorb.
- finalPrice von „Kopfhörer“ folgt dem versteckten Pfeil und liest discountPercent: 80 * (100 - 10) / 100 = 72.0.
- finalPrice von „Hülle“ folgt dem versteckten Pfeil und liest discountPercent: 20 * (100 - 10) / 100 = 18.0.
Genauer, für Neugierige
- Von außen wird ein inneres Objekt über das Objekt der äußeren Klasse erzeugt:
cart.new Item(...). Die Schreibweise ist selten, weil die äußere Klasse die inneren meist selbst erzeugt, wieaddhier:
Cart cart = new Cart(10);Cart.Item cable = cart.new Item("Kabel", 10);System.out.println(cable.finalPrice());9.0- Jedes innere Objekt hält eine versteckte Referenz auf das äußere. Heißen die Felder der beiden Klassen gleich, schreibst du das äußere als
Cart.this.discountPercent. - Benutzt die verschachtelte Klasse die Felder der äußeren nicht, mach sie
static. So hält sie keine Referenz, die sie nicht braucht.
Probier es aus
Zwei Warenkörbe
Die Klasse Cart ist dieselbe. Ein Warenkorb hat keinen Rabatt, der andere 20 % für Clubmitglieder.
Was gibt das Programm aus?
Cart regular = new Cart(0);Cart member = new Cart(20);regular.add("Buch", 15);member.add("Buch", 15);regular.print();member.print();Jedes Item sieht den Rabatt des Warenkorbs, der es erzeugt hat. Das Buch in regular hat 0 %, das in member 20 %.
Probier es aus
Finde den Fehler
Main.java:9: error: non-static variable currency cannot be referenced from a static context return product + ": " + price + " " + currency; ^In welcher Zeile liegt das Problem?
Wähle die Zeile mit dem Fehler.
Line ist static, also an keine Bestellung gebunden. Es gibt keine Bestellung, deren Währung es lesen könnte. Die Lösung: Entferne static. Dann wird Line eine innere Klasse und sieht die currency ihrer eigenen Bestellung. Ein anderer Weg ist, currency static zu machen, wenn die Währung für alle Bestellungen dieselbe ist.Die Idee
Eine Klasse in einer Methode
Eine Klasse kann man auch in einer Methode deklarieren. Dann ist sie nur dort sichtbar und heißt lokale Klasse:
static void printBestSeller(String[] names, int[] sold) { class Sale { String name; int count;
Sale(String name, int count) { this.name = name; this.count = count; } }
Sale best = new Sale(names[0], sold[0]); for (int i = 1; i < names.length; i++) { if (sold[i] > best.count) { best = new Sale(names[i], sold[i]); } } System.out.println("Bestseller: " + best.name + " (" + best.count + " Stk.)");}String[] names = { "Kaffee", "Tee", "Wasser" };int[] sold = { 120, 45, 300 };printBestSeller(names, sold);Bestseller: Wasser (300 Stk.)Sale hält den Namen und die Anzahl zusammen. Außerhalb von printBestSeller benutzt sie niemand, darum steht sie dort. Solche Klassen sind selten, aber es ist gut, sie zu kennen.
Die Idee
Eine Klasse ohne Namen
Die Rabatte im Shop sind ein Interface:
interface Discount { double apply(double price);}Bisher hast du für jeden Rabatt eine Klasse gemacht: class BlackFriday implements Discount { ... }. Wird der Rabatt nur an einer Stelle benutzt, kann man die Klasse genau dort schreiben, ohne Namen:
Discount blackFriday = new Discount() { @Override public double apply(double price) { return price * 70 / 100; }};Discount none = new Discount() { @Override public double apply(double price) { return price; }};System.out.println(blackFriday.apply(100));System.out.println(none.apply(100));70.0100.0new Discount() { ... } macht zwei Dinge. Es deklariert eine Klasse ohne Namen, die Discount implementiert, und erzeugt ein Objekt davon. So eine Klasse heißt anonyme Klasse. Innen ist sie wie eine normale Klasse: @Override und die Methoden des Interfaces.
Genauer, für Neugierige
- Eine anonyme Klasse kann auch eine Klasse erweitern, nicht nur ein Interface implementieren:
new Product("Kaffee", 12.5) { ... }ruft den Konstruktor vonProductauf und überschreibt Methoden. - Eine anonyme Klasse kann die lokalen Variablen der Methode lesen, in der sie geschrieben ist, aber nur, wenn sie sich danach nicht mehr ändern.
- Ein Interface mit einer einzigen Methode, wie
Discount, wird im Modul „Modernes Java“ viel kürzer geschrieben. Die anonyme Klasse ist die Stufe dorthin.
Die Idee
Comparator: Sortieren nach eigener Regel
Collections.sort hat Text und Zahlen sortiert. Schüler kann es aber nicht sortieren:
Collections.sort(students);Der Compiler sagt no suitable method found for sort(List<Student>). Er weiß nicht, wonach er Schüler sortieren soll: nach Name, nach Note, nach Alter?
Die Regel gibst du getrennt an, als Comparator. Das ist ein Interface mit einer Methode, compare(a, b). Sie gibt eine negative Zahl zurück, wenn a vor b kommen soll, null, wenn beide gleich sind, und eine positive Zahl, wenn a nach b kommt. Als anonyme Klasse ist das praktisch:
class Student { String name; double grade;
Student(String name, double grade) { this.name = name; this.grade = grade; }
@Override public String toString() { return name + " " + grade; }}Collections.sort(students, new Comparator<Student>() { @Override public int compare(Student a, Student b) { return Double.compare(b.grade, a.grade); }});System.out.println(students);[Maria 6.0, Jonas 5.5, Anna 5.5, Peter 4.75]Double.compare(b.grade, a.grade)vergleicht andersherum, darum steht die höchste Note vorn.- Jonas und Anna haben dieselbe Note und bleiben in der Reihenfolge, in der sie waren.
Collections.sortvertauscht gleiche Elemente nicht. - Für Text und Zahlen in umgekehrter Reihenfolge gibt es einen fertigen
Comparator:Collections.reverseOrder().
List<String> cities = new ArrayList<>();cities.add("Hamburg");cities.add("Berlin");cities.add("München");Collections.sort(cities, Collections.reverseOrder());System.out.println(cities);[München, Hamburg, Berlin]Genauer, für Neugierige
Comparatorliegt injava.util, mit einem Typparameter:Comparator<Student>vergleicht Schüler.- Text und Zahlen werden ohne
Comparatorsortiert, weilStringundIntegerdas InterfaceComparablemit der MethodecompareToimplementieren. Auch deine Klasse kann es implementieren, wenn sie eine natürliche Reihenfolge hat.Comparatorist für die Fälle, in denen es mehrere Reihenfolgen gibt. Double.compareundInteger.compareschützen dich vor Fehlern. Mitintsiehta.age - b.agekürzer aus, aber bei sehr großen Zahlen läuft die Subtraktion über, und das Vorzeichen kippt.
Probier es aus
Was steht vorn?
Was gibt das Programm aus?
List<Product> products = new ArrayList<>();products.add(new Product("Monitor", 220));products.add(new Product("Maus", 25));products.add(new Product("Tastatur", 60));
Collections.sort(products, new Comparator<Product>() { @Override public int compare(Product a, Product b) { return Double.compare(a.price, b.price); }});for (Product p : products) { System.out.println(p.name);}Double.compare(a.price, b.price) ist negativ, wenn a billiger ist. Darum kommt das Billigere zuerst: 25, 60, 220. Damit das Teuerste vorn steht, vertausche a und b.Übung
Klassen, die zusammengehören
Löse die Aufgaben in deinem Editor.
1. Warenkorb. Eine Klasse ShoppingCart mit einer statischen verschachtelten Klasse Item (Name und Preis) und den Methoden addItem(Item item), getTotal() und count(). Füge in main den Artikel new ShoppingCart.Item("Milch", 2.5) hinzu, dazu Brot für 1,20 € und Kaffee für 7,50 €.
Artikel: 3Gesamt: 11.2 €2. Monitor für einen Webserver. Eine Klasse WebServer mit einer statischen verschachtelten Klasse Request (Adresse und Statuscode) und den Methoden addRequest(Request r) und printStats(). Die Statistik sagt, wie viele Anfragen es gibt, wie viele es von jedem Statuscode gibt (sortiert) und wie viel Prozent erfolgreich sind, also mit einem Code von 200 bis 299. Die Anfragen sind /home 200, /cart 200, /login 401, /home 200, /api/orders 500.
Anfragen: 5200: 3401: 1500: 1Erfolgreich: 60%3. Hausverwaltung. Eine Klasse Building mit einer statischen verschachtelten Klasse Apartment (Nummer, Anzahl der Zimmer, Eigentümer) und den Methoden addApartment(Apartment a) und getApartmentInfo(int number). Halte die Wohnungen in einer Map<Integer, Apartment>. Füge 101 mit 3 Zimmern von Schmidt und 102 mit 2 Zimmern von Müller hinzu. Suche 101 und 999.
Wohnung 101: 3 Zimmer, Eigentümer SchmidtWohnung 999 gibt es nicht.4. Rangliste. Sortiere die Schüler aus der Lektion nach Note, von der höchsten abwärts. Bei gleicher Note sortiere sie nach Namen, alphabetisch. Alles steckt in einem Comparator: Ist der Vergleich nach Note nicht 0, gib ihn zurück, sonst vergleiche die Namen mit compareTo.
Maria 6.0Anna 5.5Jonas 5.5Peter 4.75Weitere Aufgaben
- Computer-Konfigurator. Eine Klasse
Computermit den statischen verschachtelten KlassenCPU(Marke, Takt in GHz),RAM(GB) undStorage(GB) und den MethodensetCPU,setRAM,setStorage.toStringbeschreibt die Konfiguration:Intel 3.5 GHz, 16 GB RAM, 512 GB. - Reiseplaner, noch einmal. Löse die Aufgabe aus der Lektion über
HashMapmit einer verschachtelten KlasseTripPlanner.Destination, die den Namen und die Liste der Aktivitäten hält. - Kontaktnetz, noch einmal. Verschiebe
Contactaus der Aufgabe in der Lektion überHashMapinContactNetwork. Sollte siestaticsein? - Kopfrechnen, noch einmal. Verschiebe
Questionaus der Aufgabe in der Lektion über Listen inMathGame. - Produkte auf zwei Arten. Sortiere eine Liste von Produkten nach Preis, vom teuersten an, und bei gleichem Preis nach Namen. Sortiere dann dieselbe Liste nur nach Namen, in umgekehrter Reihenfolge.
- Dein eigener Rabatt. Schreib drei anonyme
Discount-Klassen: 10 % ab 100 €, feste 5 € Rabatt und „zweites Produkt zum halben Preis“. Wende sie auf ein und denselben Warenkorb an.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Strings · Listen · Stack, Queue und verkettete Liste · HashMap und HashSet
- Geht weiter in: Lambdas · Streams · Entwurfsmuster · Tests mit JUnit
- Aufgaben zum Thema: Collections