Skip to content

Factory pattern using generics

I would like to build a class that caches classes of type CachedObject using Map.

public class CachedObject {
    protected Long id;
    public CachedObject(Long id) {
        this.id = id;
    }
}

Below is the factory class.

public class CachedObjectFactory<T extends CachedObject> {

private static Logger logger = LoggerFactory.getLogger(CachedObjectFactory.class);
private Map<Long, T> cacheMap = new HashMap<>();

public T get(Class<T> type, Long id) throws CachedObjectInstantiationException {
    T cachedObject = cacheMap.get(id);
    try {
        if(cachedObject == null) {
            cachedObject = type.getDeclaredConstructor().newInstance(id);
        }
    } catch(Exception e) {
        throw new CachedObjectInstantiationException(e.getMessage());
    }
    return cachedObject;
}

}

I have a class that extends CacheableObject as below:

@Component
class X extends CachedObject {
   
    public X(Long id) {
           super(id);
       }
       ....

}

When I try to create an instance of class X that extends CachedObject using the get method in the factory as below: (please note that cachedObjectFactory is autowired using Spring)

@Component
class Y extends CachedObject {

       CachedObjectFactory<CachedObject> cachedObjectFactory;
       Y(Long id, CachedObjectFactory cachedObjectFactory) {
          super(id);
          this.cachedObjectFactory = cachedObjectFactory;
       } 
    
       public void someMethod() {
          X x = cachedFactory.get(X.class, id);
       }

}

I get the compile time error “The method get(Class, Long) in the type CachedObjectFactory is not applicable for the arguments (Class, Long)”. How should I instantiate an object X using the factory method?

Answer

Declaring a field as CachedObjectFactory<CachedObject> doesn’t really mean anything — the parameter already has CachedObject as an upper bound.

You can get your code to compile by changing you factory to look like this:

public class CachedObjectFactory {

        private Map<Long, Object> cacheMap = new HashMap<>();

        public <T extends CachedObject> T get(Class<T> type, Long id) {
            T cachedObject = (T)cacheMap.get(id);
            try {
                if(cachedObject == null) {
                    cachedObject = type.getDeclaredConstructor().newInstance(id);
                }
            } catch(Exception e) {
                throw new RuntimeException(e.getMessage());
            }
            return cachedObject;
        }
    }

As you are using your factory for many classes, making it generic doesn’t really make sense.

Of course if two instances of different subclasses of CachedObject have the same id you’ll get a runtime ClassCastException.