-
ממשק מתאר רק התנהגות. אין לו הון. אבל למעמד מופשט יש מצב: הוא מתאר את שניהם.
בואו ניקח מחלקה מופשטת
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 הציגה שיטות ממשק ברירת מחדל שיש להן יישום. אתה כבר יודע עליהם, אז לא נחזור עליהם.
-
כיתה מופשטת מקשרת ומשלבת שיעורים שיש להם קשר הדוק מאוד. יחד עם זאת, אותו ממשק יכול להיות מיושם על ידי מחלקות שאין להן שום דבר במשותף.
נחזור לדוגמה שלנו עם ציפורים.
הכיתה המופשטת שלנו
Bird
נחוצה כדי ליצור ציפורים המבוססות עליה. רק ציפורים ולא אף אחד אחר! כמובן שהם יהיו שונים.עם הממשק
Flyable
הכל שונה. הוא מתאר רק את ההתנהגות המתאימה לשמה - "עוף". ההגדרה של "עוף", "מסוגל לעוף" כוללת אובייקטים רבים שאינם קשורים זה לזה.4 הישויות הללו אינן קשורות זו לזו בשום צורה. מה אני יכול לומר, לא כולם אפילו מונפשים. עם זאת, כולם
Flyable
מסוגלים לעוף.לא נוכל לתאר אותם באמצעות מחלקה מופשטת. אין להם מדינה משותפת או שדות זהים. כדי לאפיין מטוס, נצטרך כנראה את השדות "דגם", "שנת ייצור" ו"מספר נוסעים מקסימלי". עבור קרלסון, יש שדות לכל הממתקים שהוא אכל היום, ורשימת משחקים שהוא ישחק עם הילד. בשביל יתוש...אה-אה...אנחנו אפילו לא יודעים... אולי "רמת מטרד"? :)
העיקר שלא נוכל לתאר אותם באמצעות מחלקה מופשטת. הם שונים מדי. אבל יש התנהגות נפוצה: הם יכולים לעוף. הממשק הוא אידיאלי לתיאור כל מה בעולם שיכול לעוף, לשחות, לקפוץ או להתנהגות אחרת.
-
מחלקות יכולות ליישם כמה ממשקים שהן רוצות, אבל הן יכולות לרשת רק ממחלקה אחת.
כבר דיברנו על זה יותר מפעם אחת. אין ירושה מרובה ב-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)
skipsbyteCount
, בית של קלט, מחזיר את מספר הבתים שהתעלמו מהם.
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("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
:
-
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
'א). במקום רישום נתונים רציף הרגיל, נעשה שימוש בהקלטה דרך מאגר "אחסון" מיוחד. באמצעות מאגר ניתן לצמצם את מספר הנסיעות הלוך ושוב ליעד הנתונים ובכך לשפר את היעילות.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
קלט /פלט ". אה , וגם תהיה לנו הרצאה נפרדת, אז יש מספיק מידע עליהם להיכרות ראשונה. זה הכל! אנו מקווים שיש לך הבנה טובה של ההבדלים בין ממשקים לשיעורים מופשטים ומוכנים לענות על כל שאלה, אפילו מסובכת :) OutputStream
FileInputStream
FileOutputStream
BufferedInputStream
GO TO FULL VERSION