CodeGym/Blog Java/Aleatoriu/Diferența dintre clasele abstracte și interfețe
John Squirrels
Nivel
San Francisco

Diferența dintre clasele abstracte și interfețe

Publicat în grup
Bună! În această lecție, vom vorbi despre modul în care clasele abstracte diferă de interfețe și vom lua în considerare câteva exemple cu clase abstracte comune. Diferența dintre clasele abstracte și interfețe - 1Am dedicat o lecție separată diferențelor dintre o clasă abstractă și o interfață, deoarece acest subiect este foarte important. Veți fi întrebat despre diferența dintre aceste concepte în 90% din interviurile viitoare. Asta înseamnă că ar trebui să fii sigur că îți dai seama ce citești. Și dacă nu înțelegeți pe deplin ceva, citiți surse suplimentare. Deci, știm ce este o clasă abstractă și ce este o interfață. Acum vom trece peste diferențele dintre ele.
  1. O interfață descrie doar comportamentul. Nu are stat. Dar o clasă abstractă include starea: le descrie pe amândouă.

    De exemplu, luați Birdclasa abstractă și CanFlyinterfața:

    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;
       }
    }

    Să creăm o MockingJayclasă de păsări și să o facem să moștenească 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());
       }
    }

    După cum puteți vedea, putem accesa cu ușurință starea clasei abstracte - ea speciesși agevariabilele.

    Dar dacă încercăm să facem același lucru cu o interfață, imaginea este diferită. Putem încerca să îi adăugăm variabile:

    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();
    }

    Nici măcar nu putem declara variabile private în interiorul unei interfețe. De ce? Deoarece modificatorul privat a fost creat pentru a ascunde implementarea de utilizator. Și o interfață nu are implementare în interiorul ei: nu există nimic de ascuns.

    O interfață descrie doar comportamentul. În consecință, nu putem implementa gettere și setari în interiorul unei interfețe. Aceasta este natura interfețelor: sunt necesare pentru a lucra cu comportament, nu cu stare.

    Java 8 a introdus metode implicite pentru interfețele care au o implementare. Știți deja despre ele, așa că nu ne vom repeta.

  2. O clasă abstractă conectează și unește clase care sunt foarte strâns legate. În același timp, o singură interfață poate fi implementată de clase care nu au absolut nimic în comun.

    Să revenim la exemplul nostru cu păsările.

    Clasa noastră Birdabstractă este necesară pentru a crea păsări care se bazează pe acea clasă. Doar păsări și nimic altceva! Desigur, vor exista diferite tipuri de păsări.

    Diferența dintre clasele abstracte și interfețe - 2

    Cu CanFlyinterfața, fiecare se descurcă în felul său. Descrie doar comportamentul (zburarea) asociat cu numele său. Multe lucruri fără legătură „pot zbura”.

    Diferența dintre clasele abstracte și interfețe - 3

    Aceste 4 entități nu sunt legate între ele. Nici măcar nu sunt toți vii. Cu toate acestea, toți CanFly.

    Nu le-am putea descrie folosind o clasă abstractă. Nu au aceeași stare sau câmpuri identice. Pentru a defini o aeronavă, probabil că am avea nevoie de câmpuri pentru model, anul de producție și numărul maxim de pasageri. Pentru Carlson, am avea nevoie de câmpuri pentru toate dulciurile pe care le-a mâncat astăzi și de o listă cu jocurile pe care le va juca cu frățiorul său. Pentru un țânțar, ... uh... nici măcar nu știu... Poate, un „nivel de enervare”? :)

    Ideea este că nu putem folosi o clasă abstractă pentru a le descrie. Sunt prea diferite. Dar au un comportament comun: pot zbura. O interfață este perfectă pentru a descrie tot ceea ce în lume poate zbura, înota, sari sau prezintă un alt comportament.

  3. Clasele pot implementa câte interfețe doriți, dar pot moșteni o singură clasă.

    Am menționat deja acest lucru de mai multe ori. Java nu are moștenire multiplă de clase, dar acceptă moștenirea multiplă de interfețe. Acest punct decurge parțial din cel precedent: o interfață conectează multe clase diferite care adesea nu au nimic altceva în comun, în timp ce o clasă abstractă este creată pentru un grup de clase foarte strâns legate. Prin urmare, este logic să poți moșteni doar o astfel de clasă. O clasă abstractă descrie o relație „este-a”.

Interfețe standard: InputStream și OutputStream

Am trecut deja peste diferite clase responsabile pentru fluxurile de intrare și ieșire. Să luăm în considerare InputStreamși OutputStream. În general, acestea nu sunt deloc interfețe, ci mai degrabă clase abstracte în întregime autentice. Acum știți ce înseamnă asta, așa că va fi mult mai ușor să lucrați cu ei :) InputStreameste o clasă abstractă responsabilă pentru intrarea octetilor. Java are mai multe clase care moștenesc InputStream. Fiecare dintre ele este conceput pentru a primi date din surse diferite. Deoarece InputStreameste părintele, oferă mai multe metode care facilitează lucrul cu fluxurile de date. Fiecare descendent al InputStreamare aceste metode:
  • int available()returnează numărul de octeți disponibili pentru citire;
  • close()închide fluxul de intrare;
  • int read()returnează o reprezentare întreagă a următorului octet disponibil din flux. Dacă sfârșitul fluxului a fost atins, va fi returnat -1;
  • int read(byte[] buffer)încearcă să citească octeții în buffer și returnează numărul de octeți citiți. Când ajunge la sfârșitul fișierului, returnează -1;
  • int read(byte[] buffer, int byteOffset, int byteCount)scrie o parte dintr-un bloc de octeți. Este folosit atunci când matricea de octeți poate să nu fi fost umplută în întregime. Când ajunge la sfârșitul fișierului, returnează -1;
  • long skip(long byteCount)omite byteCount bytes în fluxul de intrare și returnează numărul de octeți ignorați.
Vă recomand să studiați lista completă a metodelor . De fapt, există mai mult de zece clase de copii. De exemplu, iată câteva:
  1. FileInputStream: cel mai comun tip de InputStream. Este folosit pentru a citi informații dintr-un fișier;
  2. StringBufferInputStream: Un alt tip util de InputStream. Acesta convertește un șir într-un InputStream;
  3. BufferedInputStream: un flux de intrare în tampon. Este folosit cel mai adesea pentru a crește performanța.
Îți amintești când ne-am dus BufferedReaderși am spus că nu trebuie să-l folosești? Când scriem:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
…nu trebuie să utilizați BufferedReader: An InputStreamReaderpoate face treaba. Dar BufferedReaderîmbunătățește performanța și poate citi, de asemenea, linii întregi de date, mai degrabă decât caractere individuale. Același lucru este valabil și pentru BufferedInputStream! Clasa acumulează datele de intrare într-un buffer special fără a accesa în mod constant dispozitivul de intrare. Să luăm în considerare un exemplu:
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();
       }
   }
}
În acest exemplu, citim date dintr-un fișier aflat pe un computer la „ D:/Users/UserName/someFile.txt ”. Creăm 2 obiecte - a FileInputStreamși a BufferedInputStreamcare îl „înfășoară”. Apoi citim octeți din fișier și îi convertim în caractere. Și facem asta până când dosarul se termină. După cum puteți vedea, nu este nimic complicat aici. Puteți copia acest cod și îl puteți rula într-un fișier real de pe computerul dvs. :) Clasa OutputStreameste o clasă abstractă care reprezintă un flux de ieșire de octeți. După cum știți deja, acesta este opusul unui InputStream. Nu este responsabil pentru citirea datelor de undeva, ci mai degrabă pentru trimiterea datelor undeva . Ca InputStream, această clasă abstractă oferă tuturor descendenților săi un set de metode convenabile:
  • void close()închide fluxul de ieșire;
  • void flush()șterge toate tampoanele de ieșire;
  • abstract void write(int oneByte)scrie 1 octet în fluxul de ieșire;
  • void write(byte[] buffer)scrie o matrice de octeți în fluxul de ieșire;
  • void write(byte[] buffer, int offset, int count)scrie o gamă de octeți de numărare dintr-o matrice, începând de la poziția offset.
Iată câțiva dintre descendenții clasei OutputStream:
  1. DataOutputStream. Un flux de ieșire care include metode pentru scrierea tipurilor de date Java standard.

    O clasă foarte simplă pentru scrierea tipurilor și șirurilor de date Java primitive. Probabil că veți înțelege următorul cod chiar și fără o explicație:

    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);
    
       }
    }

    Are metode separate pentru fiecare tip — writeDouble(), writeLong(), writeShort()și așa mai departe.


  2. FileOutputStream. Această clasă implementează un mecanism pentru trimiterea datelor către un fișier de pe disc. Apropo, l-am folosit deja în ultimul exemplu. Ai observat? L-am transmis lui DataOutputStream, care a acționat ca un „wrapper”.

  3. BufferedOutputStream. Un flux de ieșire tamponat. Nici aici nu este nimic complicat. Scopul său este analog cu BufferedInputStream(sau BufferedReader). În loc de citirea secvențială obișnuită a datelor, scrie datele folosind un buffer special „cumulativ”. Bufferul face posibilă reducerea numărului de accesări ale rezervorului de date, crescând astfel performanța.

    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);
         }
    }

    Din nou, vă puteți juca singur cu acest cod și puteți verifica dacă va funcționa pe fișiere reale de pe computer.

Vom avea o lecție separată despre FileInputStream, FileOutputStreamși BuffreredInputStream, deci acestea sunt suficiente informații pentru o primă cunoștință. Asta este! Sperăm că înțelegeți diferențele dintre interfețe și clasele abstracte și sunteți gata să răspundeți la orice întrebare, chiar și la întrebări truc:)
Comentarii
  • Popular
  • Nou
  • Vechi
Trebuie să fii conectat pentru a lăsa un comentariu
Această pagină nu are încă niciun comentariu