
-
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
Bird
abstrakte klasse ogCanFly
græ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
MockingJay
fugleklasse og få den til at arveBird
: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
species
ogage
variabler.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.
-
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
Bird
abstrakte 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.Med
CanFly
græ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'.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.
-
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 overvejeInputStream
og 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 :) InputStream
er 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 InputStream
er forælderen, giver den flere metoder, der gør det nemt at arbejde med datastrømme. Hver efterkommer af InputStream
har 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.
FileInputStream
: den mest almindelige typeInputStream
. Det bruges til at læse information fra en fil;StringBufferInputStream
: En anden nyttig typeInputStream
. Det konverterer en streng til enInputStream
;BufferedInputStream
: En bufferet inputstrøm. Det bruges oftest til at øge ydeevnen.
BufferedReader
og 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 InputStreamReader
kan klare opgaven. Men BufferedReader
forbedrer 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 FileInputStream
og 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 OutputStream
er 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 InputStream
denne 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.
OutputStream
:
-
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. 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'.BufferedOutputStream
. En bufferet outputstrøm. Der er heller ikke noget kompliceret her. Dets formål er analogt medBufferedInputStream
(ellerBufferedReader
). 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.
FileInputStream
, FileOutputStream
og 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 :)
GO TO FULL VERSION