输入到Generic

时间:2017-06-17 06:10:45

标签: java generics

我对Generics非常了解。根据我的理解,在调用Generic类或方法时必须提供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编译器如何成功地在运行时将类型转换为具体子类(ProgrammerEngineer)?

如果您想推荐Java Generics,请引导我进一步阅读。

3 个答案:

答案 0 :(得分: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无法强制转换为GenericTest $ Programmer

当你进行这个演员表时,你应该确保在运行时类型是正确的,否则会出现丑陋的异常。

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

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

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

希望这有助于您了解运行时会发生什么。

如需进一步阅读,请考虑Oracle Tutorial for Generics

答案 1 :(得分:1)

  

Java编译器如何成功地在运行时向Programmer(或Engineer)执行类型转换?

Java编译器不进行强制转换。

演员阵容只是对编译器说的一种方式:&#34;我比你知道更多;相信我。&#34;编译器仍会试图阻止你进行可以确定绝对不安全的强制转换(比如将String强制转换为Integer),但是通过强制转换,你将负责远离编译器的类型安全。只有你知道 - 通过你的代码/系统的语义 - 它才是安全的。

此特定模式通常用于从存储库中检索异构实体;但它不是类型安全的。

该方法的调用方有责任仅使用&#34;正确类型的ID&#34;来调用该方法。问题是代码中没有迹象表明它可能会失败。

这种模式总是让我感到烦恼的原因是它隐藏了问题发生的实际位置。回想一下,Java泛型基本上只是强制转换。这意味着方法&#34;真的&#34;看起来像这样:

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方法中取消警告,但实际异常发生在呼叫站点。因此,警告 - 可能在那里出现问题的迹象 - 是在错误的地方。

如果您在没有泛型的情况下明确地编写它,您可以确切地看到问题实际发生的位置。

人们经常说他们想以这种方式使用泛型,因为它更清洁&#34;:你不需要在每个呼叫站点进行明确的强制转换或抑制。就个人而言,我想要那里有额外的东西:它让它看起来像那里有危险的东西(有!)所以你知道你需要格外小心。

值得指出的是,不应将<{1}}添加到您的代码中 - 无论是@SuppressWarnings("unchecked")方法还是通话网站 - 除非您可以绝对肯定演员是安全的。

与强制转换一样,警告抑制是对编译器无法证明的事情负责的一种方式。通过抑制警告当然,你只是忽略了编译器试图提供的帮助。与任何警示性建议一样,您可以自由地忽略它,但如果不完全理解其后果,这样做是不明智的。

答案 2 :(得分: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();
    }
}

如果我们尝试运行代码,则会出现编译错误

  

错误:(28,22)java:找不到符号符号:方法   engineerMethod()location:class GenericTest.Employee

要修复错误,您需要添加一个&#34;抽象方法&#34;或&#34;方法&#34;像这样的抽象类:

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