如何在单个约束中同时使用方法和 class 类型参数?
How can I use both method and class type parameters in single constraint?
我将尝试在以下简化示例中说明我的问题:
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
,returns DataHolder
由this
和[=] 的类型参数T
的公共超类型参数化18=] 参数,所以以后我可以写例如
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 类型系统中使用非静态方法实现相同的灵活性?
我认为没有任何简单且类型安全的方法可以做到这一点。我尝试了几种方法,但我发现唯一可行的方法是从 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
方法。您当然可以想出一些非常复杂(并且类型不安全)的方法来做到这一点,但这里应该主要关注可读性。
尝试按如下方式更改您的方法:
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!!!)
这是不可能的。 Guava ran into the same issue with Optional.or
的作者。来自该方法的文档:
Note about generics: The signature public T or(T defaultValue)
is
overly restrictive. However, the ideal signature, public <S super T> S or(S)
, is not legal Java. As a result, some sensible operations
involving subtypes are compile errors:
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
As a workaround, it is always safe to cast an
Optional<? extends T> to Optional<T>
. Casting either of the above
example Optional
instances to Optional<Number>
(where Number
is the
desired output type) solves the problem:
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's answer to Bounding generics with 'super' keyword
我将尝试在以下简化示例中说明我的问题:
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
,returns DataHolder
由this
和[=] 的类型参数T
的公共超类型参数化18=] 参数,所以以后我可以写例如
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 类型系统中使用非静态方法实现相同的灵活性?
我认为没有任何简单且类型安全的方法可以做到这一点。我尝试了几种方法,但我发现唯一可行的方法是从 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
方法。您当然可以想出一些非常复杂(并且类型不安全)的方法来做到这一点,但这里应该主要关注可读性。
尝试按如下方式更改您的方法:
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!!!)
这是不可能的。 Guava ran into the same issue with Optional.or
的作者。来自该方法的文档:
Note about generics: The signature
public T or(T defaultValue)
is overly restrictive. However, the ideal signature,public <S super T> S or(S)
, is not legal Java. As a result, some sensible operations involving subtypes are compile errors: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
As a workaround, it is always safe to cast an
Optional<? extends T> to Optional<T>
. Casting either of the above exampleOptional
instances toOptional<Number>
(whereNumber
is the desired output type) solves the problem: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's answer to Bounding generics with 'super' keyword