Why ArrayList<T> can assign to ArrayList<Integer> - java

public class Demo {
public static void main(String[] args) {
ArrayList<Integer> list = getList();
System.out.println(list.toString());
list.add(1);
System.out.println(list.toString());
}
public static <T> ArrayList<T> getList(){
ArrayList<String> strings = new ArrayList<>();
strings.add("qwert");
return (ArrayList<T>) strings;
}
}
As the code shown above why it is allowed to assign to ArrayList
JDK11 out put
[qwert]
1

Why ArrayList<T> can assign to ArrayList<Integer>
It's because you have provided explicit typecast.
Probably, what you are not able to understand is how ArrayList<Integer> list can hold qwert. Again, it's because you have asked the compiler to trust you that you are putting Integer into it.
Eventually, all the type information is removed when it comes to runtime. You need to understand the concept of Type Erasure.

return (ArrayList<T>) strings;
Here, the String ArrayList is not directly casted to Integer ArrayList, instead, it is casted to T ArrayList. According to 'Target Type Inference' in the docs:
static <T> List<T> emptyList();
List<String> listOne = Collections.emptyList();
This statement is expecting an instance of List; this data type is the target type. Because the method emptyList returns a value of type List, the compiler infers that the type argument T must be the value String.

Related

Generic method with genric argument and return type

I would like to create a generic method with a return type that contains a generic element sent in to the method.
This works fine as long as the input argument is of exact same type as the generic type. However I would like it to be possible to send an extension of the generic type.
Basically, I would like to create a method, createList, that would work for both of these calls:
List<Object> list = createList("foo");
List<String> list2 = createList("bar");
This generic method below works fine for the second call, but not for the first one.
private static <T> List<T> createList(T element) {
List<T> list = new ArrayList<>();
list.add(element);
return list;
}
For the second call I get a compilation error saying "Incompatible types. Required Object. Found String".
Below is the non-generic version which works fine for the first call, but not for the second:
private static List<Object> createList(Object element) {
List<Object> list = new ArrayList<>();
list.add(element);
return list;
}
Same compilation error here as on the other version but this time String is required, and Object was found.
Is it possible to create a method (preferrably using generics) in a way that both these calls would work? (using Java 7)
There were some type inference changes in Java 8 that fix this problem. So the straightforward solution is to update.
If you can't or don't want to update to Java 8, you can also provide the generic type explicitly:
List<Object> list = EnclosingClass.<Object>createList("foo");
Where EnclosingClass is the class that declares createList.
I'm doing something similiar, and solved the problem using a method like this.
Also this is not limited to any certain "Type" of Elements. It allows Every Object and/or primitive. IT also gives you a one-liner to fill a created list with some elements.
#SafeVarargs
private static <T extends Collection<S>, S> T makeList(T collection, S... objects) {
Collections.addAll(collection, objects);
return collection;
}
//example:
List<String> list1 = makeList(new LinkedList<String>(), "foo", "bar");
List<Integer> list2 = makeList(new ArrayList<Integer>(), 1,2,3,4);

Java / Generics / ClassCastException

I have a LinkedList<T> which contains Object toArray() method:
public Object[] toArray()
{
Object[] array = new Object[size];
int c=0;
for(Node<T> i = first;i != null;i=i.next)
{
array[c++] = i.data;
}
return array;
}
I would like to sort my LinkedList with a generic method: <T extends Comparable> void sort (List<T> list). To sort lists, i must represent them as array in this method:
T[] elements = (T[])list.toArray();`
However, i get ClassCastException at this line and i don't know why. Since the generic type of the method equivalent to the element's runtime type in the returned array, this cast is not a lie!
toArray() returns Object[]. The type information is lost and you can't cast it back to T[]. If you want to keep the type information you can use the following. Then you give the method an predifined array which get filled. If you don't give anything - toArray will create a new Object[].
T[] elements = list.toArray(new T[list.size()]);
just filling an array (another writing style):
T[] elements = new T[list.size()];
list.toArray(elements);
or if you use Java 8:
T[] elements = list.stream().toArray(T[]::new);
The method LinkedList.toArray() creates a new array of the type Object[]. It does not create a new array of the type T[]. This is important, because even though the array only contains instances of T, you cannot assign the array to a variable of the type T[], because the array itself has the type Object[]. You can reproduce this error with the following code:
String[] array = (String[]) new Object[0];
If I understand you correctly, you want to convert the list into an array to be able to implement your own search function. Given a List<T>, that contains elements of the type T you want to convert this list into an array of the type T[]. However, you cannot simply call new T[list.size()], since Java looses the generic type information at compile time. To create the correctly typed array, you need to use the reflection method Array.newInstance().
Here is an example:
#SuppressWarnings("unchecked")
private <T extends Comparable<T>> void sort(List<T> list, Class<T> clazz) {
T[] array = list.toArray((T[]) Array.newInstance(clazz, list.size()));
// sort array and write result to the list
}
And here the usage:
List<String> list = new LinkedList<String>();
// populate the list
sort(list, String.class);
System.out.println(list); // -> the sorted list
You should use T[] toArray(new T[list.size()]) instead. No need to cast.

Polymorphism with generic collections in Java

Given this method:
public static <E extends Number> List<E> process(List<E> num)
I want to do this :
ArrayList<Integer> input = null ;
ArrayList<Integer> output = null ;
output = process(input);
I get an exception : Type mismatch: cannot convert from List<Integer> to ArrayList<Integer>
I know that I can have something correct like that :
List<Integer> list = new ArrayList<Integer>;
Why doesn't it work here ?
The problem is the return type. process() returns a List<E> and you're trying to stuff that into an ArrayList<E>
Your process method needs to return an implementation of the interface List. This could as well be something incompatible to ArrayList, eg. a LinkedList. The compiler notices that and forbids it.

better solution with cast in generic java

Suppose I have the following code:
Object li = new Object();
// this causes a compiler warning
ArrayList<String> lo = (ArrayList<String>)(li);
Okay, I have edited the question, sorry, I made a mistake, the point is, since it will cause a compile time warning when we cast to the target, whose target is a parameterized type, is there any better solution so that I can prevent the code from causing a compile time warning?
When dealing with arrays, we have Arrays.newInstance to replace the use of cast.
code from original question:
// List<String> is a superclass of ArrayList
List<String> listOne = new List<String>();
ArrayList<String> listTwo = (List<String>) listOne;
You can't instantiate a List, because List is an interface.
An ArrayList is a class that implements the List interface.
You can do this:
List<String> listOne = new ArrayList<String>();
List<String> listTwo = listOne;
This is the correct approach when the code using listOne doesn't care what kind of List it is.
Here's an example that demonstrates why this is a good approach:
List<String> someList = createList();
Here someList is set to the return value of a method named createList. In this situation we have no idea what kind of list someList is. It could be an ArrayList, a Vector, a Stack, etc... As long as createList returns an object that implements the List interface, the above code will work. With this approach, the code in createList can be modified without affecting the code that calls it.
You can also do this:
ArrayList<String> listOne = new ArrayList<String>();
ArrayList<String> listTwo = listOne;
This is not as flexible, but it allows you to treat your lists specifically as ArrayLists.
Technically, you can do this, but it's not a good idea:
List<String> listOne = new ArrayList<String>();
ArrayList<String> listTwo = (ArrayList<String>) listOne;
It's better to program to an interface by declaring a List and instantiating an ArrayList.
Here's an example that shows some benefits of programming to interfaces:
public static void testIt() {
List someList;
ArrayList anArrayList;
/*
* all of these will work
*/
someList = createVectorAsList();
printList(someList);
someList = createStackAsList();
printList(someList);
someList = createArrayListAsList();
printList(someList);
// you CAN assign an ArrayList to a List
someList = createArrayList();
printList(someList);
// you CAN treat an ArrayList as a List
anArrayList = createArrayList();
printList(anArrayList);
/*
* none of these work
*/
// you can NOT assign List to an ArrayList
anArrayList = createStackAsList();
anArrayList = createVectorAsList();
// you can NOT pass anything but an ArrayList to printArrayList
printArrayList(createStackAsList());
printArrayList(createVectorAsList());
printArrayList(createArrayListAsList());
}
/** Prints any List */
public void printList(List someList) {
for (Object o : someList) {
System.out.println(o.toString());
}
}
/** Prints ArrayLists only */
public void printArrayList(ArrayList someList) {
for (Object o : someList) {
System.out.println(o.toString());
}
}
public List createVectorAsList() {
Vector v = new Vector();
v.add("I am a vector element");
return v;
}
public List createStackAsList() {
Stack s = new Stack();
s.add("I am a stack element");
return s;
}
public List createArrayListAsList() {
ArrayList ar = new ArrayList();
ar.add("I am an array list element");
return ar;
}
public ArrayList createArrayList() {
ArrayList ar = new ArrayList();
ar.add("My array is not returned as a list...");
return ar;
}
If you are in such a situation, you have two options. The first is to do an unchecked cast:
Object o = getObjectFromSomewhereMysterious();
List<String> lst = (List<String>)o; //compiler warning
This will cause the warning you mention - what it means is that, because of type erasure,
at runtime the cast can only check to make sure o is a List, but not a List<String>. So for example if o is a HashMap the cast will fail immediately, but if it's a List<Integer> it won't fail... until some later time when you try to treat an element of lst like a String. This is known as "polluting the heap":
//some later place in the code path - who knows, it could be miles away
String str = lst.get(0); //ClassCastException if lst isn't really a List<String>
As long as you can be sure that the cast is safe, you can suppress the compiler warning:
#SuppressWarnings("unchecked") //this is safe because blah blah blah
List<String> lst = (List<String>)o;
Always document why the warning can be suppressed - this helps to keep the code maintainable.
The second option is to play it safe:
List<?> lst = (List<?>)o;
This means lst is a List of some unknown type. It allows you to avoid the unchecked cast, but places restrictions on what you can do with lst:
lst.add("some string"); //compiler error
That statement is illegal since we don't know whether lst is allowed to hold Strings. The best we can do is read from it, but even then elements are only typed as Object:
Object element = lst.get(0);

Java Generic Method Question

Consider this code:
public <T> List<T> meth(List<?> type)
{
System.out.println(type); // 1
return new ArrayList<String>(); // 2
}
It does not compile at line 2, saying that List is required.
Now, if it's changed to:
public <T> List<?> meth(List<T> type)
{
System.out.println(type); // 1
return new ArrayList<String>(); // 2
}
It does compile. Why? I thought the difference between declaring a generic type with T and using the wildcard was that, when using the wildcard, one cannot add new elements to a collection. Why would <?> allow a subtype of List to be returned? I'm missing something here, what's the explicit rule and how it's being applied?
The difference is in the return type declaration. List<String> is not a subtype of List<T>, but it is a subtype of List<?>.
List<?> makes no assumptions regarding its type variable, so the following statements are valid:
List<?> l0 = new ArrayList<String>();
List<?> l1 = new ArrayList<Object>();
List<? extends Number> ltemp = null;
List<?> l2 = ltemp;
List<T> assumes that the type argument will be resolved in the context of client (e.g. type use), when you declared it as List<String> or List<Object>. Within the method body, you cannot make any assumptions about it either.
In the first case, T is not necessarily a superclass of String. If you choose a T like Integer and call the method, it'll fail; so it won't compile. However, the second will compile as surely, any ArrayList<String> is a valid List of something.
As said earlier, String isn't a subtype of T, so it's why it does not work. However, this code works :
public <T> List<T> meth(List<?> type)
{
System.out.println(type); // 1
return new ArrayList<T>(); // 2
}
and is more in the idea of what you want, I think.

Categories