-
O interfață descrie doar comportamentul. Nu are stat. Dar o clasă abstractă include starea: le descrie pe amândouă.
De exemplu, luați
Bird
clasa abstractă șiCanFly
interfaț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
MockingJay
clasă 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
șiage
variabilele.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.
-
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ă
Bird
abstractă 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.Cu
CanFly
interfaț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”.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.
-
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 considerareInputStream
ș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 :) InputStream
este 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 InputStream
este părintele, oferă mai multe metode care facilitează lucrul cu fluxurile de date. Fiecare descendent al InputStream
are 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.
FileInputStream
: cel mai comun tip deInputStream
. Este folosit pentru a citi informații dintr-un fișier;StringBufferInputStream
: Un alt tip util deInputStream
. Acesta convertește un șir într-unInputStream
;BufferedInputStream
: un flux de intrare în tampon. Este folosit cel mai adesea pentru a crește performanța.
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 InputStreamReader
poate 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 BufferedInputStream
care î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 OutputStream
este 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.
OutputStream
:
-
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. 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”.BufferedOutputStream
. Un flux de ieșire tamponat. Nici aici nu este nimic complicat. Scopul său este analog cuBufferedInputStream
(sauBufferedReader
). Î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.
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:)
GO TO FULL VERSION