
-
Ett gränssnitt beskriver bara beteende. Den har ingen stat. Men en abstrakt klass inkluderar tillstånd: den beskriver båda.
Ta till exempel den
Bird
abstrakta klassen ochCanFly
gränssnittet: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; } }
Låt oss skapa en
MockingJay
fågelklass och få den att ärvaBird
: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 komma åt den abstrakta klassens tillstånd - dess
species
ochage
variabler.Men om vi försöker göra samma sak med ett gränssnitt är bilden en annan. Vi kan försöka lägga till variabler till 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 inte ens deklarera privata variabler i ett gränssnitt. Varför? Eftersom den privata modifieraren skapades för att dölja implementeringen för användaren. Och ett gränssnitt har ingen implementering inuti det: det finns inget att dölja.
Ett gränssnitt beskriver bara beteende. Följaktligen kan vi inte implementera getters och seters i ett gränssnitt. Detta är gränssnittens natur: de behövs för att arbeta med beteende, inte stat.
Java 8 introducerade standardmetoder för gränssnitt som har en implementering. Du känner redan till dem, så vi kommer inte att upprepa oss.
-
En abstrakt klass kopplar ihop och förenar klasser som är mycket nära besläktade. Samtidigt kan ett enda gränssnitt implementeras av klasser som absolut inte har något gemensamt.
Låt oss återgå till vårt exempel med fåglar.
Vår
Bird
abstrakta klass behövs för att skapa fåglar som är baserade på den klassen. Bara fåglar och inget annat! Självklart kommer det att finnas olika sorters fåglar.Med
CanFly
gränssnittet kommer alla vidare på sitt eget sätt. Den beskriver bara beteendet (flygning) som är förknippat med dess namn. Många orelaterade saker "kan flyga".Dessa 4 enheter är inte relaterade till varandra. De lever inte ens alla. Men de alla
CanFly
.Vi kunde inte beskriva dem med en abstrakt klass. De delar inte samma tillstånd eller identiska fält. För att definiera ett flygplan skulle vi förmodligen behöva fält för modell, tillverkningsår och maximalt antal passagerare. För Carlson skulle vi behöva åkrar för allt godis han åt idag, och en lista över de spel han kommer att spela med sin lillebror. För en mygga, ...eh... Jag vet inte ens... Kanske, en "irritationsnivå"? :)
Poängen är att vi inte kan använda en abstrakt klass för att beskriva dem. De är för olika. Men de har delat beteende: de kan flyga. Ett gränssnitt är perfekt för att beskriva allt i världen som kan flyga, simma, hoppa eller uppvisa något annat beteende.
-
Klasser kan implementera så många gränssnitt du vill, men de kan bara ärva en klass.
Vi har redan nämnt detta mer än en gång. Java har inte flera arv av klasser, men det stöder flera arv av gränssnitt. Denna punkt följer delvis från den föregående: ett gränssnitt kopplar ihop många olika klasser som ofta inte har något annat gemensamt, medan en abstrakt klass skapas för en grupp mycket närbesläktade klasser. Därför är det vettigt att du bara kan ärva en sådan klass. En abstrakt klass beskriver ett "är-ett"-förhållande.
Standardgränssnitt: InputStream och OutputStream
Vi har redan gått igenom olika klasser som ansvarar för in- och utströmmar. Låt oss övervägaInputStream
och OutputStream
. I allmänhet är dessa inte gränssnitt alls, utan snarare helt äkta abstrakta klasser. Nu vet du vad det betyder, så det blir mycket lättare att arbeta med dem :) InputStream
är en abstrakt klass som ansvarar för byte-inmatning. Java har flera klasser som ärver InputStream
. Var och en av dem är utformad för att ta emot data från olika källor. Eftersom InputStream
är förälder, tillhandahåller det flera metoder som gör det enkelt att arbeta med dataströmmar. Varje ättling till InputStream
har dessa metoder:
int available()
returnerar antalet byte tillgängliga för läsning;close()
stänger ingångsströmmen;int read()
returnerar en heltalsrepresentation av nästa tillgängliga byte i strömmen. Om slutet av strömmen har nåtts kommer -1 att returneras;int read(byte[] buffer)
försöker läsa byte till buffert, och returnerar antalet lästa byte. När den når slutet av filen returnerar den -1;int read(byte[] buffer, int byteOffset, int byteCount)
skriver en del av ett block av byte. Den används när byte-arrayen kanske inte har fyllts helt. När den når slutet av filen returnerar den -1;long skip(long byteCount)
hoppar över byteCount byte i inmatningsströmmen och returnerar antalet ignorerade byte.
FileInputStream
: den vanligaste typen avInputStream
. Den används för att läsa information från en fil;StringBufferInputStream
: En annan användbar typ avInputStream
. Den konverterar en sträng till enInputStream
;BufferedInputStream
: En buffrad ingångsström. Det används oftast för att öka prestandan.
BufferedReader
och sa att du inte behöver använda den? När vi skriver:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
…du behöver inte använda BufferedReader
: En InputStreamReader
kan göra jobbet. Men BufferedReader
förbättrar prestandan och kan också läsa hela rader med data snarare än enskilda tecken. Samma sak gäller BufferedInputStream
! Klassen ackumulerar indata i en speciell buffert utan att ständigt komma åt inmatningsenheten. Låt oss överväga ett exempel:
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 det här exemplet läser vi data från en fil som finns på en dator på ' D:/Users/UserName/someFile.txt ' . Vi skapar 2 objekt — ett FileInputStream
och ett BufferedInputStream
som "lindar" det. Sedan läser vi bytes från filen och konverterar dem till tecken. Och det gör vi tills filen tar slut. Som du kan se är det inget komplicerat här. Du kan kopiera den här koden och köra den på en riktig fil på din dator :) OutputStream
Klassen är en abstrakt klass som representerar en utdataström av bytes. Som du redan vet är detta motsatsen till en InputStream
. Det är inte ansvarigt för att läsa data någonstans, utan snarare för att skicka data någonstans . Som InputStream
, den här abstrakta klassen ger alla sina ättlingar en uppsättning bekväma metoder:
void close()
stänger utströmmen;void flush()
rensar alla utgångsbuffertar;abstract void write(int oneByte)
skriver 1 byte till utgångsströmmen;void write(byte[] buffer)
skriver en byte-array till utgångsströmmen;void write(byte[] buffer, int offset, int count)
skriver ett intervall av räknebyte från en array, med start vid offsetpositionen.
OutputStream
:
-
DataOutputStream
. En utdataström som inkluderar metoder för att skriva standard Java-datatyper.En mycket enkel klass för att skriva primitiva Java-datatyper och strängar. Du kommer förmodligen att förstå följande kod även utan en förklaring:
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 separata metoder för varje typ — , , ,
writeDouble()
ochwriteLong()
såwriteShort()
vidare. FileOutputStream
. Denna klass implementerar en mekanism för att skicka data till en fil på disken. Förresten, vi använde det redan i det förra exemplet. Märkte du? Vi skickade det till DataOutputStream, som fungerade som en "inpackning".BufferedOutputStream
. En buffrad utström. Det är inte heller något komplicerat här. Dess syfte är analogt medBufferedInputStream
(ellerBufferedReader
). Istället för den vanliga sekventiella läsningen av data, skriver den data med hjälp av en speciell "kumulativ" buffert. Bufferten gör det möjligt att minska antalet gånger datasänkan nås och därigenom öka prestandan.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); } }
Återigen, du kan själv leka med den här koden och verifiera att den fungerar på riktiga filer på din dator.
FileInputStream
och FileOutputStream
, BuffreredInputStream
så detta är tillräckligt med information för en första bekantskap. Det är allt! Vi hoppas att du förstår skillnaderna mellan gränssnitt och abstrakta klasser och är redo att svara på alla frågor, även trickfrågor :)
GO TO FULL VERSION