Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't add(String) on a List<Integer> passed to a List parameter throw an exception?

Tags:

java

generics

Why is it possible to insert a String into a List<Integer> in the following code? I have a class which inserts numbers into a List of Integers:

public class Main {    
    public static void main(String[] args) {        
        List<Integer> list = new ArrayList<Integer>();
        list.add(2);
        list.add(3);
        list.add(4);

        Inserter inserter = new Inserter();
        inserter.insertValue(list);
        System.out.print(list);        
    }
}

Then I have a separate class which inserts a String into a List, with the numeric string value "42":

public class Inserter {    
    void insertValue(List list)
    {
        list.add(new String("42"));
    }
}

Why does the compiler not raise a compiler error, or the runtime throw a runtime exception such as *CastException, when I add the String to the List of Integer? Also, why does System.out.print(list) produce output like the following without throwing any exceptions?

[2, 3, 4, 42]

What is the fundamental reason that allows all this to happen?

like image 641
java_user Avatar asked Jan 30 '15 13:01

java_user


People also ask

Can we add String to a integer List?

Let's discuss certain ways in which we can perform string append operations in the list of integers. Method #1 : Using + operator + list conversion In this method, we first convert the string into a list and then perform the task of append using + operator.

Can we add String to integer List in Java?

But a cast does not need to be inserted to call the Object overload, so an exception doesn't throw. Using a raw type List to add String to a List<Integer> is called heap pollution. The code in the question is an example of why heap pollution is so bad: because sometimes we don't find out about it.

Can you pass List String to the method which accepts List object?

Any Collection can be passed as an argument to the constructor as long as its type extends the type of the ArrayList , as String extends Object . The constructor takes a Collection , but List is a subinterface of Collection , so you can just use the List<String> .


1 Answers

This is probably an example to illustrate type erasure for generics (I recommend reading that link to fully understand this and the crucial role it plays in Java generics).

  1. list is declared as a List<Integer>
  2. When it is passed as an argument to listValue it is cast to a raw type
  3. From this point onwards in that method the program at run time has no knowledge that it was originally a "list of Integer", so it can insert any object with no exception - within the class declaration the type variable is erased to Object
  4. In the main method the print command simply calls toString on the list, which doesn't mind what it contains, and so it prints the elements including the string.

If you want to see an exception, try adding a line:

Integer myInt = list.get(3); // try to get the string

This will throw a ClassCastException as the compiler, during erasure, inserts casts where necessary to protect type safety.

Casts of parameterized types, such as List<Integer> to the raw type, such as List, give you a compiler warning to tell you that exactly this kind of problem might be about to happen. You can suppress that warning with @SuppressWarnings("unchecked") (or "rawtypes", if it is available to you). This is a good way of "acknowledging" that you are about to do something that will lead to an unchecked runtime exception, and also helps tell other future coders that something funky might be about to happen. E.g.:

void insertValue(@SuppressWarnings("unchecked") List list)
{
    list.add(new String("42"));
}
like image 83
Andy Brown Avatar answered Sep 23 '22 03:09

Andy Brown