当前位置:  开发笔记 > 编程语言 > 正文

如何使方法返回类型通用?

如何解决《如何使方法返回类型通用?》经验,为你挑选了11个好方法。

考虑这个例子(典型的OOP书籍):

我有一个Animal班级,每个人都Animal可以有很多朋友.
和子类一样Dog,Duck,Mouse等它添加特定的行为一样bark(),quack()等等.

这是Animal班级:

public class Animal {
    private Map 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 callFriend(String name, T unusedTypeObj){
    return (T)friends.get(name);        
}

有没有办法在运行时找出返回类型而不使用额外的参数instanceof?或者至少通过传递类型的类而不是虚拟实例.
我理解泛型用于编译时类型检查,但是有一个解决方法吗?



1> laz..:

你可以这样定义callFriend:

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

然后这样称呼它:

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

此代码的好处是不会生成任何编译器警告.当然,这实际上只是从通用前日期开始的更新版本,并没有增加任何额外的安全性.


...但仍然没有在callFriend()调用的参数之间进行编译时类型检查.
这是迄今为止最好的答案 - 但你应该以同样的方式改变addFriend.这使得编写错误变得更加困难,因为在这两个地方都需要该类文字.

2> David Schmit..:

不.编译器无法知道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();


虽然mmyers方法可行,但我认为这种方法是更好的OO编程,将来会为您省去一些麻烦.
我并不反对,但我们正在处理Java及其所有设计决策/缺陷.我认为这个问题只是试图了解Java泛型中的可能性,而不是需要重新编写的xyproblem(http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) -engineered.像任何模式或方法一样,有时候我提供的代码是合适的,并且需要完全不同的东西(比如你在这个答案中建议的那样).
@laz:是的,最初的问题 - 提出的 - 不是关于类型安全的.这并没有改变这样一个事实,即有一种安全的方法来实现它,消除了类铸失败.另见http://weblogs.asp.net/alex_papadimoulis/archive/2005/05/25/408925.aspx

3> Michael Myer..:

你可以像这样实现它:

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

(是的,这是合法代码;请参阅Java Generics:仅定义为返回类型的泛型类型.)

返回类型将从调用者推断.但是,请注意@SuppressWarnings注释:它告诉您此代码不是类型安全的.您必须自己验证它,或者您可以ClassCastExceptions在运行时获得它.

不幸的是,你使用它的方式(没有将返回值赋给临时变量),让编译器满意的唯一方法就是这样调用它:

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

虽然这可能比投射更好,但你可能最好给这个Animal班级一个抽象的talk()方法,正如大卫施密特所说.



4> Craig P. Mot..:

这个问题非常类似于Effective Java中的第29项 - "考虑类型安全的异构容器".Laz的答案是最接近Bloch的解决方案.但是,put和get都应该使用Class literal来保证安全.签名将成为:

public  void addFriend(String name, Class type, T animal);
public  T callFriend(String name, Class type);

在这两种方法中,你应该检查参数是否合理.有关详细信息,请参阅Effective Java和Class javadoc.



5> isuru chathu..:

此外,您可以要求方法以这种方式返回给定类型的值

 T methodName(Class var);

更多的例子在这里在甲骨文的Java文档



6> webjockey..:

这是更简单的版本:

public  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 friends = new HashMap<>();

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

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

        }
    }



7> Fabian Steeg..:

正如你所说的,传递一个类就没问题,你可以这样写:

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

然后像这样使用它:

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

并不完美,但这与Java泛型相当.有一种方法可以使用超类型标记来实现Typesafe Heterogenous Containers(THC),但这又有其自身的问题.


Nemo,如果你查看发布时间,你会看到我们在同一时刻发布了它们.而且,它们并不完全相同,只有两条线.

8> Mike Houston..:

基于与Super Type Tokens相同的想法,您可以创建一个使用的类型ID而不是字符串:

public abstract class TypedID {
  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 spike = new TypedID("spike") {};
TypedID quacker = new TypedID("quacker") {};

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

但是你现在可以按照你原来想要的方式使用这个类,没有强制转换.

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

这只是隐藏了id中的type参数,但它确实意味着如果你愿意,你可以稍后从标识符中检索类型.

如果您希望能够比较两个相同的id实例,则还需要实现TypedID的比较和散列方法.



9> Antti Siisko..:

"有没有办法在没有使用instanceof的额外参数的情况下在运行时找出返回类型?"

作为替代解决方案,您可以像这样使用访问者模式.使动物抽象并使其实现可访问:

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

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

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

可访问只是意味着Animal实现愿意接受访问者:

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语言都应该是相同的.



10> Michael Borg..:

不可能.如果只给出一个String键,Map应该如何知道它将获得哪个Animal类?

唯一可行的方法是,如果每个Animal只接受一种类型的朋友(那么它可能是Animal类的参数),或者callFriend()方法获得一个类型参数.但是看起来你似乎错过了继承点:只有在使用超类方法时才能统一处理子类.



11> Richard Gome..:

我写了一篇文章,其中包含概念证明,支持类和测试类,它演示了类在运行时如何检索超类型标记.简而言之,它允许您根据调用者传递的实际通用参数委派替代实现.例:

TimeSeries 委托给使用的私人内部类 double[]

TimeSeries 委托给使用的私人内部类 ArrayList

请参阅: 使用TypeTokens检索通用参数

谢谢

理查德戈麦斯 - 博客

推荐阅读
周扒pi
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有