How can an Integer be added to a String ArrayList? - java

List list = new ArrayList<String>() ;
list.add(1) ;
Integer hello = (Integer) list.get(0) ;
System.out.println(hello);
The above code has a reference of type List referring to an instance of ArrayList of type String. When the line list.add(1) is executed, isn't the 1 added to the ArrayList (of type String) ? If yes, then why is this allowed?

You have used type erasure, which means you have ignored previously set generic checks. You can get away with this as this as generics are a compile time feature which isn't checked at runtime.
What you have the same as
List list = new ArrayList() ;
list.add(1) ;
Integer hello = (Integer) list.get(0) ;
System.out.println(hello);
or
List<Integer> list = new ArrayList<Integer>() ;
list.add(1) ;
Integer hello = list.get(0); // generics add an implicit cast here
System.out.println(hello);
If you look at the byte code generated by the compiler, there is no way to tell the difference.
Interestingly, you can do this
List<String> strings = new ArrayList<String>();
#SuppressWarnings("unchecked");
List<Integer> ints = (List) strings;
ints.add(1);
System.out.println(strings); // ok
String s= strings.get(0); // throws a ClassCastException

The problem is that your list variable has a raw type and you can add objects of any type to this list. To solve the problem just declare it as a List of String's:
List<String> list = new ArrayList<String>() ;

It compiles because the declaration List list uses the raw type List, not the bound type List<String>. The second line compiles because your list variable can accept anything. The third line compiles because list.get(0) returns Object. The fourth line has no reason to fail.
By accident, nothing in the compiled code caused the Integer 1 to be cast to a String. But you must not depend on that. Had the first line been the proper
List<String> list = new ArrayList<>();
your mistake would have been caught at compile time.

When you declare a list like this:
List list = new ArrayList<String>() ;
You are using what's called a Raw Type. It's a type that has a generic type parameter, like List, but you failed to supply one. If you check the compiler warnings you will see it is telling you about this. Mixing parameterized types with raw types is generally considered a programming error. This is ok:
List<String> list = new ArrayList<String>() ;
and this is ok (but obsolete):
List list = new ArrayList();
But the way you wrote it should be avoided.
The problem is generics are checked only at compiler time, and you told the compiler List, not List<String>, so it will let you put anything you want in there! The fact that the right side has the parameter doesn't really mean anything, it's the type of the actual reference that matters to the compiler.

List list = new ArrayList<String>() ; // WARNING!!!
An unsafe List refers to a safe ArrayList
list.add(1) ; // WARNING!!!
An Integer (int 1 converted to Integer is Autoboxing) not a String added to the List
Integer hello = (Integer) list.get(0) ;
Casting is required because unsafe List can contain anything.
System.out.println(hello);
Integer.toString() called
When the line list.add(1) is executed, isn't the 1 added to the ArrayList (of type String) ?
Yes. Integer was added to an ArrayList that is supposed to contain String.
If yes, then why is this allowed?
Backward Compatibility. Java has to support pre-generic codes.
List list; means it is unsafe so you can do anything with it and the referred ArrayList() has to tolerate that.
Moral: Don't ignore the compiler warnings otherwise Integer might enter into an ArrayList that is supposed to contain Strings.

Yes... the 1 is addeded to Array, because Java, like other languages, implements implicit (automatic) conversion (casting) of primitives data types.
More information here Conversions and Promotions

Try this
List.add(Integer.toString(1));
You also can try this
List list = new ArrayList<Object>() ;
If there is no need that the arraylist must be a Arraylist of String

No, because Java sees the 1 as an Integer. You will need to convert your number as a String first with Integer.toString(n)

Related

Java - Generic types and collections [duplicate]

This question already has answers here:
Java generics type erasure: when and what happens?
(7 answers)
Closed 5 years ago.
I'm trying to learn about the use of generic types and I've noticed something weird when I was experimenting with some lines of code.
The first piece of code is inside a class named "A":
public void func(int k, List list) {
list.add(9);
list.add(true);
list.add("a string");
}
The second piece of code is in a different class, inside the main function:
List<Integer> arr = new ArrayList<Integer>();
arr.add(14);
System.out.println(arr.toString());
a.func(8, arr);
System.out.println(arr.toString());
Running the code results in this lines being printed:
[14]
[14, 9, true, a string]
This got me pretty confused since arr is an ArrayList of type Integer, how can it contain objects of type boolean and String? Is there a transformation of the list in the function func to a raw type (which mean it becomes of generic type Object)? And if so how is it possible since you cannot do this for example: List<Integer> arr = new ArrayList<Object>();?
Would love some clarification on this, maybe it will help me grasp this subject of generic types better. Thanks!
Java does not allow the creation of generic Arrays. The Java Collection Classes are mainly implemented using Object arrays. The ArrayList class may look like the following
public class ArrayList<T> implements List<T>, Serializable {
private transient Object[] data;
// more content...
}
When creating a new Instance of the ArrayList a new Object[] array is created that can hold objects of any type. Typesafety is only achieved through using the generic Type Parameter.
Since List did not provide any Type parameter it makes use of the rawtype and anything can be added to the list. Therefore always make sure to infer template arguments to keep the typesafety.
public void func(int k, List<Integer> list) {
list.add(9); // works
list.add(true); // compile error
list.add("a string"); // compile error
}
You should never use rawtypes. Depending on your compiler settings warnings will be omitted.
It's better to use (bound/unbound) wildcards.
Reason behind such kind of output is that you are passing List as an parameter to func( int k , List list ) .
And list in func method is non- generic which allows you to add string as well so you are getting such output.
List<Integer> arr = new ArrayList<Integer>();
arr.add(14);
System.out.println(arr.toString());
a.func(8, arr); // here you are passing a list
System.out.println(arr.toString());
public void func(List list) { // here List in non-generic
list.add(9);
list.add(true);
list.add("a string");
}
The whole point of a Generic code in Java is to provide type-safety before the actual code compilation. The compiled code have no type, that's why we don't face any problem in our code becuase when actually func() method is called, the List arr doesn't have any type.
When we're calling System.out.println(arr.toString()), we're just printing an object. This will fine on any type (i.e., int, boolean, String etc...). That's why we don't encounter any exception.
However, just try to assign a value from this list and we'll get java.lang.ClassCastException
...
func(8, arr);
System.out.println(arr.toString());
int a = arr.get(2);

Generics and Collections, don`t understand

Is there any difference between these 3 lines?
List list2 = new ArrayList<String>();
List list2 = new ArrayList<>();
List list2 = new ArrayList();
As I understand, all information about generics is erased in runtime. Consequently, only type of veriable is important. So the above lines of code mean the same thing to me. If I am wrong can anybody give me some exapmle that shows the difference?
p.s. sorry for my english
While it is true that generic data is erased at compile-time, that doesn't mean they are totally useless. The only line that would compile in your example is the last one, but you would get a warning that you are using RawTypes. A RawType is a generic class that does not have a generic object. The first two lines are only half-built.
The reason you pretty much have to use generics is that the list is type-safe. If you use generics, you could use something like this:
List<String> list = new ArrayList<>();
String out = list.get(0);
If you use raw types, you would have to do this:
List list = new ArrayList();
String out = (String) list.get(0);
This may seem OK, but what if you add a value to the list that is not a String? The program crashes. For example:
List unsafe = new ArrayList();
List<String> safe = new ArrayList<>();
unsafe.add("hi");
unsafe.add(new Tree());
safe.add("hi");
safe.add(new Tree()); // This line would throw an exception.
String out = (String) unsafe.get(0);
String out1 = safe.get(0);
String out2 = (String) unsafe.get(1); // This line would throw an exception.
If you still don't quite see why to use generic types, the final nail in the coffin for raw types is this: If my (String) unsafe.get(1) is in a separate class, on the hundredth line, I know that that line is the problematic line. I, however, don't know where the non-string object is being added, only where it's being accessed.
If you use generic types, you know exactly where the problematic addition is made, and you can prevent it.

Array of generic holders

If I have a map of Entry .. objects , and I have an array in a class
private Entry<K,V> array;
Can I say
array = new Entry[someInt];
which I've done, or do I need a typecast like my instructor says is necessary such as
array = (Entry<K,V> E[]) new Entry[someInt];
Note that the first one did work when I ran my JUnits.
Have you thought about doing a List ?
Entry<K,V> array = new ArrayList<Entry<K,V>>();
array.add(new HashMap<K,V>());
You can do either one. Implicit conversions to and from raw types are allowed without a cast.
array = new Entry[someInt]; will produce an unchecked conversion warning.
array = (Entry<K,V>[])new Entry[someInt]; will produce an unchecked cast warning.
So neither one is really better than the other.
Note that if you create the array with the wildcard type, you will need to have a cast:
array = (Entry<K,V>[])new Entry<?,?>[someInt]; // compiles
array = new Entry<?,?>[someInt]; // doesn't compile

Non-generic object of generic type

For following codes:
ArrayList<String> ar = new ArrayList<String>();
ar.add(45);
And
ArrayList<String> ar = new ArrayList();
ar.add(45);
I am getting compile time error at line ar.add(45) as:
cannot find symbol
symbol : method add(int)
location: class java.util.ArrayList<java.lang.String>
al.add(45);
^
Both piece of code is failing for invalid input . Then why compiler is raising warning of unchecked or unsafe operation for second piece of code?
Then why compiler is raising warning of unchecked or unsafe operation for second piece of code?
Because you're assigning an ArrayList to a variable with type ArrayList<String>. That means that while the compiler will enforce the expectation that the array list will only contain strings when you reference that list through ar, it can't be sure that you don't have other references to the non-parameterized ArrayList that you'll use to add non-strings to it, like this:
ArrayList anythingGoes = new ArrayList();
ArrayList<String> onlyStrings = anythingGoes; // Unchecked/unsafe op
anythingGoes.add(new Date());
for (String s : onlyStrings) { // Blows up
// ...
}
Because in the second code you do not specify the type parameter of the ArrayList. You could write it in Java 7 as:
ArrayList<String> ar = new ArrayList<>();
There are two distinct issues here.
Firstly -
ArrayList<String> ar = new ArrayList();
You're telling the compiler that ar is a list of strings, but you're assigning it to a list of raw types (i.e. unbounded). Hence the compiler will warn you of an unchecked or unsafe operation. You should use something like either option below:
ArrayList<String> ar = new ArrayList<String>();
or
ArrayList<String> ar = new ArrayList<>();
(the second option is a Java 7 example and simply reduces the amount of typing you have to do. The result is the same).
Secondly -
ar.add(45);
You're adding an integer (45) into a list of strings. The compiler won't allow you to do this.
Change your generic data type as Integer
if you want to add integers

What Benefit Generics provide over legacy Code:

i have read on docs.oracle site that The following code snippet without generics requires casting:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
but if i write code with Generics then it is still prone to Error:
List<Object>= new List<Object>;
list.add("hello");
String s=(String)list.get(0);
what is then the real use of generics....:( thnx in advance..
List<Object>= new List<Object>;
list.add("hello");
String s=(String)list.get(0);
Should be
List<String>= new ArrayList<String>(); // this is now a list of String, not a list of object
^^^^^^ ^^^^^^
list.add("hello");
String s=list.get(0); // no casting needed
^
You parameterize by the type you want. Your example are 2 ways to do the same thing, since you parameterize by the most basic class.
The advantage of generics is that you can write classes that are more specific to one class, String here. This gives you better type safety to catch bugs early during compilation. This prevents issues arising from the casting approach.
Using generics makes your code Type Safe. You can prevent ClassCastException.
Suppose you want to store a list of names(string)
List listNames = new ArrayList();
listNames.add("Durgesh");//ok
But I could also add an integer to it
listNames.add(5000);//storing int instead of string
Now do this
String name2=listNames.get(1);//throws exception{int->string}
Without generics you could add invalid types to collection which could break your code.
With generics you could solve the problem
List<String> listNames = new ArrayList();
listNames.add("Durgesh");
listNames.add(3000);//would through error at compile time
So,generics provides typesafety
With List<Object> you intend to add any kind of Object.Due to Object parameter,it would allow you to add any kind of object(string,int).
Also List<x> cannot be assinged(=) to List<y> or vice versa if x can be converted to y or y can be converted to x..They both should be x or y thus providing type safety
So,you wont be able to assign(=) List<String> to List<Object> or vice versa..
Generics are used to detect runtime exceptions at compile-time itself.
Assume that you created a List to store Strings and passed it to a method.. enhanceList(List).. and after the execution, you will iterate through the list and get all strings
before genercis, it could have been possible that enhanceList(List) method will add other type of objects into the list creating possible ClassCastException
void someMethod() {
List listOfStrings = new List();
enhanceList(listOfStrings);
for(Iterator i : listOfStrings.iterator(); i.hasNext();) {
String s = (String) i.next(); //RuntimeException here
}
}
void enhanceList(List l) {
l.add(new Integer(1)); //error code
}
with generics, you can very well "bind" the type of objects the list contains
void someMethod() {
List<String> listOfStrings = new List<String>();
enhanceList(listOfStrings);
for(String s : listOfStrings) {
//no error here
}
}
void enhanceList(List<String> l) {
l.add(new Integer(1)); //compile-time error
}
However, generics should be used with caution, List<Object> doesn't help much with binding types because, it can hold any objects (since Object is super class of all the java classes). I recommend to create List of Specific type always.

Categories

Resources