Java 泛型方法需要冗余转换

Java redundant casts required in generic method

我正在寻找一种方法来优化我们代码库中的一些东西,使用通用函数。有一个 return 类型 List<Object> 的函数可以 return 类型 List<SpecifiedType>.

Bellow 是该函数的极简版本,称为 function。它接受一个参数类型,根据它调用相应的函数(这里限制为String)或泛型函数。

public static ArrayList<String> forString(){
    ArrayList<String> res = new ArrayList<>();
    // Fetching and processing data specific to String
    return res;
}

public static <T> ArrayList<T> forGeneric(Class<T> type){
    ArrayList<T> res = new ArrayList<>();
    // Fetching data
    return res;
}

public static <T> ArrayList<T> function(Class<T> type){
    if(type == String.class)
        return (ArrayList<T>) forString();
    return forGeneric(type);
}

目标是这样调用上面的函数:ArrayList<SomeType> someTypes = function(SomeType.class);

关于上面的代码,我注意到两件事:

  1. Cast to ArrayList<T> 是必需的,即使我们知道如果类型 String 作为参数传递,它将 return ArrayList<String> 就像forString()方法

  2. 转换为 ArrayList<T> 给出 Unchecked cast 警告,即使 return 类型将是 ArrayList<String>

我的问题是有没有更好的方法(最好没有强制转换),如果没有,那为什么

首先,这个说法在逻辑上是错误的

if(type.isInstance(String.class))

如果 typeClass<String>,则 isInstance 正在检查参数是否为字符串实例。您传递的参数是一个 class 实例(具体来说,是一个 Class<String>)。

如果你愿意,

String.class.isInstance(String.class) == false

你的意思是

if(type == String.class)

但是,即使解决了这个逻辑错误,您的代码仍然会有一个未经检查的转换警告。

你错过的部分就在这里

Cast to ArrayList<T> is required even though we know that if type String is passed as a parameter it will return ArrayList<String> just like forString() method

没错。 我们知道。但是我们知道的和编译器知道的是两件不同的事情。编译器不够聪明,无法检查条件并意识到类型没问题。可以想象,可以足够聪明,但事实并非如此。

这就是为什么它显示为警告而不是错误的原因。这是一个警告,因为您正在做的事情 可能 是错误的;不是绝对错了,不然根本编译不出来。在这种情况下,警告应该作为提示,让您仔细检查您正在做的事情是否正确,然后您可以愉快地抑制它。

@SuppressWarnings("unchecked")
public static <T> ArrayList<T> function(Class<T> type){
    if(type == String.class)
        return (ArrayList<T>) forString();
    return forGeneric(type);
}

最后 - 它可能是您人为设计的示例的产物 - 但所有这些方法都是无用的。与直接调用 new ArrayList<>() 相比,似乎没有任何优势。在运行时,无论它来自 3 种方法中的哪一种,实际实例都是相同的。

运行时代码无法根据运行时信息 return 不同的泛型类型,因为泛型是一种编译时机制。

要真正摆脱编译器警告,我会return一个新列表:

public static <T> ArrayList<T> function(Class<T> type){
    ArrayList<?> result;
    if (type.equals(String.class)) {
        result = forString();
    } else {
        result = forGeneric(type);
    }

    ArrayList<T> typedResult = new ArrayList<>(result.size());
    for (Object item : result) {
        typedResult.add(type.cast(item));
    }

    return typedResult;
}

这可能看起来很浪费,但事实并非如此。 new List 不是新对象的列表,只是新的引用。引用非常小,可能不超过 256 字节,因此 4,000 个对象的列表占用的 RAM 不超过 1 兆字节。