كيف يمكنني جعل طريقة العائد نوع من العمومية ؟

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

  •  19-08-2019
  •  | 
  •  

سؤال

النظر في هذا المثال (نموذجي في OOP الكتب):

لدي Animal فئة ، حيث كل Animal يمكن أن يكون العديد من الأصدقاء.
و فرعية مثل Dog, Duck, Mouse الخ والتي تضيف سلوك معين مثل bark(), quack() الخ.

هنا Animal الدرجة:

public class Animal {
    private Map<String,Animal> friends = new HashMap<>();

    public void addFriend(String name, Animal animal){
        friends.put(name,animal);
    }

    public Animal callFriend(String name){
        return friends.get(name);
    }
}

وهنا بعض التعليمات البرمجية المتكررة مع الكثير من تلبيس:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();

هل هناك أي طريقة يمكنني استخدام الأدوية من أجل عودة نوع للتخلص من تلبيس, حتى أستطيع أن أقول

jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();

هنا بعض التعليمات البرمجية الأولى مع عودة نوع نقلها إلى طريقة المعلمة التي لم تستخدم.

public<T extends Animal> T callFriend(String name, T unusedTypeObj){
    return (T)friends.get(name);        
}

هل هناك طريقة لمعرفة نوع الإرجاع في وقت التشغيل دون اضافية المعلمة باستخدام instanceof?أو على الأقل عن طريق تمرير فئة من نوع بدلا من دمية سبيل المثال.
أنا أفهم الأدوية هي وقت الترجمة نوع التحقق ، ولكن هل هناك حل لهذا ؟

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

المحلول

هل يمكن تحديد callFriend بهذه الطريقة:

public <T extends Animal> T callFriend(String name, Class<T> type) {
    return type.cast(friends.get(name));
}

وبعد ذلك يطلق عليه على هذا النحو:

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

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

نصائح أخرى

ولا. المترجم لا يمكن أن نعرف ما jerry.callFriend("spike") نوع سيعود. أيضا، التطبيق الخاص بك فقط يخفي المدلى بها في طريقة دون أي نوع سلامة إضافية. النظر في هذا:

jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast

في هذه الحالة المحددة، وخلق أسلوب talk() المجرد وتجاوز بشكل مناسب في الفئات الفرعية سيكون خدمتك بشكل أفضل من ذلك بكثير:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();

هل يمكن تنفيذ مثل هذا:

@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
    return (T)friends.get(name);
}

(نعم, هذا هو قانوني المدونة ؛ انظر الأدوية جافا:نوع عام بأنها نوع الإرجاع فقط.)

نوع الإرجاع سوف يكون الاستدلال من المتصل.ومع ذلك لاحظ @SuppressWarnings الشرح:أن يقول لك أن هذا الرمز ليس typesafe.لديك للتحقق من ذلك بنفسك أو يمكنك الحصول على ClassCastExceptions في وقت التشغيل.

للأسف الطريقة كنت تستخدم (بدون تعيين قيمة الإرجاع إلى متغير مؤقت) ، فإن الطريقة الوحيدة لجعل مترجم سعيدة هو أن نسميها مثل هذا:

jerry.<Dog>callFriend("spike").bark();

في حين أن هذا قد يكون أفضل قليلا من الصب ، ربما كنت أفضل حالا مما Animal فئة مجردة talk() طريقة ديفيد شميت قال.

هذا السؤال هي مشابهة جدا البند 29 في فعالية جافا - "النظر في typesafe غير متجانسة الحاويات." لاز الجواب هو الأقرب إلى بلوخ هو الحل.ومع ذلك ، سواء وضع على أن استخدام فئة حرفية من أجل السلامة.تواقيع سوف تصبح:

public <T extends Animal> void addFriend(String name, Class<T> type, T animal);
public <T extends Animal> T callFriend(String name, Class<T> type);

داخل كل الطرق يجب عليك التحقق من أن المعلمات عاقل.انظر فعالة جافا ، الدرجة javadoc للحصول على مزيد من المعلومات.

وبالإضافة إلى ذلك يمكنك أن تطلب من طريقة لإرجاع القيمة في نوع معين بهذه الطريقة

<T> T methodName(Class<T> var);

هنا في وثائق جافا أوراكل

وهنا هو صيغة أبسط:

public <T> T callFriend(String name) {
    return (T) friends.get(name); //Casting to T not needed in this case but its a good practice to do
}

وقانون العمل كاملا:

    public class Test {
        public static class Animal {
            private Map<String,Animal> friends = new HashMap<>();

            public void addFriend(String name, Animal animal){
                friends.put(name,animal);
            }

            public <T> T callFriend(String name){
                return (T) friends.get(name);
            }
        }

        public static class Dog extends Animal {

            public void bark() {
                System.out.println("i am dog");
            }
        }

        public static class Duck extends Animal {

            public void quack() {
                System.out.println("i am duck");
            }
        }

        public static void main(String [] args) {
            Animal animals = new Animal();
            animals.addFriend("dog", new Dog());
            animals.addFriend("duck", new Duck());

            Dog dog = animals.callFriend("dog");
            dog.bark();

            Duck duck = animals.callFriend("duck");
            duck.quack();

        }
    }

وكما قلت يمر من شأنه أن فئة ما يرام، هل يمكن أن يكتب هذا:

public <T extends Animal> T callFriend(String name, Class<T> clazz) {
   return (T) friends.get(name);
}

وثم استخدامها مثل هذا:

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

وليس الكمال، ولكن هذا هو الى حد كبير بقدر ما تحصل عليه مع الوراثة جافا. هل هناك طريقة لتنفيذ Typesafe غير متجانسة حاويات (THC) باستخدام سوبر نوع الرموز ، إلا أن لديها مشاكلها الخاصة مرة أخرى.

واستنادا إلى نفس الفكرة كما سوبر نوع الرموز، يمكنك إنشاء معرف كتبته لاستخدام بدلا من سلسلة:

public abstract class TypedID<T extends Animal> {
  public final Type type;
  public final String id;

  protected TypedID(String id) {
    this.id = id;
    Type superclass = getClass().getGenericSuperclass();
    if (superclass instanceof Class) {
      throw new RuntimeException("Missing type parameter.");
    }
    this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
  }
}

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

Mouse jerry = new Mouse();
TypedID<Dog> spike = new TypedID<Dog>("spike") {};
TypedID<Duck> quacker = new TypedID<Duck>("quacker") {};

jerry.addFriend(spike, new Dog());
jerry.addFriend(quacker, new Duck());

ولكن يمكنك الآن استخدام فئة في الطريقة التي يريد في الأصل، دون قوالب.

jerry.callFriend(spike).bark();
jerry.callFriend(quacker).quack();

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

وأنت كنت بحاجة إلى تنفيذ المقارنة والتجزئة طرق TypedID جدا إذا كنت تريد أن تكون قادرة على المقارنة بين حالات مماثلة لمعرف.

و"هل هناك طريقة لمعرفة نوع الإرجاع في وقت التشغيل بدون المعلمة إضافية باستخدام instanceof؟"

وكحل بديل يمكن الاستفادة نمط الزوار من هذا القبيل. جعل مجردة الحيوانية وجعله تنفيذ Visitable:

abstract public class Animal implements Visitable {
  private Map<String,Animal> friends = new HashMap<String,Animal>();

  public void addFriend(String name, Animal animal){
      friends.put(name,animal);
  }

  public Animal callFriend(String name){
      return friends.get(name);
  }
}

وVisitable يعني فقط أن التنفيذ الحيوان هو على استعداد لقبول الزوار:

public interface Visitable {
    void accept(Visitor v);
}

والتنفيذ الزائر هو قادرة على زيارة جميع الفئات الفرعية للحيوان:

public interface Visitor {
    void visit(Dog d);
    void visit(Duck d);
    void visit(Mouse m);
}

وهكذا على سبيل المثال فإن تنفيذ الكلب ثم تبدو هذه:

public class Dog extends Animal {
    public void bark() {}

    @Override
    public void accept(Visitor v) { v.visit(this); }
}

والخدعة هنا هو أن مثل الكلب يعرف ما اكتب هو انها يمكن أن تؤدي إلى طريقة زيارة مثقلة ذات الصلة الزائر الخامس عن طريق تمرير "هذا" كمعلمة. سوف فرعية أخرى تنفذ استعرض () بنفس الطريقة تماما.

والطبقة التي تريد استدعاء أساليب محددة فرعية ثم يجب تطبيق واجهة الزوار مثل هذا:

public class Example implements Visitor {

    public void main() {
        Mouse jerry = new Mouse();
        jerry.addFriend("spike", new Dog());
        jerry.addFriend("quacker", new Duck());

        // Used to be: ((Dog) jerry.callFriend("spike")).bark();
        jerry.callFriend("spike").accept(this);

        // Used to be: ((Duck) jerry.callFriend("quacker")).quack();
        jerry.callFriend("quacker").accept(this);
    }

    // This would fire on callFriend("spike").accept(this)
    @Override
    public void visit(Dog d) { d.bark(); }

    // This would fire on callFriend("quacker").accept(this)
    @Override
    public void visit(Duck d) { d.quack(); }

    @Override
    public void visit(Mouse m) { m.squeak(); }
}

وأنا أعلم أنه من الكثير من واجهات وأساليب مما كنت تتمناه، لكنه وسيلة معيارية للحصول على التعامل مع كل سلالة معينة مع بالضبط الصفر الشيكات instanceof ونوع الصفر يلقي. وانها كل ذلك بلغة الأزياء الملحد القياسية حتى انها ليست فقط للجافا ولكن أي لغة OO يجب أن تعمل نفس الشيء.

وليس من الممكن. كيف يتم خارطة المفترض أن تعرف أي فئة فرعية من الحيوان انها سوف يحصل، تعطى إلا مفتاح سلسلة؟

والطريقة الوحيدة هذا من شأنه أن يكون من الممكن هي حالة قبول كل حيوان نوع واحد فقط من صديق (فلا يمكن أن يكون معلمة من فئة الحيوان)، أو من طريقة callFriend () حصلت على معلمة نوع. ولكن يبدو حقا مثل كنت في عداد المفقودين وجهة الميراث: فهو أن تتمكن من علاج فقط الفئات الفرعية بشكل موحد عند استخدام حصرا الأساليب الفائقة

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

  • TimeSeries<Double> مندوب خاص الطبقة الداخلية الذي يستخدم double[]
  • TimeSeries<OHLC> مندوب خاص الطبقة الداخلية الذي يستخدم ArrayList<OHLC>

انظر:باستخدام TypeTokens لاسترداد المعلمات عامة

شكرا

ريتشارد غوميز - بلوق

هناك الكثير من الإجابات هنا ، ولكن هذا هو النهج أخذت على Appium الاختبار حيث يتصرف على عنصر واحد يمكن أن يؤدي إلى الذهاب إلى تطبيق مختلف الدول على أساس إعدادات المستخدم.في حين أنها لا تتبع اتفاقيات OP سبيل المثال ، وآمل أن يساعد شخص ما.

public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //signInButton.click();
    return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
}
  • MobilePage هو سوبر فئة نوع يمتد معنى يمكنك استخدام أي من الأطفال (دوه)
  • نوع.getConstructor(Param.class إلخ) يسمح لك بالتفاعل مع منشئ نوع.هذا البناء يجب أن يكون نفسه بين جميع الطبقات.
  • مثيل جديد يأخذ أعلن المتغير الذي تريد تمرير إلى كائنات جديدة منشئ

إذا كنت لا ترغب في رمي الأخطاء التي يمكن أن يصاب بها مثل ذلك:

public <T extends MobilePage> T tapSignInButton(Class<T> type) {
    // signInButton.click();
    T returnValue = null;
    try {
       returnValue = type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return returnValue;
}

وليس حقا، لأنه كما تقول، المترجم يعرف فقط أن callFriend () سيعود حيوان، وليس الكلب أو البط.

هل لا إضافة makeNoise المجرد () طريقة لالحيوانية التي سيتم تنفيذها في اللحاء أو الدجال من قبل الفئات الفرعية؟

ما كنت تبحث عنه هنا هو التجريد. كود ضد اجهات أكثر، وينبغي عليك أن تفعل أقل الصب.

والمثال التالي هو في C # ولكن هذا المفهوم لا يزال هو نفسه.

using System;
using System.Collections.Generic;
using System.Reflection;

namespace GenericsTest
{
class MainClass
{
    public static void Main (string[] args)
    {
        _HasFriends jerry = new Mouse();
        jerry.AddFriend("spike", new Dog());
        jerry.AddFriend("quacker", new Duck());

        jerry.CallFriend<_Animal>("spike").Speak();
        jerry.CallFriend<_Animal>("quacker").Speak();
    }
}

interface _HasFriends
{
    void AddFriend(string name, _Animal animal);

    T CallFriend<T>(string name) where T : _Animal;
}

interface _Animal
{
    void Speak();
}

abstract class AnimalBase : _Animal, _HasFriends
{
    private Dictionary<string, _Animal> friends = new Dictionary<string, _Animal>();


    public abstract void Speak();

    public void AddFriend(string name, _Animal animal)
    {
        friends.Add(name, animal);
    }   

    public T CallFriend<T>(string name) where T : _Animal
    {
        return (T) friends[name];
    }
}

class Mouse : AnimalBase
{
    public override void Speak() { Squeek(); }

    private void Squeek()
    {
        Console.WriteLine ("Squeek! Squeek!");
    }
}

class Dog : AnimalBase
{
    public override void Speak() { Bark(); }

    private void Bark()
    {
        Console.WriteLine ("Woof!");
    }
}

class Duck : AnimalBase
{
    public override void Speak() { Quack(); }

    private void Quack()
    {
        Console.WriteLine ("Quack! Quack!");
    }
}
}

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

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

abstract class AnimalExample {
    private Map<String,Class<?>> friends = new HashMap<String,Class<?>>();
    private Map<String,Object> theFriends = new HashMap<String,Object>();

    public void addFriend(String name, Object friend){
        friends.put(name,friend.getClass());
        theFriends.put(name, friend);
    }

    public void makeMyFriendSpeak(String name){
        try {
            friends.get(name).getMethod("speak").invoke(theFriends.get(name));
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    } 

    public abstract void speak ();
};

class Dog extends Animal {
    public void speak () {
        System.out.println("woof!");
    }
}

class Duck extends Animal {
    public void speak () {
        System.out.println("quack!");
    }
}

class Cat extends Animal {
    public void speak () {
        System.out.println("miauu!");
    }
}

public class AnimalExample {

    public static void main (String [] args) {

        Cat felix = new Cat ();
        felix.addFriend("Spike", new Dog());
        felix.addFriend("Donald", new Duck());
        felix.makeMyFriendSpeak("Spike");
        felix.makeMyFriendSpeak("Donald");

    }

}

وماذا عن

public class Animal {
private Map<String,<T extends Animal>> friends = new HashMap<String,<T extends Animal>>();

public <T extends Animal> void addFriend(String name, T animal){
    friends.put(name,animal);
}

public <T extends Animal> T callFriend(String name){
    return friends.get(name);
}

و}

وفعلت ما يلي في بلدي kontraktor ليب:

public class Actor<SELF extends Actor> {
    public SELF self() { return (SELF)_self; }
}

وشاء subclasses ترث:

public class MyHttpAppSession extends Actor<MyHttpAppSession> {
   ...
}

وهذا يعمل على الأقل داخل الطبقة الحالية وعند وجود مرجع مكتوب قوية. يعمل راثة متعددة، ولكن يحصل صعبة حقا ثم:)

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

public <X,Y> X nextRow(Y cursor) {
    return (X) getRow(cursor);
}

private <T> Person getRow(T cursor) {
    Cursor c = (Cursor) cursor;
    Person s = null;
    if (!c.moveToNext()) {
        c.close();
    } else {
        String id = c.getString(c.getColumnIndex("id"));
        String name = c.getString(c.getColumnIndex("name"));
        s = new Person();
        s.setId(id);
        s.setName(name);
    }
    return s;
}

ويمكنك العودة أي نوع واستلامها مباشرة مثل. لا تحتاج إلى التلبيس.

Person p = nextRow(cursor); // cursor is real database cursor.

وهذا هو الأفضل إذا كنت ترغب في تخصيص أي نوع آخر من سجلات بدلا من المؤشرات الحقيقية.

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