CodeGym /Java blog /Tilfældig /Forskellen mellem abstrakte klasser og grænseflader
John Squirrels
Niveau
San Francisco

Forskellen mellem abstrakte klasser og grænseflader

Udgivet i gruppen
Hej! I denne lektion vil vi tale om, hvordan abstrakte klasser adskiller sig fra grænseflader og overveje nogle eksempler med almindelige abstrakte klasser. Forskellen mellem abstrakte klasser og grænseflader - 1Vi har viet en separat lektion til forskellene mellem en abstrakt klasse og en grænseflade, fordi dette emne er meget vigtigt. Du vil blive spurgt om forskellen mellem disse begreber i 90 % af fremtidige interviews. Det betyder, at du skal være sikker på at finde ud af, hvad du læser. Og hvis du ikke helt forstår noget, så læs yderligere kilder. Så vi ved, hvad en abstrakt klasse er, og hvad en grænseflade er. Nu vil vi gennemgå deres forskelle.
  1. En grænseflade beskriver kun adfærd. Det har ingen stat. Men en abstrakt klasse inkluderer tilstand: den beskriver begge dele.

    Tag for eksempel den Birdabstrakte klasse og CanFlygrænsefladen:

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

    Lad os oprette en MockingJayfugleklasse og få den til at arve 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());
       }
    }
    

    Som du kan se, kan vi nemt få adgang til den abstrakte klasses tilstand - dens speciesog agevariabler.

    Men hvis vi forsøger at gøre det samme med en grænseflade, er billedet anderledes. Vi kan prøve at tilføje variabler til det:

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

    Vi kan ikke engang erklære private variabler inde i en grænseflade. Hvorfor? Fordi den private modifikator blev oprettet for at skjule implementeringen for brugeren. Og en grænseflade har ingen implementering i sig: der er ikke noget at skjule.

    En grænseflade beskriver kun adfærd. Derfor kan vi ikke implementere gettere og sættere i en grænseflade. Dette er grænsefladers natur: de er nødvendige for at arbejde med adfærd, ikke stat.

    Java 8 introducerede standardmetoder til grænseflader, der har en implementering. Du kender dem allerede, så vi gentager os ikke.

  2. En abstrakt klasse forbinder og forener klasser, der er meget nært beslægtede. På samme tid kan en enkelt grænseflade implementeres af klasser, der absolut intet har til fælles.

    Lad os vende tilbage til vores eksempel med fugle.

    Vores Birdabstrakte klasse er nødvendig for at skabe fugle, der er baseret på den klasse. Kun fugle og intet andet! Selvfølgelig vil der være forskellige slags fugle.

    Forskellen mellem abstrakte klasser og grænseflader - 2

    Med CanFlygrænsefladen kommer alle videre på deres egen måde. Den beskriver kun den adfærd (flyvning), der er forbundet med dens navn. Mange ikke-relaterede ting 'kan flyve'.

    Forskellen mellem abstrakte klasser og grænseflader - 3

    Disse 4 enheder er ikke relateret til hinanden. De er ikke engang alle i live. Men de alle CanFly.

    Vi kunne ikke beskrive dem ved hjælp af en abstrakt klasse. De deler ikke den samme tilstand eller identiske felter. For at definere et fly ville vi sandsynligvis have brug for felter for model, produktionsår og maksimalt antal passagerer. For Carlson ville vi have brug for marker til alt det slik, han spiste i dag, og en liste over de spil, han vil spille med sin lillebror. For en myg, ... øh... jeg ved det ikke engang... Måske et 'irritationsniveau'? :)

    Pointen er, at vi ikke kan bruge en abstrakt klasse til at beskrive dem. De er for forskellige. Men de har fælles adfærd: de kan flyve. En grænseflade er perfekt til at beskrive alt i verden, der kan flyve, svømme, hoppe eller udvise anden adfærd.

  3. Klasser kan implementere så mange grænseflader, som du vil, men de kan kun arve én klasse.

    Vi har allerede nævnt dette mere end én gang. Java har ikke multipel nedarvning af klasser, men det understøtter multipel nedarvning af grænseflader. Dette punkt følger til dels af det foregående: en grænseflade forbinder mange forskellige klasser, der ofte ikke har andet til fælles, mens der skabes en abstrakt klasse for en gruppe af meget nært beslægtede klasser. Derfor giver det mening, at du kun kan arve én sådan klasse. En abstrakt klasse beskriver et 'er-a' forhold.

Standardgrænseflader: InputStream og OutputStream

Vi har allerede gennemgået forskellige klasser, der er ansvarlige for input- og outputstrømme. Lad os overveje InputStreamog OutputStream. Generelt er disse slet ikke grænseflader, men snarere helt ægte abstrakte klasser. Nu ved du, hvad det betyder, så det bliver meget nemmere at arbejde med dem :) InputStreamer en abstrakt klasse, der er ansvarlig for byte-input. Java har flere klasser, der arver InputStream. Hver af dem er designet til at modtage data fra forskellige kilder. Fordi InputStreamer forælderen, giver den flere metoder, der gør det nemt at arbejde med datastrømme. Hver efterkommer af InputStreamhar disse metoder:
  • int available()returnerer antallet af tilgængelige bytes til læsning;
  • close()lukker inputstrømmen;
  • int read()returnerer en heltalsrepræsentation af den næste tilgængelige byte i strømmen. Hvis slutningen af ​​streamen er nået, vil -1 blive returneret;
  • int read(byte[] buffer)forsøger at læse bytes ind i bufferen og returnerer antallet af læste bytes. Når den når slutningen af ​​filen, returnerer den -1;
  • int read(byte[] buffer, int byteOffset, int byteCount)skriver en del af en blok af bytes. Det bruges, når byte-arrayet muligvis ikke er blevet udfyldt helt. Når den når slutningen af ​​filen, returnerer den -1;
  • long skip(long byteCount)springer byteCount bytes over i inputstrømmen og returnerer antallet af ignorerede bytes.
Jeg anbefaler, at du studerer den komplette liste over metoder . Der er faktisk mere end ti børneklasser. For eksempel, her er et par stykker:
  1. FileInputStream: den mest almindelige type InputStream. Det bruges til at læse information fra en fil;
  2. StringBufferInputStream: En anden nyttig type InputStream. Det konverterer en streng til en InputStream;
  3. BufferedInputStream: En bufferet inputstrøm. Det bruges oftest til at øge ydeevnen.
Kan du huske, da vi gik over BufferedReaderog sagde, at du ikke behøver at bruge det? Når vi skriver:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
…du behøver ikke at bruge BufferedReader: En InputStreamReaderkan klare opgaven. Men BufferedReaderforbedrer ydeevnen og kan også læse hele datalinjer frem for individuelle tegn. Det samme gælder for BufferedInputStream! Klassen akkumulerer inputdata i en speciel buffer uden konstant at få adgang til inputenheden. Lad os overveje et eksempel:

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();
       }
   }
}
I dette eksempel læser vi data fra en fil placeret på en computer ved ' D:/Users/UserName/someFile.txt '. Vi skaber 2 objekter — et FileInputStreamog et BufferedInputStream, der 'omslutter' det. Derefter læser vi bytes fra filen og konverterer dem til tegn. Og det gør vi indtil filen slutter. Som du kan se, er der ikke noget kompliceret her. Du kan kopiere denne kode og køre den på en rigtig fil på din computer :) Klassen OutputStreamer en abstrakt klasse, der repræsenterer en outputstrøm af bytes. Som du allerede ved, er dette det modsatte af en InputStream. Det er ikke ansvarligt for at læse data fra et sted, men snarere for at sende data et sted hen . Ligesom InputStreamdenne abstrakte klasse giver alle dens efterkommere et sæt praktiske metoder:
  • void close()lukker udgangsstrømmen;
  • void flush()sletter alle outputbuffere;
  • abstract void write(int oneByte)skriver 1 byte til outputstrømmen;
  • void write(byte[] buffer)skriver et byte-array til outputstrømmen;
  • void write(byte[] buffer, int offset, int count)skriver en række tællebytes fra et array, startende ved offsetpositionen.
Her er nogle af klassens efterkommere OutputStream:
  1. DataOutputStream. En outputstrøm, der inkluderer metoder til at skrive standard Java-datatyper.

    En meget simpel klasse til at skrive primitive Java-datatyper og strenge. Du vil sandsynligvis forstå følgende kode selv uden en forklaring:

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

    Det har separate metoder for hver type — writeDouble(), writeLong(), writeShort(), og så videre.


  2. FileOutputStream. Denne klasse implementerer en mekanisme til at sende data til en fil på disken. Forresten brugte vi det allerede i det sidste eksempel. Lagde du mærke til det? Vi videregav det til DataOutputStream, som fungerede som en 'indpakning'.

  3. BufferedOutputStream. En bufferet outputstrøm. Der er heller ikke noget kompliceret her. Dets formål er analogt med BufferedInputStream(eller BufferedReader). I stedet for den sædvanlige sekventielle læsning af data, skriver den data ved hjælp af en speciel 'kumulativ' buffer. Bufferen gør det muligt at reducere antallet af gange, datasinket tilgås, og derved øge ydelsen.

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

    Igen kan du selv lege med denne kode og kontrollere, at den fungerer på rigtige filer på din computer.

Vi vil have en separat lektion om FileInputStream, FileOutputStreamog BuffreredInputStream, så dette er nok information til et første bekendtskab. Det er det! Vi håber, du forstår forskellene mellem grænseflader og abstrakte klasser og er klar til at besvare ethvert spørgsmål, selv trickspørgsmål :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION