Adressbuch mit Schul-E-Mail-Adressen
AFB I–IIEin 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";
| Methode | Beschreibung |
|---|---|
+ length(): Ganzzahl | liefert die Anzahl der Zeichen |
+ charAt(index: Ganzzahl): Zeichen | liefert das Zeichen an der Position index; das erste Zeichen hat den Index 0 |
+ indexOf(s: Zeichenkette): Ganzzahl | liefert den Index des ersten Vorkommens von s, −1, falls s nicht vorkommt |
+ substring(beginn: Ganzzahl, ende: Ganzzahl): Zeichenkette | liefert die Teilzeichenkette von Index beginn bis ausschließlich Index ende |
+ substring(beginn: Ganzzahl): Zeichenkette | liefert die Teilzeichenkette von Index beginn bis zum Ende |
+ toUpperCase(): Zeichenkette | liefert eine neue Zeichenkette in Großbuchstaben; das Original bleibt unverändert |
+ equals(s: Zeichenkette): Wahrheitswert | liefert true, wenn beide Zeichenketten dieselben Zeichen in derselben Reihenfolge enthalten |
+ verketten.- Wenden Sie die dokumentierten Methoden auf
adressean: Geben Sie die Ergebnisse vonadresse.length(),adresse.indexOf("@"),adresse.charAt(5),adresse.substring(5, 9),adresse.substring(adresse.indexOf("@") + 1).toUpperCase()undadresse.indexOf("x")an. - Ü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); } - Implementieren Sie in der Klasse
Kontaktdie MethodengetDomain(): Zeichenkette, die den Teil nach dem@liefert (leere Zeichenkette, falls kein@vorkommt), undgetAnzeigename(): Zeichenkette, die für die Beispieladresse"Lena Hoffmann"liefert.
Hinweise
Hinweis zu Aufgabe a)
charAt auf den Rückgabetyp und bei substring auf das ausschließliche Ende.Hinweis zu Aufgabe b)
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)
. und @. Für den großen Anfangsbuchstaben: das erste Zeichen als Teilzeichenkette groß schreiben und den Rest anhängen.Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
| Aufruf | Ergebnis |
|---|---|
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.
Playlist einer Musik-Streaming-App
AFB II–IIIEin 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.
| Methode | Beschreibung |
|---|---|
| Klasse Song | |
c Song(titel: Zeichenkette, interpret: Zeichenkette, dauer: Ganzzahl) | erzeugt einen Song; dauer in Sekunden, Aufrufzähler 0 |
+ getTitel(): Zeichenkette, + getInterpret(): Zeichenkette, + getDauer(): Ganzzahl | liefern Titel, Interpret bzw. Dauer in Sekunden |
+ getAufrufe(): Ganzzahl | liefert, 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): Wahrheitswert | hä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(): Ganzzahl | liefert die Anzahl der Einträge |
+ getSong(i: Ganzzahl): Song | liefert den Eintrag an Position i (erster Eintrag: 0); für i < 0 oder i ≥ getAnzahl() wird null geliefert |
+ getGesamtdauer(): Ganzzahl | liefert die Summe der Dauern aller Einträge in Sekunden |
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;
}
}
Musikapp soll erweitert werden.- Ermitteln Sie allein mithilfe der Dokumentation die Ausgaben (1) bis (4) des Testprogramms.
- Erweitern Sie die Klasse
Musikappum die Methodenmeistgespielt(): Song, die den Song mit den meisten Aufrufen liefert (bei leerer Listenull), unddauerText(): Zeichenkette, die die Gesamtdauer im Format Minuten:Sekunden liefert, z. B."10:15"für 615 Sekunden und"10:05"für 605 Sekunden. - Erklären Sie, warum das Team die Klasse
Wiedergabelistenutzen kann, ohne ihren Quelltext zu kennen, und warum der Aufrufliste.getSong(liste.getAnzahl()).getTitel()trotzdem zum Programmabbruch führt. - Bewerten Sie den Wunsch des App-Teams, der Zulieferer möge zusätzlich eine Methode
getSongs(): Reihung vom Typ Songanbieten, die die intern verwendete Reihung direkt zurückgibt.
Hinweise
Hinweis zu Aufgabe a)
s1? Jeder Eintrag wird in der Schleife einmal abgespielt.Hinweis zu Aufgabe b)
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)
getSong genau: Welche Positionen sind gültig?Hinweis zu Aufgabe d)
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.
