Java泛型似乎对类的工作方式与方法的工作方式不同

时间:2014-05-22 05:45:21

标签: java generics duck-typing

我正在关注此事:

http://rickyclarkson.blogspot.com/2006/07/duck-typing-in-java-and-no-reflection.html

我正在尝试改编:

<T extends CanQuack & CanWalk> void doDucklikeThings(T t)
{
    t.quack();
    t.walk();
}

对此:

public class Activate<D extends CanQuack & CanWalk> {

    D d = new MyWaterFowl(); //Type mismatch
}

即使MyWaterFowl实现了这些接口。

我想要一个从未在&lt;&gt;中提及MyWaterFowl的解决方案,因为我最终会注入它(或者其他任何实现这些接口的东西)。


如果你的答案基本上是“你不能那样做,它甚至会是什么类型的?”。请解释为什么它正在为doDucklikeThings方法工作,并确定如果不可能对类做同样的事情,或者如果可能的话,如何去做。

doDucklikeThings中的T必须是有效的,因为它正在工作。如果我把它传递给一个班级,我会传递什么?


这里要求的是MyWaterFowl代码:

package singleResponsibilityPrinciple;

interface CanWalk { public void walk(); }
interface CanQuack { public void quack(); }
interface CanSwim { public void swim(); }


public class MyWaterFowl implements CanWalk, CanQuack, CanSwim {

    public void walk() { System.out.println("I'm walkin` here!"); }
    public void quack() { System.out.println("Quack!"); }
    public void swim() { System.out.println("Stroke! Stroke! Stroke!"); }
}

记得我已经确认doDucklikeThings有效。我需要的语法可以让我注入任何实现所需接口的东西。

6 个答案:

答案 0 :(得分:26)

这不起作用,因为类/方法是通用的,您的类/方法的调用者可以将D设置为MyAmericanEagle

 Activate<MyAmericanEagle> active = new Activate<>();

然后您的代码将导致

 MyAmericanEagle d = new MyWaterFowl(); 

由于这没有意义(会导致ClassCastException),编译器会拒绝它。

答案 1 :(得分:11)

  

// Type mismatch

     

即使MyWaterFowl实现了这些接口。

它不是关于实现这些接口的类型D(和/或扩展类)。泛型类型变量绑定到特定类型参数。该类型可能与MyWaterFowl完全不同,因此您无法互换使用它们。


要回答您的编辑,您在两个片段中做了两件完全不同的事情。使用某些边界声明类型变量。因此,它保证是一种实现某种界面(或扩展某些类)的类型,但在任​​何一种情况下你都不知道它是哪种类型。


我想澄清你做的两件事,即。您对问题的期望以及您在答案中提供的解决方案。

泛型是编译时功能,其中服务器代码,例如

class Activate<D extends CanWalk & CanQuack> {
    D instance;
    public Activate(D d) {
        this.instance = d;
    }

    public D getInstance() {
        return instance ;
    }
}

声明类型变量。这是一个变量。在其声明环境中,您在编译时不知道其确切类型。

客户端代码,例如

new Activate<>(new MyWaterFowl());

将类型MyWaterFowl绑定到Activate中声明的类型变量。所以客户端代码知道编译时D是什么。

如果以下

public D getInstance() {
    D someRef = new MyWaterFowl();
    return someRef;
}
服务器代码中允许

,这将失败

Activate<SomeOtherBird> activate = new Activate<>(new SomeOtherBird());
SomeOtherBird reference = activate.getInstance();

泛型保证getInstance()是类型安全的,因为它被声明为返回绑定到类型变量D的任何类型。在这种情况下,即SomeOtherBird。如果允许上面的getInstance()代码,则类型安全性将被破坏,因为getInstance()将返回除绑定之外的其他内容。

这并没有改变你的服务器代码(泛型类)中的事实,你知道D的界限,即。它既是CanQuack又是CanWalk。因此,这些类型的对象可以执行任何操作,D变量引用的对象也可以这样做。

答案 2 :(得分:6)

实际上可以做到。

&lt;&gt;&#;&gt;之间的通用代码很好。方法和类没有什么不同。只需要完成依赖注入:

public class Activate<D extends CanQuack & CanWalk> {
    private D d;

    Activate(D d) {
        this.d = d;
    }

    void doDuckLikeThings() {
        d.quack();
        d.walk();
        //d.swim(); //Doesn't work 
        //And shouldn't since that was the whole point of ISP.
    }
}

public class MainClass {
    public static void main(String[] args) {
       Activate<MyWaterFowl> a = new Activate<>(new MyWaterFowl());
       a.doDuckLikeThings();
    }
}

我以为我会提供一个答案,说明如何解决这个问题。

答案 3 :(得分:2)

扩展Object的类的类型与Object的类型不同。这意味着你无法为其扩展器实例化超类。

编译时:

1    public class Sample<T extends Object> {
2      public Sample() {
3         T t = new Object();
4       }
5    }

您收到incompatible types错误。

Sample.java:3: error: incompatible types
    T t = new Object();
          ^
  required: T
  found:    Object
  where T is a type-variable:
    T extends Object declared in class Sample
1 error

与非通用形式时相同:

1 public class Sample{
2    public Sample() {
3    }
4 }
5
6 class SampleExtends extends Sample {
7   public SampleExtends() {
8
9    }
10 }
11
12 class User {
13    public User() {
14        SampleExtends se = new Sample();
15    }
16 }

你从编译器得到这个错误:不兼容的类型

Sample.java:14: error: incompatible types
    SampleExtends se = new Sample();
                       ^
  required: SampleExtends
  found:    Sample
1 error

答案 4 :(得分:1)

您希望第二个代码段中D的实际类型是什么?

让我们说出一些事情:

Activate<Daffy> myActivate = Activate<Daffy>();

那会发生什么?这意味着D必须属于Daffy类型,但您尝试将d设置为MyWaterFowl的实例。

答案 5 :(得分:1)

泛型对于类和方法的作用相同。

你似乎误解了泛型。类型参数<T extends CanQuack & CanWalk> 对方法或类的范围有效的新类型别名的定义,它是某种类型的占位符,后来由其他人填写。

对于像你的例子那样的通用方法

<T extends CanQuack & CanWalk> void doDucklikeThings(T t)
{
    t.quack();
    t.walk();
}

方法的调用者决定用哪种类型替换T(在边界规定的限制范围内)。 所以可以使用

doDuckLikeThings(myWaterfowl);

并且编译器猜测你想要的类型参数(可能是myWaterfowl变量的类型)。

对于泛型类,它是创建类的实例的代码,该类确定实际的类型参数。 所以我可以说

Activate<Duck> duckActivator = new Activate<Duck>();

在此之后,duckActivator实例是Activate<Duck>的实例,在其中,D现在绑定到Duck。 现在显然你的变量声明说

Duck d = new MyWaterfowl();

这并不起作用。

它也不会在通用方法中工作 - 这个也是无效的:

<T extends CanQuack & CanWalk> void doDucklikeThings()
{
    T t = new MyWaterfowl();
}
  

我想要一个永远不会在MyWaterFowl中提到<>的解决方案,因为我最终会去   只是注入它(或任何其他实现这些接口的东西)。

然后也不要提及它的构造函数。 无论是谁注射你的鸭子对象也必须决定实际的类型参数 - 即这应该是创建Activate实例的人。在构建Activate对象后,这不会发生变化。

实际问题的候选解决方案

如果这个限制不适合你(例如,因为同一个Activate实例需要能够一个接一个地处理不同类型的鸭子对象),也许类型参数是不适合你。

也许带有变量的通配符类型参数的通用包装器可以帮助改为。

采用这种通用包装(或类似的东西):

public class Holder<T> {
    private final T t;
    public T get() { return t; }
    public Holder(T t) { this.t = t; }
}

然后你的Activate类不需要type参数,只是拥有一个带有通配符类型参数的holder变量。 (请注意,实际的Holder实例化具有非通配符类型参数。)

public class Activate {
    private Holder<? extends CanQuack & CanWalk> holder;

    public <D extends CanQuack & CanWalk> void setDuckLike(D d) {
        this.holder = new Holder<D>(d);
    }

    public Activate() {}

    public void doDucklikeThings() {
         holder.get().quack();
         holder.get().walk();
    }
}

现在你做这样的事情:

Activate a = new Activate();
a.setDuckLike(new Duck());
a.doDucklikeThings();
a.setDuckLike(new MyWaterfowl());
a.doDucklikeThings();