【问题标题】:usage of generics as return type使用泛型作为返回类型
【发布时间】:2015-08-18 19:59:14
【问题描述】:

我有这样的结构:

abstract class MyDomain{...}
abstract class FooDomain extends MyDomain{...}
abstract class BarDomain extends MyDomain{...}
class FirstConcreteBarDomain extends BarDomain{...}
class SecondConcreteBarDomain extends BarDomain{...}

我需要一个创建MyDomain 对象的工厂。我的第一次尝试是这样的:

public interface ISpecializedObjectsFactory {
    public <T extends MyDomain> T create(Class<?> clazz);
}

实现为:

public class FirstSpecializedObjectsFactory implements ISpecializedObjectsFactory {

    @Override
    public <T extends MyDomain> T create(Class<?> clazz) {
        if(clazz.equals(BarDomain.class))
            return new FirstBarDomain();
        throw new InvalidParameterException();
    }

SecondBarDomain 也是如此。

第一个问题:为什么这会产生一个错误,说它不能将FirstBarDomain 转换为T

在这个错误之后,我引入了一个演员表:return (T) new FirstBarDomain();

问题是强制转换是不安全的,我想对结果有信心,所以我引入了另一个约束(假设每个MyDomain 对象总是有 2 级派生):

public <T extends AnagrafeDomain, S extends T> S create(Class<T> clazz)

第二个问题: 假设这个工厂是创建MyDomain 对象的唯一入口点,并且对工厂的调用从不使用具体类(但总是像:@987654332 @),问题是:这个新版本安全吗?

【问题讨论】:

    标签: java generics return type-safety


    【解决方案1】:

    演员表不安全的原因是因为这个特定的行:

    public <T extends MyDomain> T create(Class<?> clazz) {
    

    这从调用站点推断返回类型;换句话说,考虑以下类:

    public abstract class MyFakeDomain extends MyDomain { }
    

    随后将编译以下代码,但在运行时失败:

    ISpecializedObjectsFactory factory = new FirstSpecializedObjectsFactory();
    MyFakeDomain broken = factory.create(BarDomain.class);
    

    由于类型推断,这将抛出ClassCastException;推断的类型将是 MyFakeDomain,导致尝试将 FirstBarDomain 转换为 MyFakeDomain,这是非法转换 - 因此是不安全的警告。

    类型推断也是强制类型转换必须存在的原因;虽然FirstBarDomain 绝对是MyDomain 的子类,但我们不知道它是否属于T,因为T 可能是any MyDomain 子类,不一定是FirstBarDomain .

    但是,如果调用者小心,您的代码将正常工作 - 您是否认为这可以接受取决于您。

    这给了我们第二个问题的答案:使用BarDomain 作为要推断的类型并不总是安全的,因为它可能是MyDomain 的另一个子类。这里唯一安全的类型是MyDomain - 但是,如果您打算只使用MyDomain 作为类型,您不妨删除泛型类型绑定,只使用返回类型MyDomain

    【讨论】:

    • OP 说“对工厂的调用从不使用具体类”
    • @sharonbn 正确,但这仍然不安全;即使MyFakeDomainabstract,它仍然会导致完全相同的问题。我会明确地将其抽象化以避免混淆。
    • 这将并且应该通过类强制转换异常。您肯定会使用BarDomain 接口将代码与特定实现分离。就像你宁愿 List&lt;Object&gt; = ... 而不是 ArrayList&lt;Object&gt; = ...
    • @Ian2thedv 对不起,我不明白你的评论。我的观点是,上面的代码会抛出 ClassCastException,作为解释为什么 OP 中的代码不安全的一部分。
    • 我明白你在说什么,但我不明白为什么有人会将变量分配给实现类型。您可以将其分配给接口类型,即BarDomain,而不是MyFakeDomain 或任何其他扩展BarDomain 的类而无需interfaceof 检查。
    【解决方案2】:

    让你有信心的约束是限制你的工厂接收的类:

    public interface ISpecializedObjectsFactory {
        public <T extends MyDomain> T create(Class<? extends MyDomain> clazz);
    }
    
    
    public class FirstSpecializedObjectsFactory implements ISpecializedObjectsFactory {
        @Override
        public <T extends MyDomain> T create(Class<? extends MyDomain> clazz) {
            if(clazz.equals(BarDomain.class))
                return (T) new FirstBarDomain();
            throw new InvalidParameterException();
        }
    }
    

    当参数不是 MyDomain 的子类时,编译器将不接受任何对 create 的调用。但是,它将接受一个抽象类。如果你想知道你收到了具体的课程,你可以在这里找到答案How can I determine whether a Java class is abstract by reflection

    【讨论】:

    • 我认为这个解决方案会接受FooDomain subj = SpecializedObjectsFactory.getFactory().create(BarDomain.class); 并且会抛出一个classCastException
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-13
    • 2015-07-18
    • 2014-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多