CodeGym/Java-Blog/Random-DE/Der Unterschied zwischen abstrakten Klassen und Schnittst...
Autor
Aditi Nawghare
Software Engineer at Siemens

Der Unterschied zwischen abstrakten Klassen und Schnittstellen

Veröffentlicht in der Gruppe Random-DE
Hallo! In dieser Lektion sprechen wir darüber, wie sich abstrakte Klassen von Schnittstellen unterscheiden, und betrachten einige Beispiele mit gängigen abstrakten Klassen. Der Unterschied zwischen abstrakten Klassen und Schnittstellen - 1Den Unterschieden zwischen einer abstrakten Klasse und einer Schnittstelle haben wir eine eigene Lektion gewidmet, da dieses Thema sehr wichtig ist. In 90 % der zukünftigen Interviews werden Sie nach dem Unterschied zwischen diesen Konzepten gefragt. Das bedeutet, dass Sie unbedingt herausfinden sollten, was Sie lesen. Und wenn Sie etwas nicht ganz verstehen, lesen Sie zusätzliche Quellen. Wir wissen also, was eine abstrakte Klasse und was eine Schnittstelle ist. Jetzt gehen wir ihre Unterschiede durch.
  1. Eine Schnittstelle beschreibt lediglich Verhalten. Es hat keinen Staat. Aber eine abstrakte Klasse beinhaltet den Zustand: Sie beschreibt beides.

    Nehmen Sie zum Beispiel die Birdabstrakte Klasse und die CanFlySchnittstelle:

    public abstract class Bird {
       private String species;
       private int age;
    
       public abstract void fly();
    
       public String getSpecies() {
           return species;
       }
    
       public void setSpecies(String species) {
           this.species = species;
       }
    
       public int getAge() {
           return age;
       }
    
       public void setAge(int age) {
           this.age = age;
       }
    }

    MockingJayLassen Sie uns eine Vogelklasse erstellen und diese erben Bird:

    public class MockingJay extends Bird {
    
       @Override
       public void fly() {
           System.out.println("Fly, bird!");
       }
    
       public static void main(String[] args) {
    
           MockingJay someBird = new MockingJay();
           someBird.setAge(19);
           System.out.println(someBird.getAge());
       }
    }

    Wie Sie sehen, können wir leicht auf den Status der abstrakten Klasse zugreifen – ihre speciesund ageVariablen.

    Wenn wir jedoch versuchen, dasselbe mit einer Schnittstelle zu erreichen, ergibt sich ein anderes Bild. Wir können versuchen, Variablen hinzuzufügen:

    public interface CanFly {
    
       String species = new String();
       int age = 10;
    
       public void fly();
    }
    
    public interface CanFly {
    
       private String species = new String(); // Error
       private int age = 10; // Another error
    
       public void fly();
    }

    Wir können nicht einmal private Variablen innerhalb einer Schnittstelle deklarieren. Warum? Weil der private Modifikator erstellt wurde, um die Implementierung vor dem Benutzer zu verbergen. Und eine Schnittstelle enthält keine Implementierung: Es gibt nichts zu verbergen.

    Eine Schnittstelle beschreibt lediglich Verhalten. Dementsprechend können wir Getter und Setter nicht innerhalb einer Schnittstelle implementieren. Das liegt in der Natur von Schnittstellen: Sie werden benötigt, um mit Verhalten zu arbeiten, nicht mit Zuständen.

    Mit Java 8 wurden Standardmethoden für Schnittstellen eingeführt, die über eine Implementierung verfügen. Sie kennen sie bereits, deshalb wiederholen wir uns nicht.

  2. Eine abstrakte Klasse verbindet und vereint Klassen, die sehr eng miteinander verbunden sind. Gleichzeitig kann eine einzelne Schnittstelle durch Klassen implementiert werden, die absolut nichts gemeinsam haben.

    Kehren wir zu unserem Beispiel mit Vögeln zurück.

    Unsere Birdabstrakte Klasse wird zum Erstellen von Vögeln benötigt, die auf dieser Klasse basieren. Nur Vögel und sonst nichts! Natürlich wird es verschiedene Vogelarten geben.

    Der Unterschied zwischen abstrakten Klassen und Schnittstellen - 2

    Mit der CanFlySchnittstelle kommt jeder auf seine Weise zurecht. Es beschreibt nur das mit seinem Namen verbundene Verhalten (Fliegen). Viele Dinge, die nichts miteinander zu tun haben, können fliegen.

    Der Unterschied zwischen abstrakten Klassen und Schnittstellen - 3

    Diese 4 Einheiten sind nicht miteinander verbunden. Sie leben noch nicht einmal alle. Allerdings sind sie alle CanFly.

    Wir konnten sie nicht mit einer abstrakten Klasse beschreiben. Sie haben nicht denselben Status oder identische Felder. Um ein Flugzeug zu definieren, benötigen wir wahrscheinlich Felder für das Modell, das Produktionsjahr und die maximale Passagierzahl. Für Carlson bräuchten wir Felder für alle Süßigkeiten, die er heute gegessen hat, und eine Liste der Spiele, die er mit seinem kleinen Bruder spielen wird. Für eine Mücke ... äh ... ich weiß nicht einmal ... Vielleicht ein „Belästigungsniveau“? :) :)

    Der Punkt ist, dass wir sie nicht mit einer abstrakten Klasse beschreiben können. Sie sind zu unterschiedlich. Aber sie haben ein gemeinsames Verhalten: Sie können fliegen. Eine Schnittstelle ist perfekt, um alles auf der Welt zu beschreiben, was fliegen, schwimmen, springen oder ein anderes Verhalten zeigen kann.

  3. Klassen können beliebig viele Schnittstellen implementieren, aber nur eine Klasse erben.

    Das haben wir bereits mehrfach erwähnt. Java bietet keine Mehrfachvererbung von Klassen, unterstützt jedoch die Mehrfachvererbung von Schnittstellen. Dieser Punkt folgt teilweise aus dem vorherigen: Eine Schnittstelle verbindet viele verschiedene Klassen, die oft sonst nichts gemeinsam haben, während eine abstrakte Klasse für eine Gruppe sehr eng verwandter Klassen erstellt wird. Daher ist es sinnvoll, dass Sie nur eine solche Klasse erben können. Eine abstrakte Klasse beschreibt eine „Ist-ein“-Beziehung.

Standardschnittstellen: InputStream und OutputStream

Wir haben bereits verschiedene Klassen besprochen, die für Eingabe- und Ausgabeströme verantwortlich sind. Betrachten wir InputStreamund OutputStream. Im Allgemeinen handelt es sich hierbei überhaupt nicht um Schnittstellen, sondern um völlig echte abstrakte Klassen. Jetzt wissen Sie, was das bedeutet, daher wird es viel einfacher, damit zu arbeiten :) InputStreamist eine abstrakte Klasse, die für die Byte-Eingabe verantwortlich ist. Java hat mehrere Klassen, die erben InputStream. Jeder von ihnen ist darauf ausgelegt, Daten aus unterschiedlichen Quellen zu empfangen. Da InputStreames das übergeordnete Element ist, bietet es mehrere Methoden, die die Arbeit mit Datenströmen erleichtern. Jeder Nachkomme von InputStreamhat diese Methoden:
  • int available()gibt die Anzahl der zum Lesen verfügbaren Bytes zurück;
  • close()schließt den Eingabestream;
  • int read()gibt eine ganzzahlige Darstellung des nächsten verfügbaren Bytes im Stream zurück. Wenn das Ende des Streams erreicht ist, wird -1 zurückgegeben;
  • int read(byte[] buffer)versucht, Bytes in den Puffer zu lesen und gibt die Anzahl der gelesenen Bytes zurück. Wenn das Ende der Datei erreicht ist, wird -1 zurückgegeben.
  • int read(byte[] buffer, int byteOffset, int byteCount)schreibt einen Teil eines Byteblocks. Es wird verwendet, wenn das Byte-Array möglicherweise nicht vollständig gefüllt ist. Wenn das Ende der Datei erreicht ist, wird -1 zurückgegeben.
  • long skip(long byteCount)überspringt byteCount Bytes im Eingabestream und gibt die Anzahl der ignorierten Bytes zurück.
Ich empfehle Ihnen, die vollständige Liste der Methoden zu studieren . Tatsächlich gibt es mehr als zehn Kinderklassen. Hier sind zum Beispiel einige:
  1. FileInputStream: die häufigste Art von InputStream. Es wird verwendet, um Informationen aus einer Datei zu lesen;
  2. StringBufferInputStream: Eine weitere hilfreiche Art von InputStream. Es konvertiert einen String in ein InputStream;
  3. BufferedInputStream: Ein gepufferter Eingabestream. Es wird am häufigsten zur Leistungssteigerung eingesetzt.
Erinnern Sie sich, als wir hinübergingen BufferedReaderund sagten, dass Sie es nicht benutzen müssen? Wenn wir schreiben:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
…Sie müssen nicht verwenden BufferedReader: Ein InputStreamReaderkann den Job machen. Verbessert aber BufferedReaderdie Leistung und kann statt einzelner Zeichen auch ganze Datenzeilen lesen. Das Gleiche gilt für BufferedInputStream! Die Klasse sammelt Eingabedaten in einem speziellen Puffer, ohne ständig auf das Eingabegerät zuzugreifen. Betrachten wir ein Beispiel:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;

public class BufferedInputExample {

   public static void main(String[] args) throws Exception {
       InputStream inputStream = null;
       BufferedInputStream buffer = null;

       try {

           inputStream = new FileInputStream("D:/Users/UserName/someFile.txt");

           buffer = new BufferedInputStream(inputStream);

           while(buffer.available()>0) {

               char c = (char)buffer.read();

                System.out.println("Character read: " + c);
           }
       } catch(Exception e) {

           e.printStackTrace();

       } finally {

           inputStream.close();
           buffer.close();
       }
   }
}
In diesem Beispiel lesen wir Daten aus einer Datei, die sich auf einem Computer unter „ D:/Users/UserName/someFile.txt “ befindet. Wir erstellen zwei Objekte – ein FileInputStreamund ein, BufferedInputStreamdas es „umhüllt“. Dann lesen wir Bytes aus der Datei und wandeln sie in Zeichen um. Und das machen wir, bis die Datei zu Ende ist. Wie Sie sehen, gibt es hier nichts Kompliziertes. Sie können diesen Code kopieren und in einer echten Datei auf Ihrem Computer ausführen :) Die OutputStreamKlasse ist eine abstrakte Klasse, die einen Ausgabestrom von Bytes darstellt. Wie Sie bereits wissen, ist dies das Gegenteil eines InputStream. Es ist nicht dafür verantwortlich, Daten von irgendwoher zu lesen, sondern vielmehr dafür, Daten irgendwohin zu senden . Diese abstrakte Klasse stellt beispielsweise InputStreamallen ihren Nachkommen eine Reihe praktischer Methoden zur Verfügung:
  • void close()schließt den Ausgabestream;
  • void flush()löscht alle Ausgabepuffer;
  • abstract void write(int oneByte)schreibt 1 Byte in den Ausgabestream;
  • void write(byte[] buffer)schreibt ein Byte-Array in den Ausgabestream;
  • void write(byte[] buffer, int offset, int count)schreibt einen Bereich von Zählbytes aus einem Array, beginnend an der Offset-Position.
Hier sind einige der Nachkommen der OutputStreamKlasse:
  1. DataOutputStream. Ein Ausgabestream, der Methoden zum Schreiben von Standard-Java-Datentypen enthält.

    Eine sehr einfache Klasse zum Schreiben primitiver Java-Datentypen und -Strings. Sie werden den folgenden Code wahrscheinlich auch ohne Erklärung verstehen:

    import java.io.*;
    
    public class DataOutputStreamExample {
    
       public static void main(String[] args) throws IOException {
    
           DataOutputStream dos = new DataOutputStream(new FileOutputStream("testFile.txt"));
    
           dos.writeUTF("SomeString");
           dos.writeInt(22);
           dos.writeDouble(1.21323);
           dos.writeBoolean(true);
    
       }
    }

    Für jeden Typ gibt es separate Methoden – writeDouble(), writeLong(), writeShort()usw.


  2. FileOutputStream. Diese Klasse implementiert einen Mechanismus zum Senden von Daten an eine Datei auf der Festplatte. Wir haben es übrigens bereits im letzten Beispiel verwendet. Hast du bemerkt? Wir haben es an DataOutputStream übergeben, der als „Wrapper“ fungierte.

  3. BufferedOutputStream. Ein gepufferter Ausgabestream. Auch hier gibt es nichts Kompliziertes. Sein Zweck ist analog zu BufferedInputStream(oder BufferedReader). Anstelle des üblichen sequentiellen Lesens von Daten werden Daten mithilfe eines speziellen „kumulativen“ Puffers geschrieben. Der Puffer ermöglicht es, die Anzahl der Zugriffe auf die Datensenke zu reduzieren und dadurch die Leistung zu steigern.

    import java.io.*;
    
    public class DataOutputStreamExample {
    
         public static void main(String[] args) throws IOException {
    
               FileOutputStream outputStream = new FileOutputStream("D:/Users/Username/someFile.txt");
               BufferedOutputStream bufferedStream = new BufferedOutputStream(outputStream);
    
               String text = "I love Java!"; // We'll convert this string to a byte array and write it to a file
    
               byte[] buffer = text.getBytes();
    
               bufferedStream.write(buffer, 0, buffer.length);
         }
    }

    Auch hier können Sie selbst mit diesem Code herumspielen und überprüfen, ob er mit echten Dateien auf Ihrem Computer funktioniert.

Wir werden eine separate Lektion über FileInputStream, FileOutputStreamund haben BuffreredInputStream, daher sind dies genügend Informationen für eine erste Bekanntschaft. Das ist es! Wir hoffen, dass Sie die Unterschiede zwischen Schnittstellen und abstrakten Klassen verstehen und bereit sind, jede Frage zu beantworten, auch Trickfragen :)
Kommentare
  • Beliebt
  • Neu
  • Alt
Du musst angemeldet sein, um einen Kommentar schreiben zu können
Auf dieser Seite gibt es noch keine Kommentare