如果构造函数的签名包含泛型类型的定义,那么对该构造函数的用户有什么额外的责任呢?

时间:2014-03-27 12:53:59

标签: java generics constructor signature

我扩展了一个在几个类中使用泛型的现有项目。

我正在开发一个名为PhiFunction的类,这个类有一个接受许多参数的construcor。这些包括两个我想强制为相同类型的参数,E。这些参数仅在构造函数中使用,它们不存储为类字段。

我不想将这种类型添加到类签名中(我希望尽可能简化类定义)。类签名目前只包含类型T.为了使类签名尽可能简单,我将此类型的定义添加到构造函数签名中,如下所示:

简化旧代码:

public class PhiFunction<T> {

    ...

    public PhiFunction(
        final MathematicalGroup<?> group, final List<?> baseElements, ...) {

简化新代码:

public class PhiFunction<T> {

    ...

    public <E extends GroupElement<T>> PhiFunction(
        final MathematicalGroup<E> group, final List<E> baseElements, ...) {

代码编译良好,工作正常。

现在令我惊讶和困惑的是,仍然可以像以前一样创建PhiFunction的实例。换句话说,在构造函数签名中添加此类型定义不会更改构造函数的使用方式(假设用户确实提供了使用相同类型的参数)。用户仍然可以像以前一样创建PhiFunction的实例,而不必担心这个类型定义被添加到构造函数中。我希望这个构造函数的用户必须将E定义为某个特定类,但他们不会。

之前我没有以这种方式使用泛型。似乎将类型定义添加到构造函数(或者我猜想的任何方法)只是允许定义一个类型,而不需要在构造函数的用户身上设置定义该类型的责任。

我想我的问题是,如果构造函数的签名包含泛型类型的规范,那么对构造函数的用户有什么额外的责任呢?

我还有一个相关的问题。是否应将此类型E作为参数添加到Java文档中?如何?我确信它应该被记录,因为构造函数的两个参数必须是该类型,但我不确定如何将它添加到Javadocs。

2 个答案:

答案 0 :(得分:1)

您无需为调用泛型方法或构造函数执行任何额外的工作。大多数情况下,compiler will be able to infer the type argument基于您传递给方法的参数,或者来自返回类型(尽管不适用于此)。

所以,当你像这样创建那个类的实例时:

MathematicalGroup<Sometype> mathematicalGroup;
List<Sometype> list;
PhiFunction<Double> phiFunction = new PhiFunction<>(mathematicalGroup, list);

...构造函数的类型参数E将自动推断为Sometype

但是,如果传递的参数不符合parmeter类型的规则,您将看到编译器错误,如下例所示:

MathematicalGroup<Sometype> mathematicalGroup;
List<SomeOthertype> list;
// This will give a compiler error.
PhiFunction<Double> phiFunction = new PhiFunction<>(mathematicalGroup, list);

如果有时类型推断无法按预期工作,您还可以选择提供显式类型参数:

// Redundant usage of explicit type argument
PhiFunction<Double> phiFunction = 
           new <Sometype>PhiFunction<Double>(mathematicalGroup, list);

虽然显式类型参数的上述用法是多余的,但是在某些情况下编译器不会推断出您期望它的类型。当您将不一致的参数传递给具有相同类型参数的参数时,就会出现这种情况。例如,考虑以下方法:

public static <T> void fill( T [] array, T elem) {
    for (int i=0; i<array.length; ++i) { array[i] = elem; }
} 

..如果您尝试将该方法调用为:

fill(new String [5], new String ("XYZ")); // This is fine
fill(new String [5], new Integer (100));  // This is also fine? How?

理想情况下,您希望第二个方法调用失败,因为StringInteger不应该替代相同的类型参数T。但令人惊讶的是。编译器将type参数推断为您传递的所有超类型参数的交集。因此,类型T被推断为:

T:=Object&Serializable&Comparable 

在这种情况下,您可能希望将<Object>作为显式类型参数:

YourClass.<Object>fill(new String[5], new Integer(100));

P.S :您是否知道you can invoke non-generic methods in generic way?嗯,仿制药充满惊喜:)

至于Javadoc,您不需要提供有关E代表什么的任何信息。只需解释一下参数grouplist的含义。而已。类型参数和形式参数无论如何都是方法签名的一部分,并且已经存在。考虑例如Arrays.binarySearch()方法。

答案 1 :(得分:0)

将其添加到Javadoc:

@param  <E>   This represents...

此构造函数

public PhiFunction(
    final MathematicalGroup<?> group, final List<?> baseElements, ...) {

允许将任何类型传递给MathematicalGroup使用。 MathematicalGroup是否已将自己的泛型与之相关联?

如果是这样,那么也许你的新构造函数是多余的。也就是说,即使原始构造函数具有<?>MathematicalGroup类本身的泛型限制也已经执行了此操作。

public <E extends GroupElement<T>> PhiFunction(
    final MathematicalGroup<E> group, final List<E> baseElements, ...) {

否则,这确实更具限制性,因为原始签名允许任何类型,并且此签名仅允许GroupElement<T>或其子类之一。

就用户的使用方式而言:

MathematicalGroup mg = new <GroupElement<Object>>MathematicalGroup<Object>(groupElementInstance, ...);

这是一个奇怪的用例。通常,这种泛型传递是通过静态最终效用函数完成的。