Szia! Ebben a leckében arról fogunk beszélni, hogy miben különböznek az absztrakt osztályok az interfészektől, és megvizsgálunk néhány példát a gyakori absztrakt osztályokra. Az absztrakt osztályok és interfészek közötti különbség - 1Külön leckét szenteltünk az absztrakt osztály és az interfész közötti különbségeknek, mert ez a téma nagyon fontos. A jövőbeli interjúk 90%-ában megkérdezik majd, hogy mi a különbség ezek között a fogalmak között. Ez azt jelenti, hogy biztosan rá kell jönnie, hogy mit olvas. És ha valamit nem értesz teljesen, olvass el további forrásokat. Tehát tudjuk, mi az absztrakt osztály és mi az interfész. Most áttekintjük a különbségeiket.
  1. Az interfész csak a viselkedést írja le. Nincs állama. De egy absztrakt osztály magában foglalja az állapotot: mindkettőt leírja.

    Vegyük például az Birdabsztrakt osztályt és a CanFlyfelületet:

    
    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;
       }
    }
    

    Hozzunk létre egy MockingJaymadárosztályt, és örököljük 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());
       }
    }
    

    Amint látja, könnyen hozzáférhetünk az absztrakt osztály állapotához – annak speciesés ageváltozóihoz.

    De ha ugyanezt egy interfésszel próbáljuk megtenni, akkor más a kép. Megpróbálhatunk változókat hozzáadni hozzá:

    
    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();
    }
    

    Még privát változókat sem deklarálhatunk egy interfészen belül. Miért? Mert a privát módosító azért jött létre, hogy elrejtse a megvalósítást a felhasználó elől. És egy felületen nincs implementáció: nincs rejtegetnivaló.

    Az interfész csak a viselkedést írja le. Ennek megfelelően nem implementálhatunk gettereket és settereket egy felületen belül. Ez az interfészek természete: a viselkedéssel, nem pedig az állapottal való munkához szükségesek.

    A Java 8 alapértelmezett metódusokat vezetett be a megvalósítással rendelkező interfészekhez. Ön már tud róluk, ezért nem ismételjük magunkat.

  2. Az absztrakt osztály olyan osztályokat köt össze és egyesít, amelyek nagyon szorosan kapcsolódnak egymáshoz. Ugyanakkor egyetlen interfészt megvalósíthatnak olyan osztályok, amelyekben semmi közös.

    Térjünk vissza a madarakkal kapcsolatos példánkhoz.

    Absztrakt osztályunkra Birdszükség van az osztályon alapuló madarak létrehozásához. Csak madarak és semmi más! Természetesen lesznek különféle madarak.

    Az absztrakt osztályok és interfészek közötti különbség - 2

    A CanFlyfelülettel mindenki a maga módján halad. Csak a nevéhez kapcsolódó viselkedést (repülést) írja le. Sok független dolog „repülhet”.

    Az absztrakt osztályok és interfészek közötti különbség - 3

    Ez a 4 entitás nem kapcsolódik egymáshoz. Még csak nem is mind élnek. Azonban mindannyian CanFly.

    Nem tudtuk leírni őket absztrakt osztály segítségével. Nem osztják meg ugyanazt az állapotot vagy nem azonos mezőket. A repülőgép meghatározásához valószínűleg a modellre, a gyártási évre és a maximális utasszámra lenne szükségünk. Carlsonnak szüksége lenne egy mezőre az összes édesség számára, amelyet ma evett, és egy listát azokról a játékokról, amelyeket az öccsével fog játszani. Egy szúnyognak... ööö... nem is tudom... Talán egy „bosszankodási szint”? :)

    A lényeg az, hogy nem használhatunk absztrakt osztályt ezek leírására. Túlságosan különböznek egymástól. De közös a viselkedésük: tudnak repülni. Egy interfész tökéletes a világon minden dolog leírására, ami képes repülni, úszni, ugrani vagy más viselkedést mutatni.

  3. Az osztályok tetszőleges számú interfészt implementálhatnak, de csak egy osztályt örökölhetnek.

    Ezt már többször említettük. A Java nem rendelkezik osztályok többszörös öröklésével, de támogatja az interfészek többszörös öröklését. Ez a pont részben következik az előzőből: egy interfész sok különböző osztályt köt össze, amelyekben gyakran semmi más nem közös, míg egy absztrakt osztály a nagyon szorosan kapcsolódó osztályok csoportjához jön létre. Ezért logikus, hogy csak egy ilyen osztályt örökölhet. Az absztrakt osztály egy „is-a” kapcsolatot ír le.

Szabványos interfészek: InputStream és OutputStream

Már átnéztük a bemeneti és kimeneti adatfolyamokért felelős különböző osztályokat. Tekintsük InputStreamés OutputStream. Általában ezek egyáltalán nem interfészek, hanem teljesen eredeti absztrakt osztályok. Most már tudod, hogy ez mit jelent, így sokkal könnyebb lesz velük dolgozni :) InputStreamegy absztrakt osztály, amely a bájtbevitelért felelős. A Java-nak számos osztálya van, amelyek öröklik InputStream. Mindegyiket úgy tervezték, hogy különböző forrásokból fogadjon adatokat. Mivel InputStreama szülő, számos olyan módszert biztosít, amelyek megkönnyítik az adatfolyamokkal való munkát. Mindegyik leszármazottja InputStreamrendelkezik a következő módszerekkel:
  • int available()visszaadja az olvasásra rendelkezésre álló bájtok számát;
  • close()bezárja a bemeneti adatfolyamot;
  • int read()a folyam következő elérhető bájtjának egész számmal történő megjelenítését adja vissza. Ha a folyam végét elértük, -1-et ad vissza;
  • int read(byte[] buffer)bájtokat próbál beolvasni a pufferbe, és visszaadja az olvasott bájtok számát. Amikor eléri a fájl végét, akkor -1;
  • int read(byte[] buffer, int byteOffset, int byteCount)egy bájtblokk egy részét írja. Akkor használatos, ha a bájttömb esetleg nincs teljesen kitöltve. Amikor eléri a fájl végét, akkor -1;
  • long skip(long byteCount)kihagyja a byteCount byte-ot a bemeneti adatfolyamban, és visszaadja a figyelmen kívül hagyott bájtok számát.
Azt javaslom, hogy tanulmányozza át a módszerek teljes listáját . Valójában több mint tíz gyerekosztály van. Például itt van néhány:
  1. FileInputStream: a leggyakoribb típus a InputStream. Fájlból információk olvasására szolgál;
  2. StringBufferInputStream: Egy másik hasznos típus InputStream. Egy karakterláncot alakít át InputStream;
  3. BufferedInputStream: Pufferelt bemeneti adatfolyam. Leggyakrabban a teljesítmény növelésére használják.
Emlékszel, amikor odamentünk BufferedReaderés azt mondtuk, hogy nem kell használnod? Amikor írunk:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
…nem kell használnod BufferedReader: Egy InputStreamReaderképes elvégezni a munkát. De BufferedReaderjavítja a teljesítményt, és az egyes karakterek helyett egész adatsort tud olvasni. Ugyanez vonatkozik rá BufferedInputStream! Az osztály egy speciális pufferben gyűjti a bemeneti adatokat anélkül, hogy folyamatosan hozzáférne a beviteli eszközhöz. Nézzünk egy példát:

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();
       }
   }
}
Ebben a példában a „ D:/Users/UserName/someFile.txt ” címen található számítógépen található fájlból olvasunk adatokat . Létrehozunk 2 objektumot – a-t FileInputStreamés a-t BufferedInputStream, amelyek „becsomagolják”. Ezután bájtokat olvasunk ki a fájlból, és karakterekké alakítjuk. És ezt addig tesszük, amíg a fájl véget nem ér. Amint látja, nincs itt semmi bonyolult. Ezt a kódot lemásolhatod és egy valós fájlon futtathatod a számítógépeden :) Az OutputStreamosztály egy absztrakt osztály, amely egy bájtokból álló kimeneti adatfolyamot képvisel. Mint már tudja, ez az ellentéte egy InputStream. Nem azért felelős, hogy adatokat olvasson valahonnan, hanem azért, hogy adatokat küldjön valahova . Hasonlóan InputStream, ez az absztrakt osztály minden leszármazottjának kényelmes metódusokat ad:
  • void close()bezárja a kimeneti adatfolyamot;
  • void flush()törli az összes kimeneti puffert;
  • abstract void write(int oneByte)1 bájtot ír a kimeneti adatfolyamba;
  • void write(byte[] buffer)bájttömböt ír a kimeneti adatfolyamba;
  • void write(byte[] buffer, int offset, int count)egy tömbből egy számláló bájt tartományt ír ki, az eltolási pozíciótól kezdve.
Íme néhány leszármazott az osztályból OutputStream:
  1. DataOutputStream. Kimeneti adatfolyam, amely szabványos Java adattípusok írási módszereit tartalmazza.

    Egy nagyon egyszerű osztály primitív Java adattípusok és karakterláncok írásához. Valószínűleg magyarázat nélkül is megérti a következő kódot:

    
    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);
    
       }
    }
    

    Minden típushoz külön metódusai vannak – writeDouble(), writeLong(), writeShort(), és így tovább.


  2. FileOutputStream. Ez az osztály egy olyan mechanizmust valósít meg, amellyel adatokat küldhet a lemezen lévő fájlba. Egyébként az utolsó példában már használtuk. Észrevetted? Átadtuk a DataOutputStreamnek, amely „csomagolóként” működött.

  3. BufferedOutputStream. Pufferelt kimeneti adatfolyam. Itt sincs semmi bonyolult. Célja analóg BufferedInputStream(vagy BufferedReader). A szokásos szekvenciális adatolvasás helyett egy speciális „halmozott” puffer segítségével ír adatokat. A puffer lehetővé teszi az adatnyelőhöz való hozzáférések számának csökkentését, ezáltal növelve a teljesítményt.

    
    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);
         }
    }
    

    Ismét játszhat ezzel a kóddal, és ellenőrizheti, hogy működik-e a számítógépén lévő valódi fájlokon.

Külön leckét tartunk a FileInputStream, FileOutputStreamés a -ról BuffreredInputStream, így ez elég információ az első ismerkedéshez. Ez az! Reméljük, megérti az interfészek és az absztrakt osztályok közötti különbségeket, és készen áll válaszolni bármilyen kérdésre, még trükkös kérdésekre is :)