Reputation: 11
I have an interface Foo and an enum Bar which implements Foo. Then I'm using a simple builder pattern class Result. See code below.
public interface Foo {}
public enum Bar implements Foo { BAR }
public class Result<T> {
public Result(T result) {}
public static <T> Result<T> of(T result) {
return new Result<>(result);
}
public Result<T> set() {
return this;
}
}
When trying to build a Result of type Bar.BAR and assign it to the Result<Foo> variable this works fine:
Result<Foo> r1 = Result.of(Bar.BAR);
But the folowing gives a compile error:
Result<Foo> r2 = Result.of(Bar.BAR).set();
Incompatible types. Required: Result<Foo>, Found: Result<Bar>
Could anyone please explain why?
Upvotes: 0
Views: 448
Reputation: 3531
Could anyone please explain why?
Because generics are hard and the compiler has a hard time guessing what you mean.
It's gotten better in Java 8. Your first attempt would have failed to compile on Java 7 and below.
of
is a generic method. Type inference on Java looks at the surrounding context, the type of the assigned variable in this case, to determine a target type argument. In your example, it could infer Foo
, since Bar.BAR
is a subtype of Foo
.
In your second example, chaining methods makes matters more difficult. And set
is not a generic method. As such, it depends on the type of the expression it's invoked on. Because of the method chaining, the compiler cannot depend on the context of the invocation of of
. It takes the type as it sees it, ie. Bar
for Bar.BAR
. The invocation then becomes (to clarify types)
((Result<Bar>) Bar.BAR).set();
where set
then has a return type of Bar
.
And because of
a Result<Bar>
is not a Result<Foo>
so one expression is not assignable to the other.
Upvotes: 3
Reputation: 178293
The first example compiles because T
is inferred to be Foo
, not Bar
, as a result of Java 8's target type inference.
Result<Foo> r1 = Result.of(Bar.BAR);
It compiles because a Bar
is a Foo
, so it can be passed as an argument to the of
method.
The second example doesn't compile because T
must be inferred to be Bar
, not Foo
.
Result<Foo> r1 = Result.of(Bar.BAR).set();
The set()
method is called before the assignment operator assigns the result to r1
. Here, Result.of(Bar.BAR).set()
must be considered in isolation, without considering the type of r1
, so T
is inferred to be Bar
.
Also, Java's generics are invariant, so even if a Bar
is a Foo
, a Result<Bar>
is not a Result<Foo>
. But you can use a wildcard to work around this situation.
Result<? extends Foo> r1 = Result.of(Bar.BAR).set();
Your first example is of course another workaround.
Another workaround, as mentioned in comments by Paul Boddington, is to use an explicit type argument to the generic method of
. This explicitly sets T
to Foo
.
Result<Foo> r2 = Result.<Foo>of(Bar.BAR).set();
Also, this is not the Builder Pattern; your of
method is just a factory method. A Builder Pattern uses a separate class whose entire purpose is to construct instances of the target class.
public class Result<T> {
// Prevent anyone except the Builder class from instantiating
// this class by making the constructor private.
private Result(T result) {}
public static class Builder<T>
{
private T result;
public void setResult(T result)
{
this.result = result;
}
public Result<T> build()
{
return new Result(result);
}
}
public Result<T> set() {
return this;
}
}
Upvotes: 3