2017-06-17 65 views
0

我对泛型非常了解。根据我的理解,当调用Generic类或方法时,必须提供Type参数。例如:类型转换为Generic

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编译器如何成功地在运行时将类型转换为具体的子类(ProgrammerEngineer)?

如果您想推荐Java泛型,请指导我进一步阅读。

+2

“但是我在理解下面的代码时遇到了一些困难”,这可能是因为这个代码是泛型的常见但滥用的使用:它不是类型安全的。 –

回答

1

How can Java compiler successfully do the type cast at the runtime to Programmer (or Engineer)?

Java编译器不做转换。

演员只是对编译器说的一种方式:“我比你更了解,相信我。”编译器仍然会试图阻止你进行强制转换,它可以确定它是绝对不安全的(例如将String转换为Integer),但是通过强制转换,你需要远离编译器负责类型安全。如果你知道 - 你的代码/系统的语义 - 它应该是安全的。

这种特殊模式对于从存储库中检索异构实体是很常见的;但它不是安全的。

负责方法的调用者只调用具有“正确类型的ID”的方法。问题在于代码中没有指示它可能会失败。

这种模式总让我感到厌烦的原因是它隐藏了问题发生的实际位置。回想一下,Java泛型基本上只是casts的一个缩写。这意味着,该方法“真的”看起来是这样的:

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方法,或者在调用的地方 - 除非你可以绝对肯定的是,中投是安全的。

与铸造一样,警告抑制是对编译器无法证明的事情负责的一种方式。当然,通过压制警告,你只是忽略了编译器试图提供的帮助。与任何警示性建议一样,您可以自由地忽略它,但如果没有充分理解后果,这样做是蛮荒的。

1

在这种情况下编译器不检查类型转换。当您的编译器选项相应地设置时,您应该会收到一条警告,告知您未经检查的转换为T

该检查将在运行时执行。请看下面的代码:

public class GenericTest { 
    public abstract static class Employee {} 

    public static class Programmer extends Employee {} 

    public static class Engineer extends Employee {} 

    public static void main(String[] args) { 
     Programmer p = getEmployee(); 
    } 

    public static <T extends Employee> T getEmployee() { 
     return (T) new Engineer(); 
    } 
} 

此代码将警告Type safety: Unchecked cast from GenericTest.Engineer to T编译。在运行时,会出现一个ClassCastException:

java.lang.ClassCastException: GenericTest$Engineer cannot be cast to GenericTest$Programmer

当你这样做演员,你应该确保该类型是在运行时正确,否则丑陋异常都会飞。

泛型在编译时被删除。因此,当您分析编译的字节码时,您会看到该方法根据其签名返回Employee。其实上面的例子中的编译的字节代码几乎相同,从下面的代码(具有相同的类)编写的字节码:

public static void main(String[] args) { 
    Programmer p = (Programmer) getEmployee(); 
} 

public static Employee getEmployee() { 
    return new Engineer(); 
} 

希望这将帮助您了解在运行时会发生什么。

进一步的阅读考虑Oracle Tutorial for Generics

+0

我添加了surpresswarning注释以隐藏未经检查的强制转换。你能否更详细地解释退货声明中的T是什么? –

+0

@ Steve.NayLinAung我已经更新了我的答案。希望对你有帮助 – SilverNak

0

返回类型T将为雇员。 以下代码确认了此操作。

public class GenericTest { 
    public abstract static class Employee { 
    } 

    public static class Programmer extends Employee { 
    } 

    public static class Engineer extends Employee { 
     void testMethod(){ 
      System.out.println("Engineer method"); 
     } 
    } 

    public static void main(String[] args) { 
     getEmployee().testMethod(); 
    } 

    public static <T extends Employee> T getEmployee() { 
     return (T) new Engineer(); 
    } 
} 

如果我们尝试运行代码,我们得到了一个编译错误

Error:(28, 22) java: cannot find symbol symbol: method engineerMethod() location: class GenericTest.Employee

要修正这个错误,你需要一个“抽象方法”或“法”添加到类的抽象类这个:

public abstract static class Employee { 
    void testMethod(){}; 
}