【问题标题】:Java Generics Class Parameter Type InferenceJava 泛型类参数类型推断
【发布时间】:2010-06-16 18:09:47
【问题描述】:

给定界面:

public interface BasedOnOther<T, U extends BasedList<T>> {

    public T getOther();

    public void staticStatisfied(final U list);

}

BasedOnOther&lt;T, U extends BasedList&lt;T&gt;&gt; 在我的用例中看起来很丑。是因为T类型参数已经在BasedList&lt;T&gt;部分定义了,所以“丑”来自T需要输入两次。

问题:是否可以让 Java 编译器在泛型类/接口定义中从 BasedList&lt;T&gt; 推断泛型 T 类型?

最终,我想使用如下界面:

class X implements BasedOnOther<Y> {
    public SomeType getOther() { ... }
    public void staticStatisfied(final Y list) { ... }
} // Does not compile, due to invalid parameter count.

在哪里Y extends BasedList&lt;SomeType&gt;

改为:

class X implements BasedOnOther<SomeType, Y> {
    public SomeType getOther() { ... }
    public void staticStatisfied(final Y list) { ... }
}

在哪里Y extends BasedList&lt;SomeType&gt;

更新:ColinD 建议

public interface BasedOnOther<T> {
    public T getOther();
    public void staticSatisfied(BasedList<T> list);
}

不可能创建这样的实现:

public class X implements BasedOnOther<SomeType> {
    public SomeType getOther() { ... }
    public void staticStatisfied(MemoryModel list);
} // Does not compile, as it does not implement the interface.

MemoryModel extends BasedList&lt;SomeType&gt;,这是需要的(因为它提供了其他方法)。

【问题讨论】:

    标签: java generics type-inference


    【解决方案1】:

    看起来你实际上不需要类型参数U extends BasedList&lt;T&gt;,如果你实际上不需要在类中做任何需要BasedList&lt;T&gt;的特定子类/实现的事情。界面可能只是:

    public interface BasedOnOther<T> {
      public T getOther();
      public void staticSatisfied(BasedList<T> list);
    }
    

    编辑:根据您的更新,我认为您无法做到这一点。我认为您要么只使用原始声明,要么制作一些指定T 的中间类型,例如:

    public interface BasedOnSomeType<U extends BasedList<SomeType>>
             extends BasedOnOther<SomeType, U>
    {
    }
    
    public class X implements BasedOnSomeType<MemoryModel> { ... }
    

    不过,这似乎有点浪费,而且我真的不认为最初的声明看起来那么糟糕。

    【讨论】:

    • 感谢您的宝贵时间。我也觉得不可能。我将使用丑陋的方法。
    【解决方案2】:

    这个怎么样?

    public interface BasedOnOther<T> {
        public T getOther();
        public <U extends BasedList<T>> void staticStatisfied(final U list);
    }
    

    【讨论】:

      【解决方案3】:

      ColinD 几乎是正确的。你可能想要的是这样的:

      public interface BasedOnOther<T> {
        public T getOther();
        public void staticSatisfied(BasedList<? extends T> list);
      }
      

      这是因为方法参数是协变的,而泛型是不变的。看这个例子:

      public test() {
        Number n1;
        Integer n2; //Integer extends Number.  Integer is a more-specific type of Number.
      
        n1 = n2; //no problem
        n2 = n1; //Type mismatch because the cast might fail.
      
        List<Number> l1;
        List<Integer> l2;
        List<? extends Number> l3;
      
        l1 = l3; //No problem.
        l1 = l2; //Type mismatch because the cast might fail.
      }
      

      为什么:

      尝试将Integer 放在Number 所属的位置是协方差,它通常对于函数参数是正确的。

      试图将Number 放在Integer 所属的位置是相反的,逆变,它通常对于函数返回值是正确的。例如,如果您定义了一个返回数字的函数,它可以返回一个整数。但是,如果将其定义为返回 Integer,则无法返回 Number,因为它可能是浮点数。

      当您处理泛型时,编译器无法判断泛型参数(在您的情况下为 T)是协变的还是逆变的。例如,在您的代码中, T 曾经是一个返回值,也是一个参数的一部分。因此,泛型参数默认是不变的。

      如果您想要协方差,请使用&lt;? extends T&gt;。对于逆变,使用&lt;? super T&gt;。根据经验,您可能总是希望在所有公共函数上指定协变/逆变。对于私有函数,这并不重要,因为您通常已经知道类型。

      这不是 java 特有的,其他面向对象的语言也有类似的问题。

      【讨论】:

        【解决方案4】:

        我最近遇到了一个非常相似的问题。

        我建议,如果您不需要专门参考MemoryModel,即如果U extends BasedList&lt;T&gt; 就足够了,那么我肯定会按照Pepe 的回答去做。

        但是,如果您必须对至少两种方法进行类型检查,这两种方法都必须专门使用 MemoryModel 并且 Pepe 的答案中的类型推断还不够,那么使用笨拙/冗长的参数化构造函数的唯一方法更简单一点,就是利用generic method参数推断。您需要为每个构造函数创建通用静态工厂方法,其中工厂方法进行类型推断(构造函数不能在 Java 中进行类型推断)。

        如何做到这一点在

        中有介绍

        Effective Java,作者 Joshua Block;第 27 条:支持泛型方法

        我也对此进行了解释并引用了解决方案(带代码)here

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-12-05
          • 1970-01-01
          相关资源
          最近更新 更多