michael.eichberg@dhbw.de, Raum 149B
1.0
Durch Modularisierung kann Code einfacher wiederverwendet werden.
Modularer Code, bei dem einzelne Module eine kohärente Funktionalität anbieten, sind einfacher zu verstehen und zu warten. Fehler lassen sich leichter finden und beheben.
Die Modularisierung von Code ermöglicht es, neue Funktionalitäten hinzuzufügen, ohne bestehenden Code zu verändern.
Durch eine Modularisierung ist es möglich effektiv mit mehreren Personen an einem Projekt zu arbeiten. Jede Person kann an einem Modul arbeiten, ohne die anderen Module zu beeinflussen.
Im Prinzip haben wir bisher nur Methoden zur Strukturierung bzw. Modularisierung kennen gelernt.
Einzelnen Methoden erlauben zwar bereits eine Modularisierung des Codes, da diese aber für sich nicht wiederverwendbar sind, ist es notwendig, diese in Klassen zu organisieren. Klassen, welche in einzelnen Dateien gespeichert werden, erlauben dann eine Wiederverwendung des Codes.
Klassen sind die Bausteine von Java Programmen und alles - bis auf einfachste Programme - ist in Klassen organisiert.
Packages sind Sammlungen von verwandten Klassen und Schnittstellen.
import
s:Imports ermöglichen den Zugriff auf Klassen aus anderen Packages, ohne deren vollständigen Namen zu schreiben.
Sichtbarkeiten steuern den (erlaubten) Zugriff auf Klassen, Methoden und Variablen und helfen somit beim Verbergen von Implementierungsdetails.
Im Folgenden werden wir nur ein kohärentes Subset der Modularisierungsmöglichkeiten von Java Programmen betrachten. Insbesondere werden wir uns auf die wesentlichen Eigenschaften der genannten Konzepte und Mechanismen beschränken:
Klassen sind die grundlegenden Bausteine von Java Programmen.
Eine Klasse wird mit dem Schlüsselwort class deklariert.
Eine Klasse kann Felder (Variablen) und Methoden enthalten.
Eine Klasse wird in einer Datei mit dem Namen der Klasse (+.java) gespeichert.
Warnung
Die Hauptfunktion einer Klasse in Java ist es als Schablone für Objekte, die eine gemeinsame Struktur und Verhalten haben, zu dienen. Dies werden wir aber erst später in der Vorlesung besprechen. Für den Moment nutzen wir Klassen zur Strukturierung bzw. Modularisierung des Codes.
Im einfachsten Fall sind die Klassen eines Java Programms alle im selben Verzeichnis gespeichert.
Dies erlaubt eine direkte Verwendung der Methoden der anderen Klassen durch Angabe des Klassennamens und des Methodennamens. (Vergleichbar mit der Verwendung von Double.parseDouble
etc.)
Datei: MyMath.java
class MyMath {
static final int ANSWER_TO_EVERYTHING = 42;
static double fibonacci(int n) { ... }
static double isPrim(int n) { ... }
}
Datei: Main.java
void main() {
println(MyMath.fibonacci(10));
}
class <KlassenName> {
<Attribute (gel. auch Felder genannt)>*
<Methoden>*
}
Der Klassenname muss ein gültiger Bezeichner sein und mit dem Dateinamen (+ .java) übereinstimmen.
Klassennamen beginnen in Java - per Konvention - immer mit einem Großbuchstaben (UpperCamelCase).
Seit Java 8 (in Verbindung mit weiteren Ergänzungen in Java 9) können auch interface
s zum Organisieren von Code verwendet werden.
Beispiel:
Datei: MyMath.java
interface MyMath {
static final int ANSWER_TO_EVERYTHING = 42;
static double fibonacci(int n) { ... }
static double isPrim(int n) { ... }
}
Datei: Main.java
void main() {
println(MyMath.fibonacci(10));
}
Die Verwendung von Interfaces zu reinen Strukturierungszwecken ist jedoch unüblich.
Wir werden uns Interfaces in einer späteren Vorlesung genauer ansehen, wenn wir objekt-orientierte Programmierung in Java detaillierter besprechen.
Statische Methoden gehören zur Klasse/Interface als solches.
Statische Attribute gehören zur Klasse/Interface als solches.
static <returnType> <methodName>(<parameters>) { <body> }
static final <type> <name> = <value>;
Das Java Development Kit (JDK) enthält viele Klassen mit statischen Methoden und Attributen, z. B. java.lang.Math
, java.lang.System
, java.io.File
, java.io.IO
etc..
Erste Refaktorisierung des Codes
Nehmen Sie Ihren Code (Berechnung der Fibonacci-Zahlen, Fakultät und Kubikwurzel sowie den Primzahltest) und ordnen Sie diesen einer Klasse zu. Überlegen Sie sich diesbezüglich einen geeigneten Namen für die Klasse und speichern Sie die Klasse in einer entsprechenden Datei. In einer zweiten Datei (Main.java) schreiben Sie eine main
-Methode, die - basierenden auf Kommandozeilenparametern - die passenden Methoden der Klasse aufruft und die Ergebnisse auf der Konsole ausgibt. Die main
Methode soll dabei die grundlegende Fehlerbehandlung übernehmen, falls die Kommandozeilenargumente nicht passen.
Beispielinteraktion:
$ java --enable-preview Main.java cbrt 1000 isPrim 97 fibonacci 30 ack 1
1000.0^⅓ = 10.0
isPrim(97) = true
fiboncci(30) = 832040
[error] Ungültige Funktion: ack
Packages sind Sammlungen von verwandten Klassen und Schnittstellen.
Sie helfen, Code in logische Gruppen zu organisieren und Namenskonflikte zu vermeiden.
Vergleichbar mit Ordnern für Dateien in einem Dateisystem.
package <packageName>;
Die Packagedeklaration steht am Anfang einer Java-Datei.
Per Konvention erfolgt die Benennung in umgekehrter Domain-Reihenfolge.
Der Packagename muss die Verzeichnisstruktur widerspiegeln.
1 package de.dhbw.mannheim.vl.programmierung;
2 class Klasse { ... }
import
s:ermöglichen den Zugriff auf Klassen aus anderen Packages, ohne deren vollständigen Namen zu schreiben.
import <packageName>.<className>;
Erleichtert das Lesen und Schreiben des Codes, da der vollständige Klassenname nicht jedes Mal geschrieben werden muss.
Das Package java.lang
wird immer automatisch importiert (enthält u. a. die Klassen String
, Math
, etc.)
Java unterstützt auch ein Wildcard-Import, z. B. import java.util.*;
. Dies sollte jedoch in nicht-trivialem Code vermieden werden, da es zu Konflikten führen kann.
import static
:ermöglicht den Import von statischen Methoden und Attributen. Danach kann ohne Angabe des Klassennamens auf die Methode bzw. das Attribut zugegriffen werden.
import static <packageName>.<className>.<methodName>;
import static <packageName>.<className>.<attribute>;
import module
:(Seit Java 23) ermöglicht den Import alles Klassen eine Moduls.
import modul <moduleName>;
Spezifischer Import einer Klasse:
import java.math.BigDecimal;
... {
var one = BigDecimal.ONE;
}
Import aller Klassen (und Interfaces) des Packages:
import java.math.*;
... {
var one = BigDecimal.ONE;
}
Import einer Klassenmethode (statisch):
import static java.lang.Math.sqrt;
... {
var x = sqrt(2);
}
Import eines Klassenattributs (statisch):
import static java.lang.System.out;
... {
out.println("Hello World!");
}
Import eines Java Modules (ab Java 23):
import module java.base;
... {
IO.println(BigDecimal.ONE);
}
Hinweis
Ein Java-Script importiert immer implizit:
import module java.base;
import static java.io.IO.*;
Wenn Sie in der JShell also auch println
und readln
direkt verwenden wollen, dann müssen Sie lediglich import static java.io.IO.*; hinzufügen.
Um festzulegen, wer auf Klassen, Methoden und Variablen zugreifen kann, verwendet Java Sichtbarkeiten (Access Modifiers). Dies ist ein Konzept aus dem Bereich Programming-in-the-Large. Für kleinere Projekte, bei denen alle Klassen im selben Package sind, ist dies nicht relevant.
Die vier Sichtbarkeiten in Java sind:
public
: Zugriff von überall
protected
: Zugriff innerhalb des gleichen Packages und von Subklassen
<default> (package-private)**: Zugriff nur innerhalb des gleichen Packages
private
: Zugriff nur innerhalb der gleichen Klasse
Abstraktes Beispiel:
public class PublicClass {
public int publicVar; // Zugriff von überall
protected int protectedVar; // Zugriff innerhalb des Packages und Subklassen
int defaultVar; // Zugriff nur im selben Package
private int privateVar; // Zugriff nur innerhalb dieser Klasse
}
Konkretes Beispiel:
public class MyMath {
public static int THE_ANSWER = 42;
private static double cbrt(double x,double guess, int steps) { ... }
public static double cbrt(double x) { cbrt(x,1.0,1); }
}
public interface MyMath {
static int THE_ANSWER = 42;
private static double cbrt(double x,double guess, int steps) { ... }
static double cbrt(double x) { cbrt(x,1.0,1); }
}
Java interfaces kennen nur die Sichtbarkeiten public
und private
. Wenn keine Sichtbarkeit angegeben wird, ist die Methode bzw. das Attribut implizit public
.
Anwendung in der Praxis
public
: Offene API, z. B. für Libraries.
private
: dient der Kapselung z. B. interne Hilfsmethoden und interner Zustand.
protected
: Ermöglicht Vererbung und Zugriff für verwandte Klassen.
<default bzw. keine explizite Angabe>: Für interne Logik innerhalb eines Packages.
Verzeichnis mit der fachlichen Logik für mathematische Funktionen:
package de.dhbw.mannheim.calculator.math;
public class Functions {
public static double cbrt(double x) { ... }
}
Code mit der Logik für die Interaktion mit dem Benutzer:
package de.dhbw.mannheim.calculator;
import de.dhbw.mannheim.calculator.math.Functions;
public class Main {
public static void main(String[] args) {
Functions.cbrt(Double.parseDouble(args[0]));
}
}
Organisiere Klassen logisch in Packages.
Die beiden bei weitem häufigsten Sichtbarkeiten sind public und private.
Nutze public nur bei notwendigen Klassen und Methoden.
Halte Klassenvariablen privat, um Daten zu kapseln.
Methoden, die nur innerhalb einer Klasse verwendet werden, sollten private sein.
Vermeide übermäßige Imports (import java.util.*; kann zu Konflikten führen).
Packages gruppieren verwandte Klassen und vermeiden Namenskonflikte.
Imports erlauben das Verwenden von Klassen aus anderen Packages.
Sichtbarkeiten steuern den Zugriff und helfen beim Schutz der Daten.
Modularisierung der Codebasis
Verschieben Sie Ihre Klasse mit den mathematischen Funktionen in das package math. Die Datei mit der main
Methode bleibt an ihrem Platz. Fügen Sie ggf. ein import Statement hinzu.
Wie müssen Sie Ihren Code ändern, wenn Sie innerhalb der Datei Main.java direkt auf die Methoden zugreifen wollen ohne jedes mal den Klassennamen voranstellen zu müssen?