Packages

Modul #J2

Ziele

  • Ich kann ohne Hilfsmittel mindestens zwei Vorteile für das Verwenden von Packages nennen.
  • Ich kann ohne Hilfsmittel die Namenkonvention für Java-Packages beschreiben.
  • Ich zeige auf, wie mehrere Klassen vom gleichen Packet mit einem import-Statement importiert werden.
  • Ich kann korrekt und ohne Hilfsmittel erklären, was ein statischer Import ist und wozu er verwendet wird.
  • Ich kann erläutern, warum es keine gute Idee ist, Klassen ohne Package-Anweisung zu schreiben.

Packages

Bevor wir uns intensiver mit den Bestandteilen einer Klasse auseinandersetzen, schauen wir uns Packages an. Ein Package dient der Gruppierung und Organisation von Klassen, Schnittstellen und anderen Packages. Es wird zwischen zwei Arten von Packages unterschieden:

  • implizit importierte Packages
  • explizit zu importierende Packages

Wenn wir beispielsweise die Klasse Scanner benötigen, müssen wir dies durch ein import Statement machen.

1
import java.util.Scanner

Die Klasse Scanner befindet sich also im Package java und darin im Package util. Ein Package kann beliebig viele andere Packages enthalten. Ein Package wird auf dem Dateisystem als Verzeichnis behandelt.

Vorteile

  • Code-Organisation. Klassen von gleicher Natur (wie beispielsweise Modelle oder Services) befinden sich im gleichen Package
  • Auffindbarkeit. Klassen sind durch die Organisation einfacher zu finden
  • Vermeiden von Namenskonflikten. Zwei Klassen dürfen den gleichen Namen haben, solange sie in unterschiedlichen Packages liegen
  • Zugriffsteuerung. Bestimmte Zugriffsmodifikatoren erlauben den Zugriff auf Klassen im gleichen Package

Namenskonventionen

Gemäss der Namenskonvention werden Package-Namen immer in Kleinbuchstaben geschrieben. Die Trennung der verschiedenen Packages erfolgt beim Import-Statement durch einen Punkt.

Ordnerstruktur

Programmcode muss organisiert sein. Obwohl die Ordnerstruktur nicht vorgegeben ist, hat sich die folgende Konvention in Java-Projekten (vor allem solche, welche Maven im Einsatz haben - dazu mehr im Maven-Modul) etabliert.

Grundsätzlich legen wir Quellcode im einem Verzeichnis ab, das src genannt wird. Innerhalb dieses Verzeichnisses legen wir ein Verzeichnis main und darin wiederum ein Verzeichnis java an. Innerhalb des Java-Verzeichnisses können wir beliebig viele eigene Packages anlegen, um unseren Programmcode zu organisieren.

Klassen mit gleichem Namen

Es kann passieren, dass wir zwei Klassen haben, die den gleichen Namen haben. Beispiel: Wir arbeiten mit der externen Bibliothek Abstract Window Toolkit (AWT). Darin gibt es eine Klasse mit dem Namen Rectangle. Es ist natürlich erlaubt eine eigene Klasse mit diesem Namen anzulegen, solang sie sich nicht in einem Package mit dem gleichen Namen befindet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package ch.sbb.main;

import ch.sbb.rectangle.Rectangle;

public class Main {
    public static void main(String[] args) {
        // Deklaration und Initialisierung eines AWT-Rectangles:
        java.awt.Rectangle rectAWT = new java.awt.Rectangle()

        // Deklaration und Initialisierung eines Rectangles aus dem Package ch.sbb.rectangle:
        Rectangle myRect = new Rectangle();
    }
}
  • Wir importieren die eigene Rectangle-Klasse mit der Import-Anweisung
  • Wir deklarieren und initialisieren ein AWT-Rectangle, indem wir den vollständigen Namen (Package und Klassennamen) angeben

Import *

Wenn sich zwei Klassen im selben Paket befinden und eine Klasse in der anderen verwendet wird, muss die Klasse nicht importiert werden. Es ist auch möglich, alle Klassen aus dem Paket zu importieren. Dazu müssen wir einen * anstelle eines bestimmten Klassennamens in das Import-Statement schreiben.

1
import java.awt.*;

Alle Klassen eines Packages, wie in diesem Beispiel, zu importieren ist schlechter Style und soll vermieden werden. Es soll immer explizit importiert werden.

Package java.lang

Obwohl wir die meisten Pakete importieren müssen, gibt es ein Java-Paket, das immer automatisch importiert wird. Es ist java.lang. Dieses Paket enthält viele weit verbreitete Klassen wie String, System, Long, Integer, NullPointerException und andere.

Statischer Import

Wir können auch statische Elemente (wie z.B. Konstanten oder auch statische Methoden) einer Klasse in eine andere Klasse importieren. Wenn wir * in die import-Anweisung schreiben, müssen wir den importierten Klassennamen nicht angeben, bevor wir statische Methoden aufrufen oder statische Felder lesen. Hier ist ein Beispiel für den statischen Import der Klasse Arrays, die viele nützliche Methoden zur Verarbeitung von Arrays enthält:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package org.hyperskill.java.packages.theory;

import static java.util.Arrays.*;

public class Main {
    public static void main(String[] args) {
        int[] numbers = { 10, 4, 5, 47, 5, 12 }; // an array
        sort(numbers); // instead of writing Arrays.sort(...)
        int[] copy = copyOf(numbers, numbers.length); // instead of writing Arrays.copyOf(...)
    }
}

Wenn wir bei der Implementation von Klassen keine Package-Anweisung schreiben, wird die Klasse ins Default-Package eingefügt. Dies sollte vermieden werden, da Klassen aus dem Default-Package nicht in andere Klasse importiert werden können, welche sich nicht auch im Default-Package befinden.


Eigene Packages definieren

Packages können auch selber definiert werden. Die Package-Struktur entspricht hierbei der Verzeichnisstruktur des Projekts. Packages erstellt man primär um den Code zu strukturieren.

In einem Spring Boot Projekt wird der Code typischerweise in folgende Packages unterteilt:

  • Controller-Package (ch.sbb.demo.controller): Beinhaltet die Controller-Klassen, die HTTP-Anfragen verarbeiten.
  • Service-Package (ch.sbb.demo.service): Enthält die Service-Klassen, die die Geschäftslogik implementieren.
  • Repository-Package (ch.sbb.demo.repository): Beinhaltet die Repository-Klassen für die Datenbankinteraktionen
  • Model-Package (ch.sbb.demo.model): Enthält die Datenmodellklassen (Entities), die die Datenbanktabellen repräsentieren.

Ein eigene Package-Struktur zu definieren ist also gar nicht so schwer, im Grunde muss man dafür nur Verzeichnise erstellen.

Wie der Import von eigen definierten Packages erfolgt, ist dem folgenden Beispiel zu entnehmen. Im Beispiel gehen wir von dieser Verzeichnisstruktur aus:

1
2
3
4
5
/src
  └── ch
      └── sbb
          └── examplepackage
              └── Beispiel.java

Die Ordner ch, sbb examplepackage wurden von Hand erstellt. Das Package enthält die Klasse Beispiel.java.

1
2
3
4
5
6
7
package ch.sbb.examplepackage;

public class Beispiel {
    public void sagHallo() {
        System.out.println("Hallo, Welt!");
    }
}

In einer anderen Klasse kann das Package wie zuvor beschrieben importiert und verwendet werden. Ein Beispiel für eine Klasse welche das Package importiert und verwendet könnte wie folgt aussehen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package ch.sbb.main;

import ch.sbb.examplepackage.*;

public class Main {
    public static void main(String[] args) {
        Beispiel b = new Beispiel();
        
        b.sagHallo();
    }
}