Running a Static Block Without Instantiating a Class - Java

585 Views Asked by At

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.

3

There are 3 best solutions below

2
On BEST ANSWER

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.

0
On

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

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