160

我想做这样的事情:

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() );

所以,我想查看我的应用程序宇宙中的所有类,当我找到一个源自 Animal 的类时,我想创建一个该类型的新对象并将其添加到列表中。这使我无需更新事物列表即可添加功能。我可以避免以下情况:

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

使用上述方法,我可以简单地创建一个扩展 Animal 的新类,它会自动被拾取。

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

这个问题已经产生了很多很好的回应——谢谢。 从回复和我的研究中,我发现我真正想做的事情在 Java 下是不可能的。有一些方法,例如 ddimitrov 的 ServiceLoader 机制可以工作——但它们对于我想要的来说非常繁重,我相信我只是将问题从 Java 代码转移到外部配置文件。 2019 年 5 月 10 日更新(11 年后!)根据@IvanNik 的回答 org.reflections看起来不错,现在有几个库可以帮助解决这个问题。@Luke Hutchison 的答案中的ClassGraph看起来也很有趣。答案中还有更多的可能性。

陈述我想要的另一种方式:我的 Animal 类中的静态函数查找并实例化从 Animal 继承的所有类——无需任何进一步的配置/编码。如果我必须配置,我还不如在 Animal 类中实例化它们。我理解,因为 Java 程序只是 .class 文件的松散联合,所以它就是这样。

有趣的是,这在 C# 中似乎相当微不足道。

4

14 回答 14

171

我使用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());
        }
    }
}
于 2012-02-11T14:29:32.627 回答
36

做你想做的事的 Java 方法是使用ServiceLoader机制。

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

于 2008-10-15T23:38:31.910 回答
10

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

所以我认为你想要的是创建一个基类(Animal)的构造函数,它将被实例化的当前类的类型添加到你的静态数组(我更喜欢 ArrayLists,我自己,但每个人自己)。

所以,大致;

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

当然,您需要 Animal 上的静态构造函数来初始化instanceatedDerivedClass ...我认为这会做您可能想要的。请注意,这取决于执行路径;如果你有一个从 Animal 派生的 Dog 类,它永远不会被调用,那么你的 Animal Class 列表中就不会有它。

于 2008-10-15T21:07:09.577 回答
8

不幸的是,这并不完全可能,因为 ClassLoader 不会告诉您哪些类可用。但是,您可以非常接近地执行以下操作:

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
            }
        }
    }
}

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

于 2008-10-15T18:41:04.693 回答
6

列出给定类的所有子类的最强大的机制目前是ClassGraph,因为它处理最广泛的类路径规范机制,包括新的 JPMS 模块系统。(我是作者。)

List<Class<Animal>> animals;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("com.zoo.animals")
        .enableClassInfo().scan()) {
    animals = scanResult
        .getSubclasses(Animal.class.getName())
        .loadClasses(Animal.class);
}
于 2018-08-01T03:51:24.367 回答
4

如果您需要简单快速的东西而不重构任何现有代码,您可以使用Stripes 框架中的ResolverUtil原始源代码) 。

这是一个没有加载任何类的简单示例:

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);
于 2013-11-21T12:07:50.730 回答
2

Java 动态加载类,因此您的类将只有那些已经加载(但尚未卸载)的类。也许您可以使用自定义类加载器来检查每个加载的类的超类型。我认为没有 API 可以查询加载的类集。

于 2008-10-15T17:55:20.387 回答
2

用这个

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);

编辑:

于 2018-04-07T19:44:44.687 回答
1

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

看来这确实是一个难以破解的难题。我最终放弃并在我的基类中创建了一个静态数组和吸气剂。

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 文件的集合,因此运行时在引用它之前不知道一个类。那时加载器加载它——我想要做的是在我引用它之前发现它,如果不去文件系统并查看它是不可能的。

我总是喜欢可以发现自己的代码,而不是我不得不告诉它自己,但唉,这也行得通。

再次感谢!

于 2008-10-15T19:04:01.917 回答
1

使用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));
}
于 2015-02-10T05:23:24.463 回答
0

这是一个棘手的问题,您需要使用静态分析来找出这些信息,它在运行时不容易获得。基本上获取您的应用程序的类路径并扫描可用的类并读取它继承的类的字节码信息。请注意,类 Dog 可能不直接继承自 Animal,但可能继承自 Pet,而 Pet 又继承自 Animal,因此您需要跟踪该层次结构。

于 2008-10-15T18:52:00.753 回答
0

一种方法是让类使用静态初始化器......我不认为这些是继承的(如果它们是它将不起作用):

public class Dog extends Animal{

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

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

于 2008-10-15T19:29:37.870 回答
0

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

查找实现接口的 Java 类

实现只需要创建一个 package-info.java 并将魔术注释与他们想要支持的类列表一起放入。

于 2011-04-29T12:15:12.787 回答
0

由于newInstance()不推荐直接使用,因此您可以使用Reflections以这种方式进行。

Reflections r = new Reflections("com.example.location.of.sub.classes")
Set<Class<? extends Animal>> classes = r.getSubTypesOf(Animal.class);

classes.forEach(c -> {
    Animal a = c.getDeclaredConstructor().newInstance();
    //a is your instance of Animal.
});
于 2021-05-03T18:43:45.667 回答