IT-Übungen

Java: Fehler finden

Jeder Fehler wurde mit OpenJDK 21 nachgewiesen (fehlerhafte und korrigierte Version ausgeführt).

Lesestand

  1. Summe eines Arrays ★☆☆ Schleifen · Off-by-one

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert die Summe aller Werte im Array.

    static int sum(int[] values) {
        int total = 0;
        for (int i = 0; i <= values.length; i++) {
            total += values[i];
        }
        return total;
    }
    Lösung anzeigen

    Richtig: Die Schleife läuft mit <= einen Index zu weit

    Gültige Indizes gehen von 0 bis length - 1. Mit <= wird zuletzt values[length] gelesen, das wirft eine ArrayIndexOutOfBoundsException. Klassischer Off-by-one-Fehler, den ein Test mit beliebigem nicht-leerem Array sofort findet.

    Korrigiert:

    static int sum(int[] values) {
        int total = 0;
        for (int i = 0; i < values.length; i++) {
            total += values[i];
        }
        return total;
    }

    Probe: sum(new int[]{1, 2, 3})
    fehlerhaft: ArrayIndexOutOfBoundsException · korrigiert: 6

  2. Rolle prüfen ★☆☆ Strings · Gleichheit

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert true, wenn die Rolle "admin" ist.

    static boolean isAdmin(String role) {
        return role == "admin";
    }
    Lösung anzeigen

    Richtig: == vergleicht Referenzen statt Inhalt

    Mit Literalen im Test funktioniert == zufällig (String-Pool). Kommt der Wert aber aus einer Eingabe, Datenbank oder substring, ist es ein anderes Objekt. Die Form "admin".equals(role) ist zusätzlich null-sicher. Tipp für Tests: Eingaben mit new String(...) erzeugen, dann fällt der Fehler auf.

    Korrigiert:

    static boolean isAdmin(String role) {
        return "admin".equals(role);
    }

    Probe: isAdmin(new String("admin"))
    fehlerhaft: false · korrigiert: true

  3. Durchschnitt ★☆☆ Arithmetik · Datentypen

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert den Durchschnitt der Werte, mit Nachkommastellen.

    static double average(List<Integer> values) {
        int sum = 0;
        for (int v : values) sum += v;
        return sum / values.size();
    }
    Lösung anzeigen

    Richtig: int / int rechnet ganzzahlig, die Nachkommastellen gehen verloren

    sum / values.size() ist eine Ganzzahldivision; erst das Ergebnis wird zu double. Aus 3 / 2 wird 1, dann 1.0. Mindestens ein Operand muss vor der Division double sein. Gute Testdaten: Werte mit ungeradem Durchschnitt.

    Korrigiert:

    static double average(List<Integer> values) {
        int sum = 0;
        for (int v : values) sum += v;
        return (double) sum / values.size();
    }

    Probe: average(List.of(1, 2))
    fehlerhaft: 1.0 · korrigiert: 1.5

  4. Negative Werte entfernen ★★☆ Collections · Iteration

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Entfernt alle negativen Zahlen aus der Liste.

    static void removeNegatives(List<Integer> values) {
        for (Integer v : values) {
            if (v < 0) values.remove(v);
        }
    }
    Lösung anzeigen

    Richtig: Die Liste wird während der for-each-Iteration verändert

    Eine for-each-Schleife nutzt intern einen Iterator. Wird die Liste nebenher verändert, wirft der Iterator beim nächsten Schritt eine ConcurrentModificationException. Tückisch: Steht der negative Wert an vorletzter Stelle, tritt der Fehler nicht auf, er bleibt dann einfach in der Liste. Lösung: removeIf oder explizit Iterator.remove().

    Korrigiert:

    static void removeNegatives(List<Integer> values) {
        values.removeIf(v -> v < 0);
    }

    Probe: removeNegatives(new ArrayList<>(List.of(1, -2, 3, -4, 5)))
    fehlerhaft: ConcurrentModificationException · korrigiert: [1, 3, 5]

  5. Begrüßung ★☆☆ null · Validierung

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert "Hallo <Name>" oder "Hallo Gast", wenn kein Name vorhanden ist (null oder leer).

    static String greet(String name) {
        if (name.isEmpty()) return "Hallo Gast";
        return "Hallo " + name;
    }
    Lösung anzeigen

    Richtig: null wird nicht abgefangen, isEmpty() wirft eine NullPointerException

    Die Spezifikation nennt null ausdrücklich, der Code behandelt nur den leeren String. Typischer Fall, den man im Test mit Äquivalenzklassen findet: gültiger Name, leerer String, null. (Der Hinweis auf " " ist berechtigt, aber nicht spezifiziert.)

    Korrigiert:

    static String greet(String name) {
        if (name == null || name.isEmpty()) return "Hallo Gast";
        return "Hallo " + name;
    }

    Probe: greet(null)
    fehlerhaft: NullPointerException · korrigiert: Hallo Gast

  6. Volljährigkeit ★☆☆ Grenzwerte

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert true, wenn eine Person volljährig ist (ab 18 Jahren).

    static boolean isAdult(int age) {
        return age > 18;
    }
    Lösung anzeigen

    Richtig: Die Grenze ist falsch: 18-Jährige gelten nicht als volljährig

    "Ab 18" schließt 18 ein, also >=. Genau dafür gibt es die Grenzwertanalyse: 17, 18 und 19 testen. Ein Test mit 30 oder 10 hätte den Fehler nie gefunden. (Negative Werte zu prüfen wäre sinnvoll, ist aber nicht gefordert.)

    Korrigiert:

    static boolean isAdult(int age) {
        return age >= 18;
    }

    Probe: isAdult(18)
    fehlerhaft: false · korrigiert: true

  7. Letztes Zeichen ★★☆ Strings · Off-by-one

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert das letzte Zeichen als String.

    static String lastChar(String s) {
        return s.substring(s.length() - 1, s.length() - 1);
    }
    Lösung anzeigen

    Richtig: Der End-Index von substring ist exklusiv, so entsteht ein leerer String

    substring(start, end) liefert die Zeichen von start bis end - 1. Sind start und end gleich, ist das Ergebnis leer, ohne jede Exception. Stille Fehler wie dieser fallen nur auf, wenn der Test das Ergebnis wirklich prüft und nicht nur "kein Absturz".

    Korrigiert:

    static String lastChar(String s) {
        return s.substring(s.length() - 1);
    }

    Probe: "[" + lastChar("Java") + "]"
    fehlerhaft: [] · korrigiert: [a]

  8. Mitte berechnen (Binärsuche) ★★★ Arithmetik · Überlauf

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert den Index in der Mitte zwischen low und high (beide nicht negativ).

    static int middle(int low, int high) {
        return (low + high) / 2;
    }
    Lösung anzeigen

    Richtig: low + high kann über Integer.MAX_VALUE laufen

    Bei großen Werten läuft die Summe über und wird negativ. Genau dieser Fehler steckte jahrelang in Arrays.binarySearch des JDK (2006 entdeckt). Mit low + (high - low) / 2 kann nichts überlaufen. Ein Test mit Werten nahe MAX_VALUE deckt es auf.

    Korrigiert:

    static int middle(int low, int high) {
        return low + (high - low) / 2;
    }

    Probe: middle(2_000_000_000, 2_100_000_000)
    fehlerhaft: -97483648 · korrigiert: 2050000000

  9. Standardrollen ★★☆ Collections · Unveränderlichkeit

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert eine Liste mit den Rollen "user" und "guest".

    static List<String> defaultRoles() {
        List<String> roles = List.of("user");
        roles.add("guest");
        return roles;
    }
    Lösung anzeigen

    Richtig: List.of liefert eine unveränderliche Liste, add wirft eine Exception

    List.of (wie auch Arrays.asList beim Hinzufügen) erzeugt eine Liste fester Größe. add wirft eine UnsupportedOperationException, aber erst zur Laufzeit, der Compiler merkt nichts.

    Korrigiert:

    static List<String> defaultRoles() {
        List<String> roles = new ArrayList<>(List.of("user"));
        roles.add("guest");
        return roles;
    }

    Probe: defaultRoles()
    fehlerhaft: UnsupportedOperationException · korrigiert: [user, guest]

  10. Eingabe normalisieren ★☆☆ Strings · Unveränderlichkeit

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Entfernt Leerzeichen am Rand und wandelt in Kleinbuchstaben um.

    static String normalize(String input) {
        input.trim();
        return input.toLowerCase();
    }
    Lösung anzeigen

    Richtig: Das Ergebnis von trim() wird verworfen

    Strings sind unveränderlich. trim() verändert input nicht, sondern liefert einen neuen String, der hier ignoriert wird. Viele IDEs warnen davor. (Der Locale-Hinweis ist ein echtes Thema, z. B. beim türkischen I, aber nicht der Fehler hier.)

    Korrigiert:

    static String normalize(String input) {
        input = input.trim();
        return input.toLowerCase();
    }

    Probe: "[" + normalize(" Hallo ") + "]"
    fehlerhaft: [ hallo ] · korrigiert: [hallo]

  11. Preise vergleichen ★★★ BigDecimal · Gleichheit

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert true, wenn zwei Preise denselben Betrag haben.

    static boolean samePrice(BigDecimal a, BigDecimal b) {
        return a.equals(b);
    }
    Lösung anzeigen

    Richtig: BigDecimal.equals vergleicht auch die Anzahl der Nachkommastellen

    2.0 und 2.00 haben einen unterschiedlichen scale, equals liefert deshalb false. Für den reinen Zahlenwert gilt compareTo(...) == 0. Das betrifft auch HashSet und HashMap mit BigDecimal-Schlüsseln.

    Korrigiert:

    static boolean samePrice(BigDecimal a, BigDecimal b) {
        return a.compareTo(b) == 0;
    }

    Probe: samePrice(new BigDecimal("2.0"), new BigDecimal("2.00"))
    fehlerhaft: false · korrigiert: true

  12. Datum formatieren ★★★ Datum · Formatierung

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Formatiert ein Datum als TT.MM.JJJJ.

    static String formatDate(LocalDate date) {
        DateTimeFormatter f = DateTimeFormatter.ofPattern("dd.MM.YYYY", Locale.GERMANY);
        return f.format(date);
    }
    Lösung anzeigen

    Richtig: YYYY ist das Jahr der Kalenderwoche, nicht das Kalenderjahr

    YYYY ist das wochenbasierte Jahr. Der 30.12.2024 liegt in Kalenderwoche 1 von 2025 und wird als 30.12.2025 ausgegeben. Der Fehler tritt nur an wenigen Tagen rund um den Jahreswechsel auf, fällt also monatelang nicht auf. Testdaten deshalb immer auch um Silvester legen.

    Korrigiert:

    static String formatDate(LocalDate date) {
        DateTimeFormatter f = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale.GERMANY);
        return f.format(date);
    }

    Probe: formatDate(LocalDate.of(2024, 12, 30))
    fehlerhaft: 30.12.2025 · korrigiert: 30.12.2024

  13. Quersumme ★★☆ Datentypen · char

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert die Quersumme einer Ziffernfolge, z. B. "123" → 6.

    static int digitSum(String number) {
        int sum = 0;
        for (char c : number.toCharArray()) {
            sum += c;
        }
        return sum;
    }
    Lösung anzeigen

    Richtig: Es wird der Zeichencode addiert, nicht der Ziffernwert

    '1' hat den Code 49, '2' den Code 50 usw. Addiert wird also 49 + 50 + 51 = 150. Mit c - '0' (oder Character.getNumericValue) erhält man den Ziffernwert.

    Korrigiert:

    static int digitSum(String number) {
        int sum = 0;
        for (char c : number.toCharArray()) {
            sum += c - '0';
        }
        return sum;
    }

    Probe: digitSum("123")
    fehlerhaft: 150 · korrigiert: 6

  14. Liste hat Einträge ★★☆ Operatoren · null

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert true, wenn die Liste existiert und nicht leer ist, sonst false.

    static boolean hasItems(List<String> list) {
        return list != null & !list.isEmpty();
    }
    Lösung anzeigen

    Richtig: & wertet auch die rechte Seite aus, bei null gibt es eine NullPointerException

    Nur && bricht ab, wenn links false steht. Das einfache & wertet beide Seiten aus, und list.isEmpty() auf null knallt. Ein Zeichen zu wenig, der Code sieht fast richtig aus.

    Korrigiert:

    static boolean hasItems(List<String> list) {
        return list != null && !list.isEmpty();
    }

    Probe: hasItems(null)
    fehlerhaft: NullPointerException · korrigiert: false

  15. Ticketnummern ★★☆ static · Objekte

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Jedes neue Ticket erhält eine eigene, fortlaufende Nummer (1, 2, 3 …).

    static class Ticket {
        private static int nextNumber = 1;
        private static int number;
    
        Ticket() {
            number = nextNumber++;
        }
    
        int getNumber() { return number; }
    }
    Lösung anzeigen

    Richtig: number ist static und wird von allen Tickets geteilt

    nextNumber gehört richtigerweise der Klasse. number muss aber pro Objekt existieren. Weil es static ist, überschreibt jedes neue Ticket die Nummer aller anderen. Ein Test mit nur einem Ticket findet das nicht, man braucht mindestens zwei.

    Korrigiert:

    static class Ticket {
        private static int nextNumber = 1;
        private int number;
    
        Ticket() {
            number = nextNumber++;
        }
    
        int getNumber() { return number; }
    }

    Probe: Arrays.stream(new Ticket[]{new Ticket(), new Ticket()}).map(Ticket::getNumber).toList()
    fehlerhaft: [2, 2] · korrigiert: [1, 2]

  16. Kunden-IDs vergleichen ★★★ Autoboxing · Gleichheit

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert true, wenn beide IDs gleich sind.

    static boolean sameCustomer(Long idA, Long idB) {
        return idA == idB;
    }
    Lösung anzeigen

    Richtig: == vergleicht bei Long die Objekt-Referenzen

    Long cached nur Werte von -128 bis 127. In Tests mit IDs wie 1, 2, 3 läuft alles grün, in Produktion mit echten IDs nicht mehr. Ein Paradebeispiel dafür, warum Testdaten realistisch groß sein sollten. (Objects.equals wäre zusätzlich null-sicher.)

    Korrigiert:

    static boolean sameCustomer(Long idA, Long idB) {
        return idA.equals(idB);
    }

    Probe: sameCustomer(1000L, 1000L)
    fehlerhaft: false · korrigiert: true

  17. Gerade Zahlen entfernen ★★★ Collections · Schleifen

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Entfernt alle geraden Zahlen aus der Liste.

    static void removeEven(List<Integer> values) {
        for (int i = 0; i < values.size(); i++) {
            if (values.get(i) % 2 == 0) {
                values.remove(i);
            }
        }
    }
    Lösung anzeigen

    Richtig: Nach dem Entfernen rücken die Elemente nach, das nächste wird übersprungen

    Wird bei Index 0 die 2 entfernt, rutscht die 4 auf Index 0, die Schleife geht aber zu Index 1 weiter. So bleibt bei zwei aufeinanderfolgenden geraden Zahlen jede zweite stehen. Rückwärts laufen oder removeIf behebt es. Testdaten: mehrere passende Werte direkt hintereinander.

    Korrigiert:

    static void removeEven(List<Integer> values) {
        for (int i = values.size() - 1; i >= 0; i--) {
            if (values.get(i) % 2 == 0) {
                values.remove(i);
            }
        }
    }

    Probe: removeEven(new ArrayList<>(List.of(2, 4, 6, 7)))
    fehlerhaft: [4, 7] · korrigiert: [7]

  18. Dateiendung ★★☆ Strings · Regex

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert die Dateiendung, z. B. "bericht.pdf" → "pdf".

    static String fileExtension(String fileName) {
        String[] parts = fileName.split(".");
        return parts[parts.length - 1];
    }
    Lösung anzeigen

    Richtig: split erwartet einen regulären Ausdruck, "." passt auf jedes Zeichen

    Der Punkt steht im Regex für "beliebiges Zeichen". Jedes Zeichen ist ein Trenner, übrig bleiben nur leere Teile, und die entfernt split am Ende. Das Array ist leer, parts[-1] wirft eine ArrayIndexOutOfBoundsException. Richtig: "\\." oder Pattern.quote(".").

    Korrigiert:

    static String fileExtension(String fileName) {
        String[] parts = fileName.split("\\.");
        return parts[parts.length - 1];
    }

    Probe: fileExtension("bericht.pdf")
    fehlerhaft: ArrayIndexOutOfBoundsException · korrigiert: pdf

  19. Index suchen ★☆☆ Kontrollfluss · Schleifen

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Liefert die Position von target im Array oder -1, wenn es nicht vorkommt.

    static int indexOf(int[] values, int target) {
        for (int i = 0; i < values.length; i++) {
            if (values[i] == target) {
                return i;
            } else {
                return -1;
            }
        }
        return -1;
    }
    Lösung anzeigen

    Richtig: Der else-Zweig beendet die Suche schon nach dem ersten Element

    Passt das erste Element nicht, kommt sofort -1 zurück. Die Methode prüft also nur values[0]. Ein Test, der das gesuchte Element an Position 0 legt, ist grün. Erst ein Treffer weiter hinten zeigt den Fehler.

    Korrigiert:

    static int indexOf(int[] values, int target) {
        for (int i = 0; i < values.length; i++) {
            if (values[i] == target) {
                return i;
            }
        }
        return -1;
    }

    Probe: indexOf(new int[]{5, 7, 9}, 9)
    fehlerhaft: -1 · korrigiert: 2

  20. Aufsteigend sortieren ★★★ Comparator · Überlauf

    Die Methode erfüllt ihre Spezifikation nicht. Wo liegt der Fehler?

    Spezifikation: Sortiert die Liste aufsteigend.

    static void sortAscending(List<Integer> values) {
        values.sort((a, b) -> a - b);
    }
    Lösung anzeigen

    Richtig: a - b kann überlaufen und liefert dann das falsche Vorzeichen

    Bei normalen Zahlen funktioniert der Subtraktions-Trick. Aber 1 - Integer.MIN_VALUE läuft über und wird negativ, also gilt MIN_VALUE plötzlich als größte Zahl und landet am Ende. Integer.compare vergleicht ohne zu rechnen. Ein schönes Beispiel für Extremwerte in Testdaten.

    Korrigiert:

    static void sortAscending(List<Integer> values) {
        values.sort(Integer::compare);
    }

    Probe: sortAscending(new ArrayList<>(List.of(1, Integer.MIN_VALUE, 0)))
    fehlerhaft: [0, 1, -2147483648] · korrigiert: [-2147483648, 0, 1]