
-
Интерфейсът описва само поведението. То няма държава. Но абстрактен клас включва състояние: той описва и двете.
Например вземете
Bird
абстрактния клас иCanFly
интерфейса: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; } }
Нека създадем
MockingJay
клас птица и да го направим наследник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()); } }
Както можете да видите, можем лесно да получим достъп до състоянието на абстрактния клас - неговите
species
иage
променливи.Но ако се опитаме да направим същото с интерфейс, картината е различна. Можем да опитаме да добавим променливи към него:
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(); }
Не можем дори да декларираме частни променливи вътре в интерфейс. Защо? Тъй като частният модификатор е създаден, за да скрие изпълнението от потребителя. И интерфейсът няма реализация вътре в себе си: няма Howво да се крие.
Интерфейсът описва само поведението. Съответно не можем да внедрим гетери и сетери вътре в интерфейс. Това е природата на интерфейсите: те са необходими за работа с поведение, а не със състояние.
Java 8 въведе методи по подразбиране за интерфейси, които имат реализация. Вече знаете за тях, така че няма да се повтаряме.
-
Абстрактен клас свързва и обединява класове, които са много тясно свързани. В същото време един интерфейс може да бъде реализиран от класове, които нямат абсолютно нищо общо.
Да се върнем към нашия пример с птиците.
Нашият
Bird
абстрактен клас е необходим за създаване на птици, които са базирани на този клас. Само птици и нищо друго! Разбира се, ще има различни видове птици.С
CanFly
интерфейса всеки се справя по свой начин. Той описва само поведението (летене), свързано с името му. Много несвързани неща „могат да летят“.Тези 4 субекта не са свързани помежду си. Дори не всички са живи. Въпреки това, всички те
CanFly
.Не можахме да ги опишем с помощта на абстрактен клас. Те не споделят едно и също състояние or идентични полета. За да дефинираме самолет, вероятно ще ни трябват полета за модел, година на производство и максимален брой пътници. За Карлсън ще ни трябват полета за всички сладкиши, които е ял днес, и списък с игрите, които ще играе с малкия си брат. За комар, ...ъъ... дори не знам... Може би, "ниво на дразнене"? :)
Въпросът е, че не можем да използваме абстрактен клас, за да ги опишем. Те са твърде различни. Но те имат споделено поведение: могат да летят. Интерфейсът е идеален за описание на всичко в света, което може да лети, плува, скача or проявява няHowво друго поведение.
-
Класовете могат да реализират толкова интерфейси, колкото искате, но те могат да наследяват само един клас.
Вече сме споменавали това повече от веднъж. Java няма множествено наследяване на класове, но поддържа множествено наследяване на интерфейси. Тази точка следва отчасти от предишната: интерфейсът свързва много различни класове, които често нямат нищо общо, докато абстрактен клас се създава за група от много тясно свързани класове. Следователно има смисъл да можете да наследите само един такъв клас. Абстрактен клас описва връзка „е-а“.
Стандартни интерфейси: InputStream и OutputStream
Вече прегледахме различни класове, отговорни за входните и изходните потоци. Нека разгледамеInputStream
и OutputStream
. Като цяло, това изобщо не са интерфейси, а по-скоро напълно истински абстрактни класове. Сега знаете Howво означава това, така че ще бъде много по-лесно да работите с тях :) InputStream
е абстрактен клас, отговорен за въвеждане на byteове. Java има няколко класа, които наследяват InputStream
. Всеки от тях е предназначен да получава данни от различни източници. Тъй като InputStream
е родител, той предоставя няколко метода, които улесняват работата с потоци от данни. Всеки потомък на InputStream
има следните методи:
int available()
връща броя byteове, налични за четене;close()
затваря входния поток;int read()
връща цяло число на следващия наличен byte в потока. Ако е достигнат краят на потока, ще бъде върнато -1;int read(byte[] buffer)
опитва се да прочете byteове в буфера и връща броя на прочетените byteове. Когато достигне края на file, той връща -1;int read(byte[] buffer, int byteOffset, int byteCount)
записва част от блок от byteове. Използва се, когато масивът от byteове може да не е запълнен изцяло. Когато достигне края на file, той връща -1;long skip(long byteCount)
пропуска byteCount byteове във входния поток и връща броя на игнорираните byteове.
FileInputStream
: най-често срещаният тип наInputStream
. Използва се за четене на информация от файл;StringBufferInputStream
: Друг полезен вид наInputStream
. Той преобразува низ вInputStream
;BufferedInputStream
: Буфериран входен поток. Най-често се използва за повишаване на производителността.
BufferedReader
и казахме, че не е нужно да го използвате? Когато пишем:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
... не е нужно да използвате BufferedReader
: An InputStreamReader
може да свърши работата. Но BufferedReader
подобрява производителността и може да чете цели редове от данни, а не отделни знаци. Същото се отнася и за BufferedInputStream
! Класът натрупва входни данни в специален буфер, без да има постоянен достъп до входното устройство. Да разгледаме един пример:
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();
}
}
}
В този пример четем данни от файл, намиращ се на компютър в „ D:/Users/UserName/someFile.txt “. Създаваме 2 обекта — a FileInputStream
и a, BufferedInputStream
който го „обвива“. След това четем byteове от file и ги преобразуваме в знаци. И правим това, докато файлът свърши. Както можете да видите, тук няма нищо сложно. Можете да копирате този code и да го стартирате в реален файл на вашия компютър :) Класът OutputStream
е абстрактен клас, който представлява изходен поток от byteове. Както вече знаете, това е обратното на InputStream
. Той не е отговорен за четене на данни отнякъде, а по-скоро за изпращане на данни някъде . Подобно на InputStream
, този абстрактен клас дава на всички свои наследници набор от удобни методи:
void close()
затваря изходящия поток;void flush()
изчиства всички изходни буфери;abstract void write(int oneByte)
записва 1 byte в изходния поток;void write(byte[] buffer)
записва масив от byteове в изходния поток;void write(byte[] buffer, int offset, int count)
записва диапазон от броени byteове от масив, започвайки от изместената позиция.
OutputStream
класа:
-
DataOutputStream
. Изходен поток, който включва методи за писане на стандартни типове данни на Java.Много прост клас за писане на примитивни типове данни и низове на Java. Вероятно ще разберете следния code дори без обяснение:
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); } }
Има отделни методи за всеки тип —
writeDouble()
,writeLong()
,writeShort()
и т.н. FileOutputStream
. Този клас реализира механизъм за изпращане на данни към файл на диск. Между другото, вече го използвахме в последния пример. Забеляза ли? Предадохме го на DataOutputStream, който действаше като „обвивка“.BufferedOutputStream
. Буфериран изходен поток. Тук също няма нищо сложно. Целта му е аналогична наBufferedInputStream
(orBufferedReader
). Вместо обичайното последователно четене на данни, той записва данни, използвайки специален „кумулативен“ буфер. Буферът позволява да се намали броят на достъпите до приемника на данни, като по този начин се повишава производителността.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); } }
Отново можете сами да си поиграете с този code и да проверите дали ще работи върху реални файлове на вашия компютър.
FileInputStream
, FileOutputStream
и BuffreredInputStream
, така че това е достатъчно информация за първо запознанство. Това е! Надяваме се, че разбирате разликите между интерфейсите и абстрактните класове и сте готови да отговорите на всеки въпрос, дори на трикови въпроси :)
GO TO FULL VERSION