CodeGym /Java-blogg /Tilfeldig /Forskjellen mellom abstrakte klasser og grensesnitt
John Squirrels
Nivå
San Francisco

Forskjellen mellom abstrakte klasser og grensesnitt

Publisert i gruppen
Hei! I denne leksjonen skal vi snakke om hvordan abstrakte klasser skiller seg fra grensesnitt og vurdere noen eksempler med vanlige abstrakte klasser. Forskjellen mellom abstrakte klasser og grensesnitt - 1Vi har viet en egen leksjon til forskjellene mellom en abstrakt klasse og et grensesnitt, fordi dette emnet er veldig viktig. Du vil bli spurt om forskjellen mellom disse konseptene i 90 % av fremtidige intervjuer. Det betyr at du bør være sikker på å finne ut hva du leser. Og hvis du ikke helt forstår noe, les flere kilder. Så vi vet hva en abstrakt klasse er og hva et grensesnitt er. Nå skal vi gå over forskjellene deres.
  1. Et grensesnitt beskriver bare atferd. Den har ingen stat. Men en abstrakt klasse inkluderer tilstand: den beskriver begge deler.

    Ta for eksempel den Birdabstrakte klassen og CanFlygrensesnittet:

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

    La oss lage en MockingJayfugleklasse og få den til å 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 enkelt få tilgang til den abstrakte klassens tilstand - dens speciesog agevariablene.

    Men hvis vi prøver å gjøre det samme med et grensesnitt, er bildet annerledes. Vi kan prøve å legge til variabler til den:

    
    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 deklarere private variabler i et grensesnitt. Hvorfor? Fordi den private modifikatoren ble opprettet for å skjule implementeringen for brukeren. Og et grensesnitt har ingen implementering inni seg: det er ikke noe å skjule.

    Et grensesnitt beskriver bare atferd. Følgelig kan vi ikke implementere gettere og settere i et grensesnitt. Dette er grensesnittenes natur: de er nødvendige for å jobbe med atferd, ikke stat.

    Java 8 introduserte standardmetoder for grensesnitt som har en implementering. Du vet allerede om dem, så vi vil ikke gjenta oss selv.

  2. En abstrakt klasse forbinder og forener klasser som er veldig nært beslektet. Samtidig kan et enkelt grensesnitt implementeres av klasser som har absolutt ingenting til felles.

    La oss gå tilbake til vårt eksempel med fugler.

    Vår Birdabstrakte klasse er nødvendig for å lage fugler som er basert på den klassen. Bare fugler og ingenting annet! Selvfølgelig vil det være forskjellige typer fugler.

    Forskjellen mellom abstrakte klasser og grensesnitt - 2

    Med CanFlygrensesnittet kommer alle videre på sin egen måte. Den beskriver bare atferden (flyging) knyttet til navnet. Mange urelaterte ting "kan fly".

    Forskjellen mellom abstrakte klasser og grensesnitt - 3

    Disse 4 enhetene er ikke relatert til hverandre. De er ikke engang alle i live. Men de alle CanFly.

    Vi kunne ikke beskrive dem med en abstrakt klasse. De deler ikke samme tilstand eller identiske felt. For å definere et fly vil vi sannsynligvis trenge felt for modell, produksjonsår og maksimalt antall passasjerer. For Carlson ville vi trenge felt for alle søtsakene han spiste i dag, og en liste over spillene han skal spille med lillebroren. For en mygg, ... eh... jeg vet ikke engang... Kanskje et "irritasjonsnivå"? :)

    Poenget er at vi ikke kan bruke en abstrakt klasse for å beskrive dem. De er for forskjellige. Men de har delt oppførsel: de kan fly. Et grensesnitt er perfekt for å beskrive alt i verden som kan fly, svømme, hoppe eller utvise annen oppførsel.

  3. Klasser kan implementere så mange grensesnitt du vil, men de kan bare arve én klasse.

    Vi har allerede nevnt dette mer enn én gang. Java har ikke multiple arv av klasser, men det støtter multippel arv av grensesnitt. Dette punktet følger delvis av det forrige: et grensesnitt kobler sammen mange forskjellige klasser som ofte ikke har noe annet til felles, mens det lages en abstrakt klasse for en gruppe veldig nært beslektede klasser. Derfor er det fornuftig at du bare kan arve én slik klasse. En abstrakt klasse beskriver et 'er-et' forhold.

Standard grensesnitt: InputStream og OutputStream

Vi har allerede gått gjennom ulike klasser som er ansvarlige for input- og outputstrømmer. La oss vurdere InputStreamog OutputStream. Generelt er dette ikke grensesnitt i det hele tatt, men snarere helt ekte abstrakte klasser. Nå vet du hva det betyr, så det blir mye lettere å jobbe med dem :) InputStreamer en abstrakt klasse som er ansvarlig for byteinndata. Java har flere klasser som arver InputStream. Hver av dem er designet for å motta data fra forskjellige kilder. Fordi InputStreamer forelderen, gir den flere metoder som gjør det enkelt å jobbe med datastrømmer. Hver etterkommer av InputStreamhar disse metodene:
  • int available()returnerer antall byte tilgjengelig for lesing;
  • close()lukker inngangsstrømmen;
  • int read()returnerer en heltallsrepresentasjon av neste tilgjengelige byte i strømmen. Hvis slutten av strømmen er nådd, vil -1 bli returnert;
  • int read(byte[] buffer)prøver å lese byte inn i buffer, og returnerer antall leste byte. Når den når slutten av filen, returnerer den -1;
  • int read(byte[] buffer, int byteOffset, int byteCount)skriver en del av en blokk med bytes. Den brukes når byte-arrayen kanskje ikke er fylt helt. Når den når slutten av filen, returnerer den -1;
  • long skip(long byteCount)hopper over byteCount byte i inndatastrømmen, og returnerer antall ignorerte byte.
Jeg anbefaler at du studerer hele listen over metoder . Det er faktisk mer enn ti barneklasser. For eksempel, her er noen:
  1. FileInputStream: den vanligste typen InputStream. Den brukes til å lese informasjon fra en fil;
  2. StringBufferInputStream: En annen nyttig type InputStream. Den konverterer en streng til en InputStream;
  3. BufferedInputStream: En bufret inngangsstrøm. Det brukes oftest for å øke ytelsen.
Husker du da vi gikk bort BufferedReaderog sa at du ikke trenger å bruke den? Når vi skriver:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
…du trenger ikke å bruke BufferedReader: En InputStreamReaderkan gjøre jobben. Men BufferedReaderforbedrer ytelsen og kan også lese hele linjer med data i stedet for individuelle tegn. Det samme gjelder BufferedInputStream! Klassen akkumulerer inndata i en spesiell buffer uten konstant tilgang til inngangsenheten. La oss vurdere 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 eksemplet leser vi data fra en fil som ligger på en datamaskin på ' D:/Users/UserName/someFile.txt '. Vi lager 2 objekter - en FileInputStreamog en BufferedInputStreamsom 'pakker inn' den. Deretter leser vi bytes fra filen og konverterer dem til tegn. Og det gjør vi til filen slutter. Som du kan se, er det ikke noe komplisert her. Du kan kopiere denne koden og kjøre den på en ekte fil på datamaskinen din :) Klassen OutputStreamer en abstrakt klasse som representerer en utdatastrøm av bytes. Som du allerede vet, er dette det motsatte av en InputStream. Det er ikke ansvarlig for å lese data fra et sted, men heller for å sende data et sted . Som InputStream, denne abstrakte klassen gir alle dens etterkommere et sett med praktiske metoder:
  • void close()lukker utgangsstrømmen;
  • void flush()sletter alle utgangsbuffere;
  • abstract void write(int oneByte)skriver 1 byte til utdatastrømmen;
  • void write(byte[] buffer)skriver en byte-array til utdatastrømmen;
  • void write(byte[] buffer, int offset, int count)skriver et område med tellebyte fra en matrise, og starter ved offsetposisjonen.
Her er noen av klassens etterkommere OutputStream:
  1. DataOutputStream. En utdatastrøm som inkluderer metoder for å skrive standard Java-datatyper.

    En veldig enkel klasse for å skrive primitive Java-datatyper og strenger. Du vil sannsynligvis forstå følgende kode selv uten 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);
    
       }
    }
    

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


  2. FileOutputStream. Denne klassen implementerer en mekanisme for å sende data til en fil på disken. Forresten, vi brukte det allerede i det siste eksemplet. La du merke til? Vi ga den videre til DataOutputStream, som fungerte som en "innpakning".

  3. BufferedOutputStream. En bufret utgangsstrøm. Det er heller ikke noe komplisert her. Dens formål er analog med BufferedInputStream(eller BufferedReader). I stedet for vanlig sekvensiell lesing av data, skriver den data ved å bruke en spesiell 'kumulativ' buffer. Bufferen gjør det mulig å redusere antall ganger datasynken blir aksessert, og dermed øke ytelsen.

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

    Igjen kan du leke med denne koden selv og bekrefte at den vil fungere på ekte filer på datamaskinen din.

Vi skal ha en egen leksjon om FileInputStream, FileOutputStreamog BuffreredInputStream, så dette er nok informasjon for et første bekjentskap. Det er det! Vi håper du forstår forskjellene mellom grensesnitt og abstrakte klasser og er klare til å svare på alle spørsmål, til og med lurespørsmål :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION