I recently finished an online course in AP Computer Science (Java) and on the final exam there was a question that went something like this:
Which of these needs a String cast to use String methods on it:
I. ArrayList a = new ArrayList();
II. ArrayList<Object> b = new ArrayList<Object>();
III. ArrayList<String> c = new ArrayList<String>();
Something about this confused me: can option I ever even be able to be casted? It has no generic definition so, unless the Java compiler defauted to ArrayList<Object>, what class is E then?
This is my test code (the suppress comments are needed because this is an "unchecked" operation):
ArrayList a = new ArrayList();
#SuppressWarnings("unchecked")
a.add(new Object());
#SuppressWarnings("unchecked")
a.add(new String("test"));
#SuppressWarnings("unchecked")
a.add(null);
System.out.println((String)(a.get(0)));
No matter what is in the arguments for the add() method, it always gives the compiler error:
test.java:14: error: <identifier> expected
a.add(new Object());
^
If I try to add an identifier anywhere on the code (e.g.: a<Object>.add(new Object())) it gives the exact same error as before.
The question is what is actually happening when no parameter is passed to the generics parameter and can anything be added to this list in the first place, let alone cast into another object? Thanks in advance!
You have a simple syntax error. #SuppressWarnings can't be used on aribrary statements like a.add(x), only class declarations, method declarations and variable declarations. That's why it asks for an identifier, the #SuppressWarnings is expecting to see one of those three, not a simple statement.
public static void main(String[] args) {
ArrayList a = new ArrayList();
a.add(new Object());
a.add(new String("test" );
a.add(null);
System.out.println((String) (a.get(0)));
}
The code compiles fine like this.
So what happens when you use a raw type? Nothing. No type checking is done. The above code produces a java.lang.ClassCastException error on the println statement. It doesn't catch that you put an Object in when you meant to put a String, and it doesn't know until runtime that your cast on the println will be bad.
When it is not specified, it defaults to the bound of the generic, and if that is not specified it is by default object.
This means that :
List lst = new ArrayList();
Is totally equivalent to :
List<Object> lst = new ArrayList<Object>();
On such a list, everything can be added or retrieved, because everything in Java extends Object, except primitives (but then autoboxing comes into play).
So, you can correctly write :
lst.add("String");
lst.add(new Date());
lst.add(new Integer(1));
lst.add(1); // It is converted to an Integer by auto-boxing
And you can correctly write :
String str = (String)lst.get(0);
But you have to be aware of possible runtime cast exceptions :
String str2 = (String)lst.get(1); // Will give error, cause element 1 is a date.
If you have a specific ArrayList subclass that defines a generic bound, like for example :
public class MyList<E extends MyClass> extends ArrayList<E> {
The you can still use it without generics :
MyList lst = new MyList();
But it will accept only MyClass or subclasses of MyClass :
lst.add(new MyClass());
lst.add(new MySubClass());
MyClass a = lst.get(0);
MyClass b = lst.get(1);
MySubClass c = (MySubClass)lst.get(1);
But nothing else :
lst.add("String"); // Will give compile time error
Related
class A {
private int a;
}
public static <T> List<T> listStrToListT(String str) {
String[] idStrs = str.replace(" ", "").split(",");
List<T> uids = new ArrayList<>();
for (String idStr : idStrs) {
uids.add((T) idStr);
}
return uids;
}
public static void main(String[] args) {
List<A> lst = listStrToListT("1,2,3");
System.err.println(lst);
}
This program don't have any error.But when I debug (in the below picture): lst is a List<String>.Why I directly assign List<String>(right side) to List<A>(left side) ?
Remember that generics in Java are only a compile-time thing. At runtime, all generic parameters are erased to non-generic types.
From the compiler's point of view, listStrToListT can return any kind of List the caller wants, not just List<String>. You convinced the compiler of this non-fact by (1) making listStrToListT generic and (2) casting idStr to T. You're saying "I'm sure this cast will work when this runs. Don't worry, Compiler!" This cast certainly smells fishy, doesn't it? What if T is A...
Anyway, now List<A> lst = listStrToListT("1,2,3"); compiles, as listStrToListT "can return any type of List" as mentioned before. You'd imagine that T is inferred to be A, and your cast in listStrToListT would fail at runtime, but that's not what happens.
Now it's runtime, all generic types get erased, making your code look like this:
public static List listStrToListT(String str) {
String[] idStrs = str.replace(" ", "").split(",");
List uids = new ArrayList();
for (String idStr : idStrs) {
uids.add((Object)idStr);
}
return uids;
}
// main method:
List lst = listStrToListT("1,2,3");
System.out.println(lst);
Note that the cast to T becomes a cast to Object, which really is just redundant here.
Printing out the list just involves calling toString on each of the Objects, so no casting is done there.
Note that what "smelled fishy" at compile time, is completely valid at compile time. The fishy cast became a perfectly valid (and redundant) cast to Object! Where'd the cast go?
Casts will only be inserted where necessary. This is just how generics work in Java. So let's create such a situation. Let's say in A you have a getter for the field a, and instead of printing the whole list, you print the a of the first element:
// main method:
List<A> lst = listStrToListT("1,2,3");
System.out.println(lst.get(0).getA());
Well, to be able to access getA, a cast needs to be inserted:
List lst = listStrToListT("1,2,3");
System.out.println(((A)lst.get(0)).getA());
otherwise lst.get(0) would be of type Object, and Objects don't have a getA method.
It is at this time that your program will crash.
While I was going through some generics question I came across this example. Will you please explain why list.add("foo") and list = new ArrayList<Object>() contain compailation issues?
In my understanding List of ? extends String means "List of Something which extends String", but String is final ? can only be String. In list.add() we are adding "foo" which is a string. Then why this compilation issue?
public class Generics {
public static void main(String[] args) {
}
public static void takelist(List<? extends String> list){
list.add("foo"); //-- > error
/*
* The method add(capture#1-of ? extends String) in the
* type List<capture#1-of ? extends String> is not applicable
* for the arguments (String)
*/
list = new ArrayList<Object>();
/*
* Type mismatch: cannot convert from ArrayList<Object> to List<? extends String>
*/
list = new ArrayList<String>();
Object o = list;
}
}
For starters, the java.lang.String class is final, meaning nothing can extend it. So there is no class which could satisfy the generic requirement ? extends String.
I believe this problem will cause all of the compiler errors/warnings which you are seeing.
list.add("foo"); // String "foo" does not extend String
list = new ArrayList<Object>(); // list cannot hold Object which does not extend String
It is true what you say. String is final. And so you can reason that List<? extends String> can only be list of string.
But the compiler isn't going to make that kind of analysis. That is to say, the compiler will not assess the final or non-final nature of String (see comments). The compiler will only let you put null into your list.
You can pull stuff out though.
String s = list.get(0);
While String is final, this information is not used.
And in fact, with Java 9, it may no longer be final (rumor has it that Java 9 may finally get different more efficient String types).
Without knowing it is final, List<? extends String> could be e.g. a List<EmptyString> of strings that must be empty.
void appendTo(List<? extends String> l) {
l.append("nonempty");
}
appendTo(new ArrayList<EmptyStrings>());
would yield a violation of the generic type.
As a rule of thumb always use:
? extends Type for input collections (get is safe)
? super Type for output collections (put is safe)
Type (or maybe a <T>) for input and output collections (get and put are safe, but the least permissive).
I.e. this is fine:
void appendTo(List<? super String> l) {
l.append("nonempty");
}
appendTo(new ArrayList<Object>());
You have already mentioned that String is a final type and therefore there is no point in repeating this fact. The point that is important to note is that none of the following Lists allows adding an element:
List<?> which is a List of anything.
List<? extends SomeType> which is a List of anything that extends SomeType.
Let's understand it with an example.
The List<? extends Number> could be List<Number> or List<Integer> or List<Double> etc. or even a List of some other type that hasn't been defined yet. Since you can not add any type of Number to a List<Integer> or any type of Number to a List<Double> etc., Java does not allow it.
Just for the sake of completeness, let's talk about List<? super Integer> which is List of anything that is a super/parent type of Integer. Will the following compile?
Object obj = 10.5;
list.add(obj);
As you can guess, of course NOT.
What about the following?
Object obj = 10.5;
list.add((Integer) obj);
Again, as you can guess, indeed it will compile but it will throw ClassCastException at runtime. The question is: why did not Java stop us in the first place by failing the compilation itself? The answer is Trust. When you cast something, the compiler trusts that you already understand the cast.
So, the following compiles and runs successfully:
Object obj = 10;
list.add((Integer) obj);
list.add(20);
This question already has answers here:
Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic?
(19 answers)
Closed 8 years ago.
Consider below method doSomething(List<Object>) which accepts List<Object> as parameter.
private void doSomething(List<Object> list) {
// do something
}
Now consider below code snippet which tries to call doSomething() where I try to pass List<String> to doSomething()
List<Object> objectList;
List<String> stringList;
doSomething(stringList); // compilation error incompatible types
doSomething(objectList); // works fine
Even below code throws compilation error
objectList = stringList; // compilation error incompatible types
My question is why List<String> can not be passed to a method which accepts List<Object>?
Because while String extends Object, List<String> does not extend List<Object>
Update:
In general, if Foo is a subtype (subclass or subinterface) of Bar, and G is some generic type declaration, it is not the case that G<Foo> is a subtype of G<Bar>.
This is because collections do change. In your case, If List<String> was a subtype of List<Object>, then types other than String can be added to it when the list is referenced using its supertype, as follows:
List<String> stringList = new ArrayList<String>;
List<Object> objectList = stringList;// this does compile only if List<String> where subtypes of List<Object>
objectList.add(new Object());
String s = stringList.get(0);// attempt to assign an Object to a String :O
and the Java compiler has to prevent these cases.
More elaboration on this Java Tutorial page.
You could put an object of a wrong type into the list IF this worked:
private void doSomething(List<Object> list) {
list.add(new Integer(123)); // Should be fine, it's an object
}
List<String> stringList = new ArrayList<String>();
doSomething(stringList); // If this worked....
String s = stringList.get(0); // ... you'd receive a ClassCastException here
This generic question in Java may look confusing to any one who is not very familiar with Generics as in first glance it looks like String is object so List<String> can be used where List<Object> is required but this is not true. It will result in compilation error.
It does make sense if you go one step further because List<Object> can store anything including String, Integer etc but List<String> can only store Strings.
Also have a look at: Why not inherit from List<T>?
The reason for these limitations have to do with variance considerations.
Take the following code:
public void doSomething(List<Object> objects)
{
objects.add(new Object());
}
Expanding your example, you could try to do the following:
List<String> strings = new ArrayList<String>();
string.add("S1");
doSomething(strings);
for (String s : strings)
{
System.out.println(s.length);
}
Hopefully it's obvious why this would break if the compiler allowed this code to be compiled (which it doesn't) - a ClassCastException would occur for the second item in the list when trying to cast the Object to a String.
To be able to pass generalized collection types, you need to do this:
public void doSomething(List<?> objects)
{
for (Object obj : objects)
{
System.out.println(obj.toString);
}
}
Again, the compiler is watching your back and were you to replace the System.out with objects.add(new Object()) the compiler wouldn't allow this because objects could have been created as List<String>.
For more background on Variance see the Wikipedia artical Covariance and contravariance
From Java Tutorials of Generics:
Let's test your understanding of generics. Is the following code snippet legal?
List<String> ls = new ArrayList<String>(); // 1
List<Object> lo = ls; // 2
Line 1 is certainly legal. The trickier part of the question is line 2. This boils down to the question: is a List of String a List of Object. Most people instinctively answer, "Sure!"
Well, take a look at the next few lines:
lo.add(new Object()); // 3
String s = ls.get(0); // 4: Attempts to assign an Object to a String!
Here we've aliased ls and lo. Accessing ls, a list of String, through the alias lo, we can insert arbitrary objects into it. As a result ls does not hold just Strings anymore, and when we try and get something out of it, we get a rude surprise.
The Java compiler will prevent this from happening of course. Line 2 will cause a compile time error.
Source : Generics and Subtyping
It is sometimes expected that a List<Object> would be a supertype of a List<String> , because Object is a supertype of String .
This expectation stems from the fact that such a type relationship exists for arrays:
Object[] is a supertype of String[] , because Object is a supertype of String . (This type relationship is known as covariance .)
The super-subtype-relationship of the component types extends into the corresponding array types.
No such a type relationship exists for instantiations of generic types. (Parameterized types are not covariant.)
Check here for more details
If you are not sure what datatype it will take in you can make use of Generics in Java as follows
public static void doSomething(List<?> data) {
}
public static void main(String [] args) {
List<Object> objectList = new ArrayList<Object>();
List<String> stringList = new ArrayList<String>();
doSomething(objectList);
doSomething(stringList);
}
But while using the data, you will be required to specify proper data type as a Type Cast
I just read 《Effective Java》and I saw a sentence which said that
As a consequence, arrays provide runtime type safety but not compile-time type safety and vice versa for generics
I don't quite clear about it and I'm confused even after I've read all the examples given.Can anyone explain this to me,thanks a million.
You can't change the type of an array (reference) at runtime. But you can compile code which tries to just fine.
String[] strings = new String[1];
Object[] objects = strings;
objects[0] = new Integer(1); // RUN-TIME FAILURE
When you compile your application, no error will be thrown by the compiler.
On the other hand, if you use generics, this WILL give you an error when you compile (build) your application.
ArrayList<String> a = new ArrayList<String>();
a.add(5); //Adding an integer to a String ArrayList - compile-time failure
In other words, you don't need to actually run your application and execute that section of code to find the problem.
Note, compile time failures are preferable to run time failures, since you find out about the problem before you release it to users (after which it's too late)!
With generic collections, this code, which tries to put an Integer into a String list, gives a compile time error on the second line: Cannot cast from List<String> to List<Object>:
List<String> listOfStrings = new ArrayList<>();
List<Object> listAgain = (List<Object>)listOfStrings;
listAgain.add(123);
The equivalent code with arrays compiles perfectly because it is legal to use a String array as an Object array. (Technically speaking, arrays are covariant.)
String[] arrayOfStrings = new String[10];
Object[] arrayAgain = arrayOfStrings;
arrayAgain[0] = 123;
However, it wouldn't be a valid String array if it actually contained integers, so every operation to store something in it is checked at run time. At run time does it blow up with an ArrayStoreException.
Java Array provides covariant return type while generic doesn't provide covariant return type.
Lets understand with simple example
public class GenericArrayDiff {
public static void main(String[] args) {
List<Vehicle> vehicleList = null;
List<Car> carList = null;
Vehicle[] vehicleArrays;
Car[] carArrays;
// illegal
carList = vehicleList;
// illegal
vehicleList = carList;
// illegal
carArrays = vehicleArrays;
// legal because array provides covariant return type
vehicleArrays = carArrays;
}
}
class Vehicle {
}
class Car extends Vehicle {
}
I don't think I really understand Java generics. What's the difference between these two methods? And why does the second not compile, with the error shown below.
Thanks
static List<Integer> add2 (List<Integer> lst) throws Exception {
List<Integer> res = lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
.
static <T extends List<Integer>> T add2 (T lst) throws Exception {
T res = lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - incompatible types
required: T
found: capture#1 of ? extends java.util.List
For the second method to compile, you have to cast the result of newInstace() to T:
static <T extends List<Integer>> T add2 (T lst) throws Exception {
T res = (T) lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
Regarding the difference between the two methods, let's forget about the implementation, and consider only the signature.
After the code is compiled, both methods will have exactly the same signature (so the compiler would give an error if the have the same name). This happens because of what is called type erasure.
In Java, all the type parameters disappear after compilation. They are replaced by the most generic possible raw type. In this case, both methods will be compiled as List add2(List).
Now, this will show the difference between the two methods:
class Main {
static <T extends List<Integer>> T add1(T lst) { ... }
static List<Integer> add2(List<Integer> lst) { ... }
public static void main(String[] args) {
ArrayList<Integer> l = new ArrayList<Integer>();
ArrayList<Integer> l1 = add1(l);
ArrayList<Integer> l2 = add2(l); // ERROR!
}
}
The line marked as // ERROR! won't compile.
In the first method, add1, the compiler knows that it can assign the result to a variable of type ArrayList<Integer>, because the signature states that the return type of the method is exactly the same as that of the parameter. Since the parameter is of type ArrayList<Integer>, the compiler will infer T to be ArrayList<Integer>, which will allow you to assign the result to an ArrayList<Integer>.
In the second method, all the compiler knows is that it will return an instance of List<Integer>. It cannot be sure that it will be an ArrayList<Integer>, so you have to make an explicit cast, ArrayList<Integer> l2 = (ArrayList<Integer>) add2(l);. Note that this won't solve the problem: you are simply telling the compiler to stop whining and compile the code. You will still get an warning (unchecked cast), which can be silenced by annotating the method with #SuppressWarnings("unchecked"). Now the compiler will be quiet, but you might still get a ClassCastException at runtime!
The first one is specified to accept a List<Integer> and return a List<Integer>. List being an interface, the implication is that an instance of some concrete class that implements List is being passed as a parameter and an instance of some other concrete class that implements List is returned as a result, without any further relationship between these two classes other than that they both implement List.
The second one tightens that up: it is specified to accept some class that implements List<Integer> as a parameter, and return an instance of exactly that same class or a descendant class as the result.
So for example you could call the second one like so:
ArrayList list; // initialization etc not shown
ArrayList result = x.add2(list);
but not the first, unless you added a typecast.
What use that is is another question. ;-)
#Bruno Reis has explained the compile error.
And why does the second not compile, with the error shown below.
The error shown is actually reporting that you have tried to run code that failed to compile. It is a better idea to configure your IDE to not run code with compilation errors. Or if you insist on letting that happen, at least report the actual compilation error together with the line number, etc.
"I don't think I really understand Java generics."
Nobody does...
The issue is related to the interesting return type of getClass(). See its javadoc. And this recent thread.
In both of your examples, lst.getClass() returns Class<? extends List>, consequently, newInstance() returns ? extends List - or more formally, a new type parameter W introduced by javac where W extends List
In your first example, we need to assign W to List<Integer>. This is allowed by assignment conversion. First, W can be converted to List because List is a super type of W. Then since List is raw type, the optional unchecked conversion is allowed, which converts List to List<Integer>, with a mandatory compiler warning.
In the 2nd example, we need to assign W to T. We are out of luck here, there's no path to convert from W to T. It makes sense because as far as javac knows at this point, W and T could be two unrelated subclass of List.
Of course, we know W is T, the assignment would have been safe if allowed. The root problem here, is that getClass() loses type information. If x.getClass() returns Class<? extends X> without erasure, both of your examples will compile without even warning. They indeed are type safe.
Generics are a way to guarantee type safety.
Eg:
int[] arr = new int[4];
arr[0] = 4; //ok
arr[1] = 5; //ok
arr[2] = 9; //ok
arr[3] = "Hello world"; // you will get an exception saying incompatible
types.
By default arrays in Java are typeSafe. An integer array is only meant to
contain integer and nothing else.
Now:
ArrayList arr2 =new ArrayList();
arr2.add(4); //ok
arr2.add(5); //ok
arr2.(9); //ok
int a = arr2.get(0);
int b = arr2.get(1);
int c = arr3.get(2);
You willa gain get an exception like what it is not possible to cast Object
instance to integer.
The reason is that ArrayList stores object and not primitive like the
above array.
The correct way would be to explicitly cast to an integer.You have to do this
because type safety is not yet guaranteed.
eg:
int a = (int)arr2.get(0);
To employ type safety for collections, you simply specify the type of objects that your collection contains.
eg:
ArrayList<Integer> a = new ArrayList<Integer>();
After insertion into the data structure, you can simply retrieve it like you
would do with an array.
eg:
int a = arr2.get(0);