CodeGym/Java блог/Случаен/Разликата между абстрактни класове и интерфейси
John Squirrels
Ниво
San Francisco

Разликата между абстрактни класове и интерфейси

Публикувано в групата
здрасти В този урок ще говорим за това How абстрактните класове се различават от интерфейсите и ще разгледаме някои примери с общи абстрактни класове. Разликата между абстрактни класове и интерфейси - 1Посветихме отделен урок на разликите между абстрактен клас и интерфейс, защото тази тема е много важна. Ще бъдете попитани за разликата между тези понятия в 90% от бъдещите интервюта. Това означава, че трябва да сте сигурни, че разбирате Howво четете. И ако нещо не разбирате напълно, прочетете допълнителни източници. И така, знаем Howво е абстрактен клас и Howво е интерфейс. Сега ще разгледаме техните различия.
  1. Интерфейсът описва само поведението. То няма държава. Но абстрактен клас включва състояние: той описва и двете.

    Например вземете 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 въведе методи по подразбиране за интерфейси, които имат реализация. Вече знаете за тях, така че няма да се повтаряме.

  2. Абстрактен клас свързва и обединява класове, които са много тясно свързани. В същото време един интерфейс може да бъде реализиран от класове, които нямат абсолютно нищо общо.

    Да се ​​върнем към нашия пример с птиците.

    Нашият Birdабстрактен клас е необходим за създаване на птици, които са базирани на този клас. Само птици и нищо друго! Разбира се, ще има различни видове птици.

    Разликата между абстрактни класове и интерфейси - 2

    С CanFlyинтерфейса всеки се справя по свой начин. Той описва само поведението (летене), свързано с името му. Много несвързани неща „могат да летят“.

    Разликата между абстрактни класове и интерфейси - 3

    Тези 4 субекта не са свързани помежду си. Дори не всички са живи. Въпреки това, всички те CanFly.

    Не можахме да ги опишем с помощта на абстрактен клас. Те не споделят едно и също състояние or идентични полета. За да дефинираме самолет, вероятно ще ни трябват полета за модел, година на производство и максимален брой пътници. За Карлсън ще ни трябват полета за всички сладкиши, които е ял днес, и списък с игрите, които ще играе с малкия си брат. За комар, ...ъъ... дори не знам... Може би, "ниво на дразнене"? :)

    Въпросът е, че не можем да използваме абстрактен клас, за да ги опишем. Те са твърде различни. Но те имат споделено поведение: могат да летят. Интерфейсът е идеален за описание на всичко в света, което може да лети, плува, скача or проявява няHowво друго поведение.

  3. Класовете могат да реализират толкова интерфейси, колкото искате, но те могат да наследяват само един клас.

    Вече сме споменавали това повече от веднъж. 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ове.
Препоръчвам ви да проучите пълния списък с методи . Всъщност има повече от десет детски класа. Например, ето няколко:
  1. FileInputStream: най-често срещаният тип на InputStream. Използва се за четене на информация от файл;
  2. StringBufferInputStream: Друг полезен вид на InputStream. Той преобразува низ в InputStream;
  3. 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класа:
  1. 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()и т.н.


  2. FileOutputStream. Този клас реализира механизъм за изпращане на данни към файл на диск. Между другото, вече го използвахме в последния пример. Забеляза ли? Предадохме го на DataOutputStream, който действаше като „обвивка“.

  3. BufferedOutputStream. Буфериран изходен поток. Тук също няма нищо сложно. Целта му е аналогична на BufferedInputStream(or BufferedReader). Вместо обичайното последователно четене на данни, той записва данни, използвайки специален „кумулативен“ буфер. Буферът позволява да се намали броят на достъпите до приемника на данни, като по този начин се повишава производителността.

    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, така че това е достатъчно информация за първо запознанство. Това е! Надяваме се, че разбирате разликите между интерфейсите и абстрактните класове и сте готови да отговорите на всеки въпрос, дори на трикови въпроси :)
Коментари
  • Популярен
  • Нов
  • Стар
Трябва да сте влезли, за да оставите коментар
Тази страница все още няма коментари