I have the next code:
ArrayList value = new ArrayList<Integer>(); // 1
value.add("Test"); // 2
I'm trying to understand line 2. Although I can see that value.add("Test"); compiles without errors, I can't see the reason it doesn't throw a runtime exception. If value is referencing a generic ArrayList object, why Java allows to add a String to it? Can anyone explain it to me?
The closest explanation I've found about this is described here, but I still don't understand the core reason:
Stack s = new Stack<Integer>()
This is a legal conversion from a parameterized type to a raw type. You will be able to push value of any type. However, any such operation will result in an "unchecked call" warning.
Generic types are erased during compilation. So at runtime, an ArrayList is a raw ArrayList, no matter if you defined it as generic or not.
In your case, the code compiles as your ArrayList declaration is not generic, and it runs fine because of type erasure.
ArrayList value this is your type declaration which is not generic. That is why compiler allows you to add any Object to the list.
Related
This question already has answers here:
What is a raw type and why shouldn't we use it?
(16 answers)
Closed 3 years ago.
Java does not allow creating generic arrays directly. I understand that due to erasure generic type is not known at runtime, whereas array require type checking at runtime and hence the two are incompatible.
This piece of code does not compile -
Holder<Integer>[] integers = new Holder<Integer>[5];
Fine, but I am unsure why does this piece of code actually compile (with warning for unsafe type conversions)?
Holder<Integer>[] holders = new Holder[5];
holders[0] = new Holder<Integer>(5);
holders[1] = new Holder<Integer>(5);
holders[2] = new Holder<Integer>(5);
I don't exactly understand what did I actually trick the compiler into by removing diamond brackets. Is this an acceptable to create generic arrays?
Further, when I add this line to the code - holders[3] = new Holder<String>("Hello");
It throws a compile error Holder<String> can not be converted to Holder<Integer>
I find this strange because as far as I understand the whole idea of not allowing generic arrays was because arrays could not differentiate between 2 different generic types because of the type erasure. But in this example the compiler can detect incorrect type conversions.
What am I missing here?
On this page, you can see exactly why creating arrays of generic types is not allowed:
Object[] stringLists = new List<String>[]; // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>(); // OK
stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException should be thrown,
// but the runtime can't detect it.
stringLists should only be able to store List<String>, but by using the above code, I can not only trick the compiler, but also the runtime, into allowing me to store a ArrayList<Integer> into stringLists, due to type erasure.
but I am unsure why does this piece of code actually compile
Well, because Holder is a raw type. See What is a raw type and why shouldn't we use it?. It's perfectly fine, as far as the compiler and runtime is concerned, to create an array of a raw type, because here you are not saying that "this array can only store Holder<Integer>", you are just saying "this array can only store Holder (of anything)".
Is this an acceptable to create generic arrays?
Well, your array is technically not generic. I can assign it to a Holder[] and assign a Holder<Foo> to one of its elements, and no exception or compiler errors will occur. As far as the compiler is concerned, this is "acceptable", but because you lose type-safety, I don't recommend you use it. You should use something like ArrayList<Holder<Integer>> instead.
I find this strange because as far as I understand the whole idea of not allowing generic arrays was because arrays could not differentiate between 2 different generic types because of the type erasure. But in this example the compiler can detect incorrect type conversions.
The compiler can detect it not because the array doesn't allow you to put in Holder<String>, but because the variable's compile time type is Holder<Integer>[]. The compiler can still check the type by looking at the compile time types, but as soon as you lose the compile time type (assigning it to a variable of type Object[] or Holder[]), then it can't do it for you. The array itself allows any kind of Holder in the first place anyway, because it is a Holder[].
This question already has answers here:
Difference between List, List<?>, List<T>, List<E>, and List<Object>
(10 answers)
Closed 9 years ago.
I was reading about unknown types and raw types in generics, and this question came to mind. In other words, is...
Set<?> s = new HashSet<String>();
and
Set s = new HashSet<String>();
... one and the same?
I tried it out, and they both seem to accomplish the same thing, but I would like to know if they are any different to the compiler.
No, they are not the same. Here's the basic difference:
Set<?> s = HashSet<String>();
s.add(2); // This is invalid
Set s = HashSet<String>();
s.add(2); // This is valid.
The point is, the first one is a unbounded parameterized type Set. Compiler will perform the check there, and since you can't add anything but null to such types, compiler will give you an error.
While the second one being a raw type, the compiler won't do any check while adding anything to it. Basically, you lose the type safety here.
And you can see the result of loosing type safety there. Adding 2 to the set will fail at compile time for Set<?>, but for a raw type Set, it will be successfully added, but it might throw exception at runtime, when you get the element from the set, and assign it to say String.
Differences apart, you should avoid using raw types in newer code. You would rarely find any places where you would use it. Few places where you use raw type is to access static fields of that type, or getting Class object for that type - you can do Set.class, but not Set<?>.class.
The first one create a Set<?>, which means: "a generic Set of some unknown class". You won't be able to add anything to this set (except null) because the compiler doesn't know what its generic type is.
The second creates a raw, non generic set, and you can add anything you want to it. It doesn't provide any type-safety.
I don't see why you would use any of them. Set<String> should be the declared type.
The first one uses generics and the second one uses the raw form of Set.
The first one uses a wildcard as the generic type parameter. It means, "a Set of some specific yet unknown type", so you won't be call methods such as add that take a generic parameter, because the compiler doesn't know which specific type it really is. It maintains type safety by disallowing such a call at compile time.
The raw form removes all generics and provides no strong typing. You can add anything to such a Set, even non-Strings, which makes the following code not type-safe:
Set<String> genericSet = new HashSet<String>();
Set rawSet = genericSet;
rawSet.add(1); // That's not a String!
// Runtime error here.
for (String s : genericSet)
{
// Do something here
}
This would result in a runtime ClassCastException when the Integer 1 is retrieved and a String is expected.
Maintaining as much generic type information as possible is the way to go.
Set<String> s = HashSet<String>();
Set<?> tells the compiler that the set contains a specific type, but the type is unknown. The compiler uses this information to provide errors when you attempt to invoke a method with a generic parameter, like add(T).
Set tells the compiler that the set is a "raw" type, where no generic type parameter is given. The compiler will raise warnings, rather than errors, when the object's generic methods are invoked.
In order to add elements to the set without warnings, you need to specify the generic type information on the variable. The compiler can infer the type parameters for the constructor. Like this:
Set<String> s = new HashSet<>();
This information allows the compiler to verify that the Set is used in a type safe way. If your code compiles without type safety warnings, and you don't use any explicit casts, you can be assured that there will be no ClassCastException raised at runtime. If you use generics, but ignore type safety warnings, you might see a ClassCastException thrown at a point where you don't have a cast in your source code.
I've a doubt reading this written in the Java tutorial:
In the introduction, we saw invocations of the generic type
declaration List, such as List. In the invocation (usually
called a parameterized type), all occurrences of the formal type
parameter (E in this case) are replaced by the actual type argument
(in this case, Integer).
but if there are no restrictions the formal type parameter is not replaced by Object?
Why is said that E is replaced by Integer?
Also, here, in the Java tutorial is said:
To reference the generic Box class from within your code, you must
perform a generic type invocation, which replaces T with some concrete
value, such as Integer:
but, again, thanks to the erasure a compile time T in box class is replaced
by Object and not by Integer. Integer type is written only for casting operations.
In fact, still in the same tutorial is said:
During the type erasure process, the Java compiler erases all type
parameters and replaces each with its first bound if the type
parameter is bounded, or Object if the type parameter is unbounded.
I'm really confused. Which is the truth?
Is T replaced by Integer or by Object?
You speak of different things.
The citations from the tutorial speak about type instantiation. This has nothing to do with type erasure, which is a IMHO misnamed concept, and simply means that the generic types are not available at runtime anymore.
But at compile time they are, and instantiation happens at compile time.
To answer your question, "at compile time" is a broad thing. THe following hapens all at compile time:
read source files
lexical analysis
parsing
...
type checking
...
code generation
The list is, by no means, complete, mind you.
However, as you see, during type checking, the compiler knows your type instantiations and can check them.
Later, it emits byte code, and since byte code has no way of representing generics, the types are "erased", which means, a cast is inserted here and there.
So, your assumption that "compile time" is somehow an instant where everything happens at once is not correct.
Further edit:
I think you take all this (i.e. the word "replace") too literally. For sure, the compiler has some data structures where the types and names and scopes of all items in the program are held.
Look, it's quite simple in principle, if we have:
static <X> List<X> meth(X[] arr) { .... }
And later, you do:
Integer arr = new Integer[100];
List<Integer> list = meth(arr);
Integer foo = list.get(1);
then you are instantiating the type of the meth method:
static List<Integer> meth(Integer[] arr) { .... }
The point of the generics is to say that meth works for any type. This is just what the compiler checks. And it will know, that, for all X if you pass an array of X, you get back a list of X, hence, since you passed Integer[], the result must be List<Integer> and the list assignment is correct. Furthermore, the compiler knows, that ** for all X **, if you get an element from a List<X>, it will be an X.
Therefore, the compiler notes and checks that foo is an Integer. Later, on code generation, it will insert there a cast to Integer, because, due to type erasure, the return value from List.get is Object.
Note also, that "replace" does not mean that the compiler somehow alters your code. It just creates (maybe temporary) from the generic type signature a non-generic one (by substituting - if you like this better - all the type parameters with their actual types), and uses this to check the type.
It is just like in math, if I say: Please replace the a with 42 and check if the equation is true:
a + 1 = 43
then it makes no sense to ask "where exactly" this replacement takes place. Most probably in your brain.
the formal type parameter is not replaced by Object?
Generic type represented as Object in runtime. But you can get information about <YourType> with reflection. Erasure relates to compatibility with old clases. It was a bad idea. Article about it.
by using generics, we detect any possible during compilation.
for example,
List<String> list = new ArrayList<String>();
//list.add(new Integer(45)); This will cause compilation error.
list.add("car");
list.add("bus");
list.add("bike");
String vehicle = list.get(0); //compiler-generated cast
when we use raw type instead of generics before Java 1.5, it needs explicit casting.
for example,
List list2 = new ArrayList();
list.add("car");
list.add("bus");
list.add("bike");
String vehicle = (String)list.get(0); //explicit casting is necessary
however with generics, type erasure occurs. that is the type information is lost in runtime.
if, that is so, how does the JVM know what object type it is retrieving during runtime, whether it is a string object or a person object (compiler generated cast above). but this valid with generics, which is can cause runtime errors.
List<Object> test = new ArrayList<Object>();
test.add("hello");
test.add(new Integer(34));
finally, Joshua Bloch mentions on page 115 (item 23, effective java) that
Set<Object> is parameterized type representing a set that can contain objects of any type,
Set<?> is a wild card type representing a set that can contain only objects of some unknown type
and Set is a raw type, which opts out of the generic type system.
I do understand what he means by the above statement. some clarifications will help
The compiler inserts cast operations when retrieving items from generic methods; this is the only way that the JVM knows to treat the result of list.get(0) as a String. This is why heap pollution (inserting the wrong type of object into a generic collection) can result in a ClassCastException at runtime.
Regarding the wildcards:
Set<Object>'s generic type is exactly Object. You can insert and retrieve Object instances from it, but you can't pass a Set<Integer> to a method expecting a Set<Object>, since the method might be planning to add a non-Integer object to the set.
Set<?> has an unspecified generic type. A method can retrieve anything from it as an Object (since everything is an Object) and can call universal methods on it like hashCode or toString, but it can't add anything to the set.
Set, as you mention, is the raw type and shouldn't be used in new code.
I am not very sure, but what I understand by type information is lost at runtime is that there is no way at runtime that a collection is of some specific type. If you add a String to a collection, it will be a String only but the collection does not enforce that all elements should be of type String
Generics are implemented by Java compiler as a front-end conversion
called erasure. Type erasure applies to the use of generics. When
generics are used, they're converted into compile time checks and run
time type casts.
Due to type erasure mechanism this code:
List<String> a = new ArrayList<String>();
a.add("foo");
String x = a.get(0);
gets compiled into this:
List a = new ArrayList();
a.add("foo");
String x = (String) a.get(0);
Notice extra cast inserted into compiled compiled-code after type erasure.
PS: #chrylis has already provided good explanation about your 2nd part of question.
Well, this stackoverflow question here can help you .
Eclipse might be using this method to find out the fields in a class and their generic type if any. Please have a look.
I just stumbled upon the compiler treating these two terms differently. when I type:
LinkedList<String> list = new LinkedList();
I get a compiler warning about a raw type. however:
LinkedList<String> list = new LinkedList<>();
removes the warning. It seems to me as though the two statements mean essentially the same thing (i.e. create a new LinkedList with no specified object type). Why then does the complier all ow the empty generics? What is the difference here?
The statements do not mean the same thing at all.
The first statement tries to fit an untyped LinkedList into a declared generic LinkedList<String> and appropriately throws a warning.
The second statement, valid in Java 1.7 onward, uses type inference to guess the type parameter by using the declaring type's type parameter. In addition, sometimes this can be used in method calls. It doesn't always work, however.
See this page for more info.
It's the diamond operator in Java 7, that helps you save writing the type again. In Java 7 this is equivalent to the same generic type argument that is used on the left side of the declaration. So the initialization is type safe and no warning is issued.
With LinkedList<>, you use the new Diamond Operator, from java 7.
The Diamod operator uses the generic value setted in the left side of the line.
In Java 6, this doesnt works!
The diamond operator, however, allows the right hand side of the
assignment to be defined as a true generic instance with the same type
parameters as the left side... without having to type those parameters
again. It allows you to keep the safety of generics with almost the
same effort as using the raw type.
I think the key thing to understand is that raw types (with no <>)
cannot be treated the same as generic types. When you declare a raw
type, you get none of the benefits and type checking of generics. You
also have to keep in mind that generics are a general purpose part of
the Java language... they don't just apply to the no-arg constructors
of Collections!
Extracted from: https://stackoverflow.com/a/10093701/1281306
Backword compatibility (Inter-operating with legacy code) is the reason why java allows above signature. Generics are compile time syntax only. At runtime "all generic" syntax will be removed. You will just see if you de-compile any class file. Read this documentation.
LinkedList list = new LinkedList();