Lesbarkeit
- Sprechende Namen:
berechneGesamtpreis,istGueltig,kundenListe - Kleine Methoden, eine Aufgabe; Klassen mit klarer Verantwortung
- Konstanten statt magischer Zahlen:
static final int MAX_VERSUCHE = 3; - Früh zurückgeben statt tief zu verschachteln
- Kommentare erklären das Warum; der Code erklärt das Was
Typische Anfängerfehler
1. Strings mit == vergleichen
public class Fehler1 {
public static void main(String[] args) {
String eingabe = new String("ja");
System.out.println((eingabe == "ja") + " " + eingabe.equals("ja"));
System.out.println("ja".equals(eingabe)); // sicher gegen null auf der linken Seite
}
}false true true
2. NullPointerException
import java.util.*;
public class Fehler2 {
static String name(Map<String, String> m, String k) {
return m.getOrDefault(k, "unbekannt").toUpperCase(); // kein null mehr
}
public static void main(String[] args) {
Map<String, String> m = Map.of("a", "Anna");
System.out.println(name(m, "a") + " " + name(m, "b"));
String s = null;
System.out.println(Objects.requireNonNullElse(s, "Standard"));
System.out.println(Optional.ofNullable(s).map(String::length).orElse(0));
}
}ANNA UNBEKANNT Standard 0
3. Liste während der Iteration ändern
import java.util.*;
public class Fehler3 {
public static void main(String[] args) {
List<Integer> l = new ArrayList<>(List.of(1, 2, 3, 4));
try {
for (Integer x : l) if (x % 2 == 0) l.remove(x);
} catch (ConcurrentModificationException e) {
System.out.println("ConcurrentModificationException");
}
l.removeIf(x -> x % 2 == 0); // richtig
System.out.println(l);
}
}ConcurrentModificationException [1, 3]
4. Integer-Division und Überlauf
public class Fehler4 {
public static void main(String[] args) {
int a = 1, b = 2;
System.out.println(a / b + " " + (double) a / b + " " + (double) (a / b));
long ms = 1000 * 60 * 60 * 24 * 30; // int-Überlauf!
long richtig = 1000L * 60 * 60 * 24 * 30;
System.out.println(ms + " " + richtig);
}
}0 0.5 0.0 -1702967296 2592000000
5. Kommazahlen für Geld
import java.math.*;
public class Fehler5 {
public static void main(String[] args) {
System.out.println(0.1 + 0.2);
BigDecimal summe = new BigDecimal("0.10").add(new BigDecimal("0.20"));
System.out.println(summe + " " + summe.setScale(1, RoundingMode.HALF_UP));
}
}0.30000000000000004 0.30 0.3
6. Ressourcen nicht schließen
Dateien, Streams und Verbindungen immer mit try-with-resources öffnen.
7. Veränderliche Zustände nach außen geben
import java.util.*;
public class Fehler7 {
static class Kurs {
private final List<String> teilnehmer = new ArrayList<>();
void anmelden(String n) { teilnehmer.add(n); }
List<String> getTeilnehmerUnsicher() { return teilnehmer; } // von außen veränderbar
List<String> getTeilnehmer() { return Collections.unmodifiableList(teilnehmer); } // sicher
}
public static void main(String[] args) {
Kurs k = new Kurs();
k.anmelden("Mia");
k.getTeilnehmerUnsicher().add("Eindringling");
System.out.println(k.getTeilnehmer());
try { k.getTeilnehmer().add("x"); } catch (UnsupportedOperationException e) { System.out.println("geschützt"); }
}
}[Mia, Eindringling] gesch?tzt
Moderne Gewohnheiten (Java 17 bis 21)
| Statt ... | besser |
|---|---|
| langer Datenklasse mit Gettern | record |
if (o instanceof X) { X x = (X) o; ... } | if (o instanceof X x) |
switch mit break | switch mit -> |
| String-Konkatenation mit Zeilenumbrüchen | Textblock """ |
Date, Calendar | java.time |
new Thread(...) für viele Aufgaben | Executor oder Virtual Threads |
null-Rückgaben | Optional oder leere Collections |
| Schleifen, die Listen bauen | Streams (wenn lesbarer) |
Namen und Struktur
- Klassen: Substantive (
Rechnung), Methoden: Verben (berechne), Booleans: Fragen (istLeer) - Eine öffentliche Klasse pro Datei; Pakete nach Funktion gliedern
- Keine Abkürzungen wie
kdNr; ausschreiben - Abhängigkeiten über Konstruktoren hereingeben, an Interfaces programmieren
Logging statt println
In echten Anwendungen schreibt man Meldungen mit einem Logger (java.util.logging, SLF4J). Er kennt Stufen (DEBUG, INFO, WARN, ERROR), Zeitstempel und Ziele:
import java.util.logging.*;
public class Log {
private static final Logger LOG = Logger.getLogger(Log.class.getName());
public static void main(String[] args) {
LOG.setUseParentHandlers(false);
Handler h = new StreamHandler(System.out, new SimpleFormatter() {
@Override public String format(LogRecord r) { return r.getLevel() + ": " + r.getMessage() + "\n"; }
});
LOG.addHandler(h);
LOG.info("Anwendung gestartet");
LOG.warning("Speicher wird knapp");
h.flush();
}
}INFO: Anwendung gestartet WARNING: Speicher wird knapp
Leistung
- Zuerst richtig, dann schnell; messen statt raten (JMH, Profiler wie VisualVM)
- Passende Collection wählen (
ArrayListvs.LinkedList,HashMapvs.TreeMap) StringBuilderin Schleifen; keine unnötigen Objekte in engen Schleifen- Datenbank- und Netzwerkzugriffe sind meist der Flaschenhals, nicht die Schleife
Sicherheit
- Eingaben prüfen und nie in SQL-Texte einfügen:
PreparedStatementmit? - Keine Passwörter im Code oder in Logs
- Abhängigkeiten aktuell halten (OWASP Dependency-Check)
- Serialisierung fremder Daten (
ObjectInputStream) vermeiden
Merke
- Strings und Objekte mit
.equals()vergleichen;nullmitOptionaloder Standardwerten entschärfen - Listen nicht während der Iteration ändern (
removeIf) - Geld mit
BigDecimal;long-Rechnungen mitL-Literalen - Unveränderliche Sichten zurückgeben; moderne Sprachmittel nutzen
- Logger statt
System.out, Eingaben prüfen, Abhängigkeiten pflegen
Aufgabe
Suche in einem eigenen Programm nach == bei Strings, nach Rückgaben veränderlicher Listen und nach null-Rückgaben und verbessere sie.