简体   繁体   English

如何自动将子类放入ArrayList?

[英]How can I automatically put subclasses in an ArrayList?

I have a superclass, and then several subclasses, like this: 我有一个超类,然后是几个子类,如下所示:

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

There are about 100 or so subclasses. 大约有100个左右的子类。 I also have a manager: 我也有一位经理:

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

How can I "magically" add an instance of all subclasses of A to list without manually creating an instance of every single subclass and adding it to the list? 如何“神奇地”将A的所有子类的实例添加到list而无需手动创建每个子类的实例并将其添加到列表中? Perhaps with using an Initialization Block? 也许使用初始化块?

EDIT 编辑

It's not important how I access list in Manager . 如何在Manager访问list并不重要。 I edited it to be static. 我把它编辑为静态。

(2nd attempt - my first attempt was based on a misunderstanding of the Question.) (第二次尝试 - 我的第一次尝试是基于对该问题的误解。)

I'm assuming that what you want to do is build a (static) list that: 我假设您要做的是构建一个(静态)列表:

  • contains exactly one instance of each of the subclasses, 只包含每个子类的一个实例,
  • is created and populated ahead of time, and 是提前创建和填充的,并且
  • doesn't involve code in each subclass creating / adding an instance of itself to the list. 不会在每个子类中创建/添加自身实例到列表中的代码。

Firstly, an instance initializer block won't do this. 首先,实例初始化程序块不会这样做。 An instance initializer is run when you create an instance ... and something has to new the class (ie each of the subclasses) for this to happen. 在创建实例时运行实例初始化程序...并且必须为该类(即每个子类)创建一个new的实例。

I think the only viable approach is to write some hairy reflective code that: 我认为唯一可行的方法是编写一些毛茸茸的反射代码:

  • iterates over all classes on the classpath, 迭代类路径上的所有类,
  • loads each one using Class.forName() , 使用Class.forName()加载每一个,
  • reflectively tests to see if the class is a subclass of A , 反思性地测试以查看该类是否是A的子类,
  • if it is, reflectively invokes the classes no-args constructor and adds the resulting instance to "the list". 如果是,反射性地调用类no-args构造函数并将结果实例添加到“列表”中。

This is (IMO) pretty hacky!! 这是(IMO)非常hacky !! And it is going to be expensive ... unless you can limit the "package space" that needs to be searched for these subclasses. 它会很昂贵......除非你能限制需要搜索这些子类的“包空间”。


Actually, this could be a problem that would be better solved using an enum ... especially if the subclasses don't have behavioural differences that require different method implementations. 实际上,这可能是一个使用enum更好解决的问题...特别是如果子类没有需要不同方法实现的行为差异。 (For instance your getValue() method could just return a private variable ... that you initialize using a constructor.) See @Paul Bellora's answer. (例如,你的getValue()方法只能返回一个私有变量......你使用构造函数初始化。)参见@Paul Bellora的答案。

(The thing that would prevent this from being applicable would be if there needed to be multiple instances of some of the subclasses. That's not possible with enums .) (阻止这种情况适用的事情是,如果需要有一些子类的多个实例。这对于enums是不可能的。)

Each class is going to represent a command. 每个类都代表一个命令。

Based on the description of your problem, it sounds like A could be an enum : 根据你的问题的描述,听起来像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();
}

Now, there is exactly one instance of each command, and you can easily iterate commands with A.values() . 现在,每个命令只有一个实例,您可以使用A.values()轻松迭代命令。

This is a bit of a hackish way to do it, but if all your subclasses are in one folder (the actual class files) you could iterate over the files in the folder and use the ClassLoader . 这有点像一种hackish方式,但如果你的所有子类都在一个文件夹(实际的类文件)中,你可以迭代文件夹中的文件并使用ClassLoader You code would look something along the lines of - 您的代码看起来像是 -

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

Look at the ClassLoader API for more info. 查看ClassLoader API以获取更多信息。 Also keep in mind that this is not very efficient, but if you are just doing this once you should be fine. 还要记住,这不是很有效,但如果你只是这样做,你应该没事。

Although it doesn't quite make sense... one way you can do is, do things similar to Spring's component scanning: make use of things like PathMatchingResourcePatternResolver and find out all possible classes. 虽然它没有多大意义......你可以做的一件事就是做类似Spring的组件扫描:使用像PathMatchingResourcePatternResolver这样的东西,找出所有可能的类。 Iterate through them and add to list if that is a subclass of A. 迭代它们并添加到列表中,如果它是A的子类。

Could be like this : 可能是这样的:

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

This way you never again have to deal with m.list.add(new A()); 这样你再也不用处理m.list.add(new A()); while subclassing. 子类化时。 But I don't know if this is what you are looking for... 但我不知道这是不是你要找的......


EDIT : 编辑:

It's not important how I access list in Manager. 如何在Manager中访问列表并不重要。 I edited it to be static. 我把它编辑为静态。

If you don't care about using singletons, here is a very basic implementation: 如果你不关心使用单例,这是一个非常基本的实现:

But read What is bad about singletons . 但阅读单身人士的坏处

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

Then: 然后:

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

public class B extends A {
}

But, again this is very not satisfying as a design... 但是,作为一种设计,这再次非常令人满意......

1) You need to find all available subclasses of class A. For that you need to scan all classes on the Java classpath. 1)您需要找到类A的所有可用子类。为此,您需要扫描Java类路径上的所有类。 To make things easier we can assume that all subclasses are in the same location as A.class. 为了简化操作,我们可以假设所有子类都与A.class位于同一位置。 A is supposed to be in a jar or in a folder. A应该在jar或文件夹中。 We can find out its actual location as 我们可以找到它的实际位置

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

2) Lets assume that it is a folder, eg file:/D:/workspace1/x/target/classes/. 2)让我们假设它是一个文件夹,例如file:/ D:/ workspace1 / x / target / classes /。 Now we should walk thru all .class files in this folder and subfolders. 现在我们应该遍历此文件夹和子文件夹中的所有.class文件。 We can use File.listFiles or Java 7 NIO2 for that. 我们可以使用File.listFiles或Java 7 NIO2。 We have 2 options 我们有2个选择

a) load each class and check its superclass a)加载每个类并检查其超类

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

b) use javaassist framework http://www.javassist.org or similar to work with class file directly 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());
...

option b is loads only the classes you actually need, option a is simpler but it loads all classes in the folder 选项b仅加载实际需要的类,选项a更简单,但它加载文件夹中的所有类

In both cases you create of an instance as 在这两种情况下,您都可以创建一个实例

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

assuming that all subclasses have no-arg constructor 假设所有子类都没有-arg构造函数

How about using a class path scanner to automatically detect your target classes : 如何使用类路径扫描程序自动检测目标类:

   List<Class<?>> classes = CPScanner.scanClasses(new ClassFilter().packageName("com.foo.*").superClass(A.class));

Since you've got the target classes, you can easily initialize them by using newInstance method. 由于您已获得目标类,因此可以使用newInstance方法轻松初始化它们。

By the way use the maven dependency below to use the given snippet: 顺便说一下,使用下面的maven依赖项来使用给定的代码段:

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

Cheers. 干杯。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM