0

I have several child classes extending an abstract parent class. I want the parent class to have a static ArrayList holding instances of each child. If possible, I would like to be able to add more child classes without having to change the parent class's code.

One solution I came up with is to give each of the child classes a static block that will add an instance of itself to the ArrayList. The only problem with this is that I then need to make some call to each child to get it to load and run the block.

abstract public class Parent {
    protected static ArrayList<Parent> children = new ArrayList<Parent>();
}

public class ChildA extends Parent {
    static {
        children.add(new ChildA());
    }    
}

public class ChildB extends Parent {
    static {
        children.add(new ChildB());
    }    
}

Is there a way around this? Can I load a class without making a call to it? Would there be any better ways of doing this?

Edit: This is actually for a tile based game. I have an abstract "Tile" class and an arbitrary number of tile types extending it. I would like to be able to easily add new tiles to the game without having to change the code all over the place. If you have a better idea for how to do this, I am open to suggestion.

Camander
  • 147
  • 1
  • 10
  • possible duplicate of [How do you find all subclasses of a given class in Java?](http://stackoverflow.com/questions/492184/how-do-you-find-all-subclasses-of-a-given-class-in-java) – A4L Nov 21 '14 at 23:20

3 Answers3

0

You can do it outside the class hierarchy. Have a utility function that uses reflection to find all descendants of Parent and then adds them to your list.

zmbq
  • 38,013
  • 14
  • 101
  • 171
  • 1
    Thanks for the quick response. Looks like I finally have an excuse to learn about reflection then :) The utility function won't need to know the names of the child classes beforehand will it? – Camander Nov 21 '14 at 23:21
  • @Camander, No you wont need to know names, while scanning a package you get class objects, use the method [Class#isAssignableFrom(java.lang.Class)](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#isAssignableFrom-java.lang.Class-) to determine whether class is a subtype the the calling class: `Parent.class.AssignableFrom(protetialChild)`, if this call retuns `true` then you have found a child, create an instance and add it to the list. – A4L Nov 22 '14 at 15:24
0

Your motivation for wanting to do this is unclear, but there are solutions that don't necessarily involve reflection.

Shift the responsibility for creating the child classes into a factory class. In that factory class, insert the children classes into your list as well.

Here's a snippet that could do just that. Remember: because your list is bound to Parent, you will have to cast to the correct child class type to actually use the classes later.

public final class ChildFactory {

    private static ChildFactory instance = new ChildFactory();

    private ChildFactory() {

    }

    public <C extends Parent> C generateChild(Class<C> childClass) {
        try {
            final C child = childClass.newInstance();
            Parent.children.add(child);
            return child;
        } catch(InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static ChildFactory getInstance() {
        return instance;
    }
}
Makoto
  • 104,088
  • 27
  • 192
  • 230
0

I think threre might be an easier way to do it but this class may be usefull:

package classFinder;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassFinder {
    protected static Class<?> getClass(String prefix, String classPath)
            throws ClassNotFoundException {
        if (!classPath.endsWith(".class") || !classPath.startsWith(prefix)) {
            return null;
        }
        return Class.forName(classPath.substring(prefix.length(),
                classPath.length() - ".class".length()).replace('/', '.'));
    }

    protected static Class<?> getClass(File rootFile, File classFile)
            throws ClassNotFoundException {
        return getClass(rootFile.getPath() + '/', classFile.getPath());
    }

    @SuppressWarnings("unchecked")
    protected static <T> Set<Class<T>> searchAllSubclassesInDirectory(
            File rootFile, File searchFile, Class<?> cls,
            boolean abstractClasses) throws ClassFinderException {
        Set<Class<T>> result = new HashSet<Class<T>>();
        if (searchFile.isDirectory()) {
            for (File file : searchFile.listFiles()) {
                result.addAll(ClassFinder.<T> searchAllSubclasses(
                        rootFile.getPath(), file.getPath(), cls,
                        abstractClasses));
            }
            return result;
        }

        String fileName = searchFile.getName();
        if (!fileName.endsWith(".class")) {
            return result;
        }
        try {
            Class<?> entry = getClass(rootFile, searchFile);
            if (entry != null
                    && (abstractClasses || !Modifier.isAbstract(entry
                            .getModifiers()))) {
                Class<?> superClass = entry;
                while (!((superClass = superClass.getSuperclass()) == null)) {
                    if (superClass.equals(cls)) {
                        result.add((Class<T>) entry);
                        return result;
                    }
                }
            }
            return result;
        } catch (ClassNotFoundException e) {
            // e.printStackTrace(); //DEBUG only
            return result;
        }
    }

    @SuppressWarnings("unchecked")
    protected static <T> Set<Class<T>> searchAllSubclassesInJar(File jar,
            Class<?> cls, boolean abstractClasses) {
        Set<Class<T>> result = new HashSet<Class<T>>();
        try {
            JarFile jarFile = new JarFile(jar);
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry file = entries.nextElement();
                if (file.isDirectory()) {
                    continue;
                }
                Class<?> entry = getClass("", file.getName());
                if (entry != null
                        && (abstractClasses || !Modifier.isAbstract(entry
                                .getModifiers()))) {
                    Class<?> superClass = entry;
                    while (!((superClass = superClass.getSuperclass()) == null)) {
                        if (superClass.equals(cls)) {
                            result.add((Class<T>) entry);
                        }
                    }
                }
            }
            jarFile.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }

    protected static <T> Set<Class<T>> searchAllSubclasses(String rootPath,
            String searchPath, Class<?> cls, boolean abstractClasses)
            throws ClassFinderException {
        if (searchPath.endsWith(".jar")) {
            return searchAllSubclassesInJar(new File(searchPath), cls,
                    abstractClasses);
            // return new HashSet<Class<T>>();
        } else {
            return searchAllSubclassesInDirectory(new File(rootPath), new File(
                    searchPath), cls, abstractClasses);
        }
    }

    // TODO create public method to search inside a not root directory/package

    public static <T> Set<Class<T>> searchAllSubclasses(Class<?> cls,
            boolean abstractClasses) throws ClassFinderException {
        Set<Class<T>> result = new HashSet<Class<T>>();
        String classpath = System.getProperty("java.class.path");
        for (String path : classpath
                .split(System.getProperty("path.separator"))) {
            result.addAll(ClassFinder.<T> searchAllSubclasses(path, path, cls,
                    abstractClasses));
        }

        return result;
        // return ClassFinder.<T> searchAllSubclasses(ROOT_URL, cls,
        // abstractClasses, "");
    }
}

If you're working with a standard java desktop application it may work. This class searches for implementations of a given superclass on a your program directory tree. Works for jar files too.

You can then initialize your static field:

abstract public class Parent {
    protected static Set<Parent> children = ClassFinder
                .<Parent> searchAllSubclasses(Parent.class, true);
}
ylima
  • 410
  • 5
  • 17