Given the generic method:
<T> List<T> getGenericList(int i) {...}
the following code compiles without any warning:
public List<String> getStringList(boolean b){
if(b)
return getGenericList(0);
else
return getGenericList(1);
}
but this one generates 'Type mismatch' compilation error:
public List<String> getStringList(boolean b) {
return (b) ? getGenericList(0) : getGenericList(1);
}
Why?
This is NOT a generics problem, but a consequence of the way the compiler has to infer the type of the ternary expression.
It happens the same with this equivalent code. This code works:
public byte function(boolean b){
if(b)
return 1;
else
return 2;
}
While this doesn't:
public byte function(boolean b) {
return (b) ? 1 : 2;
}
The reason is that when the compiler tries infer the type of this expression
return (b) ? 1 : 2;
It first has to obtain the type of each one of the operands, and check if they are compatible (reference) to evaluate wether the ternary expression is valid or not. If the type of the "return" were propagated to automatically cast or promote each one of the operands, it could lead to resolve the type of a ternary expression differently depending on the context of this expression.
Given that the type of the "return" cannot be propagated to the operands, then in the menctioned case:
return (b) ? getGenericList(0) : getGenericList(1);
the binding of the generic type cannot be done, so the type of each one of the operands is resolved to List<Object>. Then the compiler concludes that the type of the whole expression is List<Object>, which cannot be automatically casted to List<Integer> (because they are not compatible types).
Whereas this other one
return getGenericList(0);
It applyes the type of the "return" to bind the generic type T, so the compiler concludes that the expression has a List<String> type, that can be returned safely.
this is because of an edge case in generic type deduction
in the explicit returns the return type of each getGenericList can be trivially set to List (outward info propagates inwards)
but in the conditional it goes the other way the type of it is the more general of the two possibilities (inward info propagates outwards)
the compiler could deduct the info implicitly here but it's not buildin yet file a bug report if you really need it
When the trinary operator is evaluated, it's result is not bound to any type. It's as if you just call:
getGenericList(0);
Try compiling the above, the compilation will fail.
In return statement, the result is bound to your function's return type and is evaluated.
Edit:
I've mistaken. The above statement compiles, but the result type is evaluated as (List < Object > ). Try compiling:
List<String> l = (List<String>)getGenericList(0);
This one will fail.
This is because javac needs to infer T, but T does not appear in argument types.
static<T> T foo(){ .. }
foo(); // T=?
Only in 2 cases, javac can infer T from the context
String s = foo(); // #1: assignment
String bar(){
return foo(); // #2: return
In other cases, javac won't, and T is simply inferred as Object
But this type of methods are dangerous anyway. How could your method know it's time to return List<String>, not a list of something else? There's no info about T available to you.
It looks likes its just inefficient compilation.
As Maurice said, generally, the compiler determines the type of T by function arguments. In this case there aren't any.
However, because getStringList() returns List<String> and it calls return getGenericList(), the compiler is smart enough to forward the return type of getStringList to getGenericList and determine the type in that manner.
My guess is that in the ternary operator, it's doesn't bind in reverse, but rather finds the common denominator in each substatement and assigns that as the output of the ternary statement, and the compiler isn't smart enough to pass the expected output of the ternary statement into its substatements.
Note, you can directly pass the type parameter into the function call, and it works fine, ie:
return b ? this.<String> getGenericList(0) : this.<String> getGenericList(1);
compiles properly.
Related
I'm quite new in Java, although I have much experience in C++ and other languages. So templates/generics are not something I don't know.
There's something that bothers me though, it is this <?> that I was told I should use everytime I use a generic instance of something when I don't know in advance of which specific type it will be:
Like:
List< MyGeneric > foo; // bad
List< MyGeneric<?> > bar; // good
IntelliJ doesn't barf on me when using the first expression, and I don't understand why it should. My coworkers have expressed that the 2nd expression was much better, but couldn't tell me exactly why.
I mean, what exactly is the difference between these two, apart from the second being explicit about the fact that it is a generic that we manipulate ?
The compiler certainly knows that it is a generic at compile time, so my guess is that the second expression is only better because it tells the programmer that he is manipulating a generic.
Am I right?
Edit: for clarification, I ovbiously use the most restrictive type, like List<MyGeneric<Double>>, whenever I know in advance what I am going to store in there. My question is for when I store unknown types of generics.
Every time? It's not applicable always, and it doesn't always make sense.
Let's describe what that actually is: <?> is an unbound wildcard, which immediately implies two things:
MyGeneric is a generic class, but
You do not know what type it's holding (and it likely doesn't matter).
It is preferable to the first expression in that the first expression always guarantees that you'll be working with a raw type, and you really don't want to use raw types. However, it is a gross overgeneralization to assume that using an unbound wildcard every time would be ideal.
If you actually know or care the type, or know or care about its bounds, use that instead.
Let's give an example of why it's bad to use the first. Assuming MyGeneric is defined like this:
class MyGeneric<T> {
private final T instance;
MyGeneric(T instance) { this.instance = instance; }
T get() { return instance; }
}
The following code would compile and run, but fail at runtime with a ClassCastException:
List<MyGeneric> list = new ArrayList<>();
list.add(new MyGeneric<>("Hello"));
for (MyGeneric instance : list) {
Integer value = (Integer) instance.get(); // Compiles, but fails at runtime.
}
This compiles because you're using raw types: the compiler doesn't know that instance.get() can't return an Integer; it would merely warn you that it might be unsafe.
On the other hand, the following code would not even compile:
List<MyGeneric<String>> list = new ArrayList<>();
list.add(new MyGeneric<>("Hello"));
for (MyGeneric<String> instance : list) {
Integer value = (Integer) instance.get(); // Won't compile, incompatible types.
}
The difference is that a raw type ignores the fact that the class is a generic, while the wildcard <?> specifies that the class is a generic but the type argument is unknown.
Raw means that you lose all compiler type-checking. Wildcard keeps type-checking intact.
Example:
public class MyGeneric<T> {
private T val;
public T get() {
return this.val;
}
public void set(T val) {
this.val = val;
}
}
MyGeneric a = new MyGeneric<Integer>();
a.set("Foo"); // accepted
Setting the value for a to a String when it was declared to be an Integer is accepted by the compiler, because a was defined raw, which means that the compiler is ignoring the fact that the class is a generic. When val is later used as an Integer, the program will crash. It's a bomb waiting to go off.
MyGeneric<?> b = new MyGeneric<Integer>();
b.set("Bar"); // compile error
Trying to set the value for b will not compile:
The method set(capture#1-of ?) in the type MyGeneric<capture#1-of ?> is not applicable for the arguments (String)
Here the compiler knows that the class is a generic and will not allow setting the value to anything (even an Integer), because it doesn't know what type would be allowed (wildcard = unknown, remember?). The compiler safeguards here, as it should.
List<?> means a list typed to an unknown type. This could be a List<A>, a List<B>, a List<String> etc.
Since the you do not know what type the List is typed to, you can only read from the collection, and you can only treat the objects read as being Object instances. Here is an example:
public void processElements(List<?> elements) {
for(Object o : elements){
System.out.println(o);
}
}
The processElements() method can now be called with any generic List as parameter. For instance a List<A>, a List<B>, List<C>, a List<String> etc. Here is a valid example:
List<A> listA = new ArrayList<A>();
processElements(listA);
Following tutorials will further help you to understand it:
https://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html
http://tutorials.jenkov.com/java-generics/wildcards.html
Let's look at the simple Java code in the following snippet:
public class Main {
private int temp() {
return true ? null : 0;
// No compiler error - the compiler allows a return value of null
// in a method signature that returns an int.
}
private int same() {
if (true) {
return null;
// The same is not possible with if,
// and causes a compile-time error - incompatible types.
} else {
return 0;
}
}
public static void main(String[] args) {
Main m = new Main();
System.out.println(m.temp());
System.out.println(m.same());
}
}
In this simplest of Java code, the temp() method issues no compiler error even though the return type of the function is int, and we are trying to return the value null (through the statement return true ? null : 0;). When compiled, this obviously causes the run time exception NullPointerException.
However, it appears that the same thing is wrong if we represent the ternary operator with an if statement (as in the same() method), which does issue a compile-time error! Why?
The compiler interprets null as a null reference to an Integer, applies the autoboxing/unboxing rules for the conditional operator (as described in the Java Language Specification, 15.25), and moves happily on. This will generate a NullPointerException at run time, which you can confirm by trying it.
I think, the Java compiler interprets true ? null : 0 as an Integer expression, which can be implicitly converted to int, possibly giving NullPointerException.
For the second case, the expression null is of the special null type see, so the code return null makes type mismatch.
Actually, its all explained in the Java Language Specification.
The type of a conditional expression is determined as follows:
If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.
Therefore the "null" in your (true ? null : 0) gets an int type and then is autoboxed to Integer.
Try something like this to verify this (true ? null : null) and you will get the compiler error.
In the case of the if statement, the null reference is not treated as an Integer reference because it is not participating in an expression that forces it to be interpreted as such. Therefore the error can be readily caught at compile-time because it is more clearly a type error.
As for the conditional operator, the Java Language Specification §15.25 “Conditional Operator ? :” answers this nicely in the rules for how type conversion is applied:
If the second and third operands have the same type (which may be the null
type), then that is the type of the conditional expression.
Does not apply because null is not int.
If one of the second and third operands is of type boolean and the type of the
other is of type Boolean, then the type of the conditional expression is boolean.
Does not apply because neither null nor int is boolean or Boolean.
If one of the second and third operands is of the null type and the type of the
other is a reference type, then the type of the conditional expression is that
reference type.
Does not apply because null is of the null type, but int is not a reference type.
Otherwise, if the second and third operands have types that are convertible
(§5.1.8) to numeric types, then there are several cases: […]
Applies: null is treated as convertible to a numeric type, and is defined in §5.1.8 “Unboxing Conversion” to throw a NullPointerException.
The first thing to keep in mind is that Java ternary operators have a "type", and that this is what the compiler will determine and consider no matter what the actual/real types of the second or third parameter are. Depending on several factors the ternary operator type is determined in different ways as illustrated in the Java Language Specification 15.26
In the question above we should consider the last case:
Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing conversion to S2. The type of the conditional expression is the result of applying capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7).
This is by far the most complex case once you take a look at applying capture conversion (§5.1.10) and most of all at lub(T1, T2).
In plain English and after an extreme simplification we can describe the process as calculating the "Least Common Superclass" (yes, think of the LCM) of the second and third parameters. This will give us the ternary operator "type". Again, what I just said is an extreme simplification (consider classes that implement multiple common interfaces).
For example, if you try the following:
long millis = System.currentTimeMillis();
return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis));
You'll notice that resulting type of the conditional expression is java.util.Date since it's the "Least Common Superclass" for the Timestamp/Time pair.
Since null can be autoboxed to anything, the "Least Common Superclass" is the Integer class and this will be the return type of the conditional expression (ternary operator) above. The return value will then be a null pointer of type Integer and that is what will be returned by the ternary operator.
At runtime, when the Java Virtual Machine unboxes the Integer a NullPointerException is thrown. This happens because the JVM attempts to invoke the function null.intValue(), where null is the result of autoboxing.
In my opinion (and since my opinion is not in the Java Language Specification many people will find it wrong anyway) the compiler does a poor job in evaluating the expression in your question. Given that you wrote true ? param1 : param2 the compiler should determine right away that the first parameter -null- will be returned and it should generate a compiler error. This is somewhat similar to when you write while(true){} etc... and the compiler complains about the code underneath the loop and flags it with Unreachable Statements.
Your second case is pretty straightforward and this answer is already too long... ;)
CORRECTION:
After another analysis I believe that I was wrong to say that a null value can be boxed/autoboxed to anything. Talking about the class Integer, explicit boxing consists in invoking the new Integer(...) constructor or maybe the Integer.valueOf(int i); (I found this version somewhere). The former would throw a NumberFormatException (and this does not happen) while the second would just not make sense since an int cannot be null...
Actually, in the first case the expression can be evaluated, since the compiler knows, that it must be evaluated as an Integer, however in the second case the type of the return value (null) can not be determined, so it can not be compiled. If you cast it to Integer, the code will compile.
private int temp() {
if (true) {
Integer x = null;
return x;// since that is fine because of unboxing then the returned value could be null
//in other words I can say x could be null or new Integer(intValue) or a intValue
}
return (true ? null : 0); //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
//value can be Integer
// then null is accepted to be a variable (-refrence variable-) of Integer
}
How about this:
public class ConditionalExpressionType {
public static void main(String[] args) {
String s = "";
s += (true ? 1 : "") instanceof Integer;
System.out.println(s);
String t = "";
t += (!true ? 1 : "") instanceof String;
System.out.println(t);
}
}
The output is true, true.
Eclipse color codes the 1 in the conditional expression as autoboxed.
My guess is the compiler is seeing the return type of the expression as Object.
I am trying to do what seems to be a relatively basic thing in the new JDK 8 land of functional programming, but I can't get it to work. I have this working code:
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;
public class so1 {
public static void main() {
List<Number> l = new ArrayList<>(Arrays.asList(1, 2, 3));
List<Callable<Object>> checks = l.stream().
map(n -> (Callable<Object>) () -> {
System.out.println(n);
return null;
}).
collect(Collectors.toList());
}
}
It takes a list of numbers and produces a list of functions that can print them out. However, the explicit cast to Callable seems redundant. It seems to me and to IntelliJ. And we both agree that this should also work:
List<Callable<Object>> checks = l.stream().
map(n -> () -> {
System.out.println(n);
return null;
}).
collect(Collectors.toList());
However I get an error:
so1.java:10: error: incompatible types: cannot infer type-variable(s) R
List<Callable<Object>> checks = l.stream().map(n -> () -> {System.out.println(n); return null;}).collect(Collectors.toList());
^
(argument mismatch; bad return type in lambda expression
Object is not a functional interface)
where R,T are type-variables:
R extends Object declared in method <R>map(Function<? super T,? extends R>)
T extends Object declared in interface Stream
1 error
You hit a limitation of Java 8’s target typing which applies to the receiver of a method invocation. While target typing works (most of the times) for parameter types it does not work for the object or expression on which you invoke the method.
Here, l.stream().
map(n -> () -> {
System.out.println(n);
return null;
}) is the receiver of the collect(Collectors.toList()) method invocation, so the target type List<Callable<Object>> is not considered for it.
It’s easy to prove that nested lambda expressions work if the target type is know, e.g.
static <T> Function<T,Callable<Object>> toCallable() {
return n -> () -> {
System.out.println(n);
return null;
};
}
works without problems and you can use it to solve your original problem as
List<Callable<Object>> checks = l.stream()
.map(toCallable()).collect(Collectors.toList());
You can also solve the problem by introducing a helper method which changes the role of the first expression from method receiver to a parameter
// turns the Stream s from receiver to a parameter
static <T, R, A> R collect(Stream<T> s, Collector<? super T, A, R> collector) {
return s.collect(collector);
}
and rewrite the original expression as
List<Callable<Object>> checks = collect(l.stream().map(
n -> () -> {
System.out.println(n);
return null;
}), Collectors.toList());
This does not reduce the complexity of the code but can be compiled without any problems. For me, it’s a déjà vu. When Java 5 and Generics came out, programmers had to repeat the type parameters on new expressions while simply wrapping the expression into a generic method proved that inferring the type is no problem. It took until Java 7 before programmers were allowed to omit these unnecessary repetition of the type arguments (using the “diamond operator”). Now we have a similar situation, wrapping an invocation expression into another method, turning the receiver into a parameter, proves that this limitation is unnecessary. So maybe we get rid of this limitation in Java 10…
I ran into this same issue and was able to solve it by explicitly specifying the generic type-parameter to map like so:
List<Callable<Object>> checks = l.stream().
<Callable<Object>>map(n -> () -> {
System.out.println(n);
return null;
}).
collect(Collectors.toList());
I haven't yet delved into the exact rules for how type inference works with lambdas. From a general language design standpoint, though, it isn't always possible to write language rules that allow the compiler to figure out everything we think it should. I've been a compiler maintainer for an Ada-language compiler and I'm familiar with many of the language design issues there. Ada uses type inference in a lot of cases (where the type of a construct can't be determined without looking at the entire expression containing the construct, which I think is the case with this Java lambda expression also). There are some language rules that cause compilers to reject some expressions as ambiguous when, in theory, there really is only one possible interpretation. One reason, if I recall correctly, is that somebody found a case where a rule that would have let the compiler figure out the correct interpretation would have required the compiler to make 17 passes through an expression in order to interpret it correctly.
So while we may think a compiler "should" be able to figure something out in a particular case, it may just plain be unfeasible.
Firstly you have to know how compiler get a lambda expression's type. It is achieved by target typing, which means the type of the variable that you assign the lambda expression to. In your case, if you
Function<Integer, Callable<Object>> fn = n -> () -> { System.out.println(n); return null; }
This is how the lambda get its type: Function<Integer, Callable<Object>>
Then you have to look at the type inference in generic type:
The return type of map is <R> Stream<R>, R would be determined by type of the parameter that you passed into the function. If you map(x->"some string"), then the result is Stream<String>. Now this is the problem, R's type the lambda's type. But lambda needs a target type, which is the variable R.
The working code works because it explicitly cast the lambda to a type.
I have a generic method T foo(T) which, depending on the actual type of the argument, performs different operations. Following is an example that illustrates the same problem. (See below for a description of what I'm actually doing in my code.)
<T extends Number> T foo(T a) {
if (a instanceof Integer) {
return ((Integer) a) + 1;
} else if (a instanceof Double) {
return ((Double) a) + 1;
} else {
throw new IllegalArgumentException();
}
}
This does not violate the requirement of the return type but it is not accepted by javac. The problem is that e.g. the return ((Integer) a) + 1; will only ever be executed when T is Integer but the compiler can't (according to the JLS) infer this and thus expects that a T is passed to return. The expression there is typed as int, which can't be converted to T.
Is there a way to deal with this situation without resorting to an unchecked cast?
What I'm actually doing in my code: In the application, there's a generic interface which is used to encapsulate some value. There are different specialized implementations of that interface for different value types and the method I'm trying to write should dynamically chose the right implementation.
If you are type-checking a at runtime, then you are defeating the point of generics. But generics don't work with mathematical operators anyway, because those operators work with primitives and type parameters work with reference types. Your workaround doesn't work with the compiler because T could be any Number type at compile time, so the compiler can't allow you to return an Integer when it thinks it could be any Number type.
Instead, I would overload two non-generic foo methods -- one with int and one with double. Neither of these methods would be generic, and each would just perform its own operation.
This isn't proper use of generics. You should use method overloading instead
Why is the compiler able to determine the generic type parameter for an
assignment, but not for the ternary operator (?)?
I have a question regarding the compiler being able to deduce the generic type
parameter in case of a "direct" assignment but failing in case of the ternary
operator (?). My examples uses Guava's Optional class, to make my point, but
I think the underlying issue is generic and not restricted to Optional.
Optional has a generic function absent():
public static <T> Optional<T> absent();
and I can assign an Optional<T> to an Optional<Double>:
// no compiler error
final Optional<Double> o1 = Optional.absent();
How does the compiler realize, that T should be Double in this case. Because
when using the ternary operator (?), I need to tell the compiler specifically
to us Integer as the generic parameter
// Type mismatch: cannot convert from Optional<capture#1-of ? extends Object> to Optional<Integer>
final Optional<Integer> o2 = true
? Optional.of(42)
: Optional.<Integer>absent();
otherwise I get the following error
Type mismatch: cannot convert from Optional<capture#1-of ? extends Object> to Optional<Integer>
Why is there a difference between a "direct" assignement and using the ternary
operator? Or is there something else I am missing?
Because of type inference rules, it appears the ternary expression does not infer the type parameter from the return type. The type of the ternary expression depends on the types of its operands. But one of the operands has undetermined type parameter (Optional.absent()). At that point the ternary expression still does not have a type, so it cannot influence the type parameter.
You can also look into this bug report for more information. You can look into the JLS .
The type of the conditional expression is the result of applying capture conversion (??5.1.10) to lub(T1, T2)
Here what the JLS says :
If the method result occurs in a context where it will be subject to assignment conversion to a type S, then let R be the declared result type of the method, and let R' = R[T1 = B(T1) ... Tn = B(Tn)] where B(Ti) is the type inferred for Ti in the previous section, or Ti if no type was inferred.
The Problem is that the result of the ternery Operator is assigned to o2.
The compiler can't deduce the type across multiple operations.
Basically I think you write the short form of:
Optional<?> tmp = true ? Optional.of(42): Optional.absent();
final Optional<Integer> o2 = tmp;
The conversion of the second line is the problem.