لماذا لا تقوم مجموعات Java بإزالة الأساليب العامة؟

StackOverflow https://stackoverflow.com/questions/104799

سؤال

لماذا لا مجموعة إزالة (الكائن س) نوعي؟

يبدو مثل Collection<E> قد يكون له boolean remove(E o);

وبعد ذلك، عندما تحاول الإزالة عن طريق الخطأ (على سبيل المثال) Set<String> بدلاً من كل سلسلة فردية من a Collection<String>, سيكون خطأ في وقت الترجمة بدلاً من مشكلة تصحيح الأخطاء لاحقًا.

هل كانت مفيدة؟

المحلول

يشير جوش بلوخ وبيل بوغ إلى هذه المشكلة في جافا الألغاز الرابع:تهديد المرجع الوهمي ، هجوم الاستنساخ ، والانتقام من التحول.

يقول Josh Bloch (6:41) أنهم حاولوا توليد طريقة الحصول على الخريطة ، وإزالة الطريقة وبعضها الآخر ، ولكن "ببساطة لم ينجح".

هناك الكثير من البرامج المعقولة التي لا يمكن توليدها إذا سمحت فقط بالنوع العام للمجموعة كنوع معلمة.المثال الذي قدمه هو تقاطع أ List ل Numberق و أList ل Longس.

نصائح أخرى

remove() (في Map وكذلك في Collection) ليس عامًا لأنه يجب أن تكون قادرًا على تمرير أي نوع من الكائنات إليه remove().ليس من الضروري أن يكون الكائن الذي تمت إزالته من نفس نوع الكائن الذي تقوم بتمريره إليه remove();فهو يتطلب فقط أن يكونوا متساوين.من مواصفات remove(), remove(o) يزيل الكائن e مثل ذلك (o==null ? e==null : o.equals(e)) يكون true.لاحظ أنه لا يوجد شيء يتطلب o و e أن يكون من نفس النوع.ويترتب على ذلك حقيقة أن equals() الطريقة تأخذ في Object كمعلمة، وليس فقط نفس نوع الكائن.

على الرغم من أنه قد يكون صحيحًا بشكل شائع أن العديد من الفئات لديها equals() محددة بحيث لا يمكن أن تكون كائناتها مساوية إلا لكائنات من فئتها الخاصة، وهذا بالتأكيد ليس هو الحال دائمًا.على سبيل المثال، مواصفات List.equals() تقول أن كائني القائمة متساويان إذا كانا قائمتين ولهما نفس المحتويات، حتى لو كانا مختلفين في التنفيذ List.لذا وبالعودة إلى المثال في هذا السؤال، فمن الممكن أن يكون لديك Map<ArrayList, Something> وبالنسبة لي للاتصال remove() مع LinkedList كوسيطة، ويجب إزالة المفتاح الذي هو عبارة عن قائمة بنفس المحتويات.وهذا لن يكون ممكنا إذا remove() كانت عامة ومقيدة بنوع الوسيطة.

لأنه إذا كانت معلمة النوع الخاصة بك عبارة عن حرف بدل، فلن تتمكن من استخدام طريقة إزالة عامة.

يبدو أنني أتذكر مواجهة هذا السؤال باستخدام طريقة get(Object) الخاصة بالخريطة.طريقة get في هذه الحالة ليست عامة، على الرغم من أنه من المتوقع بشكل معقول أن يتم تمرير كائن من نفس نوع معلمة النوع الأول.أدركت أنه إذا كنت تقوم بتمرير الخرائط باستخدام حرف البدل كمعلمة النوع الأول، فلن تكون هناك طريقة للحصول على عنصر من الخريطة بهذه الطريقة، إذا كانت هذه الوسيطة عامة.لا يمكن استيفاء وسيطات حرف البدل، لأن المترجم لا يمكنه ضمان صحة النوع.أعتقد أن سبب الإضافة عام هو أنه من المتوقع منك التأكد من صحة النوع قبل إضافته إلى المجموعة.ومع ذلك، عند إزالة كائن، إذا كان النوع غير صحيح، فلن يطابق أي شيء على أي حال.إذا كانت الوسيطة عبارة عن حرف بدل، فستكون الطريقة ببساطة غير قابلة للاستخدام، على الرغم من أنه قد يكون لديك كائن يمكنك ضمان أنه ينتمي إلى تلك المجموعة، لأنك حصلت للتو على إشارة إليه في السطر السابق....

ربما لم أشرح ذلك جيدًا، لكنه يبدو منطقيًا بدرجة كافية بالنسبة لي.

بالإضافة إلى الإجابات الأخرى، هناك سبب آخر وراء قبول الطريقة لـ Object, ، وهو المسندات.النظر في العينة التالية:

class Person {
    public String name;
    // override equals()
}
class Employee extends Person {
    public String company;
    // override equals()
}
class Developer extends Employee {
    public int yearsOfExperience;
    // override equals()
}

class Test {
    public static void main(String[] args) {
        Collection<? extends Person> people = new ArrayList<Employee>();
        // ...

        // to remove the first employee with a specific name:
        people.remove(new Person(someName1));

        // to remove the first developer that matches some criteria:
        people.remove(new Developer(someName2, someCompany, 10));

        // to remove the first employee who is either
        // a developer or an employee of someCompany:
        people.remove(new Object() {
            public boolean equals(Object employee) {
                return employee instanceof Developer
                    || ((Employee) employee).company.equals(someCompany);
        }});
    }
}

النقطة المهمة هي أن الكائن الذي يتم تمريره إلى remove الطريقة هي المسؤولة عن تحديد equals طريقة.يصبح بناء المسندات بسيطًا جدًا بهذه الطريقة.

افترض أن المرء لديه مجموعة من Cat, وبعض مراجع الكائنات من الأنواع Animal, Cat, SiameseCat, ، و Dog.سؤال المجموعة عما إذا كانت تحتوي على الكائن المشار إليه بواسطة Cat أو SiameseCat المرجع يبدو معقولا.السؤال عما إذا كان يحتوي على الكائن المشار إليه بواسطة Animal قد تبدو الإشارة مراوغة، لكنها لا تزال معقولة تمامًا.الكائن المعني قد يكون، بعد كل شيء، أ Cat, ، وقد يظهر في المجموعة.

علاوة على ذلك، حتى لو كان الكائن شيئًا آخر غير أ Cat, ، ليست هناك مشكلة في تحديد ما إذا كان يظهر في المجموعة أم لا، ما عليك سوى الإجابة بـ "لا، لا يظهر".يجب أن تكون مجموعة "نمط البحث" من نوع ما قادرة على قبول مرجع أي نوع فائق بشكل هادف وتحديد ما إذا كان الكائن موجودًا داخل المجموعة.إذا كان مرجع الكائن الذي تم تمريره من نوع غير ذي صلة، فلا توجد طريقة يمكن أن تحتوي عليه المجموعة، وبالتالي فإن الاستعلام ليس له معنى إلى حد ما (سيجيب دائمًا بـ "لا").ومع ذلك، نظرًا لعدم وجود أي طريقة لتقييد المعلمات لتكون أنواعًا فرعية أو أنواعًا فائقة، فمن الأكثر عملية قبول أي نوع والإجابة بـ "لا" لأي كائنات لا يرتبط نوعها بالمجموعة.

اعتقدت دائمًا أن السبب في ذلك هو أن الإزالة () ليس لديها سبب للاهتمام بنوع الكائن الذي تقدمه له.من السهل، بغض النظر، التحقق مما إذا كان هذا الكائن هو أحد الكائنات التي تحتوي عليها المجموعة، حيث يمكنه استدعاء يساوي () على أي شيء.من الضروري التحقق من النوع في add() للتأكد من أنه يحتوي فقط على كائنات من هذا النوع.

الإزالة ليست طريقة عامة، لذا فإن التعليمات البرمجية الموجودة التي تستخدم مجموعة غير عامة ستظل مترجمة وستظل لها نفس السلوك.

يرى http://www.ibm.com/developerworks/Java/library/j-jtp01255.html للتفاصيل.

يحرر:يسأل أحد المعلقين عن سبب كون طريقة الإضافة عامة.[...أزال شرحي...] أجاب المعلق الثاني على سؤال firebird84 أفضل مني بكثير.

سبب آخر هو بسبب الواجهات.هنا مثال لإظهار ذلك:

public interface A {}

public interface B {}

public class MyClass implements A, B {}

public static void main(String[] args) {
   Collection<A> collection = new ArrayList<>();
   MyClass item = new MyClass();
   collection.add(item);  // works fine
   B b = item; // valid
   collection.remove(b); /* It works because the remove method accepts an Object. If it was generic, this would not work */
}

لأنه سيؤدي إلى كسر الكود الموجود (ما قبل Java5).على سبيل المثال،

Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);

الآن قد تقول إن الكود أعلاه خاطئ، لكن لنفترض أن o جاء من مجموعة غير متجانسة من الكائنات (أي أنها تحتوي على سلاسل أو أرقام أو كائنات وما إلى ذلك).أنت تريد إزالة كافة التطابقات، وهو أمر قانوني لأن الإزالة ستتجاهل السلاسل غير المتطابقة لأنها غير متساوية.ولكن إذا قمت بإزالة (String o)، فلن يعمل ذلك بعد الآن.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top