Kurs
OOP in Java: Klassen, Objekte, Kapselung, Vererbung und Abstraktion
Java ist durchgehend eine der drei beliebtesten Sprachen der Welt. Seine Verbreitung in Bereichen wie der Entwicklung von Unternehmenssoftware, mobilen Android-Apps und groß angelegten Webanwendungen ist unübertroffen. Sein starkes Typensystem, sein umfangreiches Ökosystem und seine "write once, run anywhere"-Fähigkeit machen es besonders attraktiv für den Aufbau robuster, skalierbarer Systeme. In diesem Artikel werden wir untersuchen, wie die objektorientierten Programmierfunktionen von Java es Entwicklern ermöglichen, diese Fähigkeiten effektiv zu nutzen und durch die richtige Organisation und Wiederverwendung von Code wartbare und skalierbare Anwendungen zu erstellen.
Ein Hinweis zum Organisieren und Ausführen von Java-Code
Bevor wir mit dem Schreiben von Code beginnen, sollten wir einige Einstellungen vornehmen.
Wie bei der Syntax gibt es auch bei Java strenge Regeln für die Codeorganisation.
Zunächst muss jede öffentliche Klasse in einer eigenen Datei stehen, die genau wie die Klasse benannt ist, aber die Erweiterung.java
hat. Wenn ich also eine Laptop-Klasse schreiben will, muss der Dateiname Laptop.java
- lauten , wobei Groß- und Kleinschreibung zu beachten sind. Du kannst nicht-öffentliche Klassen in der gleichen Datei haben, aber es ist am besten, sie zu trennen. Ich weiß, dass wir zu weit vorpreschen und über die Organisation von Klassen sprechen, noch bevor wir sie schreiben, aber es ist eine gute Idee, sich vorher eine grobe Vorstellung davon zu machen, wo man die Dinge unterbringen will.
Alle Java-Projekte müssen eine Main.java
Datei mit der KlasseMain haben . Hier testest du deine Klassen, indem du aus ihnen Objekte erstellst.
Um Java-Code auszuführen, verwenden wir IntelliJ IDEA, eine beliebte Java-IDE. Nach der Installation von IntelliJ:
- Erstelle ein neues Java-Projekt (Datei > Neu > Projekt)
- Klicken Sie mit der rechten Maustaste auf den Ordner src, um die
Main.java
Datei und füge den folgenden Inhalt ein:
public class Main {
public static void main(String[] args) {
// Create and tests objects here
}
}
Wenn es um Klassen geht, schreiben wir den Code in andere Dateien als die Main.java
Datei. Wenn es aber um das Erstellen und Testen von Objekten geht, wechseln wir zu Main.java
.
Um das Programm zu starten, klickst du auf den grünen Play-Button neben der Hauptmethode:
Die Ausgabe wird unten im Fenster des Ausführungswerkzeugs angezeigt.
Wenn du ganz neu in Java bist, schau dir unseren Kurs Einführung in Javader die Grundlagen der Java-Datentypen und des Kontrollflusses behandelt, bevor du fortfährst.
Ansonsten lass uns gleich loslegen.
Java-Klassen und -Objekte
Was genau sind also Klassen?
Klassen sind Programmierkonstrukte in Java zur Darstellung von Konzepten aus der realen Welt. Betrachte zum Beispiel diese MenuItem-Klasse (erstelle eine Datei, um diese Klasse in deiner IDE zu schreiben):
public class MenuItem {
public String name;
public double price;
}
Die Klasse gibt uns eine Blaupause oder eine Vorlage, um verschiedene Menüpunkte in einem Restaurant darzustellen. Indem wir die beiden Attribute der Klasse name
und price
ändern, können wir unzählige Menüobjekte wie einen Burger oder einen Salat erstellen .
Um also eine Klasse in Java zu erstellen, beginnst du mit einer Zeile, die die Zugriffsebene der Klasse beschreibt (private
,public
oder protected
), gefolgt von dem Klassennamen. Unmittelbar nach der Klammer umreißt du die Eigenschaften deiner Klasse.
Aber wie erstellen wir Objekte, die zu dieser Klasse gehören? Java ermöglicht dies durch Konstruktormethoden:
public class MenuItem {
public String name;
public double price;
// Constructor
public MenuItem(String name, double price) {
this.name = name;
this.price = price;
}
}
Ein Konstruktor ist eine spezielle Methode, die aufgerufen wird, wenn wir ein neues Objekt aus einer Klasse erstellen. Sie initialisiert die Attribute des Objekts mit den von uns angegebenen Werten. Im obigen Beispiel nimmt der Konstruktor einen Namen und einen Preis als Parameter und weist sie den Feldern des Objekts zu.
Die Syntax für den Konstruktor unterscheidet sich von anderen Klassenmethoden, da du keinen Rückgabetyp angeben musst. Außerdem muss der Konstruktor denselben Namen wie die Klasse haben und er sollte dieselbe Anzahl von Attributen haben, die du nach der Klassendefinition deklariert hast. Oben hat der Konstruktor zwei Attribute erstellt, weil wir zwei nach der Klassendefinition deklariert haben: name
und price
.
Nachdem du deine Klasse und ihren Konstruktor geschrieben hast, kannst du in deiner Hauptmethode Instanzen (Objekte) von ihr erstellen:
public class Main {
public static void main(String[] args) {
// Create objects here
MenuItem burger = new MenuItem("Burger", 3.5);
MenuItem salad = new MenuItem("Salad", 2.5);
System.out.println(burger.name + ", " + burger.price);
}
}
Ausgabe:
Burger, 3.5
Oben haben wir zwei MenuItem
Objekte in den Variablen burger
und salad
erstellt . Wie in Java erforderlich, muss der Typ der Variablen deklariert werden, der MenuItem
ist. Um eine Instanz unserer Klasse zu erstellen, schreiben wir das Schlüsselwort new und rufen anschließend die Konstruktormethode auf.
Neben dem Konstruktor kannst du auch reguläre Methoden erstellen, die deiner Klasse ein Verhalten verleihen. Unten fügen wir zum Beispiel eine Methode hinzu, um den Gesamtpreis nach Steuern zu berechnen:
public class MenuItem {
public String name;
public double price;
// Constructor
public MenuItem(String name, double price) {
this.name = name;
this.price = price;
}
// Method to calculate price after tax
public double getPriceAfterTax() {
double taxRate = 0.08; // 8% tax rate
return price + (price * taxRate);
}
}
Jetzt können wir den Gesamtpreis inklusive Steuern berechnen:
public class Main {
public static void main(String[] args) {
MenuItem burger = new MenuItem("Burger", 3.5);
System.out.println("Price after tax: $" + burger.getPriceAfterTax());
}
}
Ausgabe:
Price after tax: $3.78
Verkapselung
Der Zweck von Klassen ist es, einen Bauplan für die Erstellung von Objekten zu erstellen. Diese Objekte können dann von anderen Skripten oder Programmen verwendet werden. Unsere MenuItem
Objekte können zum Beispiel von einer Benutzeroberfläche verwendet werden, die ihren Namen, ihren Preis und ihr Bild auf einem Bildschirm anzeigt.
Aus diesem Grund müssen wir unsere Klassen so gestalten, dass ihre Instanzen nur so verwendet werden können, wie wir es beabsichtigt haben. Im Moment ist unsere MenuItem
Klasse sehr einfach und fehleranfällig. Eine Person könnte Objekte mit lächerlichen Eigenschaften erschaffen, wie z.B. einen Apfelkuchen zu einem schlechten Preis oder ein Sandwich für eine Million Dollar:
// Inside Main.java
MenuItem applePie = new MenuItem("Apple Pie", -5.99); // Negative price!
MenuItem sandwich = new MenuItem("Sandwich", 1000000); // Unreasonably expensive
System.out.println("Apple pie price: $" + applePie.price);
System.out.println("Sandwich price: $" + sandwich.price);
Nachdem du also eine Klasse geschrieben hast, musst du zunächst ihre Attribute schützen, indem du ihre Erstellung und den Zugriff darauf einschränkst. Zunächst wollen wir nur positive Werte für den Preis zulassen und einen Maximalwert festlegen, damit nicht versehentlich lächerlich teure Artikel angezeigt werden.
In Java können wir dies mit Hilfe von Setter-Methoden erreichen:
public class MenuItem {
private String name;
private double price;
private static final double MAX_PRICE = 100.0;
public MenuItem(String name, double price) {
this.name = name;
setPrice(price);
}
public void setPrice(double price) {
if (price < 0) {
throw new IllegalArgumentException("Price cannot be negative");
}
if (price > MAX_PRICE) {
throw new IllegalArgumentException("Price cannot exceed $" + MAX_PRICE);
}
this.price = price;
}
}
Schauen wir uns an, was in dem obigen Codeblock neu ist:
1. Wir haben die Attribute privat gemacht, indem wir das Schlüsselwort private
hinzugefügt haben. Das bedeutet, dass auf sie nur innerhalb der MenuItem-Klasse zugegriffen werden kann. Die Verkapselung beginnt mit diesem entscheidenden Schritt.
2. Wir haben eine neue Konstante hinzugefügt: MAX_PRICE
:
- privat (nur innerhalb der Klasse zugänglich)
- statisch (gemeinsam für alle Instanzen)
- final (kann nach der Initialisierung nicht geändert werden)
- auf $100,0 als angemessenen Höchstpreis setzen
3. Wir haben eine setPrice()
Methode hinzugefügt, die:
- Nimmt einen Preisparameter an
- Bestätigt, dass der Preis nicht negativ ist
- Prüft, ob der Preis MAX_PRICE nicht übersteigt
- Wirft IllegalArgumentException mit beschreibenden Meldungen, wenn die Validierung fehlschlägt
- Legt den Preis nur fest, wenn alle Validierungen erfolgreich sind
4. Wir haben den Konstruktor so geändert, dass er setPrice()
verwendet, anstatt den Preis direkt zuzuweisen. Dadurch wird sichergestellt, dass die Preisvalidierung während der Objekterstellung erfolgt.
Wir haben gerade einen der Grundpfeiler eines guten objektorientierten Designs implementiert - dieKapselung. Dieses Paradigma erzwingt Data Hiding und einen kontrollierten Zugriff auf Objektattribute und stellt sicher, dass interne Implementierungsdetails vor externen Eingriffen geschützt sind und nur über wohldefinierte Schnittstellen geändert werden können.
Um das zu verdeutlichen, wenden wir die Kapselung auf das Attributname an. Stell dir vor, wir haben einen Coffee Shop, der nur Lattes, Cappuccinos, Espressos, Americanos und Mokkas serviert.
Unsere Menüpunktnamen können also nur einer der Punkte in dieser Liste sein. Hier ist, wie wir das im Code durchsetzen können:
// Rest of the class here
...
private static final String[] VALID_NAMES = {"latte", "cappuccino", "espresso", "americano", "mocha"};
private String name;
public void setName(String name) {
String lowercaseName = name.toLowerCase();
for (String validName : VALID_NAMES) {
if (validName.equals(lowercaseName)) {
this.name = name;
return;
}
}
throw new IllegalArgumentException("Invalid drink name. Must be one of: " + String.join(", ", VALID_NAMES));
}
Der obige Code implementiert die Namensvalidierung für Menüpunkte in einem Coffee Shop. Lass uns das mal aufschlüsseln:
1. Zuerst wird ein privates, statisches, finales Array VALID_NAMES
definiert, das die einzigen erlaubten Getränkenamen enthält: Latte, Cappuccino, Espresso, Americano und Mokka. Diese Anordnung ist:
- privat: nur innerhalb der Klasse zugänglich
- statisch: gemeinsam für alle Instanzen
- final: kann nach der Initialisierung nicht geändert werden
2. Er deklariert ein privates String-Feld, um den Namen des Getränks zu speichern
3. Die Methode setName()
implementiert die Validierungslogik:
- Nimmt einen String-Namen als Parameter
- Konvertiert es in Kleinbuchstaben, um den Vergleich unabhängig von Groß- und Kleinschreibung zu machen
- Schleifen durch das Array
VALID_NAMES
- Wenn eine Übereinstimmung gefunden wird, setzt sie den Namen und gibt
- Wenn keine Übereinstimmung gefunden wird, wird eine IllegalArgumentException mit einer beschreibenden Meldung geworfen, die alle gültigen Optionen auflistet
Hier ist die ganze Klasse bis jetzt:
public class MenuItem {
private String name;
private double price;
private static final String[] VALID_NAMES = {"latte", "cappuccino", "espresso", "americano", "mocha"};
private static final double MAX_PRICE = 100.0;
public MenuItem(String name, double price) {
setName(name);
setPrice(price);
}
public void setName(String name) {
String lowercaseName = name.toLowerCase();
for (String validName : VALID_NAMES) {
if (validName.equals(lowercaseName)) {
this.name = name;
return;
}
}
throw new IllegalArgumentException("Invalid drink name. Must be one of: " + String.join(", ", VALID_NAMES));
}
public void setPrice(double price) {
if (price < 0) {
throw new IllegalArgumentException("Price cannot be negative");
}
if (price > MAX_PRICE) {
throw new IllegalArgumentException("Price cannot exceed $" + MAX_PRICE);
}
this.price = price;
}
}
Nachdem wir die Art und Weise, wie Attribute erstellt werden, geschützt haben, wollen wir auch den Zugriff auf sie schützen. Dies geschieht mit Hilfe von Getter-Methoden:
public class MenuItem {
// Rest of the code here
...
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
Getter-Methoden bieten kontrollierten Zugriff auf private Attribute einer Klasse. Sie lösen das Problem des direkten Attributzugriffs, der zu unerwünschten Änderungen führen und die Kapselung aufbrechen kann.
Ohne Getter könnten wir zum Beispiel direkt auf Attribute zugreifen:
MenuItem item = new MenuItem("Latte", 4.99);
String name = item.name; // Direct access to attribute
item.name = "INVALID"; // Can modify directly, bypassing validation
Mit Gettern erzwingen wir den richtigen Zugriff:
MenuItem item = new MenuItem("Latte", 4.99);
String name = item.getName(); // Controlled access through getter
// item.name = "INVALID"; // Not allowed - must use setName() which validates
Diese Einkapselung:
- Schützt die Datenintegrität, indem es ungültige Änderungen verhindert
- Ermöglicht es uns, die interne Implementierung zu ändern, ohne den Code zu beeinflussen, der die Klasse verwendet
- Bietet einen einzigen Zugangspunkt, der bei Bedarf zusätzliche Logik enthalten kann
- Macht den Code wartbarer und weniger anfällig für Fehler
Vererbung
Unsere Klasse fängt an, gut auszusehen, aber es gibt ziemlich viele Probleme mit ihr. Für ein großes Restaurant, das viele verschiedene Gerichte und Getränke anbietet, ist die Klasse zum Beispiel nicht flexibel genug.
Wenn wir verschiedene Arten von Lebensmitteln hinzufügen wollen, stoßen wir auf mehrere Herausforderungen. Einige Gerichte können zum Mitnehmen zubereitet werden, während andere sofort verzehrt werden müssen. Die Menüpunkte können unterschiedliche Preise und Rabatte haben. Es kann sein, dass die Gerichte eine Temperaturkontrolle oder eine besondere Lagerung benötigen. Die Getränke können heiß oder kalt sein und die Zutaten können individuell angepasst werden. Die Artikel müssen eventuell mit Allergeninformationen und Portionsangaben versehen werden. Das aktuelle System wird diesen unterschiedlichen Anforderungen nicht gerecht.
Die Vererbung bietet eine elegante Lösung für all diese Probleme. Sie ermöglicht es uns, spezielle Versionen von Menüpunkten zu erstellen, indem wir eine Basisklasse MenuItem
mit gemeinsamen Attributen definieren und dann Unterklassen erstellen, die diese Grundlagen erben und einzigartige Funktionen hinzufügen.
Wir könnten z.B. eine Klasse Drink
für Getränke mit Temperaturoptionen, eine Klasse Food
für Artikel, die sofort verzehrt werden müssen, und eine Klasse Dessert
für Artikel mit besonderen Lagerungsanforderungen haben - die alle die Kernfunktionalität von Menüpunkten übernehmen.
Erweitern von Klassen
Lasst uns diese Ideen umsetzen, beginnend mit Drink
:
public class Drink extends MenuItem {
private boolean isCold;
public Drink(String name, double price, boolean isCold) {
this.name = name;
this.price = price;
this.isCold = isCold;
}
public boolean getIsCold() {
return isCold;
}
public void setIsCold(boolean isCold) {
this.isCold = isCold;
}
}
Um eine Kindklasse zu definieren, die von einer Elternklasse erbt, verwenden wir das Schlüsselwortextends
nach dem Namen der Kindklasse, gefolgt von der Elternklasse. Nach der Klassendefinition definieren wir alle neuen Attribute, die dieses Kind hat, und implementieren seinen Konstruktor.
Beachte aber, dass wir die Initialisierung von name
und price
zusammen mit isCold
wiederholen müssen . Das ist nicht ideal, denn die übergeordnete Klasse kann Hunderte von Attributen haben. Außerdem wird der obige Code einen Fehler auslösen, wenn du ihn kompilierst, weil das nicht der richtige Weg ist, um Attribute der Elternklasse zu initialisieren. Der richtige Weg wäre, das Schlüsselwort super
zu verwenden:
public class Drink extends MenuItem {
private boolean isCold;
public Drink(String name, double price, boolean isCold) {
super(name, price);
this.isCold = isCold;
}
public boolean getIsCold() {
return isCold;
}
public void setIsCold(boolean isCold) {
this.isCold = isCold;
}
}
Das Schlüsselwortsuper
wird verwendet, um den Konstruktor der Elternklasse aufzurufen. In diesem Fall ruft super(name, price)
den Konstruktor vonMenuItem
auf, um diese Attribute zu initialisieren und vermeidet so doppelten Code. Wir müssen nur das neue Attribut isCold
initialisieren, das für die KlasseDrink
gilt .
Das Schlüsselwort ist sehr flexibel, denn du kannst es verwenden, um in jedem Teil der Kindklasse auf die Elternklasse zu verweisen. Um zum Beispiel eine übergeordnete Methode aufzurufen, verwendest du super.methodName()
, während super.attributeName
für Attribute steht.
Überschreiben von Methoden
Nehmen wir nun an, wir wollen unseren Klassen eine neue Methode hinzufügen, um den Gesamtpreis nach Steuern zu berechnen. Da verschiedene Menüpunkte unterschiedliche Steuersätze haben können (z. B. zubereitete Speisen im Vergleich zu verpackten Getränken), können wir mithilfe von Methodenüberschreibungen spezifische Steuerberechnungen in jeder Unterklasse implementieren, während wir in der Oberklasse einen gemeinsamen Methodennamen beibehalten.
So sieht das aus:
public class MenuItem {
// Rest of the MenuItem class
public double calculateTotalPrice() {
// Default tax rate of 10%
return price * 1.10;
}
}
public class Food extends MenuItem {
private boolean isVegetarian;
public Food(String name, double price, boolean isVegetarian) {
super(name, price);
this.isVegetarian = isVegetarian;
}
@Override
public double calculateTotalPrice() {
// Food has 15% tax
return super.getPrice() * 1.15;
}
}
public class Drink extends MenuItem {
private boolean isCold;
public Drink(String name, double price, boolean isCold) {
super(name, price);
this.isCold = isCold;
}
@Override
public double calculateTotalPrice() {
// Drinks have 8% tax
return super.getPrice() * 1.08;
}
}
In diesem Beispiel erlaubt das Methoden-Overriding jeder Unterklasse, ihre eigene Implementierung von calculateTotalPrice()
:
Die Basis MenuItem
Klasse definiert eine Standardsteuerberechnung von 10%.
Wenn Food
und Drink
Klassen erweitern MenuItem
erweitern, setzen sie diese Methode außer Kraft, um ihre eigenen Steuersätze zu implementieren:
- Für Lebensmittel gilt ein höherer Steuersatz von 15 %.
- Getränke haben einen niedrigeren Steuersatz von 8%
Die Anmerkung@Override
wird verwendet, um explizit darauf hinzuweisen, dass diese Methoden die Methode der Elternklasse außer Kraft setzen. Das hilft, Fehler abzufangen, wenn die Signatur der Methode nicht mit der übergeordneten Klasse übereinstimmt.
Jede Unterklasse kann weiterhin auf den Preis der Elternklasse zugreifen, indem sie super.getPrice()
zugreifen. Das zeigt, wie überschriebene Methoden die Funktionalität der Elternklasse nutzen und gleichzeitig ihr eigenes Verhalten hinzufügen können.
Kurz gesagt ist die Methodenüberschreibung ein integraler Bestandteil der Vererbung, der es Unterklassen ermöglicht, ihre eigene Implementierung von Methoden bereitzustellen, die in der Elternklasse definiert sind, und so ein spezifischeres Verhalten zu ermöglichen, während die gleiche Methodensignatur beibehalten wird.
Abstrakte Klassen
Unsere MenuItem
Klassenhierarchie funktioniert, aber es gibt ein Problem: Sollte jeder in der Lage sein, ein einfaches MenuItem
Objekt zu erstellen ? Schließlich ist in unserem Restaurant jeder Menüpunkt entweder ein Essen oder ein Getränk - es gibt keine "allgemeinen Menüpunkte".
Wir können dies verhindern, indem wir MenuItem
zu einer abstrakten Klasse machen . Eine abstrakte Klasse bietet nur einen Basisentwurf - sie kann nur als Elternklasse für die Vererbung verwendet und nicht direkt instanziiert werden.
Um zu MenuItem
abstrakt zu machen, fügen wir das abstract
Schlüsselwort nach dem Zugriffsmodifikator hinzu:
public abstract class MenuItem {
private String name;
private double price;
public MenuItem(String name, double price) {
setName(name);
setPrice(price);
}
// Existing getters/setters remain the same
// Make this method abstract - every subclass MUST implement it
public abstract double calculateTotalPrice();
}
Abstrakte Klassen können auch abstrakte Methoden haben, wie calculateTotalPrice()
oben. Diese abstrakten Methoden dienen als Verträge, die Unterklassen dazu zwingen, ihre Implementierungen bereitzustellen. Mit anderen Worten: Jede abstrakte Methode in einer abstrakten Klasse muss von den untergeordneten Klassen implementiert werden.
Schreiben wir also um Food
und Drink
mit diesen Änderungen im Hinterkopf:
public class Food extends MenuItem {
private boolean isVegetarian;
public Food(String name, double price, boolean isVegetarian) {
super(name, price);
this.isVegetarian = isVegetarian;
}
@Override
public double calculateTotalPrice() {
return getPrice() * 1.15; // 15% tax
}
}
public class Drink extends MenuItem {
private boolean hasCaffeine;
public Drink(String name, double price, boolean hasCaffeine) {
super(name, price);
this.hasCaffeine = hasCaffeine;
}
@Override
public double calculateTotalPrice() {
return getPrice() * 1.10; // 10% tax
}
}
Anhand dieser Menüsystem-Implementierung haben wir gesehen, wie Abstraktion und Vererbung zusammenarbeiten, um flexiblen, wartbaren Code zu erstellen, der sich leicht an unterschiedliche Geschäftsanforderungen anpassen lässt.
Fazit
Heute haben wir einen Blick darauf geworfen, wozu Java als objektorientierte Programmiersprache fähig ist. Wir haben die Grundlagen von Klassen, Objekten und einigen Grundpfeilern der OOP behandelt: Kapselung, Vererbung und Abstraktion anhand eines Restaurant-Menüsystems.
Um dieses System produktionsreif zu machen, musst du noch so viele Dinge lernen, wie Schnittstellen (Teil der Abstraktion), Polymorphismus und OOP-Entwurfsmuster. Um mehr über diese Konzepte zu erfahren, lesen Sie unsere Einführung in OOP in Java Kurs.
Wenn du deine Java-Kenntnisse testen möchtest, kannst du versuchen, einige der Fragen in unserem unserem Artikel Java-Interview-Fragen.
OOP in Java FAQs
Was sind die Voraussetzungen, um dieses Java OOP-Tutorial zu besuchen?
Du solltest über Grundkenntnisse der Java-Programmierung verfügen, einschließlich Datentypen, Variablen, Kontrollfluss (if/else, Schleifen) und grundlegende Syntax. Unser Kurs Einführung in Java deckt diese Grundlagen ab, falls du eine Auffrischung brauchst.
Warum sollte man ein Restaurant-Menüsystem verwenden, um OOP-Konzepte zu erklären?
Ein Restaurant-Menüsystem ist ein intuitives Beispiel, das die Anwendung der OOP-Prinzipien in der Praxis zeigt. Es zeigt natürlich Vererbung (verschiedene Arten von Menüpunkten), Kapselung (Schutz von Preis- und Namenswerten) und Schnittstellen (Treueprogramme, Temperaturkontrolle). Die meisten Menschen wissen, wie Restaurants funktionieren, sodass die Konzepte leichter zu verstehen sind.
Was ist der Unterschied zwischen abstrakten Klassen und Schnittstellen in Java?
- Eine Klasse kann nur eine abstrakte Klasse erweitern, aber mehrere Schnittstellen implementieren.
- Abstrakte Klassen können Felder und Konstruktoren haben, Schnittstellen nicht.
- Abstrakte Klassen können sowohl abstrakte als auch konkrete Methoden haben; Schnittstellen haben traditionell nur abstrakte Methoden (vor Java 8).
- Abstrakte Klassen bieten eine Basisimplementierung, während Schnittstellen einen Vertrag definieren.

Ich bin ein Data Science Content Creator mit über 2 Jahren Erfahrung und einem der größten Follower auf Medium. Ich schreibe gerne ausführliche Artikel über KI und ML mit einem etwas sarkastischen Stil, denn man muss etwas tun, damit sie nicht so langweilig sind. Ich habe mehr als 130 Artikel verfasst und einen DataCamp-Kurs gemacht, ein weiterer ist in Vorbereitung. Meine Inhalte wurden von über 5 Millionen Augenpaaren gesehen, von denen 20.000 zu Followern auf Medium und LinkedIn wurden.
Top Java Kurse
Kurs
Introduction to Object-Oriented Programming in Java
Kurs
Introduction to Data Visualization with Plotly in Python

Der Blog
Lehrer/innen und Schüler/innen erhalten das Premium DataCamp kostenlos für ihre gesamte akademische Laufbahn
Der Blog
2022-2023 DataCamp Classrooms Jahresbericht
Der Blog
Die 32 besten AWS-Interview-Fragen und Antworten für 2024
Der Blog
Q2 2023 DataCamp Donates Digest
Der Blog
Top 30 Generative KI Interview Fragen und Antworten für 2024

Hesam Sheikh Hassani
15 Min.
Der Blog
Die 20 besten Snowflake-Interview-Fragen für alle Niveaus

Nisha Arya Ahmed
20 Min.