Dein Code:Informatik/Skripte/07_Wahlen, 05_Strings/03_CharacterTwice
Alle Codes und Ausgaben in dieser Notiz wurden mit Java getestet.
So lernst du damit
Theorie Teil 1–5 lesen, die Merksätze aus den Folien können
Teil 6: Lehrer-Beispiele: Ausgabe selbst vorhersagen, dann vergleichen
Teil 7 + 8: Übungsblätter selbst in VS Code programmieren, erst dann die Lösung aufklappen
Teil 10: Compilerfehler-Fallen
Teil 1 – Abstrakte Klassen und Methoden
1.1 Grundidee (Folie 1)
Bisher: immer konkrete Klassen, von denen man Objekte erzeugen kann
Das ist nicht immer erwünscht oder sinnvoll
Von abstrakten Klassen gibt es keine Objekte → new ergibt einen Compilerfehler
Oberklassen werden manchmal nur als Modellierungsklassen verwendet:
Sie geben nur Signaturen für die Unterklassen vor
Die Unterklassen implementieren die Methoden dann aus
Kennzeichnung mit dem Modifier abstract
UML: Klassenname kursiv oder {abstract} unter den Namen
Folie 1, Randnotiz
Soll nur verhindert werden, dass ein Exemplar der Klasse gebildet wird, verwendet man besser einen private oder protected Konstruktor.
1.2 Arten von abstrakten Klassen (Folie 2)
Abstrakte Methoden geben die Signatur vor, aber keine Implementierung:
public abstract void fahre(double speed); // kein Rumpf, Semikolon!
Art
Inhalt
Reine (pure) abstrakte Klasse
enthält ausschließlich abstrakte Methoden
Partiell abstrakte Klasse
enthält abstrakte und nicht abstrakte Methoden; die nicht abstrakten haben eine konkrete Implementierung
Regeln (Folie 2)
Ist mindestens eine Methode abstract, so ist es auch die Klasse.
Unterklassen von abstrakten Klassen müssen selbst abstrakt sein oder alle abstrakten Methoden ausimplementieren.
1.3 Grundgerüst
public abstract class Form { // partiell abstrakt protected String name; public Form(String name) { // Konstruktor ist erlaubt this.name = name; } public abstract double flaeche(); // abstrakt public abstract double umfang(); public void ausgeben() { // konkrete Methode System.out.printf("%s: Fläche=%.2f, Umfang=%.2f%n", name, flaeche(), umfang()); }}public class Kreis extends Form { private double r; public Kreis(double r) { super("Kreis"); // Konstruktor der abstrakten Klasse this.r = r; } @Override public double flaeche() { return Math.PI * r * r; } @Override public double umfang() { return 2 * Math.PI * r; }}
Form f = new Kreis(1); // ✅ Referenz vom abstrakten TypForm g = new Form("x"); // ❌ Form is abstract; cannot be instantiatedf.ausgeben(); // Kreis: Fläche=3.14, Umfang=6.28
Teil 2 – Schnittstellen (Interfaces)
2.1 Wozu? (Folie 3)
In Java gibt es keine Mehrfachvererbung
Damit eine Klasse trotzdem mehrere Typen besitzen kann, gibt es Schnittstellen
Eine Klasse kann beliebig viele Schnittstellen implementieren
In der OO trennt man immer das Was vom Wie:
Abstrakte Klasse und Interface
Was? Leere Methodenrümpfe
Erbende / implementierende Klasse
Wie? Ausimplementierte Methoden
Alle Variablen sind public static final, also Klassenvariablen (Konstanten)
Im Gegensatz zu abstrakten Klassen dürfen Schnittstellen keine Objektvariablen haben, immer nur Konstanten
2.2 Deklaration (Folie 4)
Schnittstellen enthalten nur Köpfe von Methoden (Modifizierer + Rückgabetyp + Signatur)
Statt class schreibt man interface
Alle Objektmethoden sind automatisch abstract und public → beides kann man weglassen
Methoden haben keinen Rumpf {} und enden mit ;
Keine Konstruktoren
Namen enden oft auf „-ble” (Accessible, Runnable, Buyable, Electable)
2.3 Implementieren (Folie 5 + 6)
Hinter den Klassennamen kommt implements
Mehrere Schnittstellen werden durch Beistrich getrennt
Eine Klasse kann gleichzeitig:
maximal 1 Klasse erweitern (extends) und
beliebig viele Schnittstellen implementieren (implements)
UML:«interface» über dem Namen
public interface Buyable { String price(); // public abstract ist implizit dabei}public class Testarossa extends Ferrari implements Buyable, Paintable { @Override public String price() { return this.preis + "€"; } // + alle Methoden von Paintable}
2.4 Subinterfaces (Folie 7)
Schnittstellen können andere Schnittstellen erweitern → extends
Die implementierende Klasse muss dann alle Methoden aller Schnittstellen in der Typhierarchie implementieren
Methoden der Oberschnittstelle können in der Unterschnittstelle überschrieben werden
Auch statische Methoden sind möglich (immer mit Implementierung)
2.5 Erlaubte Eigenschaften einer Schnittstelle (Folie 8)
Attribut
Methode
Objekt-
❌ nein, nicht erlaubt
✅ ja, üblicherweise abstrakt
Statische(s)
✅ ja, als Konstante
✅ ja, immer mit Implementierung
Objektmethoden in Schnittstellen müssen nicht immer abstrakt sein, sie können auch ausimplementiert sein (→ default)
Schnittstellen nur mit statischen Methoden ersetzt man besser durch eine finale Klasse mit privatem Konstruktor (wie Math)
2.6 Default-Methoden (Folie 9)
Schnittstellen sollten nicht im Nachhinein geändert werden, sonst funktionieren alle implementierenden Klassen nicht mehr
Lösung: Default-Methode
enthält eine Implementierung
Schnittstellen mit Default-Methode heißen erweiterte Schnittstellen
Default-Methoden können überschrieben werden
public interface Paintable { void paint(Color farbe); default void paintRed() { // neue Methode, alte Klassen funktionieren weiter paint(Color.RED); }}
2.7 Beispiel zum Üben
public interface Fahrbar { int MAX_SPEED = 130; // public static final void fahren(int kmh); // public abstract default void hupen() { System.out.println("Tuut!"); }}public interface Aufladbar { void laden(int prozent);}public class EAuto implements Fahrbar, Aufladbar { private int akku = 50; @Override public void fahren(int kmh) { if (kmh > MAX_SPEED) kmh = MAX_SPEED; System.out.println("E-Auto fährt " + kmh + " km/h"); } @Override public void laden(int prozent) { akku = Math.min(100, akku + prozent); System.out.println("Akku: " + akku + "%"); }}public class Fahrrad implements Fahrbar { @Override public void fahren(int kmh) { System.out.println("Fahrrad fährt " + kmh + " km/h"); } @Override public void hupen() { System.out.println("Kling kling!"); } // default überschrieben}
Teil 3 – (Abstrakte) Klassen vs. Schnittstellen (Folie 10)
Abstrakte Klasse verwenden, wenn …
die Unterklasse nur 1 Klasse erweitern soll
man Gemeinsamkeiten von Klassen in eine Oberklasse auslagern möchte
die Klasse Objektzustände (Attribute) enthält
man eine Basisimplementierung vorgeben möchte
Schnittstelle verwenden, wenn …
die Unterklasse mehrere Klassen (Schnittstellen) erweitern soll
Vorschriften für Verhalten weitergegeben werden sollen
die Implementierung fast ausschließlich in die Unterklasse ausgelagert werden soll
Abstrakte Klasse
Interface
Schlüsselwort
abstract class
interface
Verwenden mit
extends
implements
Wie viele pro Klasse?
eine
beliebig viele
Objektvariablen
✅
❌ nur Konstanten (public static final)
Konstruktor
✅
❌
Methoden mit Rumpf
✅
nur default und static
Methoden-Sichtbarkeit
beliebig
automatisch public
new möglich?
❌
❌
UML
kursiv / {abstract}
«interface»
Teil 4 – Assertions
4.1 Motivation (3.1)
Programme müssen auf Fehlersituationen vorbereitet sein. Java hat dafür zwei Möglichkeiten:
Assertions: Absicherung der inneren Programmlogik
Exceptions: Einhalten beliebiger Randbedingungen
4.2 Assertions allgemein (3.2.1 + 3.2.2)
Assertion (Zusicherung) = boolescher Ausdruck, der immer zutreffen muss
Wird von der JVM zur Laufzeit überwacht
Ist sie false → das Programm stoppt mit einem AssertionError, die nachfolgenden Anweisungen werden nicht mehr ausgeführt; der Ort der gescheiterten Assertion wird ausgegeben
assert ausdruck; // ausdruck muss true liefernassert ausdruck : string; // string = Fehlermeldungassert a <= b && b <= c;assert x >= 0 : "Negatives x";
4.3 Aktivierung (3.2.3)
Assertions kosten Rechenzeit → sie sind standardmäßig deaktiviert!
Aktivieren per Flag beim Start, ohne neu zu kompilieren:
Befehl
Wirkung
java -ea Test
enable assertions: aktivieren
java -da Test
disable assertions: deaktivieren (Default)
-ea:Klassenname
nur in einer Klasse
-ea:paketname...
in einem Paket (3 Punkte!)
java -ea:project2... -da:project2.datastore... -ea:project2.datastore.Store Test
mehrere Schalter kombiniert
IntelliJ: Run → Edit Configurations → -ea bei VM options
VS Code: in launch.json"vmArgs": "-ea"
4.4 Wann einsetzen? (3.2.4 + 3.2.5)
✅ Geeignet für Bedingungen, deren Einhaltung das Programm selbst garantieren kann:
Berechnungsergebnisse prüfen, die nicht möglich sein dürfen (z.B. sqrt(-2))
Postconditions: was am Ende einer Methode erfüllt sein muss
Schleifeninvarianten
„Tote Zweige” in if/switch markieren, die nie erreicht werden sollten → assert false;
❌ Nicht geeignet für Bedingungen, die von äußeren Einflüssen abhängen, z.B. Parameter am Anfang einer öffentlichen Methode prüfen → dafür Exceptions!
Teil 5 – Exceptions
5.1 Motivation und Vorteile (3.3.1 + 3.3.2)
Fehler im laufenden Programm: logische Fehler, falsche Bedienung durch den Benutzer, Probleme im Laufzeitsystem
Exceptions (Ausnahmen) = Sprachmittel zur kontrollierten Reaktion auf Laufzeitfehler (Assertions sind ein Sonderfall davon)
Zwei große Vorteile:
Fehler können nicht ignoriert werden, es müssen Maßnahmen getroffen werden
Code für Programmablauf und Fehlerbehandlung wird getrennt
5.2 Grundprinzip (3.3.3 + 3.3.4)
An bestimmten Stellen werden Ausnahmesituationen geprüft und gemeldet; an einer anderen Stelle steht der Code zur Reaktion
Die Information über die Ausnahme wird in ein Objekt verpackt: eine Instanz von Exception (oder einer Unterklasse)
Explizit auslösen mit throw
Abfangen in einer catch-Klausel (äußerer Block, aufrufende Methode, …)
Bei einer Ausnahme vom Typ E wird eine passende catch-Klausel vom Typ E gesucht, oder von einer Oberklasse von E (Substitution)
Die Ausnahme wird der Reihe nach mit den catch-Klauseln verglichen
Es wird die erste kompatible ausgeführt, alle weiteren werden ignoriert
Deshalb: Reihenfolge von spezifisch zu allgemein (z.B. erst MyNewException, dann MyException, dann Exception)
Kein passender catch → das Programm bricht ab
Mit passendem catch → Ausnahme gefangen, das Programm läuft weiter (nach dem try-catch)
5.5 Termination Model vs. Resumption Model (3.3.7)
Termination Model (Java!)
Der Kontrollfluss kehrt nicht an die Stelle zurück, an der die Ausnahme aufgetreten ist. Der Rest des try-Blocks wird übersprungen.
Resumption Model (nicht in Java)
Das Programm setzt an der Stelle der Ausnahme fort
5.6 Arten von Ausnahmen (3.3.8 + 3.3.9)
Systemausnahmen (runtime / unchecked)
Benutzerausnahmen (checked)
Errors
Auslösung
automatisch (illegale Instruktionen, Bugs)
explizit vom Programmierer (throw)
JVM-Probleme
Beispiele
ArithmeticException (Division durch 0), NullPointerException (Zugriff auf null), ArrayIndexOutOfBoundsException
java.io.IOException, eigene Exceptions
OutOfMemoryError
Abfangen?
kann, muss aber nicht
muss immer (try-catch oder throws), die IDE fordert dazu auf
muss nicht, man kann nicht reagieren
Oberklasse
RuntimeException
Exception
Error
Nicht behandelte Ausnahme → sie steigt im Stacktrace nach oben (Methode → aufrufende Methode → … → main → Java VM Runtime stoppt). Am Stacktrace sieht man, wo der Fehler war.
Beispiel aus den Folien: sc.nextInt() bekommt ein Double:
Scanner.nextInt → ReadTest.readInt (return sc.nextInt();)
→ ReadTest.main (println("Deine Zahl ist: " + readInt))
→ Java VM Runtime → Programmabbruch (InputMismatchException)
try-catch rahmt den Fehler ein und behandelt ihn direkt
throws gibt die Exception nach oben an die aufrufende Methode weiter, irgendwo muss sie dann behandelt werden
Ausnahmen sind Objekte mit Informationen über den Fehler
Ein catch für Klasse E fängt auch alle Unterklassen von E
Methoden von Throwable:
Methode
liefert
String getMessage()
Text der Ausnahme
String toString()
Klassenname + ": " + getMessage() (ohne Message nur der Klassenname)
void printStackTrace()
Stacktrace auf die Konsole
Konstruktoren
leer, mit Fehlermeldung, mit Ursache (cause), …
5.8 Eigene Exceptions (3.3.10.2)
Eigene Klassen erben immer von Exception
Sie haben zuerst nur einen Defaultkonstruktor. Für eine Fehlermeldung braucht man einen eigenen Konstruktor mit super(...):
class MyException extends Exception { public MyException() { } public MyException(final String specialinfo) { super(specialinfo); }}
Auslösen (werfen) mit throw; throw unterbricht die Ausführung sofort:
throw new MyException();throw new MyException("Falsche Eingabe");
Kann eine Methode eine (checked) Exception werfen, muss das in der Signatur stehen:
int test(int x) throws IOException { ... }
→ Die Methode reicht die Ausnahme damit weiter; die aufrufende Methode muss sie behandeln.
throw
throws
in der Methode
im Methodenkopf
wirft jetzt ein Exception-Objekt
kündigt Typen an, die geworfen werden können
throw new MyException("x");
void m() throws MyException, IOException
5.9 finally (3.3.11)
Steht nach den catch-Klauseln
Wird immer am Ende ausgeführt, egal ob eine Ausnahme auftrat oder nicht (auch bei return)
Zweck: im try begonnene Arbeiten sauber abschließen
Beispiel aus den Folien: Im try wird eine Datei geöffnet, im finally wird sie geschlossen
5.10 Exception-Chaining (3.3.12)
Problem: Werden Ausnahmen immer weitergereicht, entstehen sehr lange throws-Signaturen; Detailfehler müssen an ganz anderer Stelle behandelt werden; Änderungen ziehen viele Änderungen nach sich
Lösung: Ausnahmen untergeordneter Aufrufe werden aufgefangen, zusammengefasst und in einer neuen Ausnahme weitergegeben (die alte ist die Ursache / cause)
try { ...} catch (E1 ex) { throw new MyException(ex); // E1 wird in MyException verpackt} catch (E2 ex) { throw new MyException(ex);}
Dafür braucht die eigene Exception einen Konstruktor mit Throwable cause:
Du darfst nicht durch 0 dividieren! · Stacktrace java.lang.ArithmeticException: / by zero at ExceptionTest.main(ExceptionTest.java:7) · nach ExceptionHandling
10 abc
Die eingegebene Zahl ist kein Integer · nach ExceptionHandling
10
Gib bitte genau 2 Parameter an · nach ExceptionHandling (args[1] existiert nicht)
Termination Model:"nach Division" kommt nur ohne Fehler. "nach ExceptionHandling" kommt immer, weil der Fehler gefangen wurde.
6.2 ExceptionTest4.java: Exception-Hierarchie und überschriebene Methoden
class MyException extends Exception { }class MyNewException extends MyException { }class MyNewExtraException extends MyException { }class A { public void a() throws MyException { throw new MyException(); }}class B extends A { } // erbt a() von Aclass C extends B { @Override public void a() throws MyNewException, MyNewExtraException { // nur Unterklassen erlaubt! if (Math.random() > 0.5) { throw new MyNewException(); } else { throw new MyNewExtraException(); } }}public class ExceptionTest4 { public static void main(String[] args) { int zahl = 3; A t1 = new A(); B t2 = new B(); C t3 = new C(); try { switch (zahl) { case 1: t1.a(); break; case 2: t2.a(); break; case 3: t3.a(); break; default: throw new MyException(); } } catch (MyNewException e) { // spezieller zuerst System.out.println(e); } catch (MyException e) { // fängt auch MyNewExtraException System.out.println(e); } }}
Ausgabe für zahl = 1, 2, 3, 4?
zahl
Ausgabe
Warum
1
MyException
A.a() wirft MyException → 2. catch
2
MyException
B erbt a() von A
3
zufälligMyNewExceptionoderMyNewExtraException
C überschreibt a(); MyNewException → 1. catch, MyNewExtraException → 2. catch (ist Unterklasse von MyException)
4
MyException
default wirft selbst
System.out.println(e) ruft toString() auf → Klassenname (keine Message vorhanden)
Regel beim Überschreiben
Eine überschreibende Methode darf nur dieselben oder speziellere (Unterklassen-)Exceptions in throws angeben, keine neuen, allgemeineren checked Exceptions.
class MyException extends Exception { MyException() { } MyException(final Throwable cause) { super(cause); }}class MyOtherException extends Exception { }public final class ExceptionTest6 { void test(final int i) throws MyException { try { switch (i) { case 1: throw new MyException(); case 2: throw new MyOtherException(); } } catch (final MyOtherException e1) { throw new MyException(e1); // Chaining: verpacken } } public static void main(final String[] args) { if (args.length != 1) { System.out.println("Bitte genau ein Argument uebergeben!"); } else { final ExceptionTest6 et = new ExceptionTest6(); try { et.test(Integer.parseInt(args[0])); } catch (final MyException e) { System.out.println(e); } finally { System.out.println("finally wurde erreicht"); } } }}
Ausgabe für Argument 1, 2, 3 und ohne Argument?
Aufruf
Ausgabe
ohne
Bitte genau ein Argument uebergeben!
1
MyException · finally wurde erreicht
2
MyException: MyOtherException · finally wurde erreicht
3
finally wurde erreicht (keine Exception)
Bei 2: Durch super(cause) wird die Message automatisch cause.toString(), also "MyOtherException". Deshalb gibt toString()MyException: MyOtherException aus.
6.4 AssertionTest.java: Fehler in der Logik finden
public class AssertionTest { static int max(int x, int y) { return x < y ? x : y; } // ⚠️ Fehler: liefert das Minimum! static int min(int x, int y) { return x > y ? x : y; } // ⚠️ Fehler: liefert das Maximum! static void test(int x, int y) { int maximum = AssertionTest.max(x, y); int minimum = AssertionTest.min(x, y); assert maximum >= minimum : "Maximum ist kleiner als Minimum"; System.out.println(maximum + " " + minimum); } public static void main(String[] args) { AssertionTest.test(10, 15); AssertionTest.test(8, 3); AssertionTest.test(2, 2); }}
Ausgabe mit java AssertionTest und mit java -ea AssertionTest?
Ohne -ea (Assertions aus, der Fehler bleibt unbemerkt):
10 15
3 8
2 2
Mit -ea:
Exception in thread "main" java.lang.AssertionError: Maximum ist kleiner als Minimum
at AssertionTest.test(AssertionTest.java:7)
at AssertionTest.main(AssertionTest.java:11)
→ Gleich beim ersten Aufruf ist maximum = 10 < minimum = 15. Die Assertion deckt den vertauschten ?:-Operator auf. Korrektur:max → x > y ? x : y, min → x < y ? x : y.
6.5 Dein CharacterTwice: try-catch mit NullPointerException
Teil 7 – OOP_Ex6: Wahlen in Südtirol (dein Projekt 07_Wahlen)
Aufgabenstellung (Kurzfassung):
Abstrakte KlassePartei: Name, Quote (Mann/Frau)
Konkrete KlassePerson: Name, Alter, Geschlecht, Wohnort, kann einen Kandidaten wählen
Konkrete KlasseSuedtirolerParteiextends Partei: Beschreibung, N Kandidaten, Anzahl, alle Kandidaten mit Slogan ausgeben
Konkrete KlasseKandidatextends Person implements Electable: gehört einer Partei an, Stimmen
InterfaceElectable: Über-18-Prüfung, Werbeslogan
Konkrete KlasseWahl: Datum (Stimmen nur an diesem Tag), Parteien, Ausgabe der Stimmverteilung + ungültige Stimmen (Person nicht 18)
Test: 3 Parteien mit 3–5 Kandidaten, 1000 zufällige Bürger, alle wählen zufällig (nur einmal), Wahl heute + Wahl vor einigen Tagen; Kandidat muss 5 Monate vor der Wahl 18 sein
Integer.parseInt(null) wirft NumberFormatException (nicht NPE!) → wird zu 0
parseInts(null) übergibt das ganze Array als null → ohne die Prüfung käme bei numbers.length eine NullPointerException. Auch das wäre „eine Ausnahme”, die eigene ist aber verständlicher.
Auf dem Blatt steht beim letzten Beispiel [0, 0, 0, 0, 0], es sind aber nur 4 Strings ("Camera, TV" ist ein String) → richtig ist [0, 0, 0, 0]
OOP_Ex7 Aufgabe 2 – Division Exception-Chain
divideNumbers(a, b) dividiert und gibt das Ergebnis zurück
Bei Divisor 0 → ArithmeticException
Im catch eine eigene DivisionException werfen, mit der ursprünglichen als Ursache
Mit 10, 0 testen, DivisionException fangen und mit printStackTrace() ausgeben
Lösung
class DivisionException extends Exception { public DivisionException(String message, Throwable cause) { super(message, cause); }}public class Division { static int divideNumbers(int a, int b) throws DivisionException { try { return a / b; } catch (ArithmeticException e) { throw new DivisionException("Division durch 0 nicht erlaubt", e); } } public static void main(String[] args) { try { System.out.println(divideNumbers(10, 2)); System.out.println(divideNumbers(10, 0)); } catch (DivisionException e) { e.printStackTrace(); } }}
5
DivisionException: Division durch 0 nicht erlaubt
at Division.divideNumbers(Division.java:12)
at Division.main(Division.java:19)
Caused by: java.lang.ArithmeticException: / by zero
at Division.divideNumbers(Division.java:10)
... 1 more
OOP_Ex7 Aufgabe 3 – DataProcessor Exception-Chain
Klasse DataProcessor mit processData(List<Object> data)
processData ruft für jedes Element processObject(Object obj) auf
processObject prüft den Typ: nur Integer und String ausgeben ("String: hallo")
Unbekanntes Objekt → IllegalArgumentException("Ungültiges Datenobjekt: " + Name des Objekts)
In processData: NullPointerException → DataProcessingException("Ungültiges Datenobjekt: Null-Wert gefunden.", Ursache)
In processData: alle anderen → DataProcessingException("Fehler bei der Verarbeitung des Datenobjekts.", Ursache)
Lösung
import java.util.ArrayList;import java.util.List;class DataProcessingException extends Exception { public DataProcessingException(String message, Throwable cause) { super(message, cause); }}public class DataProcessor { public static void main(String[] args) { List<Object> data = new ArrayList<>(); data.add(10); data.add("Hello"); data.add(new DataProcessor()); try { processData(data); } catch (DataProcessingException e) { e.printStackTrace(); } } static void processData(List<Object> data) throws DataProcessingException { for (Object obj : data) { try { processObject(obj); } catch (NullPointerException e) { // spezieller zuerst throw new DataProcessingException("Ungültiges Datenobjekt: Null-Wert gefunden.", e); } catch (Exception e) { // alle anderen throw new DataProcessingException("Fehler bei der Verarbeitung des Datenobjekts.", e); } } } static void processObject(Object obj) { if (obj instanceof Integer) { System.out.println("Integer: " + obj); } else if (obj instanceof String) { System.out.println("String: " + obj); } else { throw new IllegalArgumentException("Ungueltiges Datenobjekt: " + (obj == null ? "null" : obj.getClass().getSimpleName())); } }}
Integer: 10
String: Hello
DataProcessingException: Fehler bei der Verarbeitung des Datenobjekts.
at DataProcessor.processData(DataProcessor.java:30)
at DataProcessor.main(DataProcessor.java:17)
Caused by: java.lang.IllegalArgumentException: Ungueltiges Datenobjekt: DataProcessor
at DataProcessor.processObject(DataProcessor.java:42)
at DataProcessor.processData(DataProcessor.java:26)
... 1 more
null instanceof X ist immer false → null landet im else → IllegalArgumentException „…: null”. Genau das zeigt auch die erwartete Ausgabe am Blatt (Beispiel 1).
obj.getClass().getSimpleName() liefert den Klassennamen (DataProcessor)
Zeilennummern im Stacktrace hängen von deiner Datei ab
OOP_Ex7 Aufgabe 4 – Konto mit Assertions
Klasse Konto mit guthaben, Konstruktor mit Startguthaben, einzahlen, abheben. Mit Assertions sicherstellen: Guthaben immer positiv, nur positive Beträge ein-/auszahlen.
Lösung
public class Konto { private double guthaben; public Konto(double startguthaben) { assert startguthaben >= 0 : "Startguthaben darf nicht negativ sein"; this.guthaben = startguthaben; } public void einzahlen(double betrag) { assert betrag > 0 : "Nur positive Beträge einzahlen"; guthaben += betrag; assert guthaben >= 0 : "Guthaben ist negativ"; } public void abheben(double betrag) { assert betrag > 0 : "Nur positive Beträge abheben"; guthaben -= betrag; assert guthaben >= 0 : "Guthaben ist negativ: " + guthaben; } public double getGuthaben() { return guthaben; } public static void main(String[] args) { Konto k = new Konto(100); k.einzahlen(50); k.abheben(30); System.out.println("Guthaben: " + k.getGuthaben()); k.abheben(500); System.out.println("Guthaben: " + k.getGuthaben()); }}
Ohne -ea:Guthaben: 120.0 · Guthaben: -380.0 ← Assertions sind aus! Mit -ea:Guthaben: 120.0 · java.lang.AssertionError: Guthaben ist negativ: -380.0
OOP_Ex7 Aufgabe 5 – ZahlenListe mit Assertions
Klasse ZahlenListe mit ArrayList<Integer> zahlen, Konstruktor (leere Liste), zahlHinzufuegen, zahlEntfernen, druckeListe. Nach jedem Hinzufügen/Entfernen mit Collections.sort sortieren und mit Assertions prüfen, dass die Liste sortiert ist. Nur positive Zahlen erlaubt.
Lösung
import java.util.ArrayList;import java.util.Collections;public class ZahlenListe { private ArrayList<Integer> zahlen; public ZahlenListe() { zahlen = new ArrayList<>(); } public void zahlHinzufuegen(int zahl) { assert zahl > 0 : "Nur positive Zahlen erlaubt: " + zahl; zahlen.add(zahl); Collections.sort(zahlen); assert istSortiert() : "Liste ist nicht sortiert"; } public void zahlEntfernen(int zahl) { zahlen.remove(Integer.valueOf(zahl)); // Wert entfernen, nicht Index! Collections.sort(zahlen); assert istSortiert() : "Liste ist nicht sortiert"; } private boolean istSortiert() { for (int i = 0; i < zahlen.size() - 1; i++) { if (zahlen.get(i) > zahlen.get(i + 1)) { return false; } } return true; } public void druckeListe() { System.out.println(zahlen); } public static void main(String[] args) { ZahlenListe l = new ZahlenListe(); l.zahlHinzufuegen(5); l.zahlHinzufuegen(2); l.zahlHinzufuegen(9); l.druckeListe(); l.zahlEntfernen(5); l.druckeListe(); l.zahlHinzufuegen(-3); }}
Mit -ea:
[2, 5, 9]
[2, 9]
Exception in thread "main" java.lang.AssertionError: Nur positive Zahlen erlaubt: -3
⚠️ zahlen.remove(5) mit int würde den Index 5 entfernen. Deshalb Integer.valueOf(zahl).
OOP_Ex5 Aufgabe 3 – Flugzeuge (abstrakte Klasse)
Gegeben ist die abstrakte Klasse Flugzeug (hersteller, maxSpeed, immatNummer, anzahlFluegel; abstract public boolean getLooping();).
Verkehrsflugzeug: genau 1 Flügelpaar, Attribut Passagiere; nie Loopings, auch nicht in Unterklassen; Konstruktor (Hersteller, maxSpeed, ImmatNr, Passagiere); Getter/Setter Passagiere
Doppeldecker: genau 2 Flügelpaare; Konstante LOOPINGSPEED = 320; getLooping() = maxSpeed > LOOPINGSPEED; offenesCockpit nach dem Setzen unveränderbar; isOffenesCockpit(); 2 Konstruktoren (der 2. ohne Cockpit → Default true); nicht erweiterbar
Lösung
abstract class Flugzeug { private String hersteller; private int maxSpeed; private String immatNummer; private int anzahlFluegel = 1; public Flugzeug(String hersteller, int maxSpeed, int anzahlFluegel) { this.hersteller = hersteller; this.maxSpeed = maxSpeed; this.anzahlFluegel = anzahlFluegel; } public String getImmatNummer() { return immatNummer; } protected void setImmatNummer(String immatNummer) { this.immatNummer = immatNummer; } public int getMaxSpeed() { return maxSpeed; } public int getAnzahlFluegel() { return anzahlFluegel; } public String getHersteller() { return hersteller; } abstract public boolean getLooping();}class Verkehrsflugzeug extends Flugzeug { private int anzahlPassagiere; public Verkehrsflugzeug(String hersteller, int maxSpeed, String immatNummer, int anzahlPassagiere) { super(hersteller, maxSpeed, 1); // genau 1 Flügelpaar setImmatNummer(immatNummer); this.anzahlPassagiere = anzahlPassagiere; } @Override public final boolean getLooping() { // final → Unterklassen können es nicht ändern return false; } public int getAnzahlPassagiere() { return anzahlPassagiere; } public void setAnzahlPassagiere(int anzahlPassagiere) { this.anzahlPassagiere = anzahlPassagiere; }}final class Doppeldecker extends Flugzeug { // final class → nicht erweiterbar public static final int LOOPINGSPEED = 320; private final boolean offenesCockpit; // final → nach dem Setzen fix public Doppeldecker(String hersteller, int maxSpeed, String immatNummer, boolean offenesCockpit) { super(hersteller, maxSpeed, 2); // genau 2 Flügelpaare setImmatNummer(immatNummer); this.offenesCockpit = offenesCockpit; } public Doppeldecker(String hersteller, int maxSpeed, int immatNummer) { this(hersteller, maxSpeed, String.valueOf(immatNummer), true); // Default: offen } @Override public boolean getLooping() { return getMaxSpeed() > LOOPINGSPEED; } public boolean isOffenesCockpit() { return offenesCockpit; }}public class Flughafen { public static void main(String[] args) { Flugzeug[] flotte = { new Verkehrsflugzeug("Airbus", 900, "D-AIBA", 180), new Doppeldecker("Bücker", 350, "D-EBJU", false), new Doppeldecker("Stampe", 200, 4711) }; for (Flugzeug f : flotte) { System.out.println(f.getHersteller() + " " + f.getImmatNummer() + ": Flügelpaare=" + f.getAnzahlFluegel() + ", Looping=" + f.getLooping()); } System.out.println("Offenes Cockpit: " + ((Doppeldecker) flotte[2]).isOffenesCockpit()); }}