skip to Main Content

We have a cache framework which we use to wire application-specific cache types (such as this authentication cache shown below) to various implementations (e.g. ehcache, redis, memcached etc). The framework is just an abstraction layer to allow the application to define and manipulate its cache similar to a map of key-value pairs, while specifying its app-specific key class and value class.

So for example we have:

public class AuthenticationCache extends BaseAuthenticationCacheImpl<AuthenticationCacheKey, AuthenticationCacheEntry> {...}

public class AuthenticationCacheKey implements IAuthenticationCacheKey {...}

public class AuthenticationCacheEntry implements IAuthenticationCacheEntry {...}

and elsewhere in the application, the app overrides an abstract method which provides a Supplier for its cache:

@Override
protected <K extends IAuthenticationCacheKey, E extends IAuthenticationCacheEntry> Supplier<BaseAuthenticationCacheImpl<K, E>> getAuthCacheSupplier() {
    Supplier<BaseAuthenticationCacheImpl<K, E>> supplier = () -> {
        return new AuthenticationCache();
    };
}

But this creates a compiler error:

Type mismatch: cannot convert from AuthenticationCache to
BaseAuthenticationCacheImpl

Generics are kicking my backside these days. Am I doing this completely wrong? Can I safely cast the supplier to (BaseAuthenticationCacheImpl<K,E>) since I know after type erasure it’ll be the same runtime and I know that the concrete key/value classes of AuthenticationCache satisfy K,E (e.g. extends IAuthenticationCacheKey/IAuthenticationCacheEntry) ?

2

Answers


  1. You could cheat the compiler by using something like this:

    protected <K extends IAuthenticationCacheKey, E extends IAuthenticationCacheEntry> Supplier<BaseAuthenticationCacheImpl<K, E>> getAuthCacheSupplier() {
        return () -> new AuthenticationCache().toBaseAuthenticationCacheImpl();
    }
    
    
    class AuthenticationCache extends BaseAuthenticationCacheImpl<AuthenticationCacheKey, AuthenticationCacheEntry> {
    
        public BaseAuthenticationCacheImpl toBaseAuthenticationCacheImpl(){
            return this;
        }
    
    }
    
    Login or Signup to reply.
  2. The cast is technically safe, as long as you can guarantee that K and E are always AuthenticationCacheKey and AuthenticationCacheEntry, but the compiler can’t give you that guarantee.

    Assuming these classes:

    class BaseAuthenticationCacheImpl<K extends IAuthenticationCacheKey, E extends IAuthenticationCacheEntry> {}
    
    interface IAuthenticationCacheKey {}
    
    interface IAuthenticationCacheEntry {}
    

    A safe solution is to change the return type to:

    @Override
    protected Supplier<BaseAuthenticationCacheImpl<?, ?>> getAuthCacheSupplier() {
        return AuthenticationCache::new;
    }
    

    As long as the BaseAuthenticationCacheImpl is just used to produce something that implements IAuthenticationCacheKey and something that implements IAuthenticationCacheEntry, but is not a consumer.

    Depending one how you actually use the type parameters of BaseAuthenticationCacheImpl you might even be able to just drop them completely and exchange them for IAuthenticationCacheKey and IAuthenticationCacheEntry directly. (Sometimes the best solution to a generics problem is to not use generics)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search