如何自动将子类放入ArrayList?

4

我有一个超类,然后有几个子类,像这样:

public abstract class A {
    public abstract int getValue();
}

public class B extends A {
    public int getValue() {
        return 1;
    }
}

public class C extends A {
    public int getValue() {
        return 123;
    }
}

public class D extends A {
    public int getValue() {
        return 15234;
    }
}

大约有100个子类。我还拥有一个管理器:

public class Manager {
    public static ArrayList<A> list = new ArrayList<A>();
}

如何“神奇地”将所有子类的实例添加到list中,而不需要手动创建每个子类的实例并将其添加到列表中?或许可以使用初始化块? 编辑 如何在 Manager 中访问 list 不重要。我已将其更改为静态。

4
听起来这似乎是一件不寻常的事情。你实际上想要达到什么目的?你能解释一下你试图解决的问题吗? - SimonC
第一个问题当然是:为什么?你做的事情需要你这样做吗?第二个问题是:为什么?列表管理器只关心 A,并将从其提取的任何项目都作为类型 A 进行转换返回。 - Mike 'Pomax' Kamermans
每个类都有一个实现...让我编辑。 - blake305
每个类将代表一个命令。将有一个获取命令名称的方法,一个在执行命令时调用的方法以及其他一些内容。我不想每次创建或删除命令时都要使用每个命令的名称进行list.add(new D()); - blake305
@RyanThames 请解释得更清楚一些。我想被子类化,因为每个命令在类型转换为超类时都可以执行。 - blake305
@blake305 - 这个数组似乎是所有类型为A的对象的容器。我会将列表传递给A的构造函数,并将自身添加到列表中。要查找接口的真正实现,请参阅https://dev59.com/h3RC5IYBdhLWcg3wAcbU中的一些想法。 - Jayan
7个回答

2
我假设你想要构建一个(静态)列表,其中:
  • 仅包含每个子类的一个实例
  • 在创建并填充之前就已经被创建了
  • 不涉及每个子类中的代码创建/将自身的实例添加到列表中。
首先,实例初始化块无法完成此任务。实例初始化程序在创建实例时运行...并且必须有代码new该类(即每个子类)才能发生。
我认为唯一可行的方法是编写一些复杂的反射代码来:
  • 迭代类路径上的所有类
  • 使用Class.forName()加载每个类
  • 利用反射测试以查看类是否为A的子类
  • 如果是,则通过反射调用类的无参数构造函数并将结果实例添加到“列表”中。
这种方法相当繁琐!除非您可以限制“包空间”以搜索这些子类,否则它会很昂贵。
实际上,这可能是一个更好的使用enum解决的问题...特别是如果子类没有需要不同方法实现的行为差异。(例如,您的getValue()方法可以返回一个私有变量...您可以使用构造函数进行初始化。)请参见@Paul Bellora的答案。
(如果需要某些子类的多个实例,则无法使用enums解决此问题。)

初始化程序永远不会被调用。我想我需要一种自动调用A所有子类初始化程序的方法。 - blake305

1
每个类将代表一个命令。
根据您问题的描述,似乎A可以是枚举
public enum A {

    B(1) {
        @Override
        public void execute() {
            //code and stuff
        }
    },
    C(123) {
        @Override
        public void execute() {
            //code and stuff
        }
    },
    D(15234) {
        @Override
        public void execute() {
            //code and stuff
        }
    };

    private final int value;

    private A(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public abstract void execute();
}

现在,每个命令都只有一个实例,并且您可以使用A.values()轻松迭代命令。

但是每个命令都将有一个实现,包括代码和其他东西。 - blake305

0

这是一种有点hackish的方法,但如果你所有的子类都在同一个文件夹中(实际的类文件),你可以迭代文件夹中的文件并使用ClassLoader。你的代码大致如下-

for(String className : classNames){
    Class clazz = classLoader.loadClass(className);
    list.add(clazz.newInstance());
}

查看ClassLoader API以获取更多信息。同时请记住,这不是非常高效的方法,但如果您只需要执行一次,那么应该没问题。


如果我有一种获取类名列表的方法,这将起作用。由于某些原因,我无法在没有手动初始化它的情况下获取类的名称。 - blake305
@blake305 像我之前说的那样,如果你知道所有实际类文件的位置,你可以创建一个文件对象来读取它们,以创建你的类名列表。 - David says Reinstate Monica
@blake305 你也可以看一下这个 - David says Reinstate Monica

0

可能是这样的:

public abstract class A {
    public A(Manager m) {
        m.list.add(this);
    }
    public abstract int getValue();
}

public class B extends A {
    public B(Manager m) {
        super(m);
    }
}

这样,您就再也不必在子类化时处理m.list.add(new A());了。但我不知道这是否符合您的要求...


编辑:

我如何访问经理中的列表并不重要。我将其编辑为静态。

如果您不介意使用单例模式,这里有一个非常基本的实现:

但请阅读单例模式的缺点

public class Manager {
   private static Manager instance = null;
   protected Manager() {
      // Exists only to defeat instantiation.
   }
   public static Manager getInstance() {
      if(instance == null) {
         instance = new Manager();
      }
      return instance;
   }
}

然后:

public abstract class A {
    public A() {
        Manager.getInstance().list.add(this);
    }
    public abstract int getValue();
}

public class B extends A {
}

但是,作为一种设计,这仍然非常不令人满意...


初始化程序永远不会被调用。我猜我需要一种自动调用A的所有子类的初始化程序的方法。 - blake305
在我看来,你想要实现的目标可以通过单例设计模式来满足。但出于某些好的原因,这种设计并不被推荐。这就是为什么经理应该传递给你的任何实例。"魔法"可以来自单例思维方式,但我建议先阅读赞成/反对的讨论。https://dev59.com/YXVC5IYBdhLWcg3w9GA9. - Gauthier Boaglio
使用单例设计模式,您永远不必传递管理器,因此也不必重新定义子类构造函数。需要一个示例吗?(或者您可能已经熟悉它了)。单例模式并不是很好,但在某些罕见情况下我仍然会使用它们... - Gauthier Boaglio

0

1) 您需要找到类A的所有可用子类。为此,您需要扫描Java类路径上的所有类。为了简化事情,我们可以假设所有子类都与A.class位于同一位置。 A应该位于一个jar文件或一个文件夹中。我们可以查找其实际位置如下:

URL url = A.class.getProtectionDomain().getCodeSource().getLocation();

2) 假设它是一个文件夹,例如 file:/D:/workspace1/x/target/classes/。现在我们应该遍历此文件夹及其子文件夹中的所有 .class 文件。我们可以使用 File.listFiles 或 Java 7 NIO2 来实现。我们有两个选项

a) 加载每个类并检查其超类

   Class cls = Class.forName();
   if (cls.getSuperClass() == A.class) {
     ...

b) 使用Javaassist框架http://www.javassist.org或类似工具直接处理类文件

DataInputStream ds = new DataInputStream(new BufferedInputStream(path));
ClassFile cf =  new ClassFile(ds);
String superClass = cf.getSuperClass();
if (superClass.equals("A")) {
    Class cls = Class.forName(cf.getName());
...

在这两种情况下,您都可以创建一个实例:

A a = (A) cls.newInstance();

假设所有子类都有无参数的构造函数。

0

虽然它并不完全合理...但你可以采用类似Spring组件扫描的方式:利用像PathMatchingResourcePatternResolver这样的工具,找出所有可能的类。遍历它们,如果是A的子类,则添加到列表中。


0
如何使用类路径扫描器自动检测目标类:
   List<Class<?>> classes = CPScanner.scanClasses(new ClassFilter().packageName("com.foo.*").superClass(A.class));

既然您已经有了目标类,那么您可以通过使用newInstance方法轻松初始化它们。

顺便提一下,要使用给定的代码片段,请使用以下Maven依赖项:

<dependency>
    <groupId>net.sf.corn</groupId>
    <artifactId>corn-cps</artifactId>
    <version>1.1.1</version>
</dependency>

祝福。


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