SOFT | Grundlagen und OOP (1-6)
Datum: September 2026
Lesedauer: 25 Minuten
Überarbeitet mit Claude Sonnet 5
Referenzmodul: Softwareprogrammierung (SOFT) - Dipl. Informatiker HFQuellen:
- Grundkurs Datenbankentwurf - Eine beispielorientierte Einführung für Studierende und Praktiker (4. Auflage)
Helmut Jarosch, Springer Vieweg - Grundkurs Datenbankentwicklung - Von der Anforderungsanalyse zur komplexen Datenbankanfrage (5. Auflage)
Stephan Kleuker, Springer Vieweg - Grundkurs JAVA - Von den Grundlagen bis zu Datenbank- und Netzanwendungen (12. Auflage)
Dietmar Abts, Springer Vieweg - WISS Schulen für Wirtschaft Informatik Immobilien Unterricht Juni - September 2026
Block 01: Grundlagen Software Engineering
Softwareentwicklung als Prozess
Software entsteht in wiederkehrenden Arbeitsschritten. Die Grenzen sind fliessend und werden in agilen Projekten regelmässig überprüft:
- Problem, Zielgruppen und Anforderungen klären
- Lösungsarchitektur und Entwurf festlegen
- Implementieren und lokal prüfen
- Komponenten integrieren und systematisch testen
- Veröffentlichen und betreiben
- Fehler beheben, beobachten und weiterentwickeln
Eine Spezifikation beschreibt dabei das erwartete Verhalten, Randbedingungen und Qualitätsziele. Sie ist überprüfbar formuliert und dient als gemeinsame Referenz.
Scrum im Überblick
Scrum organisiert komplexe Produktentwicklung in kurzen Sprints. Der Product Owner verantwortet den Produktwert und priorisiert das Backlog. Der Scrum Master unterstützt die Zusammenarbeit und beseitigt Prozesshindernisse. Die Entwicklerinnen und Entwickler erstellen gemeinsam ein fertiges Inkrement. Fachpersonen wie Product Manager, Requirements Engineers, Business Analysts und Architektinnen liefern je nach Kontext zusätzliche Perspektiven.
Software und Programmiersprachen
Typische Softwareklassen sind:
- Anwendungssoftware: etwa ERP-, CRM- oder Fachanwendungen
- Middleware: Laufzeitumgebungen, Messaging und Datenbankzugriffe
- Betriebssysteme: Desktop-, Server- und mobile Plattformen
- Firmware: hardwarenahe Programme wie UEFI oder Gerätecode
Generationen der Programmiersprachen
| Generation | Sprachebene | Beispiele |
|---|---|---|
| 1 | Maschinensprache | Binärcode |
| 2 | Assemblersprache | Assembler |
| 3 | höhere, allgemeine Programmiersprachen | C, Java, Python |
| 4 | problemorientierte beziehungsweise deklarative Sprachen | SQL, MATLAB |
| 5 | KI-orientierte und deklarative Sprachen | Prolog, Lisp |
Die Einteilung ist historisch und nicht vollständig trennscharf. Eine zweite, für die Ausführung wichtige Sicht unterscheidet Compiler-, Intermediate-Code- und Skriptsprachen:
| Sprachtyp | Übersetzung und Ausführung | Beispiele |
|---|---|---|
| Compilersprache | Quellcode wird in plattformspezifischen Maschinencode übersetzt | C, C++, Swift |
| Sprache mit Intermediate Code | Compiler erzeugt Zwischencode; eine Laufzeitumgebung führt ihn aus | Java, C#, F# |
| Skript- oder Interpretersprache | Laufzeitumgebung verarbeitet den Quelltext beziehungsweise eine Zwischenform | Python, JavaScript, PHP |
Java verbindet dabei Quelltext, Bytecode, JAR-Archiv und JVM: javac erzeugt .class-Dateien, ein Archiv bündelt sie, und die JVM führt den Bytecode auf dem jeweiligen Betriebssystem aus. Deshalb muss für jedes Zielsystem eine passende JVM vorhanden sein.
Paradigmen als Denkmodelle
Ein Paradigma beschreibt, wie ein Problem in Code ausgedrückt wird. Die Kategorien überschneiden sich:
| Paradigma | Typische Frage | Beispiel |
|---|---|---|
| imperativ | Welche Schritte ändern den Zustand? | Schleife in Java |
| deklarativ | Welches Ergebnis wird benötigt? | SQL-Abfrage |
| prozedural | Welche Funktionen zerlegen den Ablauf? | C-Funktion |
| objektorientiert | Welche Objekte besitzen Zustand und Verhalten? | Java-Klasse |
| funktional | Welche Werte werden aus anderen Werten berechnet? | Stream-Pipeline |
| logisch | Welche Fakten und Regeln führen zur Lösung? | Prolog-Regel |
| generisch | Wie bleibt ein Algorithmus typsicher wiederverwendbar? | List<T> |
Strukturierte Programmierung verwendet Sequenz, Auswahl und Wiederholung. Modulare Programmierung trennt ein System in überschaubare Einheiten. AOP kapselt Querschnittsthemen wie Logging, generative Verfahren erzeugen Code aus Modellen oder Vorlagen.
Programmiersprachen können nach ihrem Paradigma beschrieben werden. Imperative und prozedurale Sprachen formulieren Abläufe. Deklarative Sprachen beschreiben ein gewünschtes Ergebnis. Objektorientierte Programme bündeln Zustand und Verhalten in Objekten, funktionale Programme arbeiten bevorzugt mit Funktionen und unveränderlichen Werten. Generische, modulare, aspektorientierte und generative Ansätze ergänzen diese Sichtweisen. Java verbindet insbesondere imperative, objektorientierte, generische und funktionale Elemente.
Algorithmisches Denken
Ein Algorithmus ist ein endlicher, ausführbarer und eindeutig beschriebener Lösungsweg. Er verarbeitet definierte Eingaben und erzeugt definierte Ausgaben. Für die Beurteilung sind zusätzlich Terminierung, Effektivität, Schrittfolge und die Übertragbarkeit auf eine ganze Problemklasse wichtig.
Informationsebenen und Zeichen
| Ebene | Frage | Beispiel |
|---|---|---|
| Bedeutung | Was soll die Information aussagen? | eine E-Mail-Adresse |
| Syntax | Wie ist sie strukturiert? | JSON oder CSV |
| Codierung | Wie wird sie als Bitfolge dargestellt? | UTF-8 |
ASCII ordnet 128 Zeichen siebenstelligen Bitmustern zu. Unicode definiert Zeichen unabhängig von einer bestimmten Sprache; UTF-8 codiert sie mit unterschiedlich vielen Bytes. Zeichensatz und Zeichencodierung sind daher nicht dasselbe.
Softwarearchitektur
Architektur beschreibt die wesentlichen Bausteine eines Systems, ihre Verantwortlichkeiten und ihre Beziehungen. Eine gute Architektur macht Entscheidungen sichtbar, begrenzt Abhängigkeiten und unterstützt Qualitätsziele wie Änderbarkeit, Sicherheit und Verfügbarkeit.
Ein Softwarearchitekt klärt Anforderungen, entwirft Strukturen, bewertet Risiken, kommuniziert Leitentscheide und begleitet die Umsetzung. Komponenten sind austauschbare Einheiten mit klarer Schnittstelle. Das Prinzip Separation of Concerns trennt Verantwortlichkeiten, sodass eine fachliche Änderung möglichst wenige Bereiche berührt.
Betriebsmodelle
| Modell | Langform | Beispiele |
|---|---|---|
| IaaS | Infrastructure as a Service | vSphere, OpenStack |
| PaaS | Platform as a Service | Windows Azure |
| SaaS | Software as a Service | Gmail, Salesforce, SAP |
| CaaS | Container as a Service | Docker, Kubernetes |
| FaaS | Function as a Service | Azure Functions |
Layer und Tier
Ein Layer ist eine logische Verantwortlichkeit innerhalb der Software. Ein Tier ist eine physisch getrennte Ausführungseinheit. Eine Anwendung kann daher drei Layer besitzen, aber auf zwei Tiers laufen.
Bei einer 2-Tier-Lösung kommuniziert der Client direkt mit einem Server. Eine 3-Tier-Lösung trennt Benutzeroberfläche, Anwendungslogik und Datenhaltung. N-Tier erweitert diese Aufteilung beispielsweise um Webserver, Cache oder spezialisierte Dienste.
Typische Tier-Varianten
Beim Fat Client liegen Präsentation und ein grosser Teil der Logik auf dem Client. Der Server stellt hauptsächlich Daten oder zentrale Dienste bereit. Das kann die Bedienung beschleunigen, erschwert aber Verteilung, Updates und Schutz von Geschäftslogik.
Bei einer Webarchitektur enthält der Browser vor allem Darstellung und Interaktion. Fachlogik und Datenzugriff liegen überwiegend auf Servern. Das vereinfacht zentrale Aktualisierungen, macht aber Netzwerk, Serverbetrieb und Verfügbarkeit wichtig.
Weitere Architekturstile sind ereignisgesteuerte Systeme, Microkernel, Microservices, Space-based Architecture und Serverless. Die Auswahl folgt Anforderungen, nicht einem Modewort. Verteilung kann Ressourcen teilen und Last verteilen, erhöht aber Kommunikations-, Betriebs- und Ausfallkomplexität.
Verteilte Systeme
Verteilung betrifft drei Fragen: Wie kommunizieren Teile miteinander, wie laufen sie parallel, und wie werden gemeinsame Daten synchronisiert? RPC und Middleware verbergen Teile der Netzwerkkommunikation. Synchronität bedeutet, dass der Aufrufer wartet; asynchrone Kommunikation arbeitet mit Nachrichten, Callbacks oder Polling. Dauerhafte Speicherung muss zusätzlich gegen Ausfälle und konkurrierende Zugriffe geschützt werden.
Die wichtigsten Architekturentscheidungen sind deshalb ein Ausgleich zwischen Einfachheit, Performance, Skalierbarkeit, Verfügbarkeit und Wartbarkeit. Ein lokaler Prozess ist oft schneller und leichter zu testen. Gemeinsame Server, Daten und Funktionen rechtfertigen Verteilung nur, wenn ihr Nutzen die zusätzliche Komplexität überwiegt.
Block 02: Java-Grundlagen
Herkunft und Plattform
Java entstand bei Sun Microsystems aus dem Projekt Oak und wurde 1995 veröffentlicht. Oracle übernahm Sun 2010. James Gosling prägte die Sprache wesentlich. Java verfolgt Objektorientierung, Portabilität und eine umfangreiche Standardbibliothek.
Der Quelltext wird in Bytecode übersetzt. Eine Java Virtual Machine (JVM) führt diesen Bytecode auf der jeweiligen Plattform aus. Dadurch bleibt derselbe Build auf unterschiedlichen Betriebssystemen nutzbar, sofern eine passende JVM vorhanden ist.
Java-Plattformen
- Java SE: Sprache, Standardbibliothek, Desktop- und Konsolenprogramme
- Jakarta EE (früher Java EE): serverseitige Anwendungen und Unternehmensdienste
- Java ME: reduzierte Laufzeit für eingebettete Geräte
Der Compiler übersetzt jede .java-Datei in eine .class-Datei. Das Java-Archivwerkzeug jar bündelt Klassen, Ressourcen und Metadaten zu einem JAR. Beim Deployment wird dieses Archiv auf ein Zielsystem kopiert; die dortige JVM lädt und verifiziert den Bytecode und führt ihn aus.
Andere JVM-Sprachen wie Scala oder Groovy können ebenfalls Bytecode erzeugen. Die JVM bildet damit eine gemeinsame Laufzeitumgebung für verschiedene Quellsprachen. Die Portabilität gilt für den Bytecode; betriebssystemabhängige Bibliotheken oder native Aufrufe können sie einschränken.
Namensregeln und Programmstart
Pakete werden kleingeschrieben. Klassen, Interfaces und Enums beginnen mit einem Grossbuchstaben. Methoden und Variablen beginnen mit einem Kleinbuchstaben und verwenden CamelCase. Konstanten stehen meist in UPPER_SNAKE_CASE.
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}Typen, Variablen und Literale
Primitive Typen speichern Werte direkt: boolean, char, byte, short, int, long, float und double. String ist dagegen ein unveränderliches Referenzobjekt. Für Geldbeträge ist BigDecimal meist geeigneter als double.
| Typ | Speicher | Typischer Wertebereich oder Inhalt |
|---|---|---|
boolean | nicht festgelegt | true oder false |
byte | 8 Bit | -128 bis 127 |
short | 16 Bit | -32’768 bis 32’767 |
int | 32 Bit | -2^31 bis 2^31 - 1 |
long | 64 Bit | -2^63 bis 2^63 - 1 |
float | 32 Bit | Gleitkommazahl mit einfacher Genauigkeit |
double | 64 Bit | Gleitkommazahl mit doppelter Genauigkeit |
char | 16 Bit | UTF-16-Codeeinheit |
Ganzzahlliterale sind standardmässig int; ein Suffix L kennzeichnet long, F kennzeichnet float. Gleitkommazahlen sind standardmässig double. Ein char verwendet einfache, ein String doppelte Anführungszeichen.
int count = 12;
long population = 9_000_000L;
double ratio = 0.75;
boolean active = true;
char initial = 'L';
String label = "Example";Zahlensysteme und Umrechnungen
| Zahlensystem | Basis | erlaubte Ziffern | Java-Schreibweise |
|---|---|---|---|
| Dezimal | 10 | 0 bis 9 | 42 |
| Binär | 2 | 0, 1 | 0b101010 |
| Oktal | 8 | 0 bis 7 | 052 |
| Hexadezimal | 16 | 0 bis 9, A bis F | 0x2A |
Stellenwertprinzip
Der Wert einer Zahl ist die Summe aller Ziffern multipliziert mit ihrer Basis und der jeweiligen Potenz. Die Position ganz rechts hat den Exponenten 0.
1068 = 1 * 10^3 + 0 * 10^2 + 6 * 10^1 + 8 * 10^0
1011_2 = 1 * 2^3 + 0 * 2^2 + 1 * 2^1 + 1 * 2^0 = 11_10
142_8 = 1 * 8^2 + 4 * 8^1 + 2 * 8^0 = 98_10
3A_16 = 3 * 16^1 + 10 * 16^0 = 58_10Umrechnungsregeln
- Von einer Basis nach Dezimal: Stellenwerte berechnen und addieren.
- Von Dezimal nach einer Basis: wiederholt durch die Zielbasis teilen; Reste von unten nach oben lesen.
- Binär nach Hexadezimal: von rechts in Vierergruppen teilen und jede Gruppe als Hexadeziffer lesen.
- Binär nach Oktal: von rechts in Dreiergruppen teilen und jede Gruppe als Oktalziffer lesen.
AB10_16 = 1010 1011 0001 0000_2
732_8 = 111 011 010_2
22_10 = 10110_2Mit $n$ Bit lassen sich $2^n$ verschiedene Muster darstellen. Bei vier Bit reichen die Muster von 0000 bis 1111, also von 0 bis 15 ohne Vorzeichen.
Bitoperationen
int value = 8; // 0000 1000
int left = value << 1; // 0001 0000 = 16
int right = value >> 1; // 0000 0100 = 4Eine Linksverschiebung entspricht bei passenden positiven Ganzzahlen einer Multiplikation mit 2, eine Rechtsverschiebung einer Division durch 2. Überläufe, Vorzeichen und der Unterschied zwischen >> und >>> müssen dabei berücksichtigt werden.
Seit Java 10 kann var bei lokalen Variablen verwendet werden. Der Compiler leitet den statischen Typ aus dem Initialwert ab; eine dynamische Typisierung entsteht dadurch nicht.
var name = "Lina"; // String
var items = 3; // int
var student = new Student("Lina");Ein Cast fordert eine explizite Umwandlung. Bei primitiven Typen kann dabei Genauigkeit verloren gehen. Bei Referenzen prüft der Cast die tatsächliche Objektart zur Laufzeit.
double price = 27.95;
int roundedDown = (int) price;
long exactCount = 20;
int countFromLong = (int) exactCount;Zeichen und Escape-Sequenzen
| Sequenz | Bedeutung | Beispielwirkung |
|---|---|---|
\b | Rückschritt | Zeichen vor dem Cursor löschen |
\t | Tabulator | Spalten ausrichten |
\n | neue Zeile | Line Feed |
\f | Seitenvorschub | Form Feed |
\r | Wagenrücklauf | Carriage Return |
\" | doppeltes Anführungszeichen | " im String |
\' | einfaches Anführungszeichen | ' im Zeichenliteral |
\\ | Backslash | \ im String |
\ddd | Zeichen über oktalen Wert | bis zu drei Oktalziffern |
\udddd | Unicode-Zeichen | vier Hexadezimalziffern |
String line = "Name:\tMira\nStatus:\tactive";
char copyright = '\u00A9';Weitere Codierungen
Neben ASCII und Unicode werden in Spezialgebieten weitere Darstellungen verwendet. EBCDIC ist eine historische IBM-Codierung. Ein Quaternärcode nutzt vier Zustände, beispielsweise die DNA-Basen A, C, G und T; er ist kein allgemeiner Ersatz für UTF-8. Bei Datenübertragung müssen Sender und Empfänger dieselbe Codierung vereinbaren.
Strings
Strings werden inhaltlich mit equals verglichen, nicht mit ==. null, der leere String und ein String mit Leerzeichen beschreiben drei verschiedene Zustände.
String text = "some content";
String part = text.substring(5);
boolean matching = text.equals("some content");
boolean present = text.contains("content");
int length = text.length();
String formatted = String.format("%s: %d", "Items", 3);StringBuilder eignet sich für viele Änderungen an einer Zeichenkette. StringBuffer bietet zusätzlich Synchronisation und ist deshalb in vielen Situationen langsamer.
| Methode | Wirkung |
|---|---|
length() | aktuelle Zeichenanzahl |
append(value) | Wert anhängen |
insert(index, value) | Wert an Position einfügen |
delete(start, end) | Bereich [start, end) entfernen |
replace(start, end, value) | Bereich ersetzen |
StringBuilder builder = new StringBuilder("Hello");
builder.append(" Java").insert(5, ',');
String result = builder.toString();StringTokenizer ist eine ältere Token-API. Für neue Programme sind split, Scanner oder ein Parser häufig klarer.
StringTokenizer tokenizer = new StringTokenizer("red,green,blue", ",");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
System.out.println(token);
}Ein- und Ausgabe
Scanner liest beispielsweise Konsoleneingaben. Ausgaben erfolgen über System.out; printf unterstützt Formatmuster wie %d, %f, %s und %n.
| Methode oder Muster | Zweck |
|---|---|
next() | nächstes Wort lesen |
nextLine() | ganze Zeile lesen |
nextInt() | Ganzzahl lesen |
nextDouble() | Gleitkommazahl lesen |
%d | Ganzzahl formatieren |
%f | Gleitkommazahl formatieren |
%.2f | Gleitkommazahl mit zwei Nachkommastellen |
%s | String formatieren |
%n | plattformunabhängiger Zeilenwechsel |
import java.util.Scanner;
Scanner scanner = new Scanner(System.in);
System.out.print("Name: ");
String name = scanner.nextLine();
System.out.printf("Hello, %s%n", name);Operatoren und Kontrollfluss
Arithmetische Operatoren sind +, -, *, / und %. Vergleiche verwenden <, <=, >, >=, == und !=. Für logische Ausdrücke sind !, &&, || und ^ üblich. && und || werten den rechten Ausdruck nur bei Bedarf aus.
if (score >= 50 && passed) {
System.out.println("Passed");
} else {
System.out.println("Retry");
}switch wählt anhand eines Werts einen Zweig. Moderne Varianten können einen Wert direkt liefern:
String message = switch (level) {
case 1 -> "Low";
case 2, 3 -> "Medium";
default -> "High";
};for und while prüfen die Bedingung vor dem Durchlauf. do-while führt den Rumpf mindestens einmal aus. break beendet die umgebende Schleife, continue überspringt den restlichen aktuellen Durchlauf. Beschriftete Sprünge sollten wegen ihrer erschwerten Lesbarkeit selten verwendet werden.
Auswertungsregeln
Operatoren werden nach Priorität ausgewertet. Klammern machen die Absicht sichtbar und vermeiden Fehler. Bei int-Division wird der Nachkommateil abgeschnitten; eine Division durch null löst bei Ganzzahlen ArithmeticException aus. ++ und -- verändern eine Variable und sollten nicht in verschachtelten Ausdrücken versteckt werden.
int whole = 7 / 2; // 3
double exact = 7.0 / 2; // 3.5
boolean valid = value >= 0 && value <= 100;
int absolute = value < 0 ? -value : value;Die bitweisen Operatoren &, |, ^ und ~ arbeiten auf Bits. && und || sind dagegen logische Operatoren mit Kurzschlussauswertung. Der bedingte Operator condition ? whenTrue : whenFalse eignet sich für kurze Wertausdrücke, nicht für komplexe Abläufe.
Zuweisungen können mit einem Operator kombiniert werden: total += amount entspricht total = total + amount. Das gilt auch für -=, *=, /=, %= sowie bitweise und verschiebende Operatoren. Die Zuweisung hat eine niedrigere Priorität als die meisten Rechen- und Vergleichsoperatoren.
Aussagenlogik und boolesche Algebra
Eine Aussage ist ein Satz mit genau einem Wahrheitswert: wahr (W, in Java true) oder falsch (F, in Java false). Eine Aussageform enthält Variablen und wird erst nach deren Belegung zu einer konkreten Aussage.
| Symbol | Name | Java |
|---|---|---|
¬p | Negation | !p |
p ∧ q | Konjunktion | p && q |
p ∨ q | Disjunktion | p || q |
p ⊕ q | exklusives Oder | p ^ q |
p → q | Implikation | !p || q |
p ↔ q | Äquivalenz | p == q |
Wahrheitstabelle
p | q | ¬p | p ∧ q | p ∨ q | p → q | p ↔ q |
|---|---|---|---|---|---|---|
| W | W | F | W | W | W | W |
| W | F | F | F | W | F | F |
| F | W | W | F | W | W | F |
| F | F | W | F | F | W | W |
Die Implikation p → q ist nur dann falsch, wenn p wahr und q falsch ist. Die Äquivalenz ist wahr, wenn beide Aussagen denselben Wahrheitswert besitzen. In Java ist && nicht dasselbe wie &: Der logische Operator arbeitet mit Kurzschluss, der bitweise Operator wertet beide Seiten aus.
Grundgesetze
| Gesetz | Formeln |
|---|---|
| Kommutativität | p ∧ q = q ∧ p; p ∨ q = q ∨ p |
| Distributivität | p ∧ (q ∨ r) = (p ∧ q) ∨ (p ∧ r) |
| Neutralität | p ∧ W = p; p ∨ F = p |
| Komplement | p ∧ ¬p = F; p ∨ ¬p = W |
| Idempotenz | p ∧ p = p; p ∨ p = p |
| Doppelnegation | ¬(¬p) = p |
| De Morgan | ¬(p ∧ q) = ¬p ∨ ¬q; ¬(p ∨ q) = ¬p ∧ ¬q |
Grundbegriffe der Mathematik
Ein Term ist ein Ausdruck ohne Wahrheitsbehauptung, etwa 2x + 1. Eine Aussage ist beispielsweise 2 + 3 = 5. Eine Aussageform wie 2x + 1 = 12 enthält noch eine Variable.
Das Summenzeichen Σ fasst Werte zusammen, das Produktzeichen Π multipliziert sie:
Σ(i = 1..5) i = 15
Π(i = 1..4) i = 24Für eine positive ganze Zahl ist n! = n * (n - 1) * ... * 1; zusätzlich gilt 0! = 1. Fakultäten und Summen treten häufig bei Laufzeit- und Kombinatorikaufgaben auf.
Arrays
Arrays enthalten Elemente eines festen Typs und haben nach der Erstellung eine unveränderliche Länge. Der erste Index ist 0.
int[] scores = {3, 5, 8};
scores[1] = 6;
System.out.println(scores[scores.length - 1]);
int[][] matrix = {{1, 2}, {3, 4}};
System.out.println(matrix[1][0]);Für eine veränderliche Anzahl von Elementen wird etwa ArrayList<Integer> eingesetzt. Die Hilfsklasse Arrays bietet unter anderem sort, copyOf, equals, fill, toString und binarySearch; für die binäre Suche muss das Array sortiert sein.
Ein Array kann nicht mit array.length = ... resized werden. Ein Index ausserhalb 0 bis length - 1 führt zu ArrayIndexOutOfBoundsException. Mehrdimensionale Arrays sind Arrays von Arrays und dürfen deshalb auch unterschiedlich lange Zeilen enthalten.
Block 03: Objektorientierung mit Java I
Modellierung mit UML
UML beschreibt Struktur und Verhalten grafisch. Klassendiagramme zeigen Typen und Beziehungen, Objektdiagramme konkrete Instanzen, Komponentendiagramme Bausteine und Schnittstellen. Aktivitäts-, Zustands-, Anwendungsfall- und Sequenzdiagramme beschreiben Abläufe und Interaktionen.
Eine Klasse ist ein Bauplan. Ein Objekt ist eine konkrete Instanz dieses Typs. Attribute beschreiben Zustand, Methoden beschreiben Verhalten. Konstruktoren initialisieren neue Objekte; Java besitzt keinen manuell aufrufbaren Destruktor, da die Speicherfreigabe durch den Garbage Collector erfolgt.
public class Student {
private final String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Student first = new Student("Mira");
Student second = new Student("Mira");
boolean sameObject = first == second; // falseMethoden und Kapselung
Eine Methodensignatur umfasst Namen und Parametertypen. Der Rückgabetyp allein unterscheidet keine überladenen Methoden. Parameter sind lokale Variablen. private hält Implementierungsdetails verborgen, public bildet die externe Schnittstelle.
public class PriceCalculator {
public double gross(double net, double taxRate) {
return net * (1 + taxRate);
}
public int add(int left, int right) {
return left + right;
}
public double add(double left, double right) {
return left + right;
}
}Statische Member gehören zur Klasse und werden über den Klassennamen angesprochen. Instanzmember gehören jeweils zu einem Objekt. Eine statische Methode kann nicht direkt auf Instanzzustand zugreifen. Ein static-Block wird beim Initialisieren der Klasse einmal ausgeführt.
public class MathTools {
public static final double PI_APPROXIMATION = 3.14159;
public static int square(int value) {
return value * value;
}
}
int result = MathTools.square(5);Varargs verpackt beliebig viele Argumente des angegebenen Typs in ein Array. Der Varargs-Parameter muss am Ende stehen.
public static int sum(int... values) {
int total = 0;
for (int value : values) {
total += value;
}
return total;
}Rekursion benötigt einen Basisfall und einen Schritt, der das Problem verkleinert. Jeder Aufruf beansprucht Stack-Speicher.
public static long factorial(int value) {
if (value <= 1) {
return 1;
}
return value * factorial(value - 1);
}Ein record ist eine kompakte Deklaration für überwiegend unveränderliche Datenträger. Komponenten, Konstruktor, Accessoren sowie equals, hashCode und toString werden bereitgestellt.
public record Point(int x, int y) {}
Point origin = new Point(0, 0);
int x = origin.x();Sichtbarkeit und Zustand
Java unterscheidet vier Sichtbarkeitsstufen: public ist überall zugänglich, protected im Paket und in Unterklassen, ein Member ohne Modifizierer nur im Paket und private nur in der deklarierenden Klasse. Felder werden in robusten Klassen meist private gehalten. Validierung erfolgt beim Erzeugen oder über Methoden, damit ein Objekt keine ungültigen Zustände annehmen kann.
final verhindert eine zweite Zuweisung an eine Variable. Bei einer Referenz ist das Objekt selbst dadurch nicht automatisch unveränderlich. static final beschreibt eine klassenweite Konstante; eine gewöhnliche Instanzvariable existiert pro Objekt.
public class Counter {
private static int created;
private int value;
public Counter() {
created++;
}
public void increment() {
value++;
}
public static int createdCount() {
return created;
}
}Eine Methode kann überladen werden, wenn ihre Parameterliste unterschiedlich ist. Überschreiben bedeutet dagegen, dass eine Unterklasse dieselbe Signatur mit einer spezialisierten Implementierung versieht. Diese beiden Begriffe dürfen nicht verwechselt werden.
Pakete und Module
Pakete ordnen Klassen und vermeiden Namenskonflikte. Die umgekehrte Domain-Schreibweise ist eine verbreitete Konvention, etwa ch.example.application. Klassen aus anderen Paketen werden importiert; java.lang wird automatisch importiert.
| Einheit | Zweck |
|---|---|
| Paket | Namensraum für zusammengehörigen Code |
| Bibliothek | ausgelieferte Sammlung, oft als JAR |
| Modul | Pakete mit expliziten Abhängigkeiten und Exporten |
Ein statischer Import macht ausgewählte statische Member ohne Klassennamen verfügbar. Er sollte sparsam eingesetzt werden, damit die Herkunft eines Aufrufs erkennbar bleibt.
Die Verzeichnisstruktur folgt normalerweise dem Paketnamen. Eine Datei darf nur eine öffentliche Top-Level-Klasse mit passendem Dateinamen enthalten. Importiert werden konkrete Typen oder Pakete; import package.* importiert keine Unterpakete und keine statischen Member.
Block 04: Objektorientierung mit Java II
Entwurf guter Klassen
Gute Klassen haben eine klar erkennbare Verantwortung, wenig Wissen über andere Klassen und eine kleine, stabile Schnittstelle. Hohe Kohäsion bündelt zusammengehörige Aufgaben. Lose Kopplung begrenzt die Auswirkungen von Änderungen. Kapselung schützt Invarianten und erlaubt, die Implementierung hinter einer Schnittstelle auszutauschen.
Refactoring verändert die interne Struktur, ohne das beobachtbare Verhalten zu erweitern. Tests sichern dabei die bestehende Funktionalität. Neue Anforderungen sind ein eigener Änderungsgrund.
Vererbung
Vererbung modelliert eine echte Ist-ein-Beziehung. Gemeinsame Eigenschaften liegen in einer Oberklasse, Spezialisierungen in Unterklassen. Jede Java-Klasse erbt letztlich von Object.
public class Submission {
private final String author;
public Submission(String author) {
this.author = author;
}
public String getAuthor() {
return author;
}
}
public class PhotoSubmission extends Submission {
private final String fileName;
public PhotoSubmission(String author, String fileName) {
super(author);
this.fileName = fileName;
}
}Ein Unterklassenkonstruktor ruft zuerst super(...) auf; dieser Aufruf steht immer am Anfang des Konstruktors. Ohne expliziten Aufruf versucht Java super() zu verwenden, was nur bei einer passenden parameterlosen Oberklassenkonstruktion gelingt. Zugängliche Methoden der Oberklasse (public oder protected) lassen sich in einer Unterklasse an beliebiger Stelle mit super.methodenName(...) aufrufen. private-Member bleiben auch Unterklassen verborgen. protected ist für Unterklassen und Paketmitglieder sichtbar, sollte aber nicht unbedacht zur öffentlichen Kopplung werden.
Eine Zwischenklasse ist sinnvoll, wenn nur ein Teil der Untertypen eine Fähigkeit teilt. Noch besser passt oft ein Interface oder Komposition, wenn die Beziehung keine echte Spezialisierung ausdrückt.
UML-Beziehungen
Eine Generalisierung wird mit einer durchgezogenen Linie und einem hohlen Dreieck dargestellt. Die Realisierung eines Interfaces verwendet eine gestrichelte Linie. Eine ausgefüllte Raute steht für Komposition, eine leere Raute für Aggregation. Multiplizitäten wie 1, 0..1 und * beschreiben, wie viele Objekte beteiligt sind.
Polymorphie und Überschreiben
Eine Variable eines Supertyps kann ein Objekt eines Subtyps referenzieren. Der statische Typ bestimmt, welche Member der Compiler anbietet; der dynamische Typ bestimmt, welche überschriebene Instanzmethode läuft.
Submission submission = new PhotoSubmission("Mira", "photo.jpg");
System.out.println(submission.getAuthor());Mit @Override prüft der Compiler, ob wirklich eine geerbte Methode ersetzt wird. instanceof testet die Laufzeitart. Pattern Matching verbindet Prüfung und lokale Variable.
if (submission instanceof PhotoSubmission photo) {
System.out.println(photo);
}Ein Aufwärts-Cast ist implizit. Ein Abwärts-Cast benötigt eine Prüfung und kann sonst eine ClassCastException auslösen. Unverträgliche Typen weist der Compiler zurück.
Object-Vertrag und Kopien
Object liefert unter anderem toString, equals und hashCode. == prüft bei Referenzen Identität. equals soll fachliche Gleichheit ausdrücken. Wird equals überschrieben, muss auch hashCode konsistent überschrieben werden, damit HashMap und HashSet korrekt arbeiten.
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Student student)) {
return false;
}
return name.equals(student.name);
}
@Override
public int hashCode() {
return name.hashCode();
}clone erzeugt bei korrekter Implementierung zunächst eine flache Kopie. Referenzfelder zeigen danach noch auf dieselben Objekte. Eine tiefe Kopie muss verschachtelte, veränderliche Objekte ebenfalls kopieren. In neuem Code sind Kopierkonstruktoren oder Fabrikmethoden oft verständlicher.
Der Vertrag von equals verlangt Reflexivität, Symmetrie, Transitivität, Konsistenz und ein Ergebnis ungleich true für null. Hash-basierte Collections vergleichen bei gleichem Hashwert zusätzlich mit equals. Deshalb dürfen relevante Schlüsselattribute nach dem Einfügen nicht verändert werden. toString ist für Diagnose gedacht und kein stabiler Austauschvertrag.
Ausgewählte Standardklassen
Wrapper wie Integer, Double und Boolean repräsentieren primitive Werte als Objekte. Autoboxing und Unboxing wandeln automatisch um; bei null kann das Unboxing eine NullPointerException auslösen.
Properties speichert einfache Schlüssel-Wert-Konfigurationen als Strings. Class<?> repräsentiert einen Typ, etwa über object.getClass() oder Student.class. Math bietet statische Funktionen wie abs, min, max, sqrt, pow, sin und random sowie PI und E.
Ältere Typen wie Vector, Hashtable und Enumeration existieren weiterhin, sind aber für neuen Code meist nicht die erste Wahl. Apache Commons ergänzt das JDK mit Bibliotheken für konkrete Aufgaben. Abhängigkeiten sollten immer hinsichtlich Version, Lizenz und Sicherheitsmeldungen geprüft werden.
Properties
username=admin
language=deProperties properties = new Properties();
try (InputStream input = Files.newInputStream(Path.of("config.properties"))) {
properties.load(input);
}
String language = properties.getProperty("language", "en");Class und Arrays
| Aufgabe | Beispiel |
|---|---|
| Laufzeittyp ermitteln | object.getClass() |
| Klassentyp referenzieren | Student.class |
| einfachen Klassennamen lesen | type.getSimpleName() |
| Array ausgeben | Arrays.toString(values) |
| Array sortieren | Arrays.sort(values) |
| Array füllen | Arrays.fill(values, 0) |
| Array kopieren | Arrays.copyOf(values, values.length) |
| Arrays vergleichen | Arrays.equals(first, second) |
| binär suchen | Arrays.binarySearch(values, target) |
binarySearch setzt ein nach derselben Ordnung sortiertes Array voraus. Für Objekt-Arrays wird eine natürliche Ordnung oder ein Comparator benötigt.
Math und verwandte Klassen
| Bereich | Methoden oder Klassen |
|---|---|
| Beträge und Grenzen | abs, min, max, signum |
| Rundung | ceil, floor, round |
| Potenzen und Wurzeln | sqrt, cbrt, pow, hypot |
| Exponentialfunktionen | exp, log, log10 |
| Trigonometrie | sin, cos, tan, asin, acos, atan |
| weitere Typen | DecimalFormat, Random, BigInteger |
Datum und Uhrzeit
Die API java.time ist unveränderlich und thread-sicher. LocalDate, LocalTime und LocalDateTime enthalten keine Zeitzone. ZonedDateTime berücksichtigt eine Zone, Instant einen UTC-Zeitpunkt. Period beschreibt Datumsdifferenzen, Duration Zeitdifferenzen.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
LocalDate date = LocalDate.of(2026, 8, 27);
String output = date.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));Block 05: Objektorientierung mit Java III
Interfaces und abstrakte Klassen
Ein Interface beschreibt einen Vertrag. Eine Klasse kann mehrere Interfaces implementieren, aber nur eine Klasse erweitern. Abstrakte Klassen können gemeinsamen Zustand und Verhalten bereitstellen, erzeugen aber keine direkten Instanzen.
Interfaces modellieren Fähigkeiten unabhängig von einer konkreten Klassenhierarchie. Eine implementierende Methode muss den Vertrag mindestens mit public erfüllen. Interfaces dürfen andere Interfaces erweitern. Treffen gleichnamige Default-Methoden zusammen, muss die konkrete Klasse die Mehrdeutigkeit auflösen.
public interface CellRule {
boolean isActive(Field field);
default boolean isInactive(Field field) {
return !isActive(field);
}
static CellRule alwaysActive() {
return field -> true;
}
}Eine abstrakte Methode besitzt keinen Rumpf und muss in konkreten Unterklassen implementiert werden. default-Methoden enthalten eine Standardimplementierung. static-Methoden gehören dem Interface und werden über dessen Namen aufgerufen. Interface-Konstanten sind implizit public static final.
| Merkmal | Abstrakte Klasse | Interface |
|---|---|---|
| Konstruktor | möglich | nicht vorhanden |
| Instanzzustand | möglich | keine Instanzfelder |
| Mehrere Typen | nur eine Oberklasse | mehrere Interfaces |
| Gemeinsamer Code | beliebige konkrete Methoden | default und static |
| Hauptzweck | gemeinsame Basis | Fähigkeit oder Vertrag |
Vererbung oder Delegation
Vererbung passt zu einer stabilen Ist-ein-Beziehung. Delegation beschreibt, dass ein Objekt eine Aufgabe an ein anderes Objekt weitergibt. Delegation stärkt Austauschbarkeit und Kapselung und ist häufig die bessere Wahl für optionale Fähigkeiten.
Nach dem Liskov-Substitutionsprinzip muss ein Subtyp die Zusagen des Supertyps einhalten. Er darf etwa keine strengeren Vorbedingungen verlangen oder erwartete Ergebnisse verletzen. Tiefe Hierarchien erhöhen die Kopplung und brauchen deshalb eine fachliche Begründung.
public class Report {
private final Formatter formatter;
private final Printer printer;
public Report(Formatter formatter, Printer printer) {
this.formatter = formatter;
this.printer = printer;
}
public void print(String text) {
printer.print(formatter.format(text));
}
}Geschachtelte Klassen
Eine static geschachtelte Klasse benötigt keine äussere Instanz. Eine nicht-statische innere Klasse besitzt implizit eine Referenz auf das umgebende Objekt. Lokale und anonyme Klassen sind auf einen kleinen Gültigkeitsbereich begrenzt. Für einfache Funktionsverträge ist ein Lambda meist lesbarer.
public class Person {
private final String name;
public Person(String name) {
if (!Validator.isValid(name)) {
throw new IllegalArgumentException("Name is required");
}
this.name = name;
}
public static class Validator {
public static boolean isValid(String value) {
return value != null && !value.isBlank();
}
}
}Debugging mit IntelliJ IDEA
Haltepunkte stoppen die Ausführung an einer interessanten Stelle. Im Debugger lassen sich Variablen, Aufrufstapel und Threads untersuchen. Step Over führt die aktuelle Zeile aus, Step Into öffnet einen aufgerufenen Methodenrumpf und Step Out beendet die aktuelle Methode.
Block 06: Objektorientierung mit Java IV
Konstanten und Enums
final verhindert eine erneute Zuweisung. Eine Konstante ist häufig zusätzlich static, wenn alle Instanzen denselben Wert teilen. Konstante Namen werden grossgeschrieben.
public class Physics {
public static final double EARTH_GRAVITY = 9.81;
}Ein enum beschreibt eine abgeschlossene Menge benannter Werte. Die Werte werden mit == verglichen. Ein Enum kann Felder, Konstruktoren und Methoden besitzen.
public enum Status {
NEW("New"), DONE("Done");
private final String label;
Status(String label) {
this.label = label;
}
public String label() {
return label;
}
}Defensive Programmierung und Logging
Eingaben werden an Systemgrenzen validiert. Ungültige Zustände werden früh abgewiesen, Fehlermeldungen erklären Ursache und Korrektur. Sicherheitsrelevante und betriebliche Ereignisse gehören in ein Log, nicht in eine zufällige Konsolenausgabe.
import java.util.logging.Logger;
public class AccountService {
private static final Logger logger = Logger.getLogger(AccountService.class.getName());
public void open(String owner) {
if (owner == null || owner.isBlank()) {
throw new IllegalArgumentException("Owner is required");
}
logger.info("Account opened");
}
}java.util.logging kennt unter anderem FINE, INFO, WARNING und SEVERE. Handler und Log-Level werden zentral konfiguriert. Geheimnisse und unnötige personenbezogene Daten dürfen nicht geloggt werden.
Ein Datei-Handler kann Meldungen dauerhaft speichern. Der zweite Parameter von FileHandler aktiviert den Append-Modus. Formatter bestimmen das Ausgabeformat.
private static void configureLogging() {
try {
FileHandler handler = new FileHandler("logs/application.log", true);
handler.setFormatter(new SimpleFormatter());
logger.addHandler(handler);
logger.setLevel(Level.ALL);
handler.setLevel(Level.ALL);
} catch (IOException exception) {
logger.log(Level.SEVERE, "Could not configure file logging", exception);
}
}Exceptions
Throwable teilt sich in Error und Exception. Checked Exceptions müssen behandelt oder mit throws weitergegeben werden. Unchecked Exceptions stammen von RuntimeException und weisen häufig auf fehlerhafte Vorbedingungen hin. Eigene Ausnahmen modellieren fachliche Fehler.
public class InvalidAgeException extends Exception {
public InvalidAgeException(int age) {
super("Age is outside the allowed range: " + age);
}
}
public void setAge(int age) throws InvalidAgeException {
if (age < 0 || age > 130) {
throw new InvalidAgeException(age);
}
}try überwacht Code, catch behandelt passende Fehler und finally eignet sich für Aufräumarbeiten. Für Ressourcen ist try-with-resources vorzuziehen.
try (var reader = Files.newBufferedReader(path)) {
String line = reader.readLine();
} catch (IOException exception) {
logger.log(Level.WARNING, "Could not read file", exception);
}Spezifische catch-Typen stehen vor allgemeineren. Multi-Catch bündelt gleichartig behandelte Typen. Eine Ausnahme sollte entweder sinnvoll behandelt oder mit Kontext weitergereicht werden, nicht bloss verschluckt werden.
Eine Ausnahmehierarchie sollte fachliche und technische Ursachen unterscheidbar machen. Error steht typischerweise für schwerwiegende JVM-Probleme und wird nicht als normale Eingabeabweichung behandelt. RuntimeException eignet sich für verletzte Programmvorbedingungen; checked Exceptions passen zu erwartbaren externen Fehlern wie fehlgeschlagenem Datei- oder Datenbankzugriff.
JOptionPane zeigt in Desktop-Anwendungen Meldungs-, Bestätigungs- und Eingabedialoge. Der Rückgabewert von showOptionDialog ist der Index der gewählten Option oder CLOSED_OPTION. In einer Schichtarchitektur gehört die Entscheidung über die Darstellung in die UI-Schicht, nicht in die Domänenlogik.
JOptionPane.showMessageDialog(
null, "Could not save the file", "Error", JOptionPane.ERROR_MESSAGE);
Object[] options = {"Yes", "No", "Wait"};
int choice = JOptionPane.showOptionDialog(
null,
"Exit the application?",
"Confirmation",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
options[2]);Entwurfsprinzipien
KISS reduziert unnötige Komplexität. DRY verhindert widersprüchliche Mehrfachdefinitionen. YAGNI hält nicht benötigte Erweiterungen aus dem aktuellen Design heraus.
| Prinzip | Leitfrage |
|---|---|
| Single Responsibility | Gibt es genau einen Grund, diese Klasse zu ändern? |
| Open/Closed | Kann Verhalten ergänzt werden, ohne stabile Teile zu verändern? |
| Liskov Substitution | Bleibt der Subtyp überall verträglich? |
| Interface Segregation | Müssen Implementierungen unpassende Methoden anbieten? |
| Dependency Inversion | Hängt die Fachlogik von Abstraktionen ab? |
Dependency Injection liefert konkrete Abhängigkeiten von aussen und erleichtert Tests:
public interface OrderRepository {
void save(Order order);
}
public class OrderService {
private final OrderRepository repository;
public OrderService(OrderRepository repository) {
this.repository = repository;
}
public void save(Order order) {
repository.save(order);
}
}Javadoc und Architektur dokumentieren
Die öffentliche Schnittstelle umfasst Namen, Sichtbarkeit, Parameter, Rückgabewerte, Zustandsregeln und mögliche Ausnahmen. Private Felder und Hilfsmethoden gehören zur Implementierung. Javadoc erklärt diesen Vertrag direkt am Code; die Implementierung bleibt veränderbar, solange der Vertrag gilt.
/** Calculates an amount including tax. */
public double gross(double net, double taxRate) {
return net * (1 + taxRate);
}Architekturdokumentation ergänzt den Code um Ziele, Randbedingungen, Kontext, Baustein- und Laufzeitsichten, Verteilung, Qualitätsanforderungen, Entscheide, Risiken und Glossar. arc42 bietet dafür eine verbreitete, technologieunabhängige Struktur.