I was reading a Java tutorial from here. I am having trouble understanding a simple line.
Tutorial says declaration of Collections.emptyList is:
static <T> List<T> emptyList();
So if we write List<String> listOne = Collections.emptyList();, it works as the Java compiler is able to infer the type parameter, as the returned value should be of type List<String>.
Now consider a method: void processStringList(List<String> stringList). Now it states:
processStringList(Collections.emptyList()); The Java SE 7 compiler
generates an error message similar to the following:
List<'Object> cannot be converted to List<'String>
The compiler requires
a value for the type argument T so it starts with the value Object.
Consequently, the invocation of Collections.emptyList returns a value
of type List<Object>, which is incompatible with the method
processStringList
Now what do they mean by: so it starts with the value Object? I mean start doing what?
Basically this is about the capabilities of the compiler. In other words: to a certain degree, the "amount" of possible type inference is an implementation detail.
With Java 7, you sometimes have to use type helpers/hints/witnesses, where you would go Collections.<String>emptyList() to tell the compiler about that missing part.
Later implementations of the compiler improved the situation that you can almost always go with Collections.emptyList().
Regarding The compiler requires a value for the type argument T so it starts with the value Object. ... that is actually quite simple: the java compiler has to implement an algorithm, that finally, infers a specific type. Giving some pseudo-code, that might look like:
Class<?> inferType(SomeSyntaxTree construct) {
I am just using Class here to indicate that the algorithm will return something that resembles a known type. Now, that method could be implemented like this:
Class<?> inferedType = Object.class
while (whatever) {
refine inferedType
}
return inferedType
In other words: that is a very common approach when you "search" for some value: you initialize with the "most generic" value (in the Java type system, that would be Object.class), and then you see if that generic value can be refined by applying whatever algorithm.
In our case, the refinement might end up in figuring "the most specific type that can be used is String", but if no further refinement is possible, then you end up with your "initial default", being Object.
The statement
processStringList(Collections.emptyList());
works fine in Java 8 (I'm assuming above 8 as well). The compiler in this case is smart enough to infer the types by checking what is the expected argument type for the method.
In older versions, when compiler sees no explicit return type (as in List<String> listOne = Collections.emptyList();), it by default infers <T> to java.lang.Object. But note that List<Object> and List<String> are not compatible.
You can declare the method like void processString(List<? super String> list) to avoid the error.
Related
I just saw this kind of code ImmutableList<String> list= ImmutableList.<String>builder().build();
which really confused me. How to understand the diamond after ImmutableList.?
Most parameterized types in java show up on a type. This looks like so:
interface List<T> {
void add(T elem);
}
So, any List type is parameterized, and as generics is really just a mechanism to link things, what it links is that a List<String> has an add method that takes String objects, and a get(int) method that returns a String, etc.
But, methods themselves may also want this linking behaviour. For example, let's say I want to make a method that takes 2 arguments of the same type, and returns the first non-null one. Here too I want to link things: The types of the 2 argument, and the return type? All the same thing, caller's choice as to what it might be.
Java supports this: Methods can ALSO have generics:
public <T> T firstNonNull(T a, T b) {
return a == null ? b : a;
}
is valid java, and you can call it:
String a = firstNonNull("hello", "world!");
Compiles without requiring a cast.
Java will infer generics if it can; it does that in my previous example (the two arguments are both strings; java infers you meant T to be String there). But you can, if you want, be explicit about it. This is where this funky syntax comes in:
Number a = ClassContainingFNN.<Number>firstNonNull(null, null);
You need the dot to use this syntax, hence why I had to make the call a little longer. With the ImmutableList builder method, java can't (easily) infer what type you wanted, as the call to builder() itself doesn't let the compiler know that you're attempting to build a list of, say, strings. That's why forcing it by explicitly telling java what you want the type param to be is useful, thus, why the usual way to call this builder is:
ImmutableList.<String>builder().add(aString).add(anotherString).build();
Java will always try to infer something if you don't explicitly pick something, but it would just infer Object here. Unless you wanted a list of objects, you need the 'forcibly pick a type' option.
See java support jls-15.12 for supporting TypeArguments after entering Type.
MethodInvocation:
MethodName ( [ArgumentList] )
TypeName . [TypeArguments] Identifier ( [ArgumentList] )
The builder is generic method
public static <E> Builder<E> builder()
And because it's static you entered before method name the type using diamond operator
In case of new instance it'll be as you expected:
new ImmutableList.Builder<Color>()
I've recently come across this unusual (to me) Java syntax...here's an example of it:
List list = new <String, Long>ArrayList();
Notice the positioning of the <String, Long> type arguments...it's not after the type as normal but before. I don't mind admitting I've never seen this syntax before. Also note there are 2 type arguments when ArrayList only has 1.
Does the positioning of the type arguments have the same meaning as putting them after the type? If not, what does the different positioning mean?
Why is it legal to have 2 type arguments when ArrayList only has 1?
I've searched the usual places, eg. Angelika Langer and on here but can't find any mention of this syntax anywhere apart from the grammar rules in the Java grammar file on the ANTLR project.
Calling a generic constructor
This is unusual alright, but fully valid Java. To understand we need to know that a class may have a generic constructor, for example:
public class TypeWithGenericConstructor {
public <T> TypeWithGenericConstructor(T arg) {
// TODO Auto-generated constructor stub
}
}
I suppose that more often than not when instantiating the class through the generic constructor we don’t need to make the type argument explicit. For example:
new TypeWithGenericConstructor(LocalDate.now(ZoneId.systemDefault()));
Now T is clearly LocalDate. However there may be cases where Java cannot infer (deduce) the type argument. Then we supply it explicitly using the syntax from your question:
new <LocalDate>TypeWithGenericConstructor(null);
Of course we may also supply it even though it is not necessary if we think it helps readability or for whatever reason:
new <LocalDate>TypeWithGenericConstructor(LocalDate.now(ZoneId.systemDefault()));
In your question you seem to be calling the java.util.ArrayList constructor. That constructor is not generic (only the ArrayList class as a whole is, that’s something else). For why Java allows you to supply type arguments in the call when they are not used, see my edit below. My Eclipse gives me a warning:
Unused type arguments for the non generic constructor ArrayList() of
type ArrayList; it should not be parameterized with arguments <String,
Long>
But it’s not an error, and the program runs fine (I additionally get warnings about missing type arguments for List and ArrayList, but that again is a different story).
Generic class versus generic constructor
Does the positioning of the type arguments have the same meaning as
putting them after the type? If not, what does the different
positioning mean?
No, it’s different. The usual type argument/s after the type (ArrayList<Integer>()) are for the generic class. The type arguments before are for the constructor.
The two forms may also be combined:
List<Integer> list = new <String, Long>ArrayList<Integer>();
I would consider this a bit more correct since we can now see that the list stores Integer objects (I’d still prefer to leave out the meaningless <String, Long>, of course).
Why is it legal to have 2 type arguments when ArrayList only has 1?
First, if you supply type arguments before the type, you should supply the correct number for the constructor, not for the class, so it hasn’t got anything to do with how many type arguments the ArrayList class has got. That really means that in this case you shouldn’t supply any since the constructor doesn’t take type arguments (it’s not generic). When you supply some anyway, they are ignored, which is why it doesn’t matter how many or how few you supply.
Why are meaningless type arguments allowed?
Edit with thanks to #Slaw for the link: Java allows type arguments on all method calls. If the called method is generic, the type arguments are used; if not, they are ignored. For example:
int length = "My string".<List>length();
Yes, it’s absurd. The Java Language Specification (JLS) gives this justification in subsection 15.12.2.1:
This rule stems from issues of compatibility and principles of substitutability. Since interfaces or superclasses may be generified
independently of their subtypes, we may override a generic method with
a non-generic one. However, the overriding (non-generic) method must
be applicable to calls to the generic method, including calls that
explicitly pass type arguments. Otherwise the subtype would not be
substitutable for its generified supertype.
The argument doesn’t hold for constructors since they cannot be directly overridden. But I suppose they wanted to have the same rule in order not to make the already complicated rules too complicated. In any case, section 15.9.3 on instantiation and new more than once refers to 15.12.2.
Links
Generics Constructor on CodesJava
JLS 15.9.3. Choosing the Constructor and its Arguments
JLS 15.12.2.1. Identify Potentially Applicable Methods
What is the point of allowing type witnesses on all method calls?
Apparently you can prefix any non-generic method/constructor with any generic parameter you like:
new <Long>String();
Thread.currentThread().<Long>getName();
The compiler doesn't care, because it doesn't have to match theses type arguments to actual generic parameters.
As soon as the compiler has to check the arguments, it complains about a mismatch:
Collections.<String, Long>singleton("A"); // does not compile
Seems like a compiler bug to me.
Consider the following code sample:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List list = new ArrayList<Integer>();
String response = getProducer(list).get();
}
static Producer<String> getProducer(List<Integer> list) {
return new Producer<String>();
}
}
class Producer<T> {
T get() {
return null;
}
}
When compiled in Java 7 it just produces an expected warning for getProducer(list):
Warning:(7, 39) java: unchecked conversion
required: java.util.List<java.lang.Integer>
found: java.util.List
However, when compiled in Java 8 it produces the following error for the response = getProducer(list).get() assignment:
Error:(7, 48) java: incompatible types: java.lang.Object cannot be converted to java.lang.String
So apparently the type returned from getProducer(list) is not Producer<String>, but erased Producer (which is also confirmed by means of the 'extract variable' feature in the IDE). This is very puzzling because getProducer method always returns Producer<String>.
Oddly enough it could be fixed by avoiding unchecked conversion while calling getProducer method, either by:
Change parameter type of getProducer from List<Integer> to List
Change type of list variable from List to List<Integer>
Updates
Java used is Oracle JDK 1.8.0_40
I have also tried using source and target options from 1.5 through 1.7 with the Java 8 compiler and the result was the same.
Questions
How could the generic type of the passed argument affect a generic type of the method return value while the generic type of the return value is fixed in the method signature?
Why is there such a backward-incompatible change in behavior between Java 7 and Java 8?
This looks like a known compatibility issue reported here and here.
From the second link:
The following code which compiled, with warnings, in JDK 7 will not
compile in JDK 8:
import java.util.List;
class SampleClass {
static class Baz<T> {
public static List<Baz<Object>> sampleMethod(Baz<Object> param) {
return null;
}
}
private static void bar(Baz arg) {
Baz element = Baz.sampleMethod(arg).get(0);
}
}
Compiling this code in JDK 8 produces the following error:
SampleClass.java:12: error:incompatible types: Object cannot be
converted to Baz
Baz element = Baz.sampleMethod(arg).get(0);
Note: SampleClass.java uses unchecked or unsafe operations. Note:
Recompile with -Xlint:unchecked for details. 1 error
Deriving from this, the OP's code can be fixed by replacing this line (the type declartion on the right hand side threw me off - I read it as a typed array list which it is not):
List list = new ArrayList<Integer>();
with
List<Integer> list = new ArrayList<Integer>();
which will not result in type being being erased from return type of method getProducer(List<Integer> list)
Quote from second link again:
In this example, a raw type is being passed to the
sampleMethod(Baz<Object>) method which is applicable by subtyping (see
the JLS, Java SE 7 Edition, section 15.12.2.2). An unchecked
conversion is necessary for the method to be applicable, so its return
type is erased (see the JLS, Java SE 7 Edition, section 15.12.2.6). In
this case the return type of sampleMethod(Baz<Object>) is
java.util.List instead of java.util.List<Baz<Object>> and thus the
return type of get(int) is Object, which is not assignment-compatible
with Baz.
Let’s look into the Java Language Specification:
Java 8
15.12.2.6. Method Invocation Type
…
If the chosen method is not generic, then:
If unchecked conversion was necessary for the method to be applicable, the parameter types of the invocation type are the parameter types of the method's type, and the return type and thrown types are given by the erasures of the return type and thrown types of the method's type.
…
Java 7
15.12.2.6. Method Result and Throws Types
The result type of the chosen method is determined as follows:
If the chosen method is declared with a return type of void, then the result is void.
Otherwise, if unchecked conversion was necessary for the method to be applicable, then the result type is the erasure (§4.6) of the method's declared return type.
It’s important to understand, what “generic method” means:
Reference
8.4.4. Generic Methods
A method is generic if it declares one or more type variables (§4.4).
In other words, the method
static Producer<String> getProducer(List<Integer> list)
has generic parameter and return types but is not generic as it doesn’t declare type variables.
So the cited parts apply, and despite making differences regarding the prerequisites, they agree on the consequences for this specific method invocation, “if unchecked conversion was necessary for the method to be applicable, then the result type is the erasure … of the method's declared return type”.
So it’s the older compiler violating the specification by using the return type Producer<String> instead of the erasure Producer.
The real question is; why can you use raw type? For backward compatibility. If it's for backward compatibility, the assumption is only raw types should be used.
How generic type of passed argument could affect generic type of method return value while generic type of return value is fixed in method signature?
A method or constructor has either of two modes. It is either completely generic, or it is completely raw typed for backward compatibility. There is no mode mode of use where it is partly raw typed and partly generic. The only reason raw types are an option is for backward compatibility and in this situation it assumes all types are/were raw types.
Why there is such backward incompatible change in behavior between Java7 and Java8?
Since Java 1.4 is not supported any more and hasn't been for a while, the backward compatibility argument doesn't hold as strongly and make some sense to give raw types a place in the current language.
I guess the reasoning goes like this, focusing on "migration" compatibility -
If the invocation supplies a raw-type argument to a method parameter of a generic-type, it is highly likely that the invocation code is the pre-generic code, and the method declaration, which used to pre-generic too, has since been "evolved" to use generic types.
To maintain perfect compatibility, the fool-proof solution is to erasure the method type, so that it's just like the pre-generic version. This way, the meaning of the invocation stays exactly the same.
A more sophisticated solution might be too complicated for JLS, for example - how do we do type inference if there are raw-type arguments.
Today, that assumption may no longer hold- the invocation is more likely a post-generic code, which, for various reasons, still uses raw type nevertheless. It's better to "correct" the raw type early on
List list0 = ...; // got it from somewhere, possibly from an old API
#SuppressWarnings("unchecked")
List<Integer> list = list0;
I recently came upon the strange syntax for explicitly declaring generic types when calling Java methods. For example:
Collections.<String>emptyList();
returns an empty List<String>. However, this seems silly as the implementation of <T> emptyList() is just the unchecked type cast (List<T>) EMPTY_LIST, such that all results have the same type erasure (and are the same object.) Moreover, this sort of explicit type declaration is usually not needed because the compiler can often infer the types:
List<String> empty = Collections.emptyList();
After doing some more digging I found two other times where you'd want to use this syntax, and they're all due to using the Guava library and apparently trying to put too many statements on one line.
Decorating a collection, for example with a synchronized wrapper, and the compiler being not able to infer the types. The following doesn't work if you take out the type declaration: cannot convert from Set<Object> to Set<String>:
Set<String> set = Collections.synchronizedSet(Sets.<String>newHashSet());
Getting less specific type parameters when they compiler tries to make ones that are too specific. For example, without the type declaration the following statement complains as well: cannot convert from Map<String, String> to Map<String, Object>:
Map<String, Object> toJson = ImmutableMap.<String, Object>of("foo", "bar");
I find it ironic that in the first case the inferred type parameters are too general and in the second case they are too specific, but I suppose that is just an artifact of the generics system in Java.
However, this language construct itself seems to be avoidable except in these strange use cases invented by the Guava team. Moreover, it seems plain to me that there is a way for the compiler to infer type arguments in both the above examples, and the developers just chose not to do so. Are there examples of it ever being necessary or useful to use this construct in Java programming or does it exist solely to make the compiler simpler / JDK developer's life easier?
How is "shutting up the compiler" not "necessary or useful?" I find it both necessary and useful for my code to compile.
There are times when the correct type cannot be inferred, as you have already found. In such cases, it is necessary to explicitly specify the type parameters. Some examples of the compiler just not being smart enough:
Why can't javac infer generic type arguments for functions used as arguments?
Generics type inference fails?
And if you really want to dig into the complexities of type inference, it starts and ends with the Java Language Specification. You'll want to focus on JLS §15.12.2.7. Inferring Type Arguments Based on Actual Arguments and §15.12.2.8. Inferring Unresolved Type Arguments.
I found at least one case where the compiler infers the types correctly, and it's still needed: when you want to use the result as a more generic type. Take this method, which basically creates a List<T> from zero or more T objects:
public static <T> List<T> listOf(T... items) {
ArrayList<T> list = new ArrayList<T>();
for (T item : items)
list.add(item);
return list;
}
The idea is that you can use it like this:
List<Integer> numbers = ListUtils.listOf(1, 2, 3);
Now, suppose you have a method that can receive List<Object>:
public static void a(List<Object> objs) {
...
}
and that you want to supply a list built via the listOf() method:
a(ListUtils.listOf(1, 2, 3));
This will not compile, as the method parameter type is List<Object> and the supplied argument is List<Integer>. In that case, we can change the invocation to:
a(ListUtils.<Object>listOf(1, 2, 3));
which does compile, as expected.
Java type inference is incredibly weak. The only time it is not necessary to include the explicit type in a generic method like emptyList() is when the result of the method defines a variable. If you try to pass an empty list as the argument of another method (example 1), a situation which arises for me on a daily basis (and I do not yet use Guava), the compiler just gives up on type inference completely. I fail to see how declaring the empty list as a local, single-use variable is "putting too many statements on one line" as you call it; the empty list is a very simple sub-expression, except that Java's miserable type inference makes it complex. Compare with Scala, which will do inference in 3 different situations.
Given this example from the generics tutorial.
List<String> list = new ArrayList<>();
list.add("A");
// The following statement should fail since addAll expects
// Collection<? extends String>
list.addAll(new ArrayList<>());
Why does the last line not compile, when it seems it should compile. The first line uses a very similar construct and compiles without a problem.
Please explain elaborately.
First of all: unless you're using Java 7 all of this will not work, because the diamond <> has only been introduced in that Java version.
Also, this answer assumes that the reader understands the basics of generics. If you don't, then read the other parts of the tutorial and come back when you understand those.
The diamond is actually a shortcut for not having to repeat the generic type information when the compiler could find out the type on its own.
The most common use case is when a variable is defined in the same line it's initialized:
List<String> list = new ArrayList<>(); // is a shortcut for
List<String> list = new ArrayList<String>();
In this example the difference isn't major, but once you get to Map<String, ThreadLocal<Collection<Map<String,String>>>> it'll be a major enhancement (note: I don't encourage actually using such constructs!).
The problem is that the rules only go that far. In the example above it's pretty obvious what type should be used and both the compiler and the developer agree.
On this line:
list.addAll(new ArrayList<>());
it seems to be obvious. At least the developer knows that the type should be String.
However, looking at the definition of Collection.addAll() we see the parameter type to be Collection<? extends E>.
It means that addAll accepts any collection that contains objects of any unknown type that extends the type of our list. That's good because it means you can addAll a List<Integer> to a List<Number>, but it makes our type inference trickier.
In fact it makes the type-inference not work within the rules currently laid out by the JLS. In some situations it could be argued that the rules could be extended to work, but the current rules imply don't do it.
The explanation from the Type Inference documentation seems to answer this question directly ( unless I'm missing something else ).
Java SE 7 and later support limited type inference for generic instance creation; you can only use type inference if the parameterized type of the constructor is obvious from the context. For example, the following example does not compile:
List<String> list = new ArrayList<>();
list.add("A");
// The following statement should fail since addAll expects
// Collection<? extends String>
list.addAll(new ArrayList<>());
Note that the diamond often works in method calls; however, for greater clarity, it is suggested that you use the diamond primarily to initialize a variable where it is declared.
In comparison, the following example compiles:
// The following statements compile:
List<? extends String> list2 = new ArrayList<>();
list.addAll(list2);
When compiling a method invocation, javac needs to know the type of the arguments first, before determining which method signature matches them. So the method parameter type isn't known before the argument type is known.
Maybe this can be improved; as of today, the type of the argument is independent of the context.