Professionelle Java-Schulung – Vor Ort und Online.

  • Einführung in die Welt der Java-Programmierung
  • Konzepte und Techniken von Algorithmen kennenlernen
  • Datenbanken und ihren Einsatz in der digitalen Welt

Interfaces und Adapterklassen



Interfaces

In Java ist ein Interface eine besondere Art von Referenztyp, der es ermöglicht, abstrakte Methoden zu definieren, die von den Klassen, die das Interface implementieren, implementiert werden müssen. Ein Interface dient als Vertragsvereinbarung zwischen einer Schnittstelle und den Klassen, die sie implementieren. Es beschreibt, welche Methoden eine Klasse implementieren muss, um als "konform" mit dem Interface zu gelten. Hier sind einige wichtige Eigenschaften von Java-Interfaces:

Interfaces sind ein mächtiges Konzept in Java und werden häufig verwendet, um Klassen zu abstrahieren, um mehrere Vererbungshierarchien zu vermeiden und um eine lose Kopplung zwischen Klassen zu erreichen. Sie spielen eine wichtige Rolle bei der Umsetzung des Prinzips der Schnittstellenseparierung und fördern eine flexible und erweiterbare Architektur in Java-Anwendungen.

  • Syntax: Ein Interface wird mit dem Schlüsselwort "interface" definiert, gefolgt von dem Namen des Interfaces und einer geöffneten geschweiften Klammer, die die Methoden und ggf. Konstanten enthält.
  • Abstrakte Methoden: In einem Interface können nur abstrakte Methoden (Methoden ohne Implementierung) deklariert werden. Diese Methoden haben keine Methodekörper, sondern werden nur durch ihre Signatur repräsentiert.
  • Implementierung: Klassen können ein oder mehrere Interfaces implementieren, indem sie das Schlüsselwort "implements" verwenden. Eine Klasse kann gleichzeitig mehrere Interfaces implementieren, aber sie kann nur von einer Basisklasse erben.
  • Mehrfachvererbung: Java unterstützt keine Mehrfachvererbung für Klassen, aber sie ermöglicht die Implementierung mehrerer Interfaces durch eine Klasse. Dies ermöglicht eine flexible Strukturierung des Codes, da eine Klasse verschiedene Funktionalitäten aus verschiedenen Interfaces erben kann.
  • Standardmethoden: Seit Java 8 können Interfaces auch sogenannte "default" und "static" Methoden enthalten. Eine "default" Methode ist eine implementierte Methode, die eine Standardimplementierung bereitstellt, die von den implementierenden Klassen überschrieben werden kann. Eine "static" Methode ist eine Methode, die statisch auf die Klasse selbst angewendet werden kann und nicht von Instanzen des Interfaces aufgerufen werden muss.

// Das Interface "Fahrzeug" definiert abstrakte Methoden für Fahrzeuge
interface Fahrzeug {
    void starten();
    void beschleunigen(int geschwindigkeit);
    void bremsen();
    void anhalten();
}

// Die Klasse "Auto" implementiert das Interface "Fahrzeug"
class Auto implements Fahrzeug {
    @Override
    public void starten() {
        System.out.println("Das Auto startet.");
    }

    @Override
    public void beschleunigen(int geschwindigkeit) {
        System.out.println("Das Auto beschleunigt auf " + geschwindigkeit + " km/h.");
    }

    @Override
    public void bremsen() {
        System.out.println("Das Auto bremst ab.");
    }

    @Override
    public void anhalten() {
        System.out.println("Das Auto hält an.");
    }
}

// Die Klasse "Fahrrad" implementiert ebenfalls das Interface "Fahrzeug"
class Fahrrad implements Fahrzeug {
    @Override
    public void starten() {
        System.out.println("Das Fahrrad startet.");
    }

    @Override
    public void beschleunigen(int geschwindigkeit) {
        System.out.println("Das Fahrrad beschleunigt auf " + geschwindigkeit + " km/h.");
    }

    @Override
    public void bremsen() {
        System.out.println("Das Fahrrad bremst ab.");
    }

    @Override
    public void anhalten() {
        System.out.println("Das Fahrrad hält an.");
    }
}

public class Main {
    public static void main(String[] args) {
        // Objekte der Klassen "Auto" und "Fahrrad" erstellen
        Auto meinAuto = new Auto();
        Fahrrad meinFahrrad = new Fahrrad();

        // Methoden der Klassen aufrufen
        meinAuto.starten();
        meinAuto.beschleunigen(60);
        meinAuto.bremsen();
        meinAuto.anhalten();

        meinFahrrad.starten();
        meinFahrrad.beschleunigen(20);
        meinFahrrad.bremsen();
        meinFahrrad.anhalten();
    }
}


In diesem Beispiel haben wir ein Interface "Fahrzeug" definiert, das vier abstrakte Methoden für Fahrzeuge deklariert: starten(), beschleunigen(), bremsen() und anhalten(). Die Klassen "Auto" und "Fahrrad" implementieren dieses Interface und müssen daher alle Methoden des Interfaces implementieren.

In der main-Methode erstellen wir Objekte der Klassen "Auto" und "Fahrrad" und rufen dann die entsprechenden Methoden auf. Da beide Klassen das Interface "Fahrzeug" implementieren, können sie auf die Methoden des Interfaces zugreifen und die Funktionalität für ihre spezifischen Fahrzeugtypen bereitstellen.

Default- und statische Methoden

In Java gibt es seit Java 8 sogenannte "Default" und "statische" Methoden in Interfaces. Diese Erweiterung wurde eingeführt, um die Rückwärtskompatibilität von bestehenden Interfaces zu gewährleisten und die Möglichkeit zu bieten, zusätzliche Methoden in Interfaces hinzuzufügen, ohne alle implementierenden Klassen zu ändern.

Default-Methoden: Eine "Default"-Methode ist eine Methode in einem Interface, die eine Standardimplementierung bereitstellt. Wenn eine Klasse ein Interface implementiert und diese Methode nicht explizit in der Klasse überschreibt, wird die Standardimplementierung aus dem Interface verwendet. Default-Methoden werden mit dem Schlüsselwort default vor der Methodendefinition gekennzeichnet.


interface Greeting {
    default void sayHello() {
        System.out.println("Hello, world!");
    }
}

class EnglishGreeting implements Greeting {
    // Die Klasse überschreibt die Default-Methode nicht.
}

class GermanGreeting implements Greeting {
    @Override
    public void sayHello() {
        System.out.println("Hallo, Welt!");
    }
}

public class Main {
    public static void main(String[] args) {
        Greeting english = new EnglishGreeting();
        Greeting german = new GermanGreeting();

        english.sayHello(); // Ausgabe: Hello, world!
        german.sayHello(); // Ausgabe: Hallo, Welt!
    }
}

Statische Methoden

Eine "statische" Methode in einem Interface ist eine Methode, die mit dem Interface selbst und nicht mit einer spezifischen Instanz des Interfaces verbunden ist. Statische Methoden können nur innerhalb des Interfaces aufgerufen werden und nicht von den implementierenden Klassen. Sie werden mit dem Schlüsselwort static vor der Methodendefinition gekennzeichnet.


interface MathOperations {
    static int add(int a, int b) {
        return a + b;
    }

    static int subtract(int a, int b) {
        return a - b;
    }
}

public class Main {
    public static void main(String[] args) {
        int result1 = MathOperations.add(5, 3);
        int result2 = MathOperations.subtract(8, 4);

        System.out.println("Addition: " + result1); // Ausgabe: 8
        System.out.println("Subtraktion: " + result2); // Ausgabe: 4
    }
}


Beachten Sie, dass statische Methoden in Interfaces nicht von implementierenden Klassen überschrieben werden können, da sie nur in Verbindung mit dem Interface selbst stehen und nicht mit einer konkreten Implementierung.

Abstrakte Klassen und Interfaces im Vergleich

Abstrakte Klassen und Interfaces sind zwei Konzepte in der objektorientierten Programmierung, die dazu dienen, gemeinsame Funktionalität und Verhaltensweisen in Java zu definieren. Obwohl sie ähnliche Ziele haben, gibt es jedoch einige wichtige Unterschiede zwischen ihnen: Abstrakte Klassen:

  • Definition: Eine abstrakte Klasse ist eine Klasse, die mit dem Schlüsselwort abstract gekennzeichnet ist. Sie kann abstrakte (nicht implementierte) Methoden sowie konkrete (implementierte) Methoden enthalten.
  • Methoden: Abstrakte Klassen können abstrakte und konkrete Methoden haben. Abstrakte Methoden haben keine Implementierung in der abstrakten Klasse und müssen von den abgeleiteten Klassen implementiert werden. Konkrete Methoden können eine Standardimplementierung bereitstellen, die von den abgeleiteten Klassen überschrieben werden kann.
  • Instanzen: Eine abstrakte Klasse kann keine Instanzen erstellen. Sie dient als Vorlage für abgeleitete Klassen.
  • Vererbung: Eine Klasse kann nur von einer abstrakten Klasse erben, da Java keine Mehrfachvererbung für Klassen unterstützt.

abstract class Shape {
    abstract void draw(); // Abstrakte Methode

    void displayInfo() { // Konkrete Methode
        System.out.println("This is a shape.");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a circle.");
    }
}

class Square extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a square.");
    }
}


Interfaces:

  • Definition: Ein Interface ist eine Sammlung von abstrakten Methoden und/oder statischen und "default"-Methoden. Es definiert nur die Methodensignaturen und keine Implementierungen.
  • Methoden: Ein Interface kann nur abstrakte Methoden und/oder statische und "default"-Methoden enthalten. Alle Methoden sind implizit abstrakt und müssen von den implementierenden Klassen implementiert werden.
  • Instanzen: Ein Interface kann keine Instanzen erstellen, da es keine Implementierungen enthält.
  • Vererbung: Eine Klasse kann mehrere Interfaces implementieren. Es ermöglicht eine Mehrfachvererbung von Verhaltensweisen.

interface Drawable {
    void draw(); // Abstrakte Methode
}

interface Movable {
    void move(); // Abstrakte Methode
}

class Circle implements Drawable, Movable {
    @Override
    public void draw() {
        System.out.println("Drawing a circle.");
    }

    @Override
    public void move() {
        System.out.println("Moving the circle.");
    }
}

Zusammenfassend lassen sich Abstrakte Klassen verwenden, wenn eine gemeinsame Basisimplementierung für mehrere verwandte Klassen erforderlich ist und wenn die abstrakte Klasse selbst keine Instanzen erstellt. Interfaces hingegen bieten eine Möglichkeit, allgemeine Funktionalitäten für nicht verwandte Klassen bereitzustellen und ermöglichen Mehrfachvererbung von Verhaltensweisen. Die Wahl zwischen einer abstrakten Klasse und einem Interface hängt von der spezifischenSituation und der gewünschten Beziehung zwischen den Klassen ab.

In Java ist eine Adapterklasse ein Entwurfsmuster, das verwendet wird, um die Implementierung mehrerer Schnittstellen zu vereinfachen. Eine Adapterklasse implementiert eine oder mehrere Schnittstellen und stellt leere oder Standardimplementierungen für alle Methoden dieser Schnittstellen bereit. Dadurch können andere Klassen nur die Methoden implementieren, die für sie relevant sind, ohne alle Methoden der Schnittstelle überschreiben zu müssen.

Das Adaptermuster ist besonders nützlich, wenn Sie eine Klasse haben, die eine Schnittstelle implementieren soll, aber Sie möchten nicht alle Methoden dieser Schnittstelle in Ihrer Klasse implementieren, weil sie für Ihre Implementierung nicht relevant sind.

Hier ist ein einfaches Beispiel, um das Konzept einer Adapterklasse zu veranschaulichen:


// Schnittstelle "DatenbankAdapter" mit mehreren abstrakten Methoden
interface DatenbankAdapter {
    void connect();
    void disconnect();
    void executeQuery(String query);
    void getResult();
}

// Adapterklasse "DatenbankAdapterImpl" mit Standardimplementierungen aller
//Methoden der Schnittstelle "DatenbankAdapter"

class DatenbankAdapterImpl implements DatenbankAdapter {
    @Override
    public void connect() {
        System.out.println("Verbindung zur Datenbank hergestellt.");
    }

    @Override
    public void disconnect() {
        System.out.println("Verbindung zur Datenbank getrennt.");
    }

    @Override
    public void executeQuery(String query) {
        System.out.println("SQL-Abfrage ausgeführt: " + query);
    }

    @Override
    public void getResult() {
        System.out.println("Abfrageergebnisse erhalten.");
    }
}

// Eine Klasse, die das Adaptermuster verwendet
class DatenbankClient {
    private DatenbankAdapter adapter;

    public DatenbankClient(DatenbankAdapter adapter) {
        this.adapter = adapter;
    }

    public void performDatabaseOperations(String query) {
        adapter.connect();
        adapter.executeQuery(query);
        adapter.getResult();
        adapter.disconnect();
    }
}

public class Main {
    public static void main(String[] args) {
        DatenbankAdapter adapter = new DatenbankAdapterImpl();
        DatenbankClient client = new DatenbankClient(adapter);

        // Client führt Datenbankoperationen mit dem Adapter aus
        client.performDatabaseOperations("SELECT * FROM employees");
    }
}


In diesem Beispiel gibt es eine Schnittstelle "DatenbankAdapter", die mehrere abstrakte Methoden definiert. Die Adapterklasse "DatenbankAdapterImpl" implementiert diese Schnittstelle und bietet Standardimplementierungen für alle Methoden. Die Klasse "DatenbankClient" verwendet das Adaptermuster, um mit dem Adapter "DatenbankAdapter" zu arbeiten. Indem sie den Adapter als Parameter im Konstruktor akzeptiert, kann die Klasse "DatenbankClient" die gewünschten Datenbankoperationen ausführen, ohne sich um die Implementierung aller Methoden in "DatenbankAdapter" kümmern zu müssen. Stattdessen ruft sie nur die benötigten Methoden des Adapters auf, während die anderen Methoden in der Adapterklasse standardmäßig leer bleiben.

Datentyp zur Sicherheit überprüfen

In Java ist instanceof ein Operator, der zur Überprüfung verwendet wird, ob ein Objekt einer bestimmten Klasse oder einer ihrer Unterklassen angehört. Der Operator gibt einen booleschen Wert (true oder false) zurück, je nachdem, ob das Objekt eine Instanz der angegebenen Klasse ist oder nicht. Die Syntax des instanceof-Operators lautet:


obj instanceof Klasse

Hier ist ein Beispiel, um den instanceof-Operator zu veranschaulichen:


class Fahrzeug {}

class Auto extends Fahrzeug {}

class Fahrrad extends Fahrzeug {}

public class Main {
    public static void main(String[] args) {
        Fahrzeug fahrzeug1 = new Auto();
        Fahrzeug fahrzeug2 = new Fahrrad();

        // Überprüfung, ob die Objekte Instanzen der Klassen Auto und Fahrrad sind
        System.out.println(fahrzeug1 instanceof Auto); // Ausgabe: true
        System.out.println(fahrzeug1 instanceof Fahrrad); // Ausgabe: false

        System.out.println(fahrzeug2 instanceof Auto); // Ausgabe: false
        System.out.println(fahrzeug2 instanceof Fahrrad); // Ausgabe: true
    }
}


In diesem Beispiel haben wir drei Klassen: Fahrzeug, Auto und Fahrrad. Die Klassen Auto und Fahrrad erben von der Klasse Fahrzeug.

Im main-Programm erstellen wir zwei Objekte, fahrzeug1 und fahrzeug2, die Instanzen der Klassen Auto bzw. Fahrrad sind.

Mit dem instanceof-Operator überprüfen wir, ob fahrzeug1 eine Instanz der Klasse Auto ist, und erhalten als Ergebnis true. Dasselbe gilt für die Überprüfung von fahrzeug2 auf Fahrrad.

Der instanceof-Operator ist besonders nützlich, wenn Sie mit polymorphen Objekten arbeiten und zur Laufzeit bestimmen müssen, welcher spezifische Typ ein Objekt hat, um entsprechende Operationen durchzuführen oder Abzweigungen im Code auszuführen. Beachten Sie jedoch, dass die Verwendung von instanceof häufig darauf hindeuten kann, dass eine unvorteilhafte Designentscheidung getroffen wurde, und es sollte nur in bestimmten Situationen und mit Bedacht verwendet werden.

Aufgabe 1: Geometrische Formen

Erstellen Sie ein Interface namens `Shape`, das die Methoden `calculateArea()` und `calculatePerimeter()` definiert. Erstellen Sie dann Klassen für verschiedene geometrische Formen wie Kreis, Rechteck und Dreieck, die das `Shape`-Interface implementieren.

1- Definieren Sie das `Shape`-Interface mit zwei Methoden


  double calculateArea();
  double calculatePerimeter();

2- Implementieren Sie diese Methoden in den Klassen Circle ,Rectangle und Triangle


class Circle implements Shape {
private double radius;

// TODO Konstruktor

@Override
// TODO

@Override
// TODO
}


class Rectangle implements Shape {
private double width;
private double height;

// TODO Konstruktor

@Override
// TODO

@Override
// TODO

}

class Triangle implements Shape {
private double side1;
private double side2;
private double side3;

// TODO Konstruktor

@Override
// TODO

@Override
// TODO


}
  

3. Nutzen Sie die abgeleiteten Klassen, um Flächeninhalte und Umfänge verschiedener geometrischer Formen zu berechnen:


public class Main {
  public static void main(String[] args) {
    Shape circle = new Circle(5.0);
    Shape rectangle = new Rectangle(4.0, 6.0);
    Shape triangle = new Triangle(3.0, 4.0, 5.0);

    System.out.println("Circle area: " + circle.calculateArea());
    System.out.println("Rectangle perimeter: " + rectangle.calculatePerimeter());
    System.out.println("Triangle area: " + triangle.calculateArea());
  }
}

In dieser Aufgabe üben Sie die Verwendung von Interfaces, indem Sie das `Shape`-Interface erstellen und von verschiedenen geometrischen Formen implementieren lassen. Dies ermöglicht es Ihnen, eine einheitliche Schnittstelle für die Berechnung von Flächeninhalten und Umfängen zu definieren und diese in den abgeleiteten Klassen zu implementieren.

 

Aufgabe 2: MediaControl

- Erstellen Sie ein Interface MediaControl mit den Methoden start(), stop() und zeigen().
- Implementieren Sie dieses Interface in den Klassen Bild, Radio und Fernsehen. Fügen Sie eine Adapter-Klasse hinzu, um Standardimplementierungen der Methoden bereitzustellen, damit abgeleitete Klassen nur die notwendigen Methoden überschreiben können.
- Schreiben Sie eine Hauptklasse, die Objekte dieser Klassen erstellt und die Methoden aufruft.

Interface MediaControl:

Methoden:
void start();: Startet das Medium.
void stop();: Stoppt das Medium.
void zeigen();: Zeigt das Medium an.

Adapter-Klasse MediaAdapter:

Implementiert das Interface MediaControl.
Bietet Standardimplementierungen der Methoden an, die nichts tun.

Klassen Bild, Radio und Fernsehen:

Erben von der Adapter-Klasse MediaAdapter.
Überschreiben nur die notwendigen Methoden.

Hauptklasse:

-Erstellen Sie Objekte der Klassen Bild, Radio und Fernsehen.
-Rufen Sie die Methoden start(), stop() und zeigen() für jedes Objekt auf.
-Das Ergebnis soll wie folgt aussehen. Medium wird durch setText() geliefert und Classen-Name über getPath()

Medium 1:
Classname: Bild , zeigen

Medium 2:
Classname: Radio , starten
Classname: Radio , stopen

Medium 3:
Classname: Fernsehen , starten
Classname: Fernsehen , zeigen
Classname: Fernsehen , stopen