嵌套泛型继承

18

我有以下类:

class Field<T> {

  private final Class<T> type;

  public Field(Class<T> type) {
    this.type = type;
  }
}

class Pick<V> {
  private final V value;
  private final Class<V> type;

  public Pick(V value, Class<V> type) {
    this.value = value;
    this.type = type;
  }
}

以及与问题相关的课程:

class PickField<T> extends Field<Pick<T>> {

  public PickField(Class<Pick<T>> type) {
    super(type);
  }
}

现在编译器似乎已经接受了这个。不幸的是,我不知道如何创建PickField的新实例,例如:String picks。

当然,下面的方法是错误的:
new PickField<String>(Pick.class)

这是不允许的(我认为我知道原因):
new PickField<String>(Pick<String>.class)

那么该怎么做呢?还是说整个方法有些“臭”?


你想对它做什么?有一些模式可以完成使用T进行一两个具体操作的功能,但如果您需要执行大量与T类相关的操作,则代码可能会出现问题。 - Morgen
4个回答

16

我认为PickField应该只使用Pick实例作为参数。

所以以下做法是正确的:

class PickField<T extends Pick<T>> extends Field<T> {

    public PickField(Class<T> c) {
        super(c);
    }
}

然后,您只需使用以下方式实例化它:
PickField<SomeSpecificPick> instance = new PickField<>(SomeSpecificPick.class);

其中 SomeSpecificPick 的定义如下:

public class SomeSpecificPick extends Pick<SomeSpecificPick> {

    public SomeSpecificPick(SomeSpecificPick value, Class<SomeSpecificPick> type) {
        super(value, type);
    }
}

更多相关信息:


谢谢回答,但是我觉得 SomeSpecificPick 中有一个错误?如果类型参数是类本身,那么这将产生一些奇怪的循环引用,对吧? - JDC
不,没有错误。这不会产生循环引用 - 这是所谓的自引用类型,JDK实际上定义了一些类似的类型,例如Integer extends Comparable<Integer>等。因此,只需尝试片段并熟悉我分享的链接即可。 :) - Konstantin Yovkov
好的,试用后似乎可以工作;)。还有一个额外的奖励问题:如果PickField看起来像这样:PickField<T extends Pick<T>> extends Field<List<T>>,那么我会得到一系列选择吗?我无法正确地使用super(type) - JDC
1
在这种情况下,您需要第二个类型参数。应该是这样的:PickField<T extends Pick<T>, L extends List<T>> extends Field<L> - Konstantin Yovkov

7
这里有各种问题。
首先,正如你所指出的,你不能在编译时获取参数化类型的类,因为对于泛型类型只编译一个类,而不是每个给定类型参数一个类(例如,Pick<String>.class 惯用语无法编译,并且实际上没有意义)。
同样,正如你提到的那样,仅使用 Pick.class 参数化 PickField<String> 构造函数将再次无法编译,因为签名不匹配。
你可以使用运行时惯用语推断正确的 Pick<T> 参数,但这会创建另一个问题:由于类型擦除,你的 T 类型参数在运行时将是未知的。
因此,你可以通过显式转换来参数化构造函数调用,如下所示:
new PickField<String>(
    (Class<Pick<String>>)new Pick<String>("", String.class).getClass()
);

这段代码会出现“unchecked cast”警告 (Type safety: Unchecked cast from Class<capture#1-of ? extends Pick> to Class<Pick<String>>)。

实际的问题可能是,为什么您需要在Pick类中知道type的值。


我不需要知道字段的类型,而是需要知道选择的类型。该字段将是Pick<String>类型,而Pick的值将是String类型。已编辑问题以进行澄清。 - JDC
2
@JDC Semantics IMO. 这里你提到的“smell”,是指你想在实例范围内知道泛型类Pick被参数化的类型。这通常违背了泛型背后的概念,并且很可能表示你的设计存在更广泛的问题。 - Mena

1

有一种方法,但并不是特别好的方法。 你需要创建一个像这样的方法:

public static <I, O extends I> O toGeneric(I input) {
    return (O) input;
}

然后,您创建对象:
new PickField<String>(toGeneric(Pick.class));

就像我说的那样,并不是一个好方法,因为你基本上只是欺骗编译器,但它能够工作。


1
这相当于显式转换为 (Class<Pick<String>>),并将生成另一个 unchecked cast 警告。更不用说,由于它被呈现为一种通用的实用方法,在某人开始发现错误之前,它可能会被滥用 很多次... - Mena

0
为了将泛型信息作为参数传递,仅使用Class<T>是不够的。您需要一些额外的能力来实现这一点。请参见this article,其中解释了什么是“超类型标记”。
简而言之,如果您有以下类:
public abstract class TypeToken<T> {

    protected final Type type;

    protected TypeToken() {
        Type superClass = getClass().getGenericSuperclass();
        this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
    }

    public Type getType() {
        return this.type;
    }
}

您可以使用它来存储泛型类型信息,例如Pick<String>.class(这是非法的)。诀窍是使用超类的泛型类型信息,该信息可通过 Class.getGenericSuperclass()ParameterizedType.getActualTypeArguments() 方法访问。

我稍微修改了您的 PickFieldPickField 类,以便它们使用超类型标记而不是 Class<t>。请查看修改后的代码:

class Field<T> {

    private final TypeToken<T> type;

    public Field(TypeToken<T> type) {
        this.type = type;
    }
}

class Pick<V> {

    private final V value;

    private final TypeToken<V> type;

    public Pick(V value, TypeToken<V> type) {
        this.value = value;
        this.type = type;
    }
}

class PickField<T> extends Field<Pick<T>> {

    public PickField(TypeToken<Pick<T>> type) {
        super(type);
    }
}

以下是一个使用示例:

TypeToken<Pick<String>> type = new TypeToken<Pick<String>>() {};
PickField<String> pickField = new PickField<>(type);

TypeToken类是抽象的,因此您需要对其进行子类化(这就解释了其声明末尾的{})。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接