Reputation: 27673
This seems like a really stupid question, but I can't understand why this use of Optional<T>
compiles:
import java.util.Optional;
public class Driver {
static void foo(Optional<String> x) { }
public static void main() {
foo(Optional.empty());
}
}
Optional::empty
is defined as returning me an Optional<T>
. Inside of Driver::main
, the expression Optional.empty()
seems like it would return an Optional<Object>
, as I am not parameterizing the use of Optional
so I would expect it to fall back to Object
as the type parameter. Then, I'm passing the Optional<Object>
to a function which expects an Optional<String>
, which is a downcast of the parameter, which should not be allowed. I would expect to see something like:
incompatible types: Optional<Object> cannot be converted to Optional<String>
However, the code compiles perfectly fine. Clearly, my thought process here is incorrect...but where?
Let me clarify what I'm looking for in an answer here...I know what type inference is. What I don't understand how it is happening here and what changed in the language from Java 7 to Java 8. For example, this bit of code compiles perfectly fine in Java 8, but fails in Java 7:
final class Opt<T> {
private final T value;
Opt(T x) {
value = x;
}
public static <T> Opt<T> empty() {
return new Opt<T>(null);
}
}
public class Driver {
static void bar(Opt<String> x) { }
public static void main() {
bar(Opt.empty());
}
}
How does this work in Java 8 when you have to deal with things like overloading? Is there a specific section of the Java language specification that talks about this sort of thing?
Upvotes: 6
Views: 12707
Reputation: 298469
Since that part of your question has not been addressed, I will try to summarize what have changed between Java 7 and Java 8.
Java 7 already had type inference, e.g. you could write
List<String> list=Collections.emptyList();
or
List<String> getList() {
return Collections.emptyList();
}
But this type inference was rather limited, e.g. what did not work (besides others) was:
List<String> list=Collections.unmodifiableList(Collections.emptyList());
or
List<String> getList() {
return condition? new ArrayList<>(): Collections.emptyList();
}
These two examples work now under Java 8. This new feature is called target type inference as it now uses the target’s type to find the appropriate type arguments. Besides making nested method invocations and conditionals work, like in the examples above, it also fixes the following example:
List<Number> numbers=Arrays.asList(1, 2, 3, 4);
As said, Java 7 had type inference too, but in this example it would infer List<Integer>
as the result type of the expression from the arguments passed to asList
and hence, generate an error.
In contrast, Java 8 has target type inference and will use the target type of the assignment to infer List<Number>
as the type of the expression and find out that the entire statement is valid as you can use Integer
objects where Number
is expected.
Note that Optional.empty()
and Collections.emptyList()
use the same kind of Generic construct. I used the latter in my examples as it already exists in Java 7.
Upvotes: 9
Reputation: 11969
It because the compiler does type inference.
When the compiler read:
static void foo(Optional<String> x) { }
public static void main() {
foo(Optional.empty());
}
Optional.<T>empty()
take a T
as a parameter.foo
expect an Optional<String>
T
is String
.It was introduced when generics where introduced, and perhaps the mechanism of it was improved with Java 8's javac
.
Note that this depends on the compiler: ECJ (Eclipse JDT Compiler) does not understands the same java source that Javac understands (they do, however, compile the "same" bytecode).
Upvotes: 5
Reputation: 336
It's because of the way way the empty() method is defined in Optional:
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
Note the method type parameter above:
public static<T> Optional<T> empty() {
^^^ method type parameter
This means that when empty() is called, it will bind T to the context of its caller, in your case String. For more information, see the section on Target Types in this page in the Java Tutorial:
http://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html
Upvotes: 13
Reputation: 533740
Java 8 added type interface which means it will work out the type of an expression based on how it is used.
A blatent example is
Object o = () -> System.out.println("Hello World");
doesn't compile because it doesn't know the type of the expression however,
Runnable r = () -> System.out.println("Hello World");
Object o = (Runnable) () -> System.out.println("Hello World");
compile fine. ie the type of the expression changes due the way it is used.
Upvotes: -1