MINT lernen

Abituraufgaben: Mit gegebenen Klassen arbeiten

Aus der Dokumentation programmieren: E-Mail-Adressen mit String-Methoden zerlegen und eine Streaming-Playlist auswerten.

Dein Fortschritt:
0 / 0 Aufgaben
1

Adressbuch mit Schul-E-Mail-Adressen

AFB I–II

Ein Adressbuch-Programm soll aus Schul-E-Mail-Adressen der Form vorname.nachname@domain automatisch Anzeigenamen und Domains gewinnen. Die Klasse Kontakt speichert dazu die Adresse im Attribut email. Zur Verfügung steht die Java-Klasse String, von der nur die Dokumentation in Material 1 bekannt ist. Die Beispieladresse lautet:

String adresse = "lena.hoffmann@schule-hameln.de";
Material 1: Auszug aus der Dokumentation der Klasse String
MethodeBeschreibung
+ length(): Ganzzahlliefert die Anzahl der Zeichen
+ charAt(index: Ganzzahl): Zeichenliefert das Zeichen an der Position index; das erste Zeichen hat den Index 0
+ indexOf(s: Zeichenkette): Ganzzahlliefert den Index des ersten Vorkommens von s, −1, falls s nicht vorkommt
+ substring(beginn: Ganzzahl, ende: Ganzzahl): Zeichenketteliefert die Teilzeichenkette von Index beginn bis ausschließlich Index ende
+ substring(beginn: Ganzzahl): Zeichenketteliefert die Teilzeichenkette von Index beginn bis zum Ende
+ toUpperCase(): Zeichenketteliefert eine neue Zeichenkette in Großbuchstaben; das Original bleibt unverändert
+ equals(s: Zeichenkette): Wahrheitswertliefert true, wenn beide Zeichenketten dieselben Zeichen in derselben Reihenfolge enthalten
Zeichenketten lassen sich mit + verketten.
  1. Wenden Sie die dokumentierten Methoden auf adresse an: Geben Sie die Ergebnisse von adresse.length(), adresse.indexOf("@"), adresse.charAt(5), adresse.substring(5, 9), adresse.substring(adresse.indexOf("@") + 1).toUpperCase() und adresse.indexOf("x") an.
  2. Überprüfen Sie die folgende Methode eines Mitschülers, die den Benutzernamen (alles vor dem @) liefern soll, am Beispiel und korrigieren Sie sie gegebenenfalls.
    public String getBenutzername() {
        return email.substring(0, email.indexOf("@") - 1);
    }
  3. Implementieren Sie in der Klasse Kontakt die Methoden getDomain(): Zeichenkette, die den Teil nach dem @ liefert (leere Zeichenkette, falls kein @ vorkommt), und getAnzeigename(): Zeichenkette, die für die Beispieladresse "Lena Hoffmann" liefert.

Hinweise

Hinweis zu Aufgabe a)
Schreibe dir die Indizes unter die Zeichen: l = 0, e = 1, n = 2, a = 3, . = 4 usw. Achte bei charAt auf den Rückgabetyp und bei substring auf das ausschließliche Ende.
Hinweis zu Aufgabe b)
Setze die Zahlen ein: Welchen Wert hat email.indexOf("@") - 1, und welches Zeichen steht an dieser Stelle? Wird es mitgeliefert?Überprüfen heißt: an einem konkreten Fall messen und den Fehler benennen, nicht nur „geht nicht“.
Hinweis zu Aufgabe c)
Bestimme die Positionen von . und @. Für den großen Anfangsbuchstaben: das erste Zeichen als Teilzeichenkette groß schreiben und den Rest anhängen.

Erwartungshorizont

Erwartungshorizont zu Aufgabe a)
AufrufErgebnis
adresse.length()30
adresse.indexOf("@")13
adresse.charAt(5)das Zeichen 'h'
adresse.substring(5, 9)"hoff" (Indizes 5 bis 8)
adresse.substring(adresse.indexOf("@") + 1).toUpperCase()"SCHULE-HAMELN.DE"
adresse.indexOf("x")−1
Erwartungshorizont zu Aufgabe b)

Für die Beispieladresse ist email.indexOf("@") - 1 = 12. substring(0, 12) liefert die Zeichen mit Index 0 bis 11, also "lena.hoffman" – das letzte n (Index 12) fehlt. Der Fehler beruht auf der Annahme, das Ende sei eingeschlossen. Da das Ende laut Dokumentation ausgeschlossen ist, muss direkt die Position des @ angegeben werden:

public String getBenutzername() {
    return email.substring(0, email.indexOf("@"));
}

Zusätzlich fällt auf: Ohne @ liefert indexOf −1; die Originalfassung würde dann substring(0, -2) aufrufen und abbrechen. Eine Prüfung auf −1 ist daher sinnvoll.

Erwartungshorizont zu Aufgabe c)
public String getDomain() {
    int at = email.indexOf("@");
    if (at == -1) {
        return "";
    }
    return email.substring(at + 1);
}

public String getAnzeigename() {
    int punkt = email.indexOf(".");
    int at = email.indexOf("@");
    String vorname = email.substring(0, punkt);
    String nachname = email.substring(punkt + 1, at);
    return gross(vorname) + " " + gross(nachname);
}

private String gross(String wort) {
    return wort.substring(0, 1).toUpperCase() + wort.substring(1);
}

Für die Beispieladresse: punkt = 4, at = 13, vorname = "lena", nachname = "hoffmann", Ergebnis "Lena Hoffmann". Die Hilfsmethode ist nicht verlangt; die Anweisung darf auch zweimal direkt im Rumpf stehen. Da toUpperCase eine neue Zeichenkette liefert, muss das Ergebnis verwendet werden – ein bloßer Aufruf vorname.toUpperCase(); hätte keine Wirkung.

2

Playlist einer Musik-Streaming-App

AFB II–III

Ein Start-up entwickelt eine Musik-Streaming-App. Ein Zulieferer stellt die Klassen Song und Wiedergabeliste nur als übersetzte Dateien mit Dokumentation bereit (Material 2); der Quelltext ist nicht einsehbar. Das Team der App schreibt die Klasse Musikapp, die eine Wiedergabeliste kennt und daraus Auswertungen für die Startseite erzeugt. Zum Kennenlernen der Klassen wurde das Testprogramm in Material 3 geschrieben.

Material 2: Dokumentation der Klassen Song und Wiedergabeliste
MethodeBeschreibung
Klasse Song
c Song(titel: Zeichenkette, interpret: Zeichenkette, dauer: Ganzzahl)erzeugt einen Song; dauer in Sekunden, Aufrufzähler 0
+ getTitel(): Zeichenkette, + getInterpret(): Zeichenkette, + getDauer(): Ganzzahlliefern Titel, Interpret bzw. Dauer in Sekunden
+ getAufrufe(): Ganzzahlliefert, wie oft der Song abgespielt wurde
+ abspielen()spielt den Song ab und erhöht den Aufrufzähler um 1
Klasse Wiedergabeliste
c Wiedergabeliste(name: Zeichenkette, maxAnzahl: Ganzzahl)erzeugt eine leere Liste mit höchstens maxAnzahl Einträgen
+ hinzufuegen(s: Song): Wahrheitswerthängt s hinten an und liefert true; ist die Liste voll oder s gleich null, bleibt sie unverändert und es wird false geliefert. Derselbe Song darf mehrfach enthalten sein.
+ getAnzahl(): Ganzzahlliefert die Anzahl der Einträge
+ getSong(i: Ganzzahl): Songliefert den Eintrag an Position i (erster Eintrag: 0); für i < 0 oder i ≥ getAnzahl() wird null geliefert
+ getGesamtdauer(): Ganzzahlliefert die Summe der Dauern aller Einträge in Sekunden
Nur diese Methoden sind öffentlich.
Material 3: Testprogramm und Klasse Musikapp
Wiedergabeliste liste = new Wiedergabeliste("Lernmusik", 3);
Song s1 = new Song("Nebel", "Kira", 214);
Song s2 = new Song("Fokus", "Lo-Fi Crew", 187);
liste.hinzufuegen(s1);
liste.hinzufuegen(s2);
liste.hinzufuegen(s1);
System.out.println(liste.hinzufuegen(new Song("Pause", "Kira", 95)));  // (1)
for (int i = 0; i < liste.getAnzahl(); i++) {
    liste.getSong(i).abspielen();
}
System.out.println(s1.getAufrufe() + " " + s2.getAufrufe());          // (2)
System.out.println(liste.getGesamtdauer());                            // (3)
System.out.println(liste.getSong(3) == null);                          // (4)
public class Musikapp {
    private Wiedergabeliste liste;

    public Musikapp(Wiedergabeliste liste) {
        this.liste = liste;
    }
}
Die Klasse Musikapp soll erweitert werden.
  1. Ermitteln Sie allein mithilfe der Dokumentation die Ausgaben (1) bis (4) des Testprogramms.
  2. Erweitern Sie die Klasse Musikapp um die Methoden meistgespielt(): Song, die den Song mit den meisten Aufrufen liefert (bei leerer Liste null), und dauerText(): Zeichenkette, die die Gesamtdauer im Format Minuten:Sekunden liefert, z. B. "10:15" für 615 Sekunden und "10:05" für 605 Sekunden.
  3. Erklären Sie, warum das Team die Klasse Wiedergabeliste nutzen kann, ohne ihren Quelltext zu kennen, und warum der Aufruf liste.getSong(liste.getAnzahl()).getTitel() trotzdem zum Programmabbruch führt.
  4. Bewerten Sie den Wunsch des App-Teams, der Zulieferer möge zusätzlich eine Methode getSongs(): Reihung vom Typ Song anbieten, die die intern verwendete Reihung direkt zurückgibt.

Hinweise

Hinweis zu Aufgabe a)
Die Liste fasst 3 Einträge. Wie viele Einträge verweisen auf s1? Jeder Eintrag wird in der Schleife einmal abgespielt.
Hinweis zu Aufgabe b)
Durchlaufe die Positionen 0 bis getAnzahl() - 1 und merke dir den bisher besten Song. Für die Zeit helfen / und % mit 60; denk an die führende Null.
Hinweis zu Aufgabe c)
Was legt eine Dokumentation fest, was lässt sie offen? Lies die Zeile zu getSong genau: Welche Positionen sind gültig?
Hinweis zu Aufgabe d)
Kriterien: Bequemlichkeit für das App-Team, Schutz des inneren Zustands (Anzahl, Lücken, Maximalgröße), Austauschbarkeit der Implementierung beim Zulieferer.

Erwartungshorizont

Erwartungshorizont zu Aufgabe a)

(1) false: Nach drei erfolgreichen Aufrufen (s1, s2, s1) ist die Liste mit maxAnzahl 3 voll.

(2) 2 1: Die Positionen 0 und 2 verweisen auf dasselbe Objekt s1; es wird zweimal abgespielt.

(3) 615: 214 + 187 + 214 = 615 Sekunden.

(4) true: Position 3 ist nicht kleiner als getAnzahl() = 3, daher liefert getSong null.

Erwartungshorizont zu Aufgabe b)
public Song meistgespielt() {
    Song bester = null;
    for (int i = 0; i < liste.getAnzahl(); i++) {
        Song s = liste.getSong(i);
        if (bester == null || s.getAufrufe() > bester.getAufrufe()) {
            bester = s;
        }
    }
    return bester;
}

public String dauerText() {
    int gesamt = liste.getGesamtdauer();
    int minuten = gesamt / 60;
    int sekunden = gesamt % 60;
    if (sekunden < 10) {
        return minuten + ":0" + sekunden;
    }
    return minuten + ":" + sekunden;
}

Für das Testprogramm: meistgespielt() liefert „Nebel“ (2 Aufrufe), dauerText() liefert "10:15". Es werden ausschließlich dokumentierte Methoden genutzt; ein direkter Zugriff auf die interne Reihung ist nicht möglich. Die Initialisierung mit liste.getSong(0) ist gleichwertig, sofern die leere Liste gesondert behandelt wird.

Erwartungshorizont zu Aufgabe c)

Die Dokumentation beschreibt die öffentliche Schnittstelle: Konstruktoren, Methodennamen, Parameter mit Datentypen, Rückgabetypen und die Wirkung jeder Methode. Das genügt, um Objekte zu erzeugen und Methoden korrekt aufzurufen. Wie die Klasse intern arbeitet (z. B. mit einer Reihung und einem Zähler), bleibt verborgen und ist für die Nutzung unerheblich (Geheimnisprinzip, Datenkapselung).

Laut Dokumentation sind nur die Positionen 0 bis getAnzahl() - 1 gültig. getSong(getAnzahl()) liefert deshalb null. Der anschließende Aufruf getTitel() richtet sich an keine Instanz und löst eine NullPointerException aus. Die Dokumentation muss also nicht nur gelesen, sondern auch bei den Randfällen beachtet werden: Der Aufrufer ist für die null-Prüfung verantwortlich.

Erwartungshorizont zu Aufgabe d)

Vorteil: Das App-Team könnte bequem mit einer eigenen Schleife über die Reihung laufen.

Nachteile: Die Rückgabe der internen Reihung liefert eine Referenz auf genau das Objekt, das die Liste selbst benutzt (Alias). Das App-Team könnte Einträge überschreiben oder auf null setzen, ohne dass die Wiedergabeliste ihre Anzahl anpasst; getGesamtdauer würde dann falsche Werte liefern oder abbrechen. Die Reihung enthält außerdem die ungenutzten Plätze bis maxAnzahl (null), sodass length nicht die Anzahl der Songs angibt – eine neue Fehlerquelle. Zudem legt sich der Zulieferer auf eine Reihung fest und kann die interne Umsetzung nicht mehr austauschen, ohne die App zu beschädigen.

Mögliches Urteil: Der Wunsch ist abzulehnen, da er die Kapselung aufhebt und der geringe Komfortgewinn die Gefahr inkonsistenter Zustände nicht aufwiegt. Mit getAnzahl und getSong ist jeder Durchlauf bereits möglich. Vertretbar wäre allenfalls eine Methode, die eine neu erzeugte, passend große Kopie der Reihung liefert – Änderungen an der Kopie ließen die Liste unberührt.