parameter - type variable java




我如何使方法返回類型通用? (13)

“有沒有一種方法可以在運行時找出返回類型,而不需要使用instanceof的額外參數呢?”

作為替代解決方案,您可以像這樣使用Visitor模式 。 使動物的抽象,並使其實現可視化:

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);
  }
}

可視化意味著動物實施願意接受訪問者:

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

訪客實現可以訪問動物的所有子類:

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

因此,例如,Dog實現將如下所示:

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

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

這裡的技巧是,由於Dog知道它是什麼類型,它可以通過傳遞“this”作為參數來觸發訪問者v的相關重載訪問方法。 其他的子類將以完全相同的方式實現accept()。

想要調用子類特定方法的類必須像這樣實現Visitor接口:

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(); }
}

我知道它比你討論的要多得多的接口和方法,但它是一種標準的方式,通過精確的零實例檢查和零類型轉換來獲得每個特定子類型的句柄。 這一切都是以標準的語言不可知的方式完成的,因此它不僅適用於Java,而且任何OO語言都應該可以工作。

考慮這個例子(典型的OOP書籍):

我有一個Animal課,每個Animal可以有很多朋友。
DogDuckMouse等子類可以添加特定的行為,如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 ? 或者至少通過傳遞一個類的類而不是一個虛擬實例。
我明白泛型是用於編譯時類型檢查的,但是有沒有解決方法?


不可能。 如果Map只知道一個String鍵,它應該知道它將獲得哪個Animal的子類?

唯一可行的方法是每隻動物只接受一種類型的朋友(然後它可以是動物類的一個參數),或者callFriend()方法獲得一個類型參數。 但是它看起來好像缺少了繼承的一點:只有在使用超類方法時,才能統一處理子類。


你可以像這樣實現它:

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

(是的,這是合法的代碼;請參閱Java泛型:Generic類型僅定義為返回類型 。)

返回類型將從調用者推斷出來。 不過,請注意@SuppressWarnings註釋:它告訴您這段代碼不是類型安全的 。 您必須親自驗證它,否則您可能會在運行時遇到ClassCastExceptions

不幸的是,你使用它的方式(沒有把返回值賦給一個臨時變量),讓編譯器高興的唯一方法就是像這樣調用它:

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

雖然這可能比鑄造好一點,但正如David Schmitt所說,你最好給Animal課一個抽象的talk()方法。


你可以這樣定義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();

此代碼具有不產生任何編譯器警告的好處。 當然,這實際上只是從通用前的日子開始的更新版本,並沒有增加任何額外的安全性。


基於與超級類型令牌相同的想法,您可以創建一個類型化的ID來代替字符串:

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];
  }
}

但我認為這可能會破壞目的,因為您現在需要為每個字符串創建新的id對象並保留它們(或使用正確的類型信息重新構建它們)。

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();

這只是隱藏id內的類型參數,儘管它意味著如果您願意,可以稍後從標識符中檢索該類型。

如果您希望能夠比較id的兩個相同實例,則需要實現TypedID的比較和散列方法。


我在自己的lib kontraktor中做了以下工作:

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

子類:

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

至少可以在當前類中使用,並且具有強類型引用。 多繼承的作品,但變得非常棘手,然後:)


我知道這是一個完全不同的事情,一個人問。 解決這個問題的另一種方法是反思。 我的意思是,這不會從泛型中受益,但它可以讓你在某種程度上效仿你想要執行的行為(製作狗吠,製作鴨子等),而不需要考慮類型轉換:

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 <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();

不完美,但這與Java泛型相差無幾。 有一種方法可以使用超級類型令牌來實現Typesafe Heterogenous Containers(THC) ,但這又有其自身的問題。


編號不知道什麼類型的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();

這個問題與Effective Java中的第29項非常相似 - “考慮類型安全的異構容器”。 拉茲的答案是最接近布洛赫的解決方案。 然而,為了安全起見,put和get都應該使用Class文字。 簽名將成為:

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

在這兩種方法中,你應該檢查參數是否理智。 有關更多信息,請參閱Effective Java和Class javadoc。


這裡有很多很好的答案,但這是我為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是類型擴展的超類,這意味著你可以使用它的任何孩子(杜)
  • type.getConstructor(Param.class等)允許你與類型的構造函數進行交互。 這個構造函數在所有期望的類中應該是相同的。
  • newInstance需要一個你想傳遞給新對象構造函數的聲明變量

如果你不想拋出錯誤,你可以像這樣捕獲它們:

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以返回該子類。 成本將是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.

如果你想定制任何其他類型的記錄而不是真正的游標,這是最好的。







return-value