Aufgabenblock — AFB II
Zehn Aufgaben aus dem ganzen Kapitel: Fehlermeldungen einordnen, Schleifen und Methodenaufrufe verfolgen, aus Klassenkarten Code machen und eine Suche in der Liste bauen — immer in mehreren Schritten, mit drei gestuften Tipps, wenn du nicht weiterkommst.
Lina schreibt ein Programm für ihre Münzsammlung. Sie probiert sechs Zeilen aus — jede einzeln in main von Muenzsammlung.java:
int anzahl = 12.5;boolean selten = "ja";char land = "M";double wert = 7 / 2;String s = "Münzen: " + 5 + 3;int[] kiste = new int[3]; kiste[3] = 5;
Analysiere die sechs Zeilen: Welche meldet schon javac, welche bricht erst beim Ausführen ab, welche laufen ohne Fehlermeldung — und mit welchem Wert?
'M' ist ein char, "M" ein String. 12.5 passt nicht verlustfrei in int. 7 / 2 ist erst eine Ganzzahldivision, dann wird erweitert.Vollständige Lösung
Compilerfehler (3) — das Programm startet gar nicht:
1: error: incompatible types: possible lossy conversion from double to int 2: error: incompatible types: String cannot be converted to boolean 3: error: incompatible types: String cannot be converted to char
Laufzeitfehler (1) — Zeile 6 wird übersetzt, bricht aber beim Ausführen ab: Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3 (erlaubt sind nur 0 bis 2).
Ohne Meldung laufen 4 und 5 — trotzdem liefern sie vermutlich nicht, was Lina wollte: wert = 3.0 (Ganzzahldivision) und s = „Münzen: 53“, weil von links nach rechts erst "Münzen: " + 5 zu einem Text wird und dann die 3 angehängt wird. Solche Logikfehler findet weder javac noch die JVM.
Jonas spart für Kopfhörer für 100 €. Jede Woche legt er 10 € ins Sparschwein, jede vierte Woche gibt es von der Oma 15 € dazu.
int guthaben = 0;
int woche = 0;
while (guthaben < 100) {
woche++;
if (woche % 4 == 0) {
guthaben += 25;
} else {
guthaben += 10;
}
}
Ermittle mit einer Tracetabelle, nach wie vielen Wochen die Schleife endet und wie viel Geld dann im Sparschwein ist.
woche, woche % 4, guthaben. Die Bedingung wird immer vor einem Durchlauf geprüft.Vollständige Lösung
Woche 1: 10 · 2: 20 · 3: 30 · 4: 55 · 5: 65 · 6: 75 · 7: 85 · 8: 85 + 25 = 110. Jetzt ist 110 < 100 falsch → Ende.
Die Schleife endet nach 8 Wochen mit 110 €. Hier passt while besser als for, weil die Anzahl der Durchläufe vorher nicht feststeht.
Ein Versandshop berechnet das Porto: bis 1000 g 5 €, danach für jede angefangenen 500 g 2 € mehr. Mia hat die Python-Funktion nach Java übertragen:
def kosten(gramm):
if gramm <= 1000:
return 5
zusatz = (gramm - 1000 + 499) // 500
return 5 + zusatz * 2
public static kosten(int gramm) {
if (gramm <= 1000) {
return 5;
}
zusatz = (gramm - 1000 + 499) / 500;
return 5 + zusatz * 2
}
Überprüfe Mias Übersetzung: Finde alle Fehler, verbessere sie und berechne mit der korrigierten Methode das Porto für 2300 g und für 3000 g.
// in Python entspricht bei int-Werten dem / in Java. Für 2300 g: (2300 − 1000 + 499) / 500 = 1799 / 500.int fehlt, int vor zusatz fehlt, Semikolon am Ende fehlt. 1799 / 500 = 3 → 5 + 6 = 11 €.Vollständige Lösung
public static int kosten(int gramm) {
if (gramm <= 1000) {
return 5;
}
int zusatz = (gramm - 1000 + 499) / 500;
return 5 + zusatz * 2;
}
Beim ersten javac-Lauf erscheinen nur zwei Meldungen: invalid method declaration; return type required und ';' expected. Erst wenn diese behoben sind, meldet der Compiler cannot find symbol … variable zusatz — Syntaxfehler werden vor den Typfehlern gefunden.
2300 g: 1799 / 500 = 3 angefangene Blöcke → 5 + 3 · 2 = 11 €. 3000 g: 2499 / 500 = 4 → 5 + 8 = 13 €. Das „+ 499“ sorgt fürs Aufrunden: 1001 g kostet schon 7 €.
Ein Kanuverleih am See zeigt an, wie viele Sitzplätze gerade frei sind.
public class Kanu {
private String name;
private int plaetze;
private boolean verliehen;
public Kanu(String name, int plaetze) {
this.name = name;
plaetze = plaetze;
}
public Kanu(String name) {
this.name = name;
this.plaetze = 1;
}
public void verleihen() {
verliehen = true;
}
public int freiePlaetze() {
if (verliehen) {
return 0;
}
return plaetze;
}
}
Kanu a = new Kanu("Otter", 3);
Kanu b = new Kanu("Biber");
Kanu c = new Kanu("Hecht", 2);
c.verleihen();
int frei = a.freiePlaetze() + b.freiePlaetze() + c.freiePlaetze();
Der Programmierer sagt: „Es sind 4 Plätze frei.“ Untersuche Zeile für Zeile, welche Werte die Objekte wirklich haben.
0, false, null). Führe dann den passenden Konstruktor aus.plaetze, wenn ein Parameter genauso heißt wie das Attribut?plaetze = plaetze; weist den Parameter sich selbst zu. Das Attribut von a bleibt 0.Vollständige Lösung
- name = "Otter"
- plaetze = 0
- verliehen = false
- name = "Biber"
- plaetze = 1
- verliehen = false
- name = "Hecht"
- plaetze = 0
- verliehen = true
Im Konstruktor mit zwei Parametern verdeckt der Parameter plaetze das Attribut. plaetze = plaetze; ändert also nur den Parameter — das Attribut behält den Standardwert 0. javac meldet dabei keinen Fehler.
a.freiePlaetze() = 0, b liefert 1 (hier steht this.), c ist verliehen → 0. frei = 0 + 1 + 0 = 1. Der Programmierer hat mit 3 + 1 = 4 gerechnet — richtig wäre das nur mit this.plaetze = plaetze;.
- - name: Zeichenkette
- - plaetze: Ganzzahl
- - verliehen: Wahrheitswert
- c Kanu(name: Zeichenkette, plaetze: Ganzzahl)
- c Kanu(name: Zeichenkette)
- + verleihen()
- + freiePlaetze(): Ganzzahl
Beim Sponsorenlauf der Schule zahlen Sponsoren für jede gelaufene Runde einen festen Betrag. Die SV hat die Klasse Laeufer geplant (siehe Karte).
Erstelle aus der Klassenkarte den vollständigen Java-Quelltext. Zu Beginn ist runden 0; spende() liefert Runden mal Betrag pro Runde. Wie viel bringt Ida mit 2,50 € pro Runde nach 13 Runden ein?
- runden: Ganzzahl → private int runden;, Fließkommazahl → double. Methoden ohne Rückgabetyp in der Karte bekommen void.name und euroProRunde kommen als Parameter — runden setzt du selbst auf 0.return runden * euroProRunde; — für Ida 13 · 2,5 = 32,5.Vollständige Lösung
public class Laeufer {
private String name;
private int runden;
private double euroProRunde;
public Laeufer(String name, double euroProRunde) {
this.name = name;
this.euroProRunde = euroProRunde;
runden = 0;
}
public void rundeLaufen() {
runden = runden + 1;
}
public int getRunden() {
return runden;
}
public double spende() {
return runden * euroProRunde;
}
}
Test: Laeufer ida = new Laeufer("Ida", 2.5);, 13-mal ida.rundeLaufen(); → ida.spende() = 32.5, also 32,50 €. runden * euroProRunde ist int * double; der int wird automatisch erweitert.
- - name: Zeichenkette
- - runden: Ganzzahl
- - euroProRunde: Fließkommazahl
- c Laeufer(name: Zeichenkette, euroProRunde: Fließkommazahl)
- + rundeLaufen()
- + getRunden(): Ganzzahl
- + spende(): Fließkommazahl
Eine Smartwatch speichert ein Tagesziel für Schritte. Erlaubt sind nur Werte von 1000 bis 30000 in 500er-Schritten. Start ist 6000.
public void setZiel(int ziel) {
if (ziel >= 1000 && ziel <= 30000 && ziel % 500 == 0) {
this.ziel = ziel;
}
}
public void erhoehen() {
setZiel(ziel + 2500);
}
Version A (private int ziel): s.setZiel(8000); s.setZiel(12750); s.setZiel(500); s.erhoehen(); s.setZiel(31000);
Version B (public int ziel, ohne Setter): s.ziel = 8000; s.ziel = 12750; s.ziel = 500; s.ziel += 2500; s.ziel = 31000;
Erläutere an beiden Abläufen, wie Kapselung ungültige Zustände verhindert. Gib jeweils den Endwert von ziel an.
erhoehen() will 8000 + 2500 · 31000 ✗.Vollständige Lösung
Version A: 6000 → 8000 → (12750 abgewiesen) → (500 abgewiesen) → 10500 → (31000 abgewiesen) = 10500. Weil ziel privat ist, führt jeder Weg zum Attribut durch den Setter — auch erhoehen() nutzt ihn. Ein ungültiger Zustand kann nie entstehen.
Version B: Jede Zuweisung wirkt direkt: 8000 → 12750 → 500 → 3000 → 31000. Das Objekt war dreimal in einem ungültigen Zustand; welche Regeln gelten, müsste jede Stelle im Programm selbst wissen.
Fazit: Mit private und Setter steht die Regel an einer Stelle in der Klasse. Wer von außen s.ziel = 500; schreibt, bekommt schon von javac die Meldung ziel has private access in Schrittziel.
Beim Kopfrechen-Wettbewerb der 6. Klassen stehen die Punkte der Teams im Array punkte. Das Programm soll das beste Team finden und zählen, wie viele Teams mindestens 10 Punkte haben.
int[] punkte = {12, 7, 15, 9, 15, 4};
int best = 0;
int anzahl = 0;
for (int i = 1; i < punkte.length; i++) {
if (punkte[i] > punkte[best]) {
best = i;
}
if (punkte[i] >= 10) {
anzahl++;
}
}
Werte das Programm aus: Welche Werte haben best und anzahl am Ende, und tut das Programm, was es soll?
best ist ein Index, kein Punktwert. Verfolge i, punkte[i], punkte[best] und anzahl in einer Tabelle.best wird nur bei i = 2 geändert. Gezählt werden nur 15 (i = 2) und 15 (i = 4) — die 12 an Index 0 fehlt.Vollständige Lösung
i = 1: 7 — nichts · i = 2: 15 > 12 → best = 2, anzahl = 1 · i = 3: 9 — nichts · i = 4: 15 > 15 ist falsch, anzahl = 2 · i = 5: 4 — nichts.
best = 2 (bei Gleichstand gewinnt das erste Team mit 15 Punkten), anzahl = 2.
Für best ist der Start bei i = 1 in Ordnung, weil Index 0 schon als Startwert dient. Für anzahl ist er ein Fehler: Team 0 hat 12 Punkte und wird nicht gezählt — richtig wären 3. Abhilfe: anzahl in einer eigenen Schleife ab i = 0 zählen oder vor der Schleife punkte[0] prüfen.
Die Saftbar im Freizeitbad rechnet in Cent. Smoothies kosten mit Extra-Obst 80 ct mehr.
public class Getraenk {
private String name;
private int preisCent;
public Getraenk(String name, int preisCent) {
this.name = name;
this.preisCent = preisCent;
}
public int getPreisCent() {
return preisCent;
}
public String beschreibung() {
return name + ": " + getPreisCent() + " ct";
}
}
public class Smoothie extends Getraenk {
private boolean extraObst;
public Smoothie(String name, int preisCent, boolean extraObst) {
super(name, preisCent);
this.extraObst = extraObst;
}
@Override
public int getPreisCent() {
if (extraObst) {
return super.getPreisCent() + 80;
}
return super.getPreisCent();
}
}
Getraenk g = new Getraenk("Tee", 150);
Smoothie s = new Smoothie("Mango", 320, true);
Getraenk t = new Smoothie("Beere", 290, false);
int summe = g.getPreisCent() + s.getPreisCent() + t.getPreisCent();
String text = s.beschreibung();
Erkläre, welche getPreisCent()-Methode bei jedem Aufruf läuft, und gib summe sowie die Zahl in text an.
new erzeugt wurde — nicht der Typ der Variablen. t ist also ein Smoothie.Getraenk geschrieben, ruft aber getPreisCent() auf — und zwar die des Objekts s.text = „Mango: 400 ct“.Vollständige Lösung
g ist ein Getraenk → die Methode aus Getraenk: 150. s ist ein Smoothie → die überschriebene Methode; sie holt mit super.getPreisCent() den gespeicherten Preis 320 und addiert 80 = 400. t ist trotz Variablentyp Getraenk ein Smoothie → überschriebene Methode, aber ohne Extra-Obst: 290.
summe = 150 + 400 + 290 = 840 ct. s.beschreibung() ist geerbt; darin läuft wieder die Smoothie-Version von getPreisCent() → „Mango: 400 ct“.
Ohne super. würde sich getPreisCent() in Smoothie endlos selbst aufrufen. Und direkt auf preisCent zugreifen darf Smoothie nicht, weil das Attribut in Getraenk privat ist.
Die Theater-AG verwaltet die Rollen eines Stücks. Die Klasse Rolle ist fertig (siehe Karten), in Theaterstueck fehlen noch drei Methoden.
import java.util.ArrayList;
public class Theaterstueck {
private String titel;
private ArrayList<Rolle> rollen;
public Theaterstueck(String titel) {
this.titel = titel;
rollen = new ArrayList<>();
}
public void hinzufuegen(Rolle r) {
rollen.add(r);
}
// suche, besetzen, anzahlFrei fehlen
}
Implementiere suche(name) (liefert die Rolle oder null), besetzen(name) (true nur, wenn es die Rolle gibt und sie noch frei ist) und anzahlFrei(). Teste mit den Rollen Kater, König, Müllerssohn, Zauberer, Prinzessin und den Aufrufen besetzen("Kater"), besetzen("Kater"), besetzen("Riese"), besetzen("Zauberer").
suche läuft mit for-each über rollen und vergleicht mit equals. Erst nach der Schleife steht return null;.besetzen ruft suche auf. Prüfe zuerst auf null — sonst stürzt r.istBesetzt() mit einer NullPointerException ab.Vollständige Lösung
public Rolle suche(String name) {
for (Rolle r : rollen) {
if (r.getName().equals(name)) {
return r;
}
}
return null;
}
public boolean besetzen(String name) {
Rolle r = suche(name);
if (r == null || r.istBesetzt()) {
return false;
}
r.besetzen();
return true;
}
public int anzahlFrei() {
int frei = 0;
for (Rolle r : rollen) {
if (!r.istBesetzt()) {
frei++;
}
}
return frei;
}
Testlauf: true, false, false, true → 2-mal true. Frei bleiben König, Müllerssohn und Prinzessin: anzahlFrei() = 3.
Wegen || wird r.istBesetzt() nur geprüft, wenn r nicht null ist. Die zweite false-Rückgabe zeigt: Der Testfall „doppelt besetzen“ ist wichtig.
- - name: Zeichenkette
- - besetzt: Wahrheitswert
- c Rolle(name: Zeichenkette)
- + getName(): Zeichenkette
- + istBesetzt(): Wahrheitswert
- + besetzen()
Ein Vokabeltrainer speichert 250 Vokabeln in einer ArrayList — unsortiert, in der Reihenfolge, in der sie eingegeben wurden. Die Suche nach einer Vokabel läuft wie in der Bücherverwaltung von vorne nach hinten und vergleicht jeden Eintrag mit equals.
Schätze die Anzahl der Vergleiche ab: höchstens, im Mittel (wenn jede Vokabel gleich oft gesucht wird) und höchstens nach einem Update mit 500 Vokabeln.
Vollständige Lösung
a) Ungünstigster Fall: Die Vokabel steht ganz hinten oder fehlt → alle 250 Einträge werden verglichen.
b) Bei gleich häufiger Suche ist der Mittelwert von 1 bis 250: (1 + 250) / 2 = 125,5 Vergleiche.
c) Doppelt so viele Vokabeln → höchstens 500 Vergleiche; auch der Mittelwert verdoppelt sich ungefähr (250,5). Die lineare Suche wächst also im gleichen Verhältnis wie die Liste. Suchen, die nicht gefunden werden, sind immer der teuerste Fall.
