Search code examples
javaclassinheritancesubclassinstances

How to determine current class type?


I had 2 classes, B and C, who needed to keep track of their instances, so each of them has an ArrayList of their respective types to which instances were added in the constructor.

Since this seemed like common behaviour, I tried to find some sort of standard Java interface or class that expresses this behaviour, something like an InstanceManager interface. I didn't find any

I ended up trying to write an abstract class for it and I got stuck because I don't know how to specify the specific type of the subclasses. For example:

public abstract class C { public static ArrayList<C> list; }
public class A extends C { }
public class B extends C { }

In this case, B.list or A.list would be lists of C objects, but I would actually want them to be lists of B and A objects, respectively.
Is there any way I could make that happen easily?
I am thinking something along the lines of

public abstract class C { public static ArrayList<thisclass> list; }

but that would obviously not work.

I realise that I could probably use generics to handle this, but it feels redundant and I can't help wishing that there is a way to somehow infer the child class type in this situation.

Also, is there any standard Java interface for handling instance management or unique instance id generation?

EDIT:
I have come to understand that static variables are not inherited, and the subclasses' static variables actually refer to the same thing. As such, is there any way I can define instance managing behaviour without resorting to redundancy, having to write the same things in all subclasses?


Solution

  • It's already been pointed out that a Map is appropriate here; however there are a few other concerns:

    1. Multithreading.
    2. Garbage collection.

    #1 is fairly easy to factor in but worthwhile to point out.

    #2 is important because you want to think carefully about whether or not keeping a list of all instances should prevent them from being garbage collected. If not, you need to become familiar with the WeakReference class.

    Here is an example of the more complicated case.

    public final class InstanceManager<T> {
        private final Map<Class<?>, List<Reference<T>>> refMap = (
            new HashMap<Class<?>, List<Reference<T>>>()
        );
    
        public synchronized <U extends T> U manage(U instance) {
            Class<?> cls = instance.getClass();
            List<Reference<T>> refList = refMap.get(cls);
    
            if(refList == null) {
                refList = new LinkedList<Reference<T>>();
                refMap.put(cls, refList);
            }
    
            refList.add(new WeakReference<T>(instance));
            return instance;
        }
    
        public synchronized <U extends T> List<U> getAll(Class<U> cls) {
            List<U> returnList = new LinkedList<U>();
    
            List<Reference<T>> refList = refMap.get(cls);
            if(refList != null) {
    
                Iterator<Reference<T>> it = refList.iterator();
                while(it.hasNext()) {
                    T instance = it.next().get();
    
                    if(instance == null) {
                        it.remove();
                    } else {
                        returnList.add(cls.cast(instance));
                    }
                }
            }
    
            return returnList;
        }
    }
    

    As an example of usage,

    InstanceManager<Object> im = new InstanceManager<Object>();
    
    Object o1 = im.manage(new Object());
    Object o2 = im.manage(new Object());
    
    String s1 = im.manage("a");
    String s2 = im.manage(new String("b"));
    
    System.out.println("Object count: " + im.getAll(Object.class).size());
    System.out.println("String count: " + im.getAll(String.class).size());
    
    o2 = s1 = s2 = null;
    
    System.gc();
    Thread.sleep(1000);
    
    System.out.println("Object count: " + im.getAll(Object.class).size());
    System.out.println("String count: " + im.getAll(String.class).size());
    

    The output here is

    Object count: 2
    String count: 2
    Object count: 1
    String count: 1
    

    because this InstanceManager allows its referents to be garbage collected. If that's not the desired behavior (you aren't keeping references to the instances elsewhere) then of course you need to release them manually.

    But either way this allows you to do something like

    public abstract class C {
        private static final InstanceManager<C> manager = new InstanceManager<C>();
    
        protected C() {
            manager.manage(this);
        }
    }
    

    where all instances of C and its subclasses are automatically managed and categorized by actual type.