我相当了解泛型。根据我的理解,调用泛型类或方法时必须提供 Type 参数。例如:
List<String> strings = new ArrayList<String>();
但是我在理解以下代码时遇到一些困难。
public static <T extends Employee> T findById(@NonNull factory, int employeeId) {
return (T) factory.findEmployeeById(employeeId);
}
员工有很多子类型。例如:
public class Programmer extends Employee {}
public class Engineer extends Employee {}
通过使用这个 findById
方法,我可以成功实现如下所示。
Programmer employee = EmployeeUtils.findById(factory, 2);
在上面的findById
方法中,如何知道T
的类型是什么?仅暗示它是 Employee 的子类型。它无法知道自己是程序员还是工程师。 Java 编译器如何在运行时成功地将类型转换为具体子类(Programmer
或 Engineer
)?
如果您想推荐有关 Java 泛型的内容,请指导我进一步阅读。
最佳答案
How can Java compiler successfully do the type cast at the runtime to Programmer (or Engineer)?
Java 编译器不进行强制转换。
强制转换只是对编译器说的一种方式:“我比你知道的更多;相信我。”编译器仍然会尝试阻止您执行它可以确定绝对不安全的强制转换(例如将 String
强制转换为 Integer
),但通过强制转换,您将对类型负责远离编译器的安全。仅当您知道(通过代码/系统的语义)它是安全的时,您才应该进行强制转换。
这种特殊模式对于从存储库检索异构实体很常见;但它不是类型安全的。
该方法的调用者有责任仅使用“正确类型的 ID”调用该方法。问题是代码中没有任何迹象表明它可能会失败。
这种模式总是让我烦恼的原因是它隐藏了问题发生的实际位置。回想一下,Java 泛型基本上只是强制转换的省略。这意味着该方法“实际上”看起来像这样:
public static Employee findById(@NonNull factory,int employeeId) {
// There is actually no cast here!
return factory.findEmployeeById(employeeId);
}
调用站点如下所示:
// The cast is here instead!
SpecificEmployeeType e = (SpecificEmployeeType) findById(someId);
(尝试像这样显式编写代码,并将字节码与通用版本进行比较)
因此,虽然您可以抑制 findById
方法中的警告,但实际的异常发生在调用站点。因此,警告(表明可能出现问题)出现在错误的位置。
如果您在不使用泛型的情况下显式编写它,您可以准确地看到问题实际发生的位置。
人们经常说他们希望以这种方式使用泛型,因为它“更干净”:您不需要在每个调用站点进行显式强制转换或抑制。就我个人而言,我想要那里有额外的东西:它看起来像是那里正在发生危险的事情(确实有!),所以你知道你需要格外小心。
<小时/>还值得指出的是,您不应该将@SuppressWarnings("unchecked")
添加到您的代码中 - 无论是在findById
中方法,或在调用站点 - 除非您可以绝对确定强制转换是安全的。
与强制转换一样,警告抑制是对编译器无法证明的事情承担责任的一种方式。当然,通过抑制警告,您只是忽略了编译器试图提供的帮助。与任何警告性建议一样,您可以随意忽略它,但在没有完全了解后果的情况下这样做是鲁莽的。
关于java - 类型转换为泛型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44601402/