简体   繁体   中英

Java generics with wildcard not compiling

I made this simplified example of my real code, it is not compiling:

public class Example {

    public static void main(String[] args) {
        Callback<BalanceResponse> balanceRequestCaller = new Callback<BalanceResponse>() {
            @Override
            public void onResponse(BalanceResponse response) {}

            @Override
            public void onFailure(String error, int code) {}

            @Override
            public void onFailure(Throwable t) {}
        };

        BalanceRequest breq = new BalanceRequest();

        Interceptor interceptor = new Interceptor(balanceRequestCaller);

        breq.startRequest((Callback<BaseResponse>) interceptor); //compile-time error!!!
    }


    public static class Interceptor implements Callback<BaseResponse> {

        public Interceptor(Callback<? extends BaseResponse> originalCaller) {
            this.originalCaller = originalCaller;
        }

        private Callback<? extends BaseResponse> originalCaller;

        @Override
        public void onResponse(BaseResponse response) {
            //do some interception stuff

            if (response instanceof BalanceResponse) {
                ((Callback<BalanceResponse>) originalCaller).onResponse((BalanceResponse) response);
            } else if (response instanceof SubscriptionResponse) {
                ((Callback<SubscriptionResponse>) originalCaller).onResponse((SubscriptionResponse) response);
            }
        }

        @Override
        public void onFailure(String error, int code) {}

        @Override
        public void onFailure(Throwable t) {}
    }

    public interface Request<T extends BaseResponse> {
        void startRequest(Callback<T> callback);
    }

    public static class BalanceRequest implements Request<BalanceResponse> {
        @Override
        public void startRequest(Callback<BalanceResponse> callback) {}
    }

    public static class SubscriptionRequest implements Request<SubscriptionResponse> {
        @Override
        public void startRequest(Callback<SubscriptionResponse> callback) {}
    }

    public static class BaseResponse {
        public String status;
    }

    public static class BalanceResponse extends BaseResponse {

    }

    public static class SubscriptionResponse extends BaseResponse {

    }

    public interface Callback<T> {
        void onResponse(T response);
        void onFailure(String error, int code);
        void onFailure(Throwable t);
    }
}

What I am trying to do is make an Interceptor that would intercept the callback, do some stuff with the response and then pass it on to the original callback.

I want to make a generic Interceptor that could intercept any callback that is Callback<? extends BaseResponse> Callback<? extends BaseResponse> .

By the way the compile-time error I am getting from NetBeans is

incompatible types: Callback<BaseResponse> cannot be converted to Callback<BalanceResponse>

Give me suggestions how could I make this work?

You should define a type parameter on your class Interceptor to prevent your compilation error and to avoid explicit casts in the method onResponse which is generally a proof of design issue.

Your Interceptor class with a type parameter :

public static class Interceptor<T extends BaseResponse> implements Callback<T> {

    public Interceptor(Callback<T> originalCaller) {
        this.originalCaller = originalCaller;
    }

    private Callback<T> originalCaller;

    @Override
    public void onResponse(T response) {
        originalCaller.onResponse(response);
    }

    ...
}

You would then instantiate it and use it as next:

Interceptor<BalanceResponse> interceptor = new Interceptor<>(balanceRequestCaller);
breq.startRequest(interceptor);

Change the signature of startRequest() signature to

void startRequest(Callback<? super T> callback);

This allows it to accept a more generic callback, instead of requiring only the narrowest type. For more info, see What is PECS (Producer Extends Consumer Super)?

EDIT: @teppic's answer is the better solution, unless you must reuse a single Interceptor instance for different request types. If that is the case, you can still reduce the boilerplate to a single delegation:

@Override
public void onResponse(BaseResponse response) {
    //do some interception stuff
    ((Callback<BaseResponse>)originalCaller).onResponse(response);
}

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