代码之家  ›  专栏  ›  技术社区  ›  JohnnyLambada

在运行时,查找扩展基类的Java应用程序中的所有类。

  •  130
  • JohnnyLambada  · 技术社区  · 16 年前

    我想这样做:

    List<Animal> animals = new ArrayList<Animal>();
    
    for( Class c: list_of_all_classes_available_to_my_app() )
       if (c is Animal)
          animals.add( new c() );
    

    所以,我想看看应用程序宇宙中的所有类,当我找到一个从动物身上下来的类时,我想创建一个新的对象,并将它添加到列表中。这允许我添加功能,而不必更新内容列表。我可以避免以下情况:

    List<Animal> animals = new ArrayList<Animal>();
    animals.add( new Dog() );
    animals.add( new Cat() );
    animals.add( new Donkey() );
    ...
    

    通过上面的方法,我可以简单地创建一个扩展动物的新类,它将被自动获取。

    更新时间:2008年10月16日上午9:00太平洋标准时间:

    这个问题得到了很多很好的回答——谢谢。从回答和我的研究中,我发现我真正想要做的是在Java下是不可能的。有一些方法,比如DimiTrv的Service EoLoad机制可以工作——但是它们对于我想要的非常重,我相信我只是把问题从Java代码移到外部配置文件。

    另一种说明我想要什么的方法是:我的动物类中的静态函数查找并实例化从动物继承的所有类,而不需要任何进一步的配置/编码。如果必须进行配置,我还是在动物类中实例化它们。我理解这是因为Java程序只是一个松散的.class文件联邦,这就是它的方式。

    有趣的是,这似乎是 fairly trivial 在C语言中。

    13 回复  |  直到 6 年前
        1
  •  137
  •   IvanNik    6 年前

    我用 org.reflections :

    Reflections reflections = new Reflections("com.mycompany");    
    Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class);
    

    另一个例子:

    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Reflections reflections = new Reflections("java.util");
        Set<Class<? extends List>> classes = reflections.getSubTypesOf(java.util.List.class);
        for (Class<? extends List> aClass : classes) {
            System.out.println(aClass.getName());
            if(aClass == ArrayList.class) {
                List list = aClass.newInstance();
                list.add("test");
                System.out.println(list.getClass().getName() + ": " + list.size());
            }
        }
    }
    
        2
  •  33
  •   community wiki 2 revs, 2 users 94% ddimitrov    9 年前

    Java做你想做的事情就是使用 ServiceLoader 机制。

    另外,许多人通过在一个众所周知的类路径位置(即/META-INF/services/myplugin.properties)拥有一个文件,然后使用 ClassLoader.getResources() 从所有jar中枚举具有此名称的所有文件。这允许每个jar导出自己的提供程序,并且可以使用class.forname()通过反射来实例化它们。

        3
  •  8
  •   Paul Sonier    16 年前

    从面向方面的角度来考虑这个问题;您真正想要做的是在运行时知道所有扩展了动物类的类。(我认为这比标题更准确地描述了您的问题;否则,我认为您没有运行时问题。)

    因此,我认为您需要创建一个基类(animal)的构造函数,它将当前正在实例化的类的类型添加到静态数组(我自己更喜欢arraylist,但不喜欢它们各自的类型)中。

    所以,粗略地说;

    public abstract class Animal
        {
        private static ArrayList<Class> instantiatedDerivedTypes;
        public Animal() {
            Class derivedClass = this.getClass();
            if (!instantiatedDerivedClass.contains(derivedClass)) {
                instantiatedDerivedClass.Add(derivedClass);
            }
        }
    

    当然,您需要在animal上有一个静态构造函数来初始化实例化的DriveClass…我想这会满足你的需求。请注意,这取决于执行路径;如果您有一个从从未被调用的动物派生的dog类,那么您的动物类列表中将不会包含它。

        4
  •  6
  •   Renaud    10 年前

    不幸的是,这并不完全可能,因为类加载器不会告诉您哪些类是可用的。但是,你可以做这样的事情:

    for (String classpathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator"))) {
        if (classpathEntry.endsWith(".jar")) {
            File jar = new File(classpathEntry);
    
            JarInputStream is = new JarInputStream(new FileInputStream(jar));
    
            JarEntry entry;
            while( (entry = is.getNextJarEntry()) != null) {
                if(entry.getName().endsWith(".class")) {
                    // Class.forName(entry.getName()) and check
                    //   for implementation of the interface
                }
            }
        }
    }
    

    编辑: JordStOK是正确的(在评论中),这只适用于独立的Java应用程序,并且在应用服务器下不工作。

        5
  •  4
  •   DJDaveMark    11 年前

    你可以使用 ResolverUtil ( raw source )来自 Stripes Framework
    如果您需要一些简单而快速的东西,而不需要重构任何现有的代码。

    下面是一个不加载任何类的简单示例:

    package test;
    
    import java.util.Set;
    import net.sourceforge.stripes.util.ResolverUtil;
    
    public class BaseClassTest {
        public static void main(String[] args) throws Exception {
            ResolverUtil<Animal> resolver = new ResolverUtil<Animal>();
            resolver.findImplementations(Animal.class, "test");
            Set<Class<? extends Animal>> classes = resolver.getClasses();
    
            for (Class<? extends Animal> clazz : classes) {
                System.out.println(clazz);
            }
        }
    }
    
    class Animal {}
    class Dog extends Animal {}
    class Cat extends Animal {}
    class Donkey extends Animal {}
    

    这也适用于应用服务器,因为它是在应用服务器上设计的;

    代码基本上执行以下操作:

    • 迭代指定包中的所有资源
    • 只保留以.class结尾的资源
    • 使用加载这些类 ClassLoader#loadClass(String fullyQualifiedName)
    • 检查是否 Animal.class.isAssignableFrom(loadedClass);
        6
  •  2
  •   thoroughly    16 年前

    Java动态地加载类,所以类的类只能是已经加载的类(尚未卸载)。也许您可以使用一个自定义类加载器来做一些事情,该加载器可以检查每个已加载类的父类型。我认为没有一个API来查询加载的类集。

        7
  •  2
  •   Ali Bagheri    7 年前

    用这个

    public static Set<Class> getExtendedClasses(Class superClass)
    {
        try
        {
            ResolverUtil resolver = new ResolverUtil();
            resolver.findImplementations(superClass, superClass.getPackage().getName());
            return resolver.getClasses();  
        }
        catch(Exception e)
        {Log.d("Log:", " Err: getExtendedClasses() ");}
    
        return null;
    }
    
    getExtendedClasses(Animals.class);
    

    编辑:

        8
  •  2
  •   Luke Hutchison    7 年前

    尝试 ClassGraph . (免责声明,我是作者)。对于给定的示例,类图代码为:

    List<Animal> animals =
        new ClassGraph()
            .whitelistPackages("com.zoo.animals")
            .enableAllInfo()
            .scan()
            .getSubclasses(Animal.class.getName())
            .loadClasses(Animal.class);
    

    类关系图也可以做得更多。-- check out the API .

        9
  •  1
  •   JohnnyLambada    16 年前

    感谢所有回答这个问题的人。

    看来这的确是一个难以破解的难题。最后我放弃了,在我的基类中创建了一个静态数组和getter。

    public abstract class Animal{
        private static Animal[] animals= null;
        public static Animal[] getAnimals(){
            if (animals==null){
                animals = new Animal[]{
                    new Dog(),
                    new Cat(),
                    new Lion()
                };
            }
            return animals;
        }
    }
    

    看来,Java并不是像C语言那样建立在自我发现能力上的。我想问题是,因为Java应用程序只是在某个目录/jar文件中的.class文件的集合,所以运行库在被引用之前不知道一个类。那时,加载程序加载它——我正在尝试在引用它之前发现它,如果不到文件系统并查看它,这是不可能的。

    我总是喜欢能够发现自己的代码,而不必告诉它自己,但遗憾的是,这也行得通。

    再次感谢!

        10
  •  1
  •   Osman Shoukry    10 年前

    使用 OpenPojo 您可以执行以下操作:

    String package = "com.mycompany";
    List<Animal> animals = new ArrayList<Animal>();
    
    for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(package, Animal.class, null) {
      animals.add((Animal) InstanceFactory.getInstance(pojoClass));
    }
    
        11
  •  0
  •   pdeva    16 年前

    这是一个棘手的问题,您需要使用静态分析找出这些信息,它在运行时不容易获得。 基本上,获取应用程序的类路径,扫描可用的类,并读取它所继承的类的字节码信息。请注意,类狗可能不会直接从动物继承,但可能会从宠物继承,而宠物又会从动物继承,所以您需要跟踪这个层次。

        12
  •  0
  •   patros    16 年前

    一种方法是使类使用静态初始值设定项…我不认为这些是遗传的(如果是的话就不起作用了):

    public class Dog extends Animal{
    
    static
    {
       Animal a = new Dog();
       //add a to the List
    }
    

    它要求您将此代码添加到所有涉及的类中。但是它避免了在某个地方有一个大的丑陋的循环,测试每个班级寻找动物的孩子。

        13
  •  0
  •   Community CDub    8 年前

    我非常优雅地使用包级注释解决了这个问题,然后将该注释作为参数设置为类列表。

    Find Java classes implementing an interface

    实现只需要创建一个package-info.java,并将magic注释与它们想要支持的类列表放在一起。