Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Trouble understanding java lambdas and generics
Of course, everyone thinks to understand lambdas and generics. I was one of those until recently.
Look at my code:
package debug;
import java.util.function.Function;
public class UnderstandingGenerix {
public static void main(String[] args) {
{
Function<?, ?> code = (String O) -> {
System.out.println("todo" + O);
return Double.valueOf(1.5);
};
// Object x = code.apply("xxx"); // compile error
// Object x = code.apply((Object) "xxx"); // compile error
// Double x = code.apply("xxx"); // compile error
}
{
Function<Object, Object> code = (O) -> {
System.out.println("todo" + O);
return Double.valueOf(1.5);
};
Object x = code.apply("xxx");
x = code.apply((Object) "xxx");
Double xx = (Double) code.apply("xxx");
}
{
Function code = (O) -> {
System.out.println("todo" + O);
return 1.5;
};
Object x = code.apply("xxx");
x = code.apply((Object) "xxx");
Double xx = (Double) code.apply("xxx");
}
}
}
From the net, the question mark is a "wildcard" matching everything (except primitive types). So "?" and "Object" should be equivalent.
What part did I miss?
1 answer
Unbounded wildcards are not equivalent to Object.
When you give code a type, you are typing the variable, not the value. So Function<Object, Object> code means that it's valid to assign to the code variable any function value that can accept an Object and return an Object, and therefore it's valid to apply code to any Object (which is, as you note, any non-primitive value). To emphasize: it must hold a function that can accept any Object, and the compiler will enforce this—it can't hold a function that only accepts Doubles, for example, because such a function would complain if you gave it a String, even though all Strings are Objects.
On the other hand, Function<?, ?> code means that it's valid to assign to the code variable any function... at all! It could be a function that can accept an Object. But it could also be a function that can only accept certain subtypes of Object, like String in your case. The value would have a more restrictive run-time type than the variable in that case, but that's legal, just like it's legal to assign a String value to a variable of type Object. So in your first commented line:
Object x = code.apply("xxx");
The compiler doesn't know that the value stored in code is expecting a String. It might accept a different type like Double. The type of code only promises that code is a function that takes... something!
In the second commented line:
Object x = code.apply((Object) "xxx");
The same objection applies. The value stored in code may not be accepting of any old Object, so you can't cast to Object and make code happy.
When typed as Function<?, ?>, the variable code could hold any function expecting any non-primitive type, and that means that as far as the compiler is concerned, it's unsafe to apply it to any value other than null, the only value that can be used no matter what (non-primitive) type is expected.
NERD ZONE
Your intuition that ? should be equivalent to Object is not entirely unfounded. Generic type parameters can have a property called ‘covariance’, meaning (approximately) that the parameter only appears in outputs of the generic interface, never in inputs. When an unbounded wildcard is used in a covariant type parameter, it is equivalent to using Object, because Object is the supertype of all non-primitive types.
There is a dual property called ‘contravariance’, meaning that the parameter only appears in inputs of the generic interface, never in outputs. In Java, there is no type that is the subtype of all non-primitive types, but in the Scala language, for example, there is a type called Null which serves this purpose (its only inhabitant is null). If Java also had the Null type in its type system, it would be valid to say that when the unbounded wildcard is used in a contravariant type parameter, it is equivalent to using Null.
(Not every type parameter is one or the other; parameters that are neither are called ‘invariant’.)
In the Function<T, R> interface, T is contravariant and R is covariant. So if you wanted to replace the wildcards in Function<?, ?>, you would have to write Function<Null, Object>—if you had a Null type to use. And that's another way to see why a Function<?, ?> variable can only accept null.

0 comment threads