4

For example, i have SimpleCallback class

public class SimpleCallback<T> implements Callback<T> {

    private SuccessListener<T> successListener;
    private ErrorListener errorListener;

    protected SimpleCallback() {

    }

    public static <C> SimpleCallback<C> success(SuccessListener<C> listener) {
        SimpleCallback<C> callback = new SimpleCallback<>();
        callback.successListener = listener;
        return callback;
    }

    public SimpleCallback<T> error(ErrorListener errorListener) {
        this.errorListener = errorListener;
        return this;
    }

    @Override
    public void onComplete(T result) {
        notifySuccess(result);
    }

    @Override
    public void onError() {
        notifyError();
    }

    public interface SuccessListener<T> {

        void onSuccess(T result);
    }

    public interface ErrorListener {

        void onError();
    }

}

Now i want to use this callback for get cats asynchronous:

SimpleCallback<List<Cat>> callback = SimpleCallback
            .success(cats -> cats.forEach(Cat::meow));

It's ok now, but when i want add error listener my cats become raw objects

SimpleCallback<List<Cat>> callback = SimpleCallback
            .success(cats -> cats.forEach(Cat::meow)) <-- Here cats become objects
            .error(() -> System.out.println("Cats error"));

One of solution use explicit generic type:

SimpleCallback<List<Cat>> callback = SimpleCallback.<List<Cat>>
            .success(cats -> cats.forEach(Cat::meow))
            .error(() -> System.out.println("Cats error"));

But it looks little bit ugly. So is any way to create callback without explicit generic type?

UPD: I think @Jesper offered good solution

Another solution is to provide the type of the argument of the lambda expression explicitly:

.success((List<Cat> cats) -> cats.forEach(Cat::meow))
hluhovskyi
  • 9,556
  • 5
  • 30
  • 42
  • 2
    Another solution is to provide the type of the argument of the lambda expression explicitly: `.success((List cats) -> cats.forEach(Cat::meow))` – Jesper Jul 08 '16 at 11:55
  • Please clarify what the problem is. If it's only the fact that it looks unfamiliar, then I would say: It is much better to get used to it than to use raw types with all the drawbacks that come with it... – martinhh Jul 08 '16 at 12:09
  • @martinhh The question is about type inference: why, when you add `.error(...)` after the call to `.success(...)`, type inference doesn't work anymore (it's now necessary to explicitly specify the type). – Jesper Jul 08 '16 at 12:20
  • For an even simpler example, the class `class Box { T t; public Box set(T t) { this.t = t; return this; } public T get() { return t;}}` Shows the same problem. `String s = new Box().set("Hello").get();` and `Box b = new Box<>(); String s = b.set("Hello").get();` work, but `String s = new Box<>().set("Hello").get()` doesn't. I think it has to do with how `<>` is resolved to a type. Maybe that gives a direction to look for the answer? – Mshnik Jul 08 '16 at 17:10

2 Answers2

0

Obviously the strategy for erasing generic type variables changes with context. Sure there must be rules how this works but I don't know them, sorry.

From a practical point of view the solution suggested by Jesper, i.e. giving type information in the success method seems perfectly acceptable to me.

.success((List<Cat> cats) -> cats.forEach(Cat::meow))

Otherwise it is possible to pass both Listeners in a single method:

public static <C> SimpleCallback<C> create(
    SuccessListener<C> successListener, ErrorListener errorListener) {
    SimpleCallback<C> callback = new SimpleCallback<>();
    callback.successListener = successListener;
    callback.errorListener = errorListener;

    return callback;
}

and create the Callback in a single step:

Callback<List<Cat>> callback = SimpleCallback
        .create(
            cats -> cats.forEach(Cat::meow),
            () -> System.out.println("Cats error")
         );
martinhh
  • 336
  • 2
  • 10
0

In short, no. From this answer about how the diamond operator functions in Java 1.8, there are type inference issues with any chained method call. Your example is similar because

SimpleCallback<List<Cat>> callback = SimpleCallback
        .success(cats -> cats.forEach(Cat::meow));

is inferring the type C from the argument given to success.

Thus your options are:

  • Explicitly declare the generic type
  • Get a named reference (with an explicitly declared generic type) before using chained calls

For your example, I'd prefer to work with the following paradigm:

public class SimpleCallback implements Callback {

private SuccessListener<T> successListener;
private ErrorListener errorListener;

protected SimpleCallback() {

}

public static <C> SimpleCallback<C> create() {
    return new SimpleCallback<>();
}

public SimpleCallback<T> withSuccessListener(SuccessListener<T> listener) {
    this.successListener = listener;
    return this;
}

public SimpleCallback<T> withErrorListener(ErrorListener errorListener) {
    this.errorListener = errorListener;
    return this;
}

@Override
public void onComplete(T result) {
    notifySuccess(result);
}

@Override
public void onError() {
    notifyError();
}

public interface SuccessListener<T> {

    void onSuccess(T result);
}

public interface ErrorListener {

    void onError();
}

}

And then create in one line, adding and chaining on the next.

SimpleCallback<List<Cat>> callback = SimpleCallback.create();
callback = callback.success(cats -> cats.forEach(Cat::meow))
                   .error(() -> System.out.println("Cats error"));
Community
  • 1
  • 1
Mshnik
  • 7,032
  • 1
  • 25
  • 38