递归查找一个类继承或实现的所有类和接口

18
我想知道有没有一种简单的方法来确定一个Java类递归地扩展或实现的完整类型列表?
例如:
class Foo extends Bar implements I1, I2 {...}
class Bar implements I3 {...}
interface I1 extends I4, I5 {...}
interface I2 {...}
interface I3 {...}
interface I4 {...}
interface I5 {...}

class ClassUtil {
    public static Set<Class<?>> getAllExtendedOrImplementedTypesRecursively(Class<?> clazz){
        ???
    }
}

import static org.junit.Assert.*;
public class ClassUtilTest {
    @Test
    public void shouldEqualClasses(){
        Set<Class<?>> types = ClassUtil.getAllExtendedOrImplementedTypesRecursively(Foo.class);
        Set<Class<?>> checklist = new HashSet<>();
        checklist.add(Foo.class);
        checklist.add(Bar.class);
        checklist.add(I1.class);
        checklist.add(I2.class);
        checklist.add(I3.class);
        checklist.add(I4.class);
        checklist.add(I5.class);
        assertTrue(checklist.containsAll(types));
        assertTrue(types.containsAll(checklist));
    }
}

思考Arquillian ShrinkWrap创建助手。

更新:由于Class对象没有实现Comparable,我还需要找到一种创建Set(或类似类)的方法,而无需实现Comparable接口(例如,仅依靠类对象的哈希码)。

更新:将测试更改为使用HashSet。傻瓜。


为什么?如果是为了测试,就自己编写各种“instanceof”测试。不要依赖于只用于测试的额外代码。 - user207421
10个回答

14
实现如下方法可以满足原帖作者的要求,它会遍历每个类和接口的继承层次结构:
public static Set<Class<?>> getAllExtendedOrImplementedTypesRecursively(Class<?> clazz) {
    List<Class<?>> res = new ArrayList<>();

    do {
        res.add(clazz);

        // First, add all the interfaces implemented by this class
        Class<?>[] interfaces = clazz.getInterfaces();
        if (interfaces.length > 0) {
            res.addAll(Arrays.asList(interfaces));

            for (Class<?> interfaze : interfaces) {
                res.addAll(getAllExtendedOrImplementedTypesRecursively(interfaze));
            }
        }

        // Add the super class
        Class<?> superClass = clazz.getSuperclass();

        // Interfaces does not have java,lang.Object as superclass, they have null, so break the cycle and return
        if (superClass == null) {
            break;
        }

        // Now inspect the superclass 
        clazz = superClass;
    } while (!"java.lang.Object".equals(clazz.getCanonicalName()));

    return new HashSet<Class<?>>(res);
}    

我使用了JFrame.class进行测试,得到了以下结果:

Set<Class<?>> classes = getAllExtendedOrImplementedTypesRecursively(JFrame.class);
for (Class<?> clazz : classes) {
    System.out.println(clazz.getName());
}

输出:

java.awt.Container
java.awt.Frame
javax.swing.JFrame
javax.swing.TransferHandler$HasGetTransferHandler
java.awt.Window
javax.accessibility.Accessible
javax.swing.RootPaneContainer
java.awt.Component
javax.swing.WindowConstants
java.io.Serializable
java.awt.MenuContainer
java.awt.image.ImageObserver

更新:对于原帖中的测试用例,它会打印出:

test.I5
test.Bar
test.I2
test.I1
test.Foo
test.I3
test.I4

这很好,但是如果一个超类也实现了根类实现的接口,那会怎样呢?另外,当一个接口扩展另一个接口时,这个函数似乎不会在我的例子中捕获到I4和I5。 - coderatchet
2
getAllExtendedOrImplementedTypesRecursivelyThatWeBelieveToBeUsefulForTheRestOfTheProgramWithoutCrashingTheJvmOrBraggingAboutOurLongMethodName - Pierre Arlaud
1
@ArlaudPierre 哈哈哈 XD,你说得对,getAllExtendedOrImplementedTypes肯定足够了,但不知道是否符合OP的要求以长名称调用它,所以我试图在问题中保持一致。 - higuaro

12

Apache Common Lang中有一个ClassUtils类,它具有你想要的两个方法:getAllSuperClasses()和.getAllInterfaces()。


2
这似乎是一个可行的解决方案,但我不想依赖整个库(即使我以后最终会依赖它)。 - coderatchet
3
它是开源的,所以你可以借用它并嵌入到你的代码集中,直到你准备接受整个库为止。祝你好运。 - TA Nguyen

2

在Java8中

import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ClassUtil {
    public static Set<Class<?>> getAllExtendedOrImplementedTypesRecursively(final Class<?> clazz) {
        return walk(clazz)
                .filter(Predicate.isEqual(java.lang.Object.class).negate())
                .collect(Collectors.toSet());
    }

    public static Stream<Class<?>> walk(final Class<?> c) {
        return Stream.concat(Stream.of(c),
                Stream.concat(
                        Optional.ofNullable(c.getSuperclass()).map(Stream::of).orElseGet(Stream::empty),
                        Arrays.stream(c.getInterfaces())
                ).flatMap(ClassUtil::walk));
    }
}

测试代码:

import java.util.Set;

class Test {
    public static void main(String[] args) {
        final Set<Class<?>> set = ClassUtil.getAllExtendedOrImplementedTypesRecursively(Foo.class);
        set.stream().map(Class::getName).forEach(System.out::println);
    }

    class Foo extends Bar implements I1, I2 {}
    class Bar implements I3 {}
    interface I1 extends I4, I5 {}
    interface I2 {}
    interface I3 {}
    interface I4 {}
    interface I5 {}
}

输出:

测试$Foo
测试$Bar
测试$I2
测试$I5
测试$I1
测试$I3
测试$I4


2

你需要的关键字在Class#getSuperclass()方法中:

public static Set<Class<?>> stuff(Class<?> target) {
    Set<Class<?>> classesInterfaces = new HashSet<>();
    classesInterfaces.add(target);
    classesInterfaces.addAll(Arrays.asList(target.getInterfaces());

    Class<?> superClass = target.getSuperclass();
    if(superClass != null)
        classesInterfaces.addAll(stuff(superClass));
}

1
如果我理解您的问题正确,您想找到特定类的所有超类(类和接口)。如果是这样,您可以查看以下解决方案。
查找超类
        Class C = getClass();
        while (C != null) {
          System.out.println(C.getSimpleName());
          C = C.getSuperclass();
        }

找到接口

        C = getClass();
        for(Class adf: C.getInterfaces()){

              System.out.println(adf.getSimpleName());
        }

1
如果你的类名是Foo,那么代码将非常简单,就像这样:
public void getClassDetails() {

    Class klass = Foo.class;
    Class<?> superKlass = klass.getSuperClass();
    Class[] interfaces = klass.getInterfaces();
}

1
这不会递归下去。 - coderatchet

1

0
如果你正在使用Spring:AnnotationUtils.findAnnotation() 正好可以做到这一点:
在提供的类上查找annotationType的单个注解,遍历其接口、注解和超类,如果注解不直接存在于给定的类本身上,则处理类级别的注解。该方法明确处理未声明为继承的类级别注解,以及元注解和接口上的注解。
算法的操作如下: 1. 在给定的类上搜索注解,如果找到则返回。 2. 递归搜索给定类声明的所有注解。 3. 递归搜索给定类声明的所有接口。 4. 递归搜索给定类的超类层次结构。
还有一种重载方法可以在方法和一般的AnnotatedElements上查找注解。

0

我曾经使用asm在一些ShrinkWrap分支上实现了类似的机制https://github.com/mmatloka/shrinkwrap/commit/39d5c3aa63a9bb85e6d7b68782879ca10cca273b。问题是有时候类可能会使用其他文件中未提及的接口实现对象,因此在部署过程中仍然可能失败。

据我所知,官方立场是不建议在ShrinkWrap内部包含这样的功能,而是依赖于工具,例如JBoss Tools,应该有一个允许递归添加类的功能。


0

对@otamega的解决方案提出了一些建议的简化:

这个解决方案不会过滤对象(那将是不一致的),并使用了一些重构和静态导入来使代码更易读。

package com.stackoverflow.question22031207;

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toSet;
import static java.util.stream.Stream.concat;
import static java.util.stream.Stream.of;
import static java.util.stream.Stream.ofNullable;

import java.util.Set;
import java.util.stream.Stream;

public class ClassUtil {
    public static Set<Class<?>> getAllExtendedOrImplementedTypesRecursively(final Class<?> clazz) {
        return walk(clazz).collect(toSet());
    }

    public static Stream<Class<?>> walk(final Class<?> clazz) {
        final Class<?> superclass = clazz.getSuperclass();
        final Class<?>[] interfaces = clazz.getInterfaces();
        return concat(of(clazz), concat(ofNullable(superclass), stream(interfaces)).flatMap(ClassUtil::walk));
    }
}

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