泛型问题

4

我在"return (T) value;"这一行收到了未检查的转换警告。有没有更好的方法来解决这个问题,或者我只应该忽略这个警告?

class SomeClass<T>
{
    /* ... other methods ... */

    private Set<T> aSet;
    public T filter(Object value)
    {
        if (this.aSet.contains(value))
            return (T) value;
        else
            return null;
    }
}

编辑:我被卡在了 public T filter(Object value) 这个签名上。


请编辑此内容,以便根据Coronatus的答案进行编译。 - jyoungdev
7个回答

4
使用泛型类型参数作为过滤器的参数类型怎么样?
class SomeClass
{
    /* ... other methods ... */

    private Set<T> aSet;
    public T filter(T value)
    {
        if (this.aSet.contains(value))
            return (T) value;
        else
            return null;
    }
}

好的,既然你被困在object签名中,你没有其他选择,只能禁用/忽略警告。Java泛型并不是在底层类型系统支持它们的“真正的泛型”。实际上,它们只是编译器的一种东西,因为它们基于类型擦除。维护与旧版本JVM的二进制兼容性所付出的代价是性能惩罚和可能不安全的强制转换。

你可以将其与.NET CLR进行对比,后者具有真正的泛型。我最近写了一篇博客文章来比较这两种方法,如果我上面说的任何事情让你感到困惑,你也可以参考一下。


你忘记了T和value之间的空格,在return语句中不需要使用(T)强制转换。 - Thomas Owens
是的,我只是想编辑一下它们,它们就在那里;-) - Johannes Rudolph
一般情况下这是可能的,但不适用于我的情况:我正在使用一个早于泛型的类,所以它只给我一个对象。 - Jason S
Jason S:如果你的类早于泛型,那么你如何拥有Set<T>和返回类型T的方法?另外,也许你应该考虑重构你的类以支持泛型,以确保更安全的代码(它不应该破坏任何未使用泛型的代码,但将使未来的代码更加安全)。 - Thomas Owens
我的类没有泛型,但我必须实现一个已存在的接口;一些方法需要一个对象作为输入,所以我试图编写一个帮助函数来处理类型转换的麻烦。 - Jason S

3
在这种情况下,您可以抑制警告并留下注释说明为什么是安全的:
   if (this.aSet.contains(value)) {
        // this following cast from Object to T is save, because ...
        @SuppressWarnings("unchecked") T result = (T) value;
        return result;
   } else
        return null;

有一种替代方案,但需要类或方法“知道”其参数化类型。然后我们可以进行强制转换而不会收到警告(但应该添加注释)。我展示了一种解决方案,其中我们引入/更改构造函数以将“泛型”信息存储为字段:

public class SomeClass<T> {

    private Set<T> aSet;

    private Class<T> genericType;
    public SomeClass(Class<T> genericType) {
        this.genericType = genericType;
    }

    public T filter(Object value)
    {
        if (this.aSet.contains(value))
            // this following cast from Object to T is save, because ...
            return genericType.cast(value);
        else
            return null;
    }
}

不要忘记 genericType.cast(...) 可能会抛出 ClassCastException 异常!当然,普通的强制类型转换语法也可能会抛出异常,但是 Class 强制类型转换只能在运行时抛出异常。 - Brian S
@Brian - 这就是前一行中的注释原因。但至少,cast 抛出的 ClassCastException 相当冗长。 - Andreas Dolk

2
可以的 :)
public T filter(Object value)
{
    if (this.aSet.contains(value)) {
        Set<T> tmp = new TreeSet<T>(aSet);
        tmp.retainAll(Collections.singleton(value));
        return tmp.iterator().next();
    }
    return null;
}

但很明显,这比进行类型转换更加丑陋。


2
也许你可以替换为:
private Set<T> aSet;

使用

private final Map<T,T> aSet;
Set 很可能已经被实现为一个 Map

2
延续Tom Hawtin的答案,您可以选择使用Map<T,T>,从而避免类型转换问题。如果您正在使用HashSet<T>来实现aSet,那么HashSet在幕后实际上使用了HashMap<T,HashSet<T>>(它使用对自身的引用作为值——不确定是否有其他原因选择这样做——并通过重新解释Map函数的返回值来执行其大部分操作)。
因此,如果您愿意(我没有立即看到任何比HashSet更低效的原因),您可以这样做:
class SomeClass<T>
{
    /* ... other methods ... */

    private Map<T,T> aSet;

    public T filter(Object value)
    {
        // Will return the properly-typed object if it's in
        // the "set" otherwise will return null
        return aSet.get(value);
    }
}

谢谢提供额外的解释,Tim。 - sixtyfootersdude

1

这是你的意思吗?

public class SomeClass<T>
{
    private Set<T> aSet;

    public T filter(Object value)
    {
        return (aSet.contains(value) ? (T) value : null);
    }
}

因为它符合规范,并且三元运算符更好看 :) - Amy B
1
@teehoo - 问题是原始代码没有编译。我发布的代码可以编译,因为在类声明中有额外的<T>。 - Amy B
你能解释一下这为什么是有效的吗?如果这是一个初级问题,那我很抱歉。 - Michael Foukarakis
@Michael Foukarakis - 当对象被创建时,T会被定义,因此T始终是已知的。这是唯一的区别。 - Amy B
确实,现在很有道理。谢谢。 - Michael Foukarakis
显示剩余2条评论

0

用这种方式做的问题是它等待着一个ClassCastException。如果你有一个不属于类T但与你的元素之一相等的对象,当你尝试将其转换为T时,它将失败。你需要的是获取包含在集合中的实际值,你知道它的类型是T。不幸的是,Set没有get方法,所以你需要暂时将它转换为具有该方法的集合,如List。

你可以尝试像这样做:

private Set<T> aSet;

public T filter(Object value) {
    List<T> list = new ArrayList<T>(aSet);
    int index = list.indexOf(value);
    if (index == -1) {
        return null;
    }
    T setValue = list.get(index);
    return setValue;
}

这可能会导致性能较慢,但它提供了更多的类型安全。


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