简体   繁体   中英

How to safely fix this type mismatch on abstract method with generics and Supplier lambda

We have a cache framework which we use to wire application-specific cache types (such as this authentication cache shown below) to various implementations (eg 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 (eg extends IAuthenticationCacheKey/IAuthenticationCacheEntry) ?

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

}

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM