Chamly Idunil
Chamly Idunil

Reputation: 1872

Java generic wildcard usages

This is related to java generic wild card.

I have interface like this.

public interface Processer<P, X> {
    void process(P parent, X result);
}

An implementation like this.

public class FirstProcesser implements Processer<User, String> {
    @Override
    public void process(User parent, String result) {

    }
}

And I'm using processer as this.

public class Executor {
    private Processer<?, String> processer;

    private  int i;
    public void setProcesser(Processer<?, String> processer) {
        this.processer = processer;
    }

    private String generateString() {
        return "String " + i++;
    }

    public <P> void execute(P parent) {
        processer.process(parent, generateString());
    }

    public static void main(String[] args) {
        Executor executor = new Executor();
        executor.setProcesser(new FirstProcesser());
        User user = new User();
        executor.execute(user);
    }
}

But here

public <P> void execute(P parent) {
    processer.process(parent, generateString());
}

it gives compile error Error:(18, 27) java: incompatible types: P cannot be converted to capture#1 of ?

I need to understand why this give an error. also solution.

Upvotes: 0

Views: 110

Answers (2)

Adrian Shum
Adrian Shum

Reputation: 40036

Because processor can have first type argument of anything. You may have assigned a Process<Foo, String> to it, and of course compiler will complain as it can be something different from P in your execute().

You may want to make your Executor a generic class:

class Executor<T> {
    private Processer<T, String> processer;

    public void setProcesser(Processer<T, String> processer) {
        this.processer = processer;
    }

    public void execute(T parent) {
        processer.process(parent, generateString());
    }
}

and your main will look like:

    Executor<User> executor = new Executor<User>();
    executor.setProcesser(new FirstProcesser());
    User user = new User();
    executor.execute(user);

In response to comments:

There is no proper solution with proper use of Generics here, because what you are doing is contradicting: On one hand you say you do not care about first type argument of Processor (hence private Processor<?, String> processor), but on the other hand you DO really care about it (your execute). Compiler is simply doing its work right as it is absolutely legal for you to assign a Processor<Foo,String> to it.

If you don't really care about generics and is willing to suffer from poor design, then don't use generics.

Just keep Processor a raw type in Executor and suppress all unchecked warning:

i.e.

class Executor {
    private Processor processor;

    @SuppressWarnings("unchecked")
    public void setProcessor(Processor<?, String> processor) {
        this.processor = processor;
    }

    // your generic method does not do any meaningful check.
    // just pass an Object to it
    @SuppressWarnings("unchecked")
    public void execute(Object parent) {
        processor.process(parent, "");
    }
}

And if it is me, I will go one step further:

Provide an Executor that is properly designed (e.g. calling it TypedExecutor). All new code should use the new, properly designed TypedExecutor. Original Executor is kept for sake of backward compatibility, and delegate its work to TypedExecutor.

Hence look like:

class TypedExecutor<T> {
    private Processor<T, String> processor;

    public void setProcessor(Processor<T, String> processor) {
        this.processor = processor;
    }

    public void execute(T parent) {
        processor.process(parent, "");
    }
}

@SuppressWarnings("unchecked")
class Executor {
    private TypedExecutor executor = new TypedExecutor();

    public void setProcessor(Processor<?, String> processor) {
        this.executor.setProcessor(processor);
    }

    public void execute(Object parent) {
        this.executor.execute(parent);
    }
}

Upvotes: 0

Steven
Steven

Reputation: 2477

The wildcard basically means "I don't care which type is used here". In your case, you definitely do care though: the first type parameter of your processor must be the same as the P type in the execute method.

With the current code, you could call execute(1), which would try to call the FirstProcesser with an integer as argument, which obviously makes no sense, hence why the compiler forbids it.

The easiest solution would be to make your Executor class generic, instead of only the execute method:

public class Executor<P> {
    private Processer<P, String> processer;

    private int i;
    public void setProcesser(Processer<P, String> processer) {
        this.processer = processer;
    }

    private String generateString() {
        return "String " + i++;
    }

    public void execute(P parent) {
        processer.process(parent, generateString());
    }

    public static void main(String[] args) {
        Executor executor = new Executor<User>();
        executor.setProcesser(new FirstProcesser());
        User user = new User();
        executor.execute(user);
    }
}

Upvotes: 1

Related Questions