JavaRush /בלוג Java /Random-HE /הבדל בין מחלקות מופשטות וממשקים

הבדל בין מחלקות מופשטות וממשקים

פורסם בקבוצה
שלום! בהרצאה זו, נדבר על איך נבדלים מחלקות מופשטות מממשקים ונסתכל על דוגמאות עם מחלקות מופשטות נפוצות. ההבדל בין שיעורים מופשטים וממשקים - 1הקדשנו הרצאה נפרדת להבדלים בין שיעור מופשט לממשק, שכן הנושא חשוב מאוד. תישאל על ההבדל בין המושגים הללו ב-90% מהראיונות העתידיים. לכן, הקפידו להבין את מה שקראתם, ואם אינכם מבינים משהו עד הסוף, קראו מקורות נוספים. אז, אנחנו יודעים מהי מחלקה מופשטת ומהו ממשק. עכשיו בואו נעבור על ההבדלים ביניהם.
  1. ממשק מתאר רק התנהגות. אין לו הון. אבל למעמד מופשט יש מצב: הוא מתאר את שניהם.

    בואו ניקח מחלקה מופשטת 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("Fly, birdie!");
       }
    
       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(); // error
       private int age = 10; // also an error
    
       public void fly();
    }

    לא נוכל אפילו ליצור משתנים פרטיים בתוך הממשק. למה? כי השינוי הפרטי נוצר כדי להסתיר את היישום מהמשתמש. אבל אין יישום בתוך הממשק: אין מה להסתיר שם.

    הממשק רק מתאר את ההתנהגות. בהתאם לכך, לא נוכל ליישם קטרים ​​ומגדירים בתוך הממשק. זה טבעו של ממשק: הוא נועד להתמודד עם התנהגות, לא מדינה.

    Java8 הציגה שיטות ממשק ברירת מחדל שיש להן יישום. אתה כבר יודע עליהם, אז לא נחזור עליהם.

  2. כיתה מופשטת מקשרת ומשלבת שיעורים שיש להם קשר הדוק מאוד. יחד עם זאת, אותו ממשק יכול להיות מיושם על ידי מחלקות שאין להן שום דבר במשותף.

    נחזור לדוגמה שלנו עם ציפורים.

    הכיתה המופשטת שלנו Birdנחוצה כדי ליצור ציפורים המבוססות עליה. רק ציפורים ולא אף אחד אחר! כמובן שהם יהיו שונים.

    ההבדל בין שיעורים מופשטים וממשקים - 2

    עם הממשק Flyableהכל שונה. הוא מתאר רק את ההתנהגות המתאימה לשמה - "עוף". ההגדרה של "עוף", "מסוגל לעוף" כוללת אובייקטים רבים שאינם קשורים זה לזה.

    ההבדל בין שיעורים מופשטים וממשקים - 3

    4 הישויות הללו אינן קשורות זו לזו בשום צורה. מה אני יכול לומר, לא כולם אפילו מונפשים. עם זאת, כולם Flyableמסוגלים לעוף.

    לא נוכל לתאר אותם באמצעות מחלקה מופשטת. אין להם מדינה משותפת או שדות זהים. כדי לאפיין מטוס, נצטרך כנראה את השדות "דגם", "שנת ייצור" ו"מספר נוסעים מקסימלי". עבור קרלסון, יש שדות לכל הממתקים שהוא אכל היום, ורשימת משחקים שהוא ישחק עם הילד. בשביל יתוש...אה-אה...אנחנו אפילו לא יודעים... אולי "רמת מטרד"? :)

    העיקר שלא נוכל לתאר אותם באמצעות מחלקה מופשטת. הם שונים מדי. אבל יש התנהגות נפוצה: הם יכולים לעוף. הממשק הוא אידיאלי לתיאור כל מה בעולם שיכול לעוף, לשחות, לקפוץ או להתנהגות אחרת.

  3. מחלקות יכולות ליישם כמה ממשקים שהן רוצות, אבל הן יכולות לרשת רק ממחלקה אחת.

    כבר דיברנו על זה יותר מפעם אחת. אין ירושה מרובה ב-Java, אבל יש יישום מרובה. נקודה זו נובעת בחלקה מהקודמת: ממשק מחבר בין מחלקות רבות ושונות שלעיתים אין להן שום דבר במשותף, ומחלקה מופשטת נוצרת עבור קבוצת מחלקות קרובות מאוד זו לזו. לכן, זה הגיוני שאתה יכול לרשת רק ממעמד אחד כזה. שיעור מופשט מתאר את מערכת היחסים "הוא".

ממשקי 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)skips byteCount, בית של קלט, מחזיר את מספר הבתים שהתעלמו מהם.
אני ממליץ לך ללמוד את רשימת השיטות המלאה . למעשה יש יותר מתריסר שיעורים ממשיכים. הנה כמה דוגמאות:
  1. FileInputStream: הסוג הנפוץ ביותר InputStream. משמש לקריאת מידע מקובץ;
  2. StringBufferInputStream: עוד סוג שימושי InputStream. זה הופך מחרוזת לזרם נתונים קלט InputStream;
  3. 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("Character was read" + c);
           }
       } catch(Exception e) {

           e.printStackTrace();

       } finally {

           inputStream.close();
           buffer.close();
       }
   }
}
בדוגמה זו, אנו קוראים נתונים מקובץ שנמצא במחשב בכתובת "D:/Users/UserName/someFile.txt" . אנו יוצרים 2 אובייקטים - FileInputStreamוכ"העטיפה BufferedInputStream" שלו. לאחר מכן, אנו קוראים את הבייטים מהקובץ וממירים אותם לתווים. וכך הלאה עד שהקובץ מסתיים. כפי שאתה יכול לראות, אין כאן שום דבר מסובך. אתה יכול להעתיק את הקוד הזה ולהריץ אותו על איזה קובץ אמיתי שמאוחסן במחשב שלך :) מחלקה OutputStreamהיא מחלקה אבסטרקטית שמגדירה פלט זרם בתים. כפי שכבר הבנתם, זהו האנטיפוד של InputStream'א. הוא אחראי לא לאן לקרוא נתונים, אלא לאן לשלוח אותם . כמו InputStream, מחלקה מופשטת זו מספקת לכל הצאצאים קבוצה של שיטות לעבודה נוחה:
  • int close()סוגר את זרם הפלט;
  • void flush()מנקה את כל מאגרי הפלט;
  • abstract void write (int oneByte)כותב 1 בייט לזרם הפלט;
  • void write (byte[] buffer)כותב מערך של בתים לזרם הפלט;
  • void write (byte[] buffer, int offset, int count)כותב טווח של בתים ספירה מהמערך, החל בהיסט המיקום.
הנה כמה מצאצאי הכיתה OutputStream:
  1. 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()וכן הלאה.

  2. מעמד FileOutputStream . מיישמת מנגנון לשליחת נתונים לקובץ בדיסק. אגב, כבר השתמשנו בזה בדוגמה הקודמת, שמתם לב? העברנו אותו בתוך ה-DataOutputStream, שפעל כ"עטיפה".

  3. BufferedOutputStream. זרם פלט ממוגן. גם שום דבר לא מסובך, המהות זהה ל- BufferedInputStream(או 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 will convert this string into an array of bytes and write it to a file
    
           byte[] buffer = text.getBytes();
    
           bufferedStream.write(buffer, 0, buffer.length);
           bufferedStream.close();
       }
    }

    שוב, אתה יכול "לשחק" עם הקוד הזה בעצמך ולבדוק איך זה יעבוד על קבצים אמיתיים במחשב שלך.

ניתן גם לקרוא על היורשים בחומר " מערכת InputStreamקלט /פלט ". אה , וגם תהיה לנו הרצאה נפרדת, אז יש מספיק מידע עליהם להיכרות ראשונה. זה הכל! אנו מקווים שיש לך הבנה טובה של ההבדלים בין ממשקים לשיעורים מופשטים ומוכנים לענות על כל שאלה, אפילו מסובכת :) OutputStreamFileInputStreamFileOutputStreamBufferedInputStream
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION