Java: Meaning of non-generic pointer pointing at an generic object? - java

I'm quite new to Java (and english), so please bear with me.
Tried to write something like...
Container con = new Container<Book>();
con.insert(new Book());
con.insert(new Car());
...and did not get any type of error. But lines like...
Car c = con.remove(); // removes the last inserted element for simplicity
said "error: incompatible types", so I changed it to
Object carObj = (Car) con.remove();
and it worked. My problem is: when I say
new Container<Book>();
I create a container that can only hold objects of type Book, but because of the pointer (which is non-generic?) I can suddenly put any kinds of objects in my container. What happened here? The pointer only sees the Object-personality in whatever is in the Container, but I didn't know the pointer allowed every object with Object-personality in a container mainly created as generic (my formulation might be wrong). So when I have a non-generic pointer, it doesn't matter whether I create a generic or non-generic container? It will always be considered as a non-generic container (where I have to cast objects when I remove them)?
new Container<Book>().insert(new Car()); // compiler error as excepted
Got curious and made the problem even worse (maybe).
Container<Car> cars = new Container();
cars.insert(new Book()); // compiler error: required Car, found Book
Now the pointer only sees the Car-personalities in the container. But it won't allow me to put in a book even though I created the container as non-generic. Why?
new Container().insert(new Car()); // works fine
Must say, it's both fascinating and irritating...

You're operating on the reference: the reference's type is what will be used at compile time. Inserting a Book into a Container<Car> is clearly wrong, just as there's nothing wrong with inserting either a Book or a Car into a Container.
Similarly, expecting a Container.remove to return a Car when the reference is simply <Container> is incorrect, because there's no reason to expect the returned object to be a Car–it might be a Book or a fish.

Your container is a raw container and not a generic container. It's declared as Container. It should be declared as Container<Book>.
Once done, the line
con.insert(new Car());
won't compile anymore.
In Java, the generic type of an object is only a compile-time thing. At runtime, due to erasure, it's just a Container. So if you don't declare the container as a Container<Book>, you'll have a raw Container and the compiler won't check anything about the type of objects you store inside.
To make it clearer (at least I hope so), the line
Container con = new Container<Book>();
is equivalent to
Container con = (Container) (new Container<Book>());
It transforms a reference to a Container<Book> into a reference to a raw Container, ruining its type-safety.

Java added generics after there was already lots of code written without generics. The designers of generics wanted to change standard library containers like List into List<X>, but there was already lots of code written to take just plain List that would no longer compile if they'd required all uses to become List<X> uses. Furthermore, they wanted people using legacy libraries of their own that, for instance, asked for a List and expected it to contain only String instances to be able to pass in a List<String>.
The way they dealt with this was to introduce the notion of a "raw type" for every generic type, which is basically just the type you get by not writing the angle brackets after a generic type (e.g. List instead of List<String>). Raw types have different type-checking rules then their non-raw equivalents; for instance if you have a List then it's legal to add any Object into it, but when you get something back it comes back as an Object that you need to downcast, whereas a List<String> will only allow you to add a String, but in return you don't need to downcast get's result to get a String back.
Unfortunately, due to the backwards-compatibility rule that the language designers made, they allowed you to write expessions like
List rawList = new ArrayList<String>();
rawList.add(new PeanutButterSandwich());
without a compile-time error. You can even do worse things, like
List<String> stringList = new ArrayList<String>();
stringList.add("string");
List rawList = stringList;
List<PeanutButterSandwich> sandwichList = (List<PeanutButterSandwich>) rawList;
PeanutButterSandwich sandwich = sandwichList.get(0);
which compiles (with a warning about an unchecked cast, meaning you made a cast to a generic type from a raw type but the compiler doesn't know that it's legal). Of course this code will definitely raise an exception at runtime since the element in position 0 is a String, not a PeanutButterSandwich.
What's important to remember is that raw types are only for backwards compatibility, and you shouldn't use them in new code. Also, if you're ever dealing with raw types, be very careful when casting them to generic types, since the compiler can't stop you from doing wrong things.

Related

Java reflection get runtime type when using generics

I am wondering how can I get the runtime type which is written by the programmer when using generics. For example if I have class Main<T extends List<String>> and the programmer write something like
Main<ArrayList<String>> main = new Main<>();
how can I understand using reflection which class extending List<String> is used?
I'm just curious how can I achieve that. With
main.getClass().getTypeParameters()[0].getBounds[]
I only can understand the bounding class (not the runtime class).
As the comments above point out, due to type erasure you can't do this. But in the comments, the follow up question was:
I know that the generics are removed after compilation, but I am wondering how then ClassCastException is thrown runtime ? Sorry, if this is a stupid question, but how it knows to throws this exception if there isn't any information about classes.
The answer is that, although the type parameter is erased from the type, it still remains in the bytecode.
Essentially, the compiler transforms this:
List<String> list = new ArrayList<>();
list.add("foo");
String value = list.get(0);
into this:
List list = new ArrayList();
list.add("foo");
String value = (String) list.get(0); // note the cast!
This means that the type String is no longer associated with the type ArrayList in the bytecode, but it still appears (in the form of a class cast instruction). If at runtime the type is different you'll get a ClassCastException.
This also explains why you can get away with things like this:
// The next line should raise a warning about raw types
// if compiled with Java 1.5 or newer
List rawList = new ArrayList();
// Since this is a raw List, it can hold any object.
// Let's stick a number in there.
rawList.add(new Integer(42));
// This is an unchecked conversion. Not always wrong, but always risky.
List<String> stringList = rawList;
// You'd think this would be an error. But it isn't!
Object value = stringList.get(0);
And indeed if you try it, you'll find that you can safely pull the 42 value back out as an Object and not have any errors at all. The reason for this is that the compiler doesn't insert the cast to String here -- it just inserts a cast to Object (since the left-hand side type is just Object) and the cast from Integer to Object succeeds, as it should.
Anyhow, this is just a bit of a long-winded way of explaining that type erasure doesn't erase all references to the given type, only the type parameter itself.
And in fact, as a now-deleted answer here mentioned, you can exploit this "vestigial" type information, through a technique called Gafter's Gadget, which you can access using the getActualTypeArguments() method on ParameterizedType.
The way the gadget works is by creating an empty subclass of a parameterized type, e.g. new TypeToken<String>() {}. Since the anonymous class here is a subclass of a concrete type (there is no type parameter T here, it's been replaced by a real type, String) methods on the type have to be able to return the real type (in this case String). And using reflection you can discover that type: in this case, getActualTypeParameters()[0] would return String.class.
Gafter's Gadget can be extended to arbitrarily complex parameterized types, and is actually often used by frameworks that do a lot of work with reflection and generics. For example, the Google Guice dependency injection framework has a type called TypeLiteral that serves exactly this purpose.

Why in Java generics right hand side type of the collection does not have any effect?

Using Java's Generics features I created a List object and on the left hand side I am using the raw type List where on the right hand side I am using the generic type ArrayList< String >.
List myList=new ArrayList<String>();
And I added one int value into the list object.
myList.add(101);
I was hoping that I will get some compilation error but this program is running fine.But if I use generic type List< String > on the left hand side and raw type ArrayList on the right hand side and try to add an int value into the list, I am getting compilation error as expected.
List<String> myList=new ArrayList();
myList.add(101);//The method add(int, String) in the type List<String> is not applicable for the arguments (int)
Why in Java generics right hand side type of the collection does not have any effect? And why Java allowing us to do so when it does not have any effect.I am using Java 1.6. Please explain.
If you don't supply a generic type parameter on the left-hand side, the List is declared as a raw type. This means the compiler doesn't know what is legal or not to store in that list, and is relying on the programmer to perform appropriate instanceof checks and casts.
Raw types also have the effect of obliterating all generic type information in the class they appear in.
The JLS provides a much more detailed look at raw types. You should be seeing a warning in your IDE or from the compiler about the assignment to a raw type as well:
To make sure that potential violations of the typing rules are always
flagged, some accesses to members of a raw type will result in
compile-time unchecked warnings. The rules for compile-time unchecked
warnings when accessing members or constructors of raw types are as
follows:
At an assignment to a field: if the type of the left-hand operand is a
raw type, then a compile-time unchecked warning occurs if erasure
changes the field's type.
At an invocation of a method or constructor: if the type of the class
or interface to search (§15.12.1) is a raw type, then a compile-time
unchecked warning occurs if erasure changes any of the formal
parameter types of the method or constructor.
No compile-time unchecked warning occurs for a method call when the
formal parameter types do not change under erasure (even if the result
type and/or throws clause changes), for reading from a field, or for a
class instance creation of a raw type.
Tom G's answer is nice and explains things in detail, but I get the feeling that you already know at least some of that stuff, because you said this:
I was hoping that I will get some compilation error
So, let me address precisely that part.
The reason you are not getting any compilation error is because generics were added as an afterthought in java, and for this reason many generics-related issues which ought to be errors have instead been demoted to warnings in order to not break existing code.
And what is most probably happening is that these warnings are turned off in your development environment.
Steps to correct the problem:
Go to the options of your IDE
Find the "warnings" section.
Enable EVERYTHING.
Pick your jaw from the floor after you have seen the enormous number
of warnings you get.
Disable all the warnings that do not make any sense, like "hard-coded string" or "member access was not qualified with this", keep everything else. Be sure that the one which says something like "Raw use of parameterized class" is among the ones you keep.
At a glance it looks like myList can only store String
At a glance, perhaps. But it's really important to realize that there is no such thing as "a list that can only store Strings", at least in the standard APIs.
There is only List, and you have to include the right instructions to the compiler to berate you if you try to add something that's not a String to it, i.e. by declaring it as List<String> myList.
If you declare it as "plain old List", the compiler has no instructions as to what to allow or disallow you to put into it, so you can store anything within the type bounds of the backing array, namely, any Object.
The fact that you say new ArrayList<String>() on the RHS of the assignment is irrelevant: Java doesn't attempt to track the value assigned to a variable. The type of a variable is the type you declare.
"... generics right hand side of type of the collection does not have any effect" is mostly true. When the "var" keyword is substituted for "List", the right hand side generic does have an effect. The code below creates an ArrayList of Strings.
var myList = new ArrayList<String>();

java arrays of parametric types

This is not a question how to do things. It is a question of why is it the way it is.
Arrays in Java know their component type at run time, and because of type erasure we cannot have array objects of generic type variables. Array types involving generics are allowed and checked for sound read/write, the only issue seem to be the allocator expressions.
Notice that the Java compiler also disallows the following:
Pong<Integer> lotsofpong [] = new Pong<Integer>[100];
...where Pong is just any old parametric class. There is nothing unknown here. Yes, at run-time, lotsofpong would just be an array of Pong, but I cannot see a reason why the compiler cannot remember the type parameter for compile-time purposes. Well, it actually does remember it, because those types exist at compile time, so the only problem seems to be the refusal to give the allocator at compile-time a particular generic-parameter-involving component type.
Even if the parameter of Pong was a generic type variable that should not make a difference either. The dynamic array would still be an array of Pong, requiring per element the size of a Pong, which does not depend on its type parameter.
Yes, I know there are ways around it - either use casts (perhaps with SuppressWarning) from the non-parametric type, or subclass Pong<Integer> with a non-parametric class and use that type instead. But is there a reason why this kind of allocator is not allowed?
Based on the link that was provided by Zeller (based on Josh Bloch - 'Effective Java Book').
Arrays are not safe because the following code will compile:
String strings [] = {"Foo"};
Object objects [] = strings;
objects[0] = 1;
You will get a special exception at run-time: java.lang.ArrayStoreException.
Java run-time cheks at run-time that you put an appropriate type into the array.
Assigning an array to an array of its super type is called 'Covariance'.
Generics are guaranteed to be safe at compile-time.
If the code snippet that you mentioned in the question was able to be compiled, the following code would also compiles:
Pong<Integer> integerPongs [] = new Pong<Integer>[100];
Object objectPongs [] = integerPongs;
objectPongs[0] = new Pong<String>();
Pong<Integer> stringPong = integerPongs[0]; // ClassCastException
Our code becomes not safe therefore it was forbidden by the specification.
The reason that :
objectPongs[0] = new Pong<String>();
does not throw java.lang.ArrayStoreException is because the run-time type of each instance of Pong is always Pong since Generics is a compile-time mechanism.

Java Error: New Generic TreeNode Array

I have generic class of TreeNode:
public class TreeNode<E> {
public E key;
public int num_of_children;
public TreeNode<E> [] children;
public TreeNode(int num_of_children)
{
this.num_of_children = num_of_children;
children = new TreeNode[num_of_children];// Why not: new TreeNode<E>[num_of_children]?
}
public TreeNode<E> clone()
{
TreeNode<E> node = new TreeNode<E>(num_of_children);
return node;
}
}
When I try to do:children = new TreeNode<E> [num_of_children];
I get error. But "new TreeNode[num_of_children]" works.
I read about type erasure, and I don't understand why TreeNode<E>[] doesn't work.
Why is that? Please enlighten me!
Things like new TreeNode<String>[] and new TreeNode<E>[] are disallowed by Java. The only things you can do are new TreeNode[] and new TreeNode<?>[] (unbounded wildcard parameter).
The reason for this is a little complicated, but instructive. Arrays in Java know their component type at runtime, and every time you put something in, it checks to see if it's an instance of the component type, and if not, throws an exception (this is related to how array types are covariant and therefore inherently unsafe at compile time).
Object[] foo = new Integer[5];
foo[2] = "bar"; // compiles fine, but throws ArrayStoreException at runtime
Now add generics. The problem with a generic component type is that, there is no way for you to check if an object is an instance of say, TreeNode<Integer> at runtime (as opposed to TreeNode<String>), since generics are erased from runtime types. It can only check TreeNode, but not the component type. But programmers might have expected this checking and exception throwing behavior from arrays, since it normally works. So to avoid this surprise failure, Java disallows it. (In most code, you won't run into this problem anyway because you won't be mixing objects of the same type but different type parameters. But it is theoretically possible to come up.)
Of course, you can simply work around the problem by creating an array of raw or wildcard parameter type, and then casting to the proper type, e.g. (TreeNode<Integer>)new TreeNode[5]. What's the difference? Well, that's an unchecked cast, which generates a warning, and you, the programmer, takes responsibility for all the unsafe things that might happen later. If it does something unexpected, the compiler can say, "we told ya so!".
Because the Java Language Specification writes:
An array creation expression creates an object that is a new array whose elements are of the type specified by the PrimitiveType or ClassOrInterfaceType.
It is a compile-time error if the ClassOrInterfaceType does not denote a reifiable type (§4.7). Otherwise, the ClassOrInterfaceType may name any named reference type, even an abstract class type (§8.1.1.1) or an interface type (§9).
The rules above imply that the element type in an array creation expression cannot be a parameterized type, other than an unbounded wildcard.
It is not clear to me why they require this. Certainly, the component type of the array must be available at runtime, and it would be misleading for the programmer if it were different from the type specified in the source code. Consider:
E[] a = new E[10];
Here, it would be bad if the compiler used the erasure of E as the array component type, as the programmer might well depend upon the array to check that nothing but instances of E is stored in it.
It's less clear what harm would come from allowing:
List<E>[] lists = new List<E>[10];
The only thing that comes to mind is that assigning an array element would amount to an unchecked cast, because the array would check the element is a List, but not that it is a List<E>, and thus fail to throw an ArrayStoreException.
In practice, you can safely suppress this warning as long as you remain aware that the array will not check the type parameters of its component type.

How can I pass the contents of a list to a varargs method?

I have a method that uses the varargs feature:
void add(Animal ...);
Now, instead of doing .add(dog, cat), I have an Animal list with unknown number of elements,
List<Animal> i = new ArrayList<Animal>();
i.add(dog);
i.add(cat);
and want to call add with the elements of this list.
I think I could use an array, but when I do .add(i.toArray()), it gives a compiler error.
What is the proper way to do it?
It's:
add(i.toArray(new Animal[i.size()]))
List.toArray returns an Object[], regardless of the type argument on the List: even though you might write new List<String>().toArray(), you will get an Object[]. However, the version of toArray that takes an array to fill returns an array with the correct type: if you write new List<String>().toArray(new String[0]), you will get an String[]. Note that the size of the array you pass in doesn't even have to match the size of the list, although it's good practice to ensure that it does.
This is ultimately due to a mildly tricky feature of generics. At first glance, you might think that String[] and List<String>mean similar things for their base types - one is an array of strings, the other is a list of strings.
However, they are in fact very different.
An array is a language primitive, and has its type baked into it. If you took a hex editor and looked at an array instance in memory in the JVM, you would be able to find (somewhere nearby) a record of the type of objects it holds. That means that if you take an instance of an array of some unknown component type, you can find out what that type is. Conversely, it means that if you're going to create an instance of an array, you need to know what component type you want.
The List, on the other hand, uses generics, which in Java is implemented with type erasure, which means that, roughly speaking, it is something that exists in the compiler, but not at runtime (the compiler can check that you get it right, but the JVM can't). This leads to a simple and efficient implementation (one simple enough to have been added to pre-generics Java without changing the JVM), but it has some shortcomings - in particular, that at runtime, there is no way to tell what the type argument on any particular instance of a generic class is, because type arguments only exist in the compiler. Because it is up to the List instance to handle toArray(), the only thing it can do is create an Object[]. It just doesn't know of a more specific type to use.
One way of looking at this is that arrays have a type argument as part of their class, whereas Lists have a type argument as part of their type, and since objects have classes but variables have types, you can't get the type argument of a List from an object, only from a variable holding an object (as an aside, you also can't get the type argument of an array from a variable holding an array (consider Object[] array = new String[0];), but that doesn't really matter because, the variable lets you get hold of an object - unless it's null).
To boil this down to code, the problem is:
public <E> E[] createSimilarlyTypedArray(List<E> list) {
Class<E> componentType = list.???; // there is no way to do this
return Arrays.newInstance(componentType, list.size());
}
when I do .add(i.toArray()) it gives an error, what is the proper way
to do it?
Use foo.addAll(i) and then convert foo to an array if need be.
Your method void add(Animal...) expects a object of the Animal class or a array with Animal objects in it. You give it an array with objects of the Object class. Give the List a generic type like so:
List<Animal> animals = new ArrayList<Animal>();
animals.add(dog);
animals.add(cat)
Then parse the list as argument, while converting it to an array, to your method like so:
add(animals.toArray(new Animal[animals.size()]);
More on generics can be found in the Java API
http://download.oracle.com/javase/1,5.0/docs/guide/language/generics.html

Categories