【问题标题】:How to return interface from generic class implementing the interface?如何从实现接口的泛型类返回接口?
【发布时间】:2020-11-28 20:03:45
【问题描述】:

我想创建一个方法来接受任何实现任何接口I 的类T

然后对类做点什么,返回实现的接口I

这是我尝试过的:

class MyLibrary {
    
    public static <I, T extends I> I registerImplementation(Class<T> classImpl) {
        I interfaceImpl = (I) classImpl.newInstance();
        return interfaceImpl;
    }
}

然后我将创建一个接口和一个实现该接口的类:

interface UserInterface {
    void doSomethingDefined();
}

class UserClass_v1_10_R2 implements UserInterface {

    @Override
    public void doSomethingDefined() {
        System.out.println("What this does is well-defined");
    }

    public void doVersionSpecificStuff() {
        System.out.println("This is not in the interface, so don't really depend on this");
    }
}

但是,当我调用引用UserClass的方法时,它返回相同的类类型T而不是接口类型I,从而允许调用所有未在接口中声明的类方法.

即在下面的语句中,即使 registerImplementation() 被声明为返回对 UserInterface 的引用,编译器仍将引用视为指向 UserClass_v1_10_R2 的实例,从而允许访问不在接口中的实现方法。

MyLibrary.registerImplementation(UserClass_v1_10_R2.class).doVersionSpecificStuff();

将其与假定的相同进行对比

UserInterface uiObj = MyLibrary.registerImplementation(UserClass_v1_10_R2.class);

uiObj.doVersionSpecificStuff(); // This does not compile

【问题讨论】:

  • 如果你明确指定泛型类型参数会发生什么:MyLibrary.&lt;UserInterface, UserClass_v1_10_R2&gt;registerImplementation(UserClass_v1_10_R2.class).doVersionSpecificStuff();
  • 可以UserClass_v1_10_R2 r = MyLibrary.registerImplementation(UserClass_v1_10_R2.class);吗?
  • @JacobG。编译错误,无法解析符号“registerImplementation”
  • @daniu 这是因为这个函数的返回值是UserClass,但是我需要返回它实现的接口而不具体声明哪个接口,因为它的名字是由用户提供的。
  • @user7401478 “无法解析符号”听起来像是 Intellij 特有的错误 - 你能直接用 javac 编译建议的代码,看看是否能解决问题吗?

标签: java generics


【解决方案1】:

这就是编译器推断类型的方式。如果您使用特殊(未记录的)标志进行编译:

javac --debug=verboseResolution=all  ...

你会看到:

  Note: Deferred instantiation of method <I,T>registerImplementation(Class<T>)
  MyLibrary.registerImplementation(UserClass_v1_10_R2.class).doVersionSpecificStuff();
                                    ^
  instantiated signature: (Class<UserClass_v1_10_R2>)UserClass_v1_10_R2

查看实例化签名:(Class&lt;UserClass_v1_10_R2&gt;)UserClass_v1_10_R2

所以你需要添加另一个参数来获取接口:

public static <I, T extends I> I registerImplementation(Class<I> interfaceClass, Class<T> implClass) {
    // do whatever checks you want if this is an interface or if a constructor can be called....
    I interfaceImpl = null;
    try {
        interfaceImpl = interfaceClass.cast(implClass.newInstance());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return interfaceImpl;
}

当然,如果您有多个接口,这会引发问题,但这是您需要考虑的问题。

【讨论】:

    【解决方案2】:

    可以通过将所需的接口类作为另一个参数传递给方法来实现这一点。这也允许验证泛型类型类是一个接口,以及添加额外的检查以确保可以实例化实现该接口的类。

    public class MyLibrary {
        public static <I> I registerImplementation(Class<I> interfaceClass, Class<? extends I> implClass) {
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException("Must be an interface class");
            }
    
            // Check constructor accessibility, etc...
            if (Modifier.isAbstract(implClass.getModifiers())) {
                throw new IllegalArgumentException("Must be instantiable");
            }
    
            return implClass.newInstance();
        }
    }
    

    这将返回作为第一个参数传递的接口的实现。第二个参数的有界类型Class&lt;? extends I&gt; 表示不需要显式转换:

    // This returns UserInterface
    MyLibrary.registerImplementation(UserInterface.class, UserClass_v1_10_R2.class);
    

    【讨论】:

      【解决方案3】:

      差异的原因是在第一次调用泛型方法时,您依赖type inference,类型参数被推断为I=UserClass_v1_10_R2T=UserClass_v1_10_R2,因为编译器无法确定I 应该被调用为 UserInterface

      如果您显式调用类型参数,则行为将是您所期望的,即编译错误:

      MyLibrary.<UserInterface, UserClass_v1_10_R2>registerImplementation(
          UserClass_v1_10_R2.class).doVersionSpecificStuff();
      

      当您使用局部变量时,它并不完全等效,因为类型推断使用了局部变量的类型声明中的类型信息,这为编译器提供了足够的信息来调用所需的类型I=UserInterface

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-10-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-16
        • 2020-11-12
        相关资源
        最近更新 更多