Кратко
- Абстрактный класс может хранить состояние (поля), интерфейс только описывает поведение.
- От абстрактного класса можно унаследоваться только одного; интерфейсов класс реализует сколько угодно.
- Абстрактный класс объединяет близкие по смыслу классы (отношение «is a»), интерфейс объединяет разные классы с общим поведением.
- В интерфейсе нельзя объявить
private-поля и написать геттеры-сеттеры: там нечего скрывать. - С Java 8 у интерфейсов есть
default-методы с реализацией. InputStreamиOutputStreamв Java это не интерфейсы, а абстрактные классы.

Разница между абстрактными классами и интерфейсами
Итак, мы знаем, что такое абстрактный класс и что такое интерфейс. Теперь пройдемся по их отличиям. Кратко разницу удобно свести в таблицу:| Критерий | Абстрактный класс | Интерфейс |
|---|---|---|
| Состояние (поля) | есть, может хранить состояние | нет, только константы |
| Что описывает | и состояние, и поведение | только поведение |
| Множественность | наследуется только один | реализовать можно сколько угодно |
| Тип связи | объединяет близкие классы (отношение «is a») | объединяет разные классы по общему поведению |
| Методы с реализацией | да, обычные методы | только default-методы (с Java 8) |
| Ключевое слово | extends | implements |
Интерфейс описывает только поведение. У него нет состояния. А у абстрактного класса состояние есть: он описывает и то, и другое.
Возьмем для примера абстрактный класс
Birdи интерфейсFlyable: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("Лети, птичка!"); } public static void main(String[] args) { Mockingjay someBird = new Mockingjay(); someBird.setAge(19); System.out.println(someBird.getAge()); } }Как видишь, мы легко получаем доступ к состоянию абстрактного класса, к его переменным
species(вид) иage(возраст).Но если мы попытаемся сделать это же с интерфейсом, картина будет другой. Можем попробовать добавить в него переменные:
public interface Flyable { String species = new String(); int age = 10; public void fly(); } public interface Flyable { private String species = new String(); // ошибка private int age = 10; // тоже ошибка public void fly(); }У нас даже не получится создать внутри интерфейса private-переменные. Почему? Потому что private-модификатор создали, чтобы скрывать реализацию от пользователя. А внутри интерфейса реализации нет: там и скрывать нечего.
Интерфейс только описывает поведение. Соответственно, мы не сможем реализовать внутри интерфейса геттеры и сеттеры. Такова природа интерфейса: он нужен для работы с поведением, а не состоянием.
В Java8 появились дефолтные методы интерфейсов, у которых есть реализация. О них ты уже знаешь, поэтому повторяться не будем.
Абстрактный класс связывает между собой и объединяет классы, имеющие очень близкую связь. В то же время, один и тот же интерфейс могут реализовать классы, у которых вообще нет ничего общего.
Вернемся к нашему примеру с птицами.
Наш абстрактный класс
Birdнужен, чтобы на его основе создавать птиц. Только птиц и никого больше! Конечно, они будут разными.
С интерфейсом
Flyableвсе обстоит по-другому. Он только описывает поведение, соответствующее его названию, «летающий». Под определение «летающий», «способный летать» попадает много объектов, не связанных между собой.
Эти 4 сущности между собой никак не связаны. Что уж там говорить, не все из них даже являются одушевленными. Тем не менее, все они
Flyable, способные летать.Мы бы не смогли описать их с помощью абстрактного класса. У них нет общего состояния, одинаковых полей. Чтобы дать характеристику самолету, нам, наверное, понадобятся поля «модель», «год выпуска» и «максимальное количество пассажиров». Для Карлсона поля для всех сладостей, которые он сегодня съел, и список игр, в которые он будет играть с Малышом. Для комара...э-э-э...даже не знаем… Может, «уровень надоедливости»? :)
Главное, что с помощью абстрактного класса описать их мы не можем. Они слишком разные. Но есть общее поведение: они могут летать. Интерфейс идеально подойдет для описания всего на свете, что умеет летать, плавать, прыгать или обладает каким-то другим поведением.
Классы могут реализовывать сколько угодно интерфейсов, но наследоваться можно только от одного класса.
Об этом мы уже говорили не раз. Множественного наследования в Java нет, а множественная реализация есть. Отчасти этот пункт вытекает из предыдущего: интерфейс связывает между собой множество разных классов, у которых часто нет ничего общего, а абстрактный класс создается для группы очень близких друг другу классов. Поэтому логично, что наследоваться можно только от одного такого класса. Абстрактный класс описывает отношения «is a».
Стандартные интерфейсы InputStream & OutputStream
Мы уже проходили различные классы, отвечающие за потоковый ввод и вывод. Давай рассмотримInputStream и OutputStream.
Вообще, никакие это не интерфейсы, а самые настоящие абстрактные классы. Теперь ты знаешь, что это такое, поэтому работать с ними будет намного проще :)
InputStream это абстрактный класс, который отвечает за байтовый ввод. В Java есть серия классов, наследующих InputStream. Каждый из них настроен таким образом, чтобы получать данные из разных источников.
Поскольку InputStream является родителем, он предоставляет несколько методов для удобной работы с потоками данных. Эти методы есть у каждого потомка InputStream:
int available()возвращает число байт, доступных для чтения;close()закрывает источник ввода;int read()возвращает целочисленное представление следующего доступного байта в потоке. Если достигнут конец потока, будет возвращено число -1;int read(byte[] buffer)пытается читать байты в буфер, возвращая количество прочитанных байтов. Когда он достигает конца файла, возвращает значение -1;int read(byte[] buffer, int byteOffset, int byteCount)считывает часть блока байт. Используется, когда есть вероятность, что блок данных был заполнен не целиком. Когда он достигает конца файла, возвращает -1;long skip(long byteCount)пропускаетbyteCount, байт ввода, возвращая количество проигнорированных байтов.
FileInputStream: самый распространенный видInputStream. Используется для чтения информации из файла;StringBufferInputStream: еще один полезный видInputStream. Он превращает строку во входной поток данныхInputStream;BufferedInputStream: буферизированный входной поток. Чаще всего он используется для повышения эффективности.
BufferedReader и говорили, что его можно не использовать?
Когда мы пишем:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
…использовать BufferedReader не обязательно: 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("Был прочитан символ " + c);
}
} catch(Exception e) {
e.printStackTrace();
} finally {
inputStream.close();
buffer.close();
}
}
}
В этом примере мы читаем данные из файла, который находится на компьютере по адресу «D:/Users/UserName/someFile.txt». Создаем 2 объекта, FileInputStream и BufferedInputStream в качестве его «обертки». После этого мы считываем байты из файла и преобразуем их в символы. И так до тех пор, пока файл не закончится.
Как видишь, ничего сложного здесь нет. Ты можешь скопировать этот код и запустить его на каком-то настоящем файле, который хранится на твоем компьютере :)
Класс OutputStream это абстрактный класс, определяющий потоковый байтовый вывод.
Как ты уже понял, это антипод InputStream’a. Он отвечает не за то, откуда считывать данные, а за то, куда их отправить.
Как и InputStream, этот абстрактный класс предоставляет всем потомкам группу методов для удобной работы:
int close()закрывает выходной поток;void flush()очищает все буферы вывода;abstract void write (int oneByte)записывает 1 байт в выходной поток;void write (byte[] buffer)записывает массив байтов в выходной поток;void write (byte[] buffer, int offset, int count)записывает диапазон из count байт из массива, начиная с позиции offset.
OutputStream:
DataOutputStream. Выходной поток, включающий методы для записи стандартных типов данных Java.Очень простой класс для записи примитивных типов Java и строк. Наверняка ты поймешь написанный код даже без объяснений:
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(или уBufferedReader’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!"; // эту строку мы преобразуем в массив байтов и запишем в файл byte[] buffer = text.getBytes(); bufferedStream.write(buffer, 0, buffer.length); bufferedStream.close(); } }Опять же, ты можешь самостоятельно «поиграть» с этим кодом и проверить, как он будет работать на реальных файлах твоего компьютера.
InputStream и OutputStream и наследниках можно почитать в материале «Система ввода/вывода».
О FileInputStream, FileOutputStream и BufferedInputStream у нас еще будет отдельная лекция, поэтому для первого знакомства информации о них достаточно.
Вот и все! Надеемся, ты хорошо разобрался в отличиях между интерфейсами и абстрактными классами и готов ответить на любой вопрос, даже с подвохом :) Больше об абстрактных классах и методах можно почитать в документации Oracle (Abstract Methods and Classes).
Читайте также
- Интерфейсы в Java: что именно описывает интерфейс и как его реализуют.
- Принципы ООП: абстракция, наследование и инкапсуляция.
- Конструкторы базовых классов: как работает наследование и отношение «is a».
Вопросы и ответы
Чем абстрактный класс отличается от интерфейса?
Абстрактный класс может хранить состояние (поля) и содержать реализацию методов, а интерфейс описывает только поведение. От класса наследуются одного, а интерфейсов реализуют сколько угодно.Может ли класс реализовать несколько интерфейсов?
Да, сколько угодно. А вот унаследоваться в Java можно только от одного класса, в том числе абстрактного.Может ли интерфейс иметь поля (состояние)?
Обычных полей-состояния у интерфейса нет. Переменные в нем это по сути константы, аprivate-поля и геттеры-сеттеры объявить нельзя.
Когда использовать абстрактный класс, а когда интерфейс?
Абстрактный класс, когда нужно объединить близкие классы с общим состоянием (отношение «is a»). Интерфейс, когда нужно задать общее поведение разным, не связанным между собой классам.Могут ли методы интерфейса иметь реализацию?
С Java 8 да: этоdefault-методы. Обычные методы интерфейса реализации не имеют, ее пишет класс, реализующий интерфейс.
InputStream и OutputStream это интерфейсы?
Нет, это абстрактные классы стандартной библиотеки Java.InputStream отвечает за байтовый ввод, OutputStream за байтовый вывод.
а то, что автор коммента пишет, что где-то там было написано что ... ЭТО ДАЛЕКО НЕ ФАКТ, пусть лучше автор сходит и убедится, что там было написано "так", а не "иначе", а потом вернется доказательствами!)))