问题 如何在单个约束中使用方法和类类型参数?


我将尝试在以下简化示例中说明我的问题:

public class DataHolder<T> {
  private final T myValue;

  public DataHolder(T value) {
    myValue = value;
  }

  public T get() {
    return myValue;
  }

  // Won't compile
  public <R> DataHolder<R super T> firstNotNull(DataHolder<? extends R> other) {
    return new DataHolder<R>(myValue != null ? myValue : other.myValue);      }

  public static <R> DataHolder<R> selectFirstNotNull(DataHolder<? extends R> first,
                                                     DataHolder<? extends R> second) {
    return new DataHolder<R>(first.myValue != null ? first.myValue : second.myValue);
  }
}

这里我想写泛型方法 firstNotNull 返回 DataHolder 由类型参数的常见超类型参数化 T 的 this 和 other 争论,所以后来我可以写,例如

DataHolder<Number> r = new DataHolder<>(3).firstNotNull(new DataHolder<>(2.0));

要么

DataHolder<Object> r = new DataHolder<>("foo").firstNotNull(new DataHolder<>(42));

问题在于这个定义 firstNotNull 编译器拒绝该消息 super T 类型约束的一部分是非法的(语法上)。 但是,没有这个约束定义也是错误的(显然),因为在这种情况下 T 和 R 彼此无关。

有趣的是,定义了类似的静态方法 selectFirstNotNull 是正确的,后者按预期工作。是否有可能在Java类型系统中实现与非静态方法相同的灵活性?


13198
2018-03-09 16:46


起源

DataHolder <R super T>。这在语法上是不可能的。此外,这不是关于静态与非静态约束的问题。静态和非静态方法在类型参数方面完全不相似。 - CKing
看起来超级通配符不能与类型一起使用,并且只能像<?超级对象>,虽然我不太确定。 - Artemkller545


答案:


这是不可能的。作者 番石榴 遇到了同样的问题 Optional.or。从该方法的文档:

关于泛型的注意事项:签名 public T or(T defaultValue) 是   过度限制。但是,理想的签名, public <S super T> S or(S),不是合法的Java。结果,一些明智的操作   涉及子类型是编译错误:

Optional<Integer> optionalInt = getSomeOptionalInt();
Number value = optionalInt.or(0.5); // error

FluentIterable<? extends Number> numbers = getSomeNumbers();   
Optional<? extends Number> first = numbers.first();
Number value = first.or(0.5); // error

作为一种解决方法,施放一个总是安全的    Optional<? extends T> to Optional<T>。铸造以上任何一种   例 Optional 实例 Optional<Number> (哪里 Number 是个   所需的输出类型)解决了这个问题:

Optional<Number> optionalInt = (Optional) getSomeOptionalInt();   
Number value = optionalInt.or(0.5); // fine

FluentIterable<? extends Number> numbers = getSomeNumbers();   
Optional<Number> first = (Optional) numbers.first();
Number value = first.or(0.5); // fine

以来 DataHolder 是不可变的 Optional,上面的解决方法也适合你。

也可以看看: Rotsor用“超级”关键字回答Bounding泛型


6
2018-03-09 19:48



谢谢!确实,我应该想到番石榴 Optional 首先。好吧,似乎我会使用@ rohit-jain建议的更严格的版本,如果谷歌中的人甚至不知道如何更好地处理这个限制。 - east825


我认为没有任何简单且类型安全的方法可以做到这一点。我尝试了几种方法,但我找到的唯一工作方法是从一开始 super 键入泛型实例,并使方法非常简单,如下所示:

public DataHolder<T> firstNotNull(DataHolder<? extends T> other) {
    return new DataHolder<T>(myValue != null ? myValue : other.myValue);
}

现在,您必须将调用更改为:

DataHolder<Number> r = new DataHolder<Number>(3).firstNotNull(new DataHolder<>(2.0));

你可能会说这并没有真正回答你的问题,但这是你最简单的事情,或者更好地诉诸于 static 方法方法。你肯定可以提出一些高度复杂(和类型不安全)的方法来做到这一点,但可读性应该是这里的主要关注点。


4
2018-03-09 17:56



@Downvoter:关注评论? - Rohit Jain


尝试按如下方式更改方法:

      public <R> DataHolder<R> firstNotNull(DataHolder<? super T> other) {
        return new DataHolder<R>((R)(this.myValue != null ? myValue : other.myValue));
     }

警告:这会编译并给出大部分正确检查的外观,但并不完美。它将限制输入参数,但不限制输出。这不可能完美。在某些方面,你最好不要这样做,而不是给出被检查的错觉。这里有些例子:

      DataHolder<BigDecimal> a = new DataHolder<>(new BigDecimal(34.0));
      DataHolder<Number> b = new DataHolder<>(new Integer(34));
      DataHolder<String> c = new DataHolder<>("");
      DataHolder<Number> p = a.firstNotNull(b); // WORKS (good)
      DataHolder<BigDecimal> q =  b.firstNotNull(a); // FAILS (good)
      DataHolder<BigDecimal> r =  b.firstNotNull(c); // FAILS (good)
      DataHolder<String> s =  a.firstNotNull(b); // WORKS (not good!!!)

0
2018-03-09 18:15



您是否尝试使用此方法编译方法调用?不起作用。 - Rohit Jain
我做了,它确实为我编译。它提供了一个未经检查的警告,可以很容易地被抑制,但它确实可以编译。您使用的Java版本是什么? - Necreaux
Java 8.它没有编译.. - Rohit Jain
更新你的Eclipse,它适用于我的Eclipse =) - Necreaux
你是对的。实际上你可以在这个例子中保留T,但这也是不安全的。我认为更改输入变量的参数可能是最好的方法。代码已编辑。该方法的内部现在未经检查,但至少是签名和所有预期的。 - Necreaux