在Java中判断一个类是否实现了一个接口

111

我有一个Class对象。我想确定该Class对象所代表的类型是否实现了特定接口。我想知道如何实现这一点?

我有以下代码。基本上它获取指定包中所有类的数组。然后,我要遍历该数组并将实现接口的类对象添加到我的映射中。问题在于isInstance()需要一个对象作为参数。我无法实例化一个接口。因此,我对此有些束手无策。有什么建议吗?

Class[] classes = ClassUtils.getClasses(handlersPackage);
for(Class clazz : classes)
{
    if(clazz.isInstance(/*Some object*/)) //Need something in this if statement
    {
        retVal.put(clazz.getSimpleName(), clazz);
    }
}
5个回答

259

您应该使用isAssignableFrom

if (YourInterface.class.isAssignableFrom(clazz)) {
    ...
}

如果项目相同,则此方法有效。但是,如果您将接口代码1:1复制到新项目和JAR中,然后尝试将该JAR作为插件加载,则该调用将返回false。按名称比较会“起作用”,就像罗迪发布的那样。但我不知道如何以Java最终验证兼容性的方式进行检查。按名称是一种不好的方法。当然,如果项目相同,则你的方法是好的。也许我做错了:我为插件文件创建了URLClassLoader实例,并像那样加载它。也许我应该尝试不同的类加载器。 - Dreamspace President
5
你遇到了类加载问题。如果你使用不同的类加载器加载同一个类,那么这两个 Class 实例将不兼容。你可能会看到类似于 java.lang.ClassCastException: com.my.CustomClass cannot be cast to com.my.CustomClass 的错误,或者其他类似的莫名其妙的错误。 - Flavio
我已经尝试了各种方法,最终发现主要问题是:虽然我的插件和主项目中的接口是相同的,但它们不在同一个位置,因此命名空间/地址是不同的。顺便说一下,我现在正在使用:myClassLoader = new URLClassLoader(new URL[] { candidateFile.toURI().toURL() }, LoadedPlugin.class.getClassLoader());classToLoad = Class.forName("com.blablabla.plugin.Main", true, myClassLoader);instance = (MyIntf) classToLoad.newInstance(); 它工作得很好。 - Dreamspace President

19

可以使用以下函数获取所有已实现的接口

Class[] intfs = clazz.getInterfaces();

11
你可以使用class.getInterfaces(),然后检查接口类是否存在其中。
Class someInterface; // the interface you want to check for 
Class x; // 
Class[] interfaces = x.getInterfaces();

for (Class i : interfaces) {
    if (i.toString().equals(someInterface.toString()) {
        // if this is true, the class implements the interface you're looking for
    }
}

这种方法在技术上是可行的,但更简单、更清晰的方法是使用isAssignableFrom,就像Flavio所提到的那样。 - jwj
是的,虽然您的答案已经被投票赞同了很多次,但我认为添加一些上下文会很有用。虽然使用isAssignableFrom可能更可取,但在某些情况下,您需要通过查看名称来扫描类实现的接口列表。 - jwj
2
这实际上是不起作用的,getInterfaces() 只有在类直接实现接口时才有效,如果一个超类实现了该接口,或者一个超级接口扩展了它,那么该接口将不会被 getInterfaces() 返回。您需要遍历所有超类和接口的树,以获取类实现的所有接口。 - James Roper
那不是问题的关键。 - Roddy of the Frozen Peas

1

您还可以通过添加“.class”来设置实例。

Class[] classes = ClassUtils.getClasses(handlersPackage);
for(Class clazz : classes)
{
    if(Interface.class.isAssignableFrom(clazz))
    {
        retVal.put(clazz.getSimpleName(), clazz);
    }
}

4
如果有人考虑采用这种方法,请参考Flavio的回答。请注意,此示例中的代码可能有几个不容易理解的地方:ClassUtils不是Java的一部分(它在Guava或Spring等框架中),上面使用的术语“Interface”是指要测试的特定接口(即在此上下文中不是Java关键字),而retVal的目的没有被解释或在任何地方提到。 - jwj

0

为所有其他答案做出贡献,如果可能的话不要使用方法isAssignableFrom最受欢迎的答案,即使使用clazz.getInterfaces()的“不太好”的答案也比isAssignableFrom性能更好。

开发人员在寻找OP问题的答案时常犯的一个错误是,在实例可用时更喜欢使用isAssignableFrom,这是错误的

if (IMyInterface.isAssignableFrom(myObject.getClass())) {
    ...

尽可能使用IMyInterface.class.isInstanceinstanceof,因为它们的性能要好得多。当然,正如OP所述,它们的缺点是必须有一个实例而不仅仅是class

if (IMyInterface.class.isInstance(myObject)) {
    ...
if (myObject instanceof IMyInterface) { // +0.2% slower than `isInstance` (*see benchmark)
    ...

一个更快但不美观的解决方案是将所有“有效”的类作为静态Set存储,而不是检查它们。当您需要经常测试类时,这个不美观的解决方案是首选,因为它的性能优于所有其他直接class检查方法。
public static final Set<Class<?>> UGLY_SET = Stream.of(MyClass1.class, MyClass2.class, MyClass3.class).collect(Collectors.toCollection(HashSet::new));
if (UGLY_SET.contains(MyClass)) {
    ...

(*) JMH 基准测试增加了 +0.2%

请访问用户@JBE, @Yura和@aleksandr-dubinsky的这个答案,感谢他们的贡献。此外,该答案中有大量细节,以使基准测试结果无效,请仔细查看。


你有数据支持这个吗?性能是复杂的,一位专家说Class.isInstance比检查列表更快 - Johannes Kuhn
你可能是对的,因为我一直在关注isAssignableFrom这个测试方法。然而,通过阅读那篇文章,我期望编译器对于一个final static map的行为与该博主从isInstance方法中描述的类似。顺便说一下,我已经将代码从List修改为Set,因为它的性能比列表更好(因为内部使用了map),无论如何,我已经计划运行一项基准测试来验证所有这些,我会发布结果。非常感谢你的询问! - luiscla27
我个人认为你应该使用最简单的方法来完成工作。如果你有一个对象并且类在编译时已知:instanceof。如果类在编译时不知道,但是有对象:Class::isInstance。没有对象,类在编译时也不知道:Class::isAssignableFrom。如果两个类在编译时都已知:手动完成。 - Johannes Kuhn
1
另外,正如文章中提到的,这取决于方法是否可以内联,因此任何关于性能的一般性陈述(不考虑内联/常量折叠)可能在至少一个情况下是错误的。 - Johannes Kuhn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接