-
الواجهة تصف السلوك فقط. ليس لها دولة. لكن الفئة المجردة تتضمن الحالة: فهي تصف كليهما.
على سبيل المثال، خذ
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(); }
لا يمكننا حتى الإعلان عن المتغيرات الخاصة داخل الواجهة. لماذا؟ لأنه تم إنشاء المعدل الخاص لإخفاء التنفيذ عن المستخدم. ولا تحتوي الواجهة على تطبيق بداخلها: لا يوجد شيء يمكن إخفاؤه.
الواجهة تصف السلوك فقط. وفقًا لذلك، لا يمكننا تنفيذ الحروف والمحددات داخل الواجهة. هذه هي طبيعة الواجهات: فهي ضرورية للتعامل مع السلوك، وليس الحالة.
قدم Java 8 الطرق الافتراضية للواجهات التي لها تطبيق. أنت تعرف عنها بالفعل، لذلك لن نكرر أنفسنا.
-
تربط الفئة المجردة وتوحد الفئات المرتبطة ارتباطًا وثيقًا. وفي الوقت نفسه، يمكن تنفيذ واجهة واحدة بواسطة فئات ليس لديها أي شيء مشترك على الإطلاق.
دعونا نعود إلى مثالنا مع الطيور.
هناك حاجة إلى فصلنا
Bird
التجريدي لإنشاء الطيور التي تعتمد على تلك الفئة. مجرد الطيور ولا شيء غير ذلك! وبطبيعة الحال، سيكون هناك أنواع مختلفة من الطيور.مع
CanFly
الواجهة، يستطيع الجميع العمل بطريقتهم الخاصة. فهو يصف فقط السلوك (الطيران) المرتبط باسمه. العديد من الأشياء غير ذات الصلة "يمكن أن تطير".هذه الكيانات الأربعة ليست مرتبطة ببعضها البعض. إنهم ليسوا جميعًا أحياء. ومع ذلك، كلهم
CanFly
.لا يمكننا وصفهم باستخدام فئة مجردة. ولا يتشاركون في نفس الحالة أو الحقول المتطابقة. لتحديد طائرة، ربما نحتاج إلى حقول للطراز وسنة الإنتاج والحد الأقصى لعدد الركاب. بالنسبة لكارلسون، سنحتاج إلى حقول لجميع الحلويات التي أكلها اليوم، وقائمة بالألعاب التي سيلعبها مع أخيه الصغير. بالنسبة للبعوضة، ...آه... أنا لا أعرف حتى... ربما، "مستوى الانزعاج"؟ :)
النقطة المهمة هي أنه لا يمكننا استخدام فئة مجردة لوصفها. إنهم مختلفون للغاية. لكن لديهم سلوك مشترك: يمكنهم الطيران. تعد الواجهة مثالية لوصف كل شيء في العالم يمكنه الطيران أو السباحة أو القفز أو إظهار بعض السلوكيات الأخرى.
-
يمكن للفئات تنفيذ أي عدد تريده من الواجهات، لكن يمكنها أن ترث فئة واحدة فقط.
لقد ذكرنا هذا بالفعل أكثر من مرة. لا تحتوي 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("Character read: " + c);
}
} catch(Exception e) {
e.printStackTrace();
} finally {
inputStream.close();
buffer.close();
}
}
}
في هذا المثال، نقرأ البيانات من ملف موجود على جهاز كمبيوتر في ' D:/Users/UserName/someFile.txt '. نقوم بإنشاء كائنين - a FileInputStream
و a BufferedInputStream
الذي "يغلفه". ثم نقرأ البايتات من الملف ونحولها إلى أحرف. ونفعل ذلك حتى ينتهي الملف. كما ترون، لا يوجد شيء معقد هنا. يمكنك نسخ هذا الرمز وتشغيله على ملف حقيقي على جهاز الكمبيوتر الخاص بك :) الفئة OutputStream
عبارة عن فئة مجردة تمثل دفق إخراج من البايتات. كما تعلم بالفعل، هذا هو عكس InputStream
. وهي ليست مسؤولة عن قراءة البيانات من مكان ما، بل هي مسؤولة عن إرسال البيانات إلى مكان ما . مثلًا InputStream
، توفر هذه الفئة المجردة لجميع أحفادها مجموعة من الأساليب الملائمة:
void close()
يغلق دفق الإخراج.void flush()
مسح كافة المخازن المؤقتة للإخراج؛abstract void write(int oneByte)
يكتب بايت واحد إلى دفق الإخراج؛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'll convert this string to a byte array and write it to a file byte[] buffer = text.getBytes(); bufferedStream.write(buffer, 0, buffer.length); } }
مرة أخرى، يمكنك تجربة هذا الرمز بنفسك والتحقق من أنه سيعمل على الملفات الحقيقية الموجودة على جهاز الكمبيوتر الخاص بك.
FileInputStream
، فهذه معلومات كافية للتعارف الأول. هذا كل شيء! نأمل أن تفهم الاختلافات بين الواجهات والفصول المجردة وأن تكون جاهزًا للإجابة على أي سؤال، حتى الأسئلة الخادعة :) FileOutputStream
BuffreredInputStream
GO TO FULL VERSION