JavaRush /مدونة جافا /Random-AR /نظرية الأدوية الجنيسة في جافا أو كيفية وضع الأقواس موضع ا...
Viacheslav
مستوى

نظرية الأدوية الجنيسة في جافا أو كيفية وضع الأقواس موضع التنفيذ

نشرت في المجموعة

مقدمة

بدءًا من JSE 5.0، تمت إضافة الأدوية العامة إلى ترسانة لغة Java.
نظرية الأدوية الجنيسة في جافا أو كيفية وضع الأقواس موضع التنفيذ - 1

ما هي الأدوية العامة في جافا؟

الأدوية العامة (التعميمات) هي وسائل خاصة للغة Java لتنفيذ البرمجة المعممة: نهج خاص لوصف البيانات والخوارزميات يسمح لك بالعمل مع أنواع مختلفة من البيانات دون تغيير وصفها. على موقع Oracle الإلكتروني، تم تخصيص برنامج تعليمي منفصل للأدوية العامة: " الدرس: الأدوية العامة ".

أولاً، لفهم الأدوية الجنيسة، عليك أن تفهم سبب الحاجة إليها على الإطلاق وما الذي تقدمه. في البرنامج التعليمي في القسم " لماذا نستخدم الأدوية الجنيسة ؟" يقال أن أحد الأغراض هو التحقق الأقوى من النوع في وقت الترجمة وإلغاء الحاجة إلى الصب الصريح.
نظرية الأدوية الجنيسة في جافا أو كيفية وضع الأقواس موضع التنفيذ - 2
دعونا نجهز برامجنا التعليمية المفضلة لمترجم جافا عبر الإنترنت للتجارب . لنتخيل هذا الكود:
import java.util.*;
public class HelloWorld{
	public static void main(String []args){
		List list = new ArrayList();
		list.add("Hello");
		String text = list.get(0) + ", world!";
		System.out.print(text);
	}
}
سيتم تشغيل هذا الرمز بشكل جيد. ولكن ماذا لو جاءوا إلينا وقالوا عبارة "مرحبا بالعالم!" للضرب ويمكنك فقط العودة مرحبا؟ دعونا نزيل التسلسل مع السلسلة من الكود ", world!". يبدو أن ما يمكن أن يكون أكثر ضررا؟ ولكن في الواقع، سوف نتلقى خطأ أثناء التجميع : error: incompatible types: Object cannot be converted to String الشيء هو أنه في حالتنا، تقوم القائمة بتخزين قائمة كائنات من النوع Object. نظرًا لأن السلسلة هي سليل الكائن (نظرًا لأن جميع الفئات موروثة ضمنيًا من الكائن في Java)، فإنها تتطلب قالبًا صريحًا، وهو ما لم نفعله. وعند التسلسل، سيتم استدعاء الطريقة الثابتة String.valueOf(obj) على الكائن، والتي ستستدعي في النهاية طريقة toString على الكائن. أي أن قائمتنا تحتوي على Object. اتضح أنه عندما نحتاج إلى نوع معين، وليس كائنًا، سيتعين علينا القيام بعملية صب النوع بأنفسنا:
import java.util.*;
public class HelloWorld{
	public static void main(String []args){
		List list = new ArrayList();
		list.add("Hello!");
		list.add(123);
		for (Object str : list) {
		    System.out.println((String)str);
		}
	}
}
ومع ذلك، في هذه الحالة، لأن تقبل القائمة قائمة الكائنات، ولا تخزن السلسلة فحسب، بل تخزن أيضًا عددًا صحيحًا. لكن أسوأ شيء هو أن المترجم في هذه الحالة لن يرى أي خطأ. وهنا سوف نتلقى خطأ أثناء التنفيذ (يقولون أيضًا أنه تم استلام الخطأ "في وقت التشغيل"). الخطأ سيكون: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String موافق، ليس الأكثر متعة. وكل هذا لأن المترجم ليس ذكاءً اصطناعيًا ولا يمكنه تخمين كل ما يعنيه المبرمج. لإخبار المترجم بالمزيد عن الأنواع التي سنستخدمها، قدم Java SE 5 الأدوية العامة . دعونا نصحح نسختنا بإخبار المترجم بما نريده:
import java.util.*;
public class HelloWorld {
	public static void main(String []args){
		List<String> list = new ArrayList<>();
		list.add("Hello!");
		list.add(123);
		for (Object str : list) {
		    System.out.println(str);
		}
	}
}
كما نرى، لم نعد بحاجة إلى طاقم الممثلين في String. بالإضافة إلى ذلك، لدينا الآن أقواس زاوية تحدد الأدوية العامة. الآن لن يسمح المترجم بتجميع الفصل حتى نزيل إضافة 123 إلى القائمة، لأن هذا عدد صحيح. سوف يخبرنا بذلك. كثير من الناس يطلقون على الأدوية العامة اسم "السكر النحوي". وهم على حق، لأن الأدوية الجنيسة ستصبح بالفعل نفس تلك الطبقات عند تجميعها. دعونا نلقي نظرة على الكود الثانوي للفئات المترجمة: مع الإرسال اليدوي واستخدام الأدوية العامة:
نظرية الأدوية الجنيسة في جافا أو كيفية وضع الأقواس موضع التنفيذ - 3
بعد التجميع، يتم مسح أي معلومات حول الأدوية العامة. وهذا ما يسمى "محو النوع" أو " محو النوع ". تم تصميم محو الكتابة والأدوية العامة لتوفير التوافق مع الإصدارات السابقة من JDK، مع السماح للمترجم بالمساعدة في استنتاج النوع في الإصدارات الأحدث من Java.
نظرية الأدوية الجنيسة في جافا أو كيفية وضع الأقواس موضع التنفيذ - 4

أنواع خام أو أنواع خام

عند الحديث عن الأدوية العامة، لدينا دائمًا فئتان: الأنواع المكتوبة (الأنواع العامة) والأنواع "الخام" (الأنواع الأولية). الأنواع الأولية هي أنواع دون تحديد "المؤهل" بين قوسين:
نظرية الأدوية الجنيسة في جافا أو كيفية وضع الأقواس موضع التنفيذ - 5
الأنواع المكتوبة هي عكس ذلك، مع الإشارة إلى "التوضيح":
نظرية الأدوية الجنيسة في جافا أو كيفية وضع الأقواس موضع التنفيذ - 6
كما نرى، استخدمنا تصميمًا غير عادي، تم وضع علامة عليه بسهم في لقطة الشاشة. هذا بناء جملة خاص تمت إضافته في Java SE 7، ويسمى " الماس "، وهو ما يعني الماس. لماذا؟ يمكنك رسم تشبيه بين شكل المعينة وشكل الأقواس المتعرجة: <> يرتبط بناء الجملة الماسي أيضًا بمفهوم " استدلال النوع "، أو استدلال الكتابة. بعد كل شيء، المترجم، الذي يرى <> على اليمين، ينظر إلى الجانب الأيسر، حيث يوجد إعلان نوع المتغير الذي تم تعيين القيمة له. ومن هذا الجزء يفهم نوع القيمة المكتوبة على اليمين. في الواقع، إذا تم تحديد عام على الجانب الأيسر ولم يتم تحديده على الجانب الأيمن، فسيكون المترجم قادرًا على استنتاج النوع:
import java.util.*;
public class HelloWorld{
	public static void main(String []args) {
		List<String> list = new ArrayList();
		list.add("Hello World");
		String data = list.get(0);
		System.out.println(data);
	}
}
ومع ذلك، سيكون هذا مزيجًا من النمط الجديد مع الأدوية العامة والنمط القديم بدونها. وهذا غير مرغوب فيه للغاية. عند تجميع الكود أعلاه سوف نتلقى الرسالة: Note: HelloWorld.java uses unchecked or unsafe operations. في الواقع، يبدو من غير الواضح سبب الحاجة إلى إضافة الماس هنا على الإطلاق. ولكن هنا مثال:
import java.util.*;
public class HelloWorld{
	public static void main(String []args) {
		List<String> list = Arrays.asList("Hello", "World");
		List<Integer> data = new ArrayList(list);
		Integer intNumber = data.get(0);
		System.out.println(data);
	}
}
كما نتذكر، يحتوي ArrayList أيضًا على مُنشئ ثانٍ يأخذ مجموعة كمدخلات. وهنا يكمن الخداع. بدون بناء جملة الماس، لا يفهم المترجم أنه تم خداعه، ولكن مع الماس يفعل ذلك. لذلك، القاعدة رقم 1 : استخدم دائمًا بناء الجملة الماسي إذا كنا نستخدم الأنواع المكتوبة. وإلا فإننا نخاطر بفقدان المكان الذي نستخدم فيه النوع الخام. لتجنب التحذيرات في السجل التي "تستخدم عمليات غير محددة أو غير آمنة"، يمكنك تحديد تعليق توضيحي خاص للطريقة أو الفئة المستخدمة: @SuppressWarnings("unchecked") تتم ترجمة القمع على أنه قمع، أي حرفيًا، لقمع التحذيرات. لكن فكر في سبب قرارك بالإشارة إلى ذلك؟ تذكر القاعدة رقم واحد وربما تحتاج إلى إضافة الكتابة.
نظرية الأدوية الجنيسة في جافا أو كيفية وضع الأقواس موضع التنفيذ - 7

طرق عامة

تسمح لك الأدوية العامة بكتابة الأساليب. يوجد قسم منفصل مخصص لهذه الميزة في برنامج Oracle التعليمي: " الطرق العامة ". من هذا البرنامج التعليمي، من المهم أن نتذكر بناء الجملة:
  • يتضمن قائمة من المعلمات المكتوبة داخل قوسين زاوية؛
  • تظهر قائمة المعلمات المكتوبة قبل الطريقة التي تم إرجاعها.
لنلقي نظرة على مثال:
import java.util.*;
public class HelloWorld{

    public static class Util {
        public static <T> T getValue(Object obj, Class<T> clazz) {
            return (T) obj;
        }
        public static <T> T getValue(Object obj) {
            return (T) obj;
        }
    }

    public static void main(String []args) {
		List list = Arrays.asList("Author", "Book");
		for (Object element : list) {
		    String data = Util.getValue(element, String.class);
		    System.out.println(data);
		    System.out.println(Util.<String>getValue(element));
		}
    }
}
إذا نظرت إلى فئة Util، فسنرى طريقتين مكتوبتين فيها. باستخدام الاستدلال النوعي، يمكننا تقديم تعريف النوع مباشرة إلى المترجم، أو يمكننا تحديده بأنفسنا. يتم عرض كلا الخيارين في المثال. بالمناسبة، بناء الجملة منطقي تمامًا إذا فكرت فيه. عند كتابة طريقة، نحدد العام قبل الطريقة لأنه إذا استخدمنا العام بعد الطريقة، فلن تتمكن Java من معرفة النوع الذي يجب استخدامه. لذلك، نعلن أولاً أننا سنستخدم T العام، ثم نقول إننا سنعيد هذا العام. وبطبيعة الحال، Util.<Integer>getValue(element, String.class)سوف تفشل مع وجود خطأ incompatible types: Class<String> cannot be converted to Class<Integer>. عند استخدام الأساليب المكتوبة، يجب أن تتذكر دائمًا أمر محو الكتابة. لنلقي نظرة على مثال:
import java.util.*;
public class HelloWorld {

    public static class Util {
        public static <T> T getValue(Object obj) {
            return (T) obj;
        }
    }

    public static void main(String []args) {
		List list = Arrays.asList(2, 3);
		for (Object element : list) {
		    System.out.println(Util.<Integer>getValue(element) + 1);
		}
    }
}
وسوف تعمل بشكل رائع. ولكن فقط طالما أن المترجم يفهم أن الطريقة المطلوبة لها نوع صحيح. دعنا نستبدل مخرجات وحدة التحكم بالسطر التالي: System.out.println(Util.getValue(element) + 1); وحصلنا على الخطأ: أنواع معاملات سيئة للمشغل الثنائي '+'، النوع الأول: Object، النوع الثاني: int أي أنه تم مسح الأنواع. يرى المترجم أنه لم يحدد أحد النوع، وتم تحديد النوع على أنه كائن وفشل تنفيذ التعليمات البرمجية بسبب وجود خطأ.
Теория дженериков в Java or How на практике ставить скобки - 8

أنواع عامة

لا يمكنك كتابة الأساليب فحسب، بل يمكنك أيضًا كتابة الفئات نفسها. لدى Oracle قسم " الأنواع العامة " مخصص لذلك في دليلها. لنلقي نظرة على مثال:
public static class SomeType<T> {
	public <E> void test(Collection<E> collection) {
		for (E element : collection) {
			System.out.println(element);
		}
	}
	public void test(List<Integer> collection) {
		for (Integer element : collection) {
			System.out.println(element);
		}
	}
}
كل شيء بسيط هنا. إذا استخدمنا فئة، يتم إدراج العام بعد اسم الفئة. لنقم الآن بإنشاء مثيل لهذه الفئة في الطريقة الرئيسية:
public static void main(String []args) {
	SomeType<String> st = new SomeType<>();
	List<String> list = Arrays.asList("test");
	st.test(list);
}
وسوف تعمل بشكل جيد. يرى المترجم أن هناك قائمة بالأرقام ومجموعة من النوع String. ولكن ماذا لو قمنا بمسح الأدوية العامة وقمنا بذلك:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
سوف نحصل على الخطأ: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer اكتب المسح مرة أخرى. نظرًا لأن الفئة لم تعد تحتوي على فئة عامة، قرر المترجم أنه نظرًا لأننا مررنا قائمة، فإن الطريقة التي تحتوي على List<Integer> هي الأكثر ملاءمة. ونسقط بالخطأ. لذلك، القاعدة رقم 2: إذا تمت كتابة فئة، فحدد دائمًا النوع في ملف .

قيود

يمكننا تطبيق قيود على الأنواع المحددة في الأدوية العامة. على سبيل المثال، نريد أن تقبل الحاوية الرقم فقط كمدخل. تم توضيح هذه الميزة في برنامج Oracle التعليمي في قسم معلمات النوع المحدود . لنلقي نظرة على مثال:
import java.util.*;
public class HelloWorld{

    public static class NumberContainer<T extends Number> {
        private T number;

        public NumberContainer(T number)  { this.number = number; }

        public void print() {
            System.out.println(number);
        }
    }

    public static void main(String []args) {
		NumberContainer number1 = new NumberContainer(2L);
		NumberContainer number2 = new NumberContainer(1);
		NumberContainer number3 = new NumberContainer("f");
    }
}
كما ترون، فقد حددنا النوع العام ليكون فئة/واجهة الأرقام وأحفادها. ومن المثير للاهتمام، أنه لا يمكنك تحديد فئة فحسب، بل يمكنك أيضًا تحديد الواجهات. على سبيل المثال: public static class NumberContainer<T extends Number & Comparable> { تحتوي الأدوية العامة أيضًا على مفهوم Wildcard https://docs.Oracle.com/javase/tutorial/java/generics/wildcards.html وهي بدورها مقسمة إلى ثلاثة أنواع: ينطبق ما يسمى بمبدأ Get Put على Wildcards . ويمكن التعبير عنها بالشكل التالي:
Теория дженериков в Java or How на практике ставить скобки - 9
ويسمى هذا المبدأ أيضًا مبدأ PECS (المنتج يوسع المستهلك الفائق). يمكنك قراءة المزيد عن حبري في المقالة " استخدام أحرف البدل العامة لتحسين قابلية استخدام Java API "، وكذلك في المناقشة الممتازة حول تدفق المكدس: " استخدام أحرف البدل في Java العامة ". فيما يلي مثال صغير من مصدر Java - طريقة Collections.copy:
Теория дженериков в Java or How на практике ставить скобки - 10
حسنًا، مثال صغير لكيفية عدم نجاح الأمر:
public static class TestClass {
	public static void print(List<? extends String> list) {
		list.add("Hello World!");
		System.out.println(list.get(0));
	}
}

public static void main(String []args) {
	List<String> list = new ArrayList<>();
	TestClass.print(list);
}
ولكن إذا قمت باستبدال الامتدادات بـ super، فسيكون كل شيء على ما يرام. وبما أننا نملأ القائمة بقيمة قبل إخراجها، فهي مستهلكة بالنسبة لنا، أي مستهلكة. لذلك نستخدم السوبر.

ميراث

هناك ميزة أخرى غير عادية للأدوية العامة - وراثتها. تم وصف وراثة الأدوية العامة في برنامج Oracle التعليمي في قسم " الأدوية العامة والوراثة والأنواع الفرعية ". الشيء الرئيسي هو أن نتذكر وندرك ما يلي. لا يمكننا أن نفعل هذا:
List<CharSequence> list1 = new ArrayList<String>();
لأن الميراث يعمل بشكل مختلف مع الأدوية العامة:
Теория дженериков в Java or How на практике ставить скобки - 11
وإليك مثال جيد آخر سيفشل بسبب وجود خطأ:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
كل شيء بسيط هنا أيضًا. List<String> ليس من سلالة List<Object>، على الرغم من أن String هو من سلالة Object.

أخير

لذلك قمنا بتحديث ذاكرتنا عن الأدوية الجنيسة. إذا نادرا ما يتم استخدامها بكل قوتها، فإن بعض التفاصيل تسقط من الذاكرة. آمل أن تساعد هذه المراجعة القصيرة في تحديث ذاكرتك. وللحصول على نتائج أكبر أنصح بشدة بقراءة المواد التالية: # فياتشيسلاف
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION