I'm playing with some functional like programming. And having issues with some pretty deeply nested generics. Here's my SCCE that fails, with an abstract class involved:
public abstract class FooGen<IN, OUT> {
OUT fn2(IN in1, IN in2) { // clever? try at a lazy way, just call the varargs version
return fnN(in1, in2);
}
abstract OUT fnN(IN...ins); // subclasses implement this
public static void main(String[] args) {
FooGen<Number, Number> foogen = new FooGen<Number, Number>() {
#Override Number fnN(Number... numbers) {
return numbers[0];
}
};
System.out.println(foogen.fn2(1.2, 3.4));
}
}
This dies with a
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Number;
However, for a non-abstract FooGen, it works fine:
public class FooGen<IN, OUT> {
OUT fn2(IN g1, IN g2) {
return fnN(g1, g2);
}
OUT fnN(IN...gs) {
return (OUT)gs[0];
}
public static void main(String[] args) {
FooGen<Number,Number> foogen = new FooGen<Number,Number>();
System.out.println(foogen.fn2(1.2, 3.4));
}
}
This prints 1.2. Ideas? It seems like somewhere Java has lost track of the generics. This is pushing the limits of my generics knowledge. :-)
(Added in response to answers)
First, thanks for the upvotes, and to Paul and Daemon for their helpful answers.
Still wondering why it works as Numbers in the 2nd version, I had an insight. As a Thought Experiment, let's add a .doubleValue() somewhere. You can't. In the code itself the variables are INs, not Numbers. And in the main() it's merely declaring the type, FooGen<Number,Number> but there's no place there to add code.
In Version #2, it really isn't "working" as Numbers. Internally, with erasure, everything is Objects, as explained by Paul and Daemon, and, looking back sheepishly, well understood by myself. Basically, in this complex example, I got overexcited and mislead by the <Number> declaration.
Don't think I'll bother with a workaround. The whole idea was to be lazy. :-) For efficiency I created parallel interfaces and code that take primitive doubles (and ints), and there this trick works just fine.
Varargs parameters are first and foremost arrays. So without the syntactic sugar, your code would look like the following:
OUT fn2(IN in1, IN in2) {
return fnN(new IN[] {in1, in2});
}
abstract OUT fnN(IN[] ins);
Except new IN[] would not be legal because arrays of type parameters cannot be instantiated, due to type erasure. An array needs to know its component type, but IN has been erased to its upper bound, Object, at runtime.
The varargs invocation hides this issue unfortunately, and at runtime you have the equivalent of fnN(new Object[] {in1, in2}), whereas fnN has been overriden to take a Number[].
However, for a non-abstract FooGen, it works fine
This is because by instantiating FooGen directly, you haven't overridden fnN. Thus it accepts an Object[] at runtime and no ClassCastException occurs.
For example, this will fail even if FooGen isn't abstract:
FooGen<Number, Number> foogen = new FooGen<Number, Number>() {
#Override
Number fnN(Number... gs) {
return super.fnN(gs);
}
};
System.out.println(foogen.fn2(1.2, 3.4));
So you can see that it really isn't related to the abstractness of FooGen, but to whether fnN gets overridden with a narrowed argument type.
SOLUTION
There are no easy workarounds. One idea is to have fnN take a List<? extends IN> instead:
OUT fn2(IN in1, IN in2) {
//safe because the array won't be exposed outside the list
#SuppressWarnings("unchecked")
final List<IN> ins = Arrays.asList(in1, in2);
return fnN(ins);
}
abstract OUT fnN(List<? extends IN> ins);
If you wanted to keep the varargs support, you could treat this method as an implementation detail and delegate to it:
abstract OUT fnNImpl(List<? extends IN> ins);
public final OUT fnN(IN... ins) {
return fnNImpl(Arrays.asList(ins));
}
This ClassCastException occurs due to a feature of Java called "type erasure". Type erasure occurs when generics are compiled. Since the Java compiler cannot know the type of a generic class at run-time, it will instead compile the generic objects as instances of Object.
In your code, when FooGen is compiled, fnN(IN... ins) receives a parameter of type Object[]. The ClassCastException occurs when you then attempt to down-cast one of these Objects to your generic type OUT.
This isn't even mentioning the fact that creation of such "generic arrays" is prohibited in Java regardless.
Here is a quote from Angelika Langer's Java Generics FAQ:
Here is another example that illustrates the potential danger of
ignoring the warning issued regarding array construction in
conjunction with variable argument lists.
Example (of a varargs method and its invocation):
public final class Test {
static <T> T[] method_1(T t1, T t2) {
return method_2(t1, t2); // unchecked warning
}
static <T> T[] method_2( T... args) {
return args;
}
public static void main(String... args) {
String[] strings = method_1("bad", "karma"); // ClassCastException
}
}
warning: [unchecked] unchecked generic array creation of type T[] for
varargs parameter
return method_2(t1, t2);
^
In this example the first method calls a second method and the second
method takes a variable argument list. In order to invoke the varargs
method the compiler creates an array and passes it to the method. In
this example the array to be created is an array of type T[] , that
is, an array whose component type is a type parameter. Creation of
such arrays is prohibited in Java and you would receive an error
message if you tried to create such an array yourself.
Related
An error occurs at new T[5] during compile-time saying => error: generic array creation
and according to my understanding, the array is created during compile-time and since we don't know the type of T at compile-time we cannot instantiate an array.
But
if T gets erased at compile-time and changes to Object then still why this error occurs ? because we can create an array of Object.
// Before Compiling
public class GenericClass<T> {
GenericClass(){
T[] obj = new T[5];
}
}
// After Compiling
public class GenericClass {
GenericClass() {
Object[] obj = new Object[5];
}
}
Similar case, like,
public class GenericClass<T> {
GenericClass(){
T obj = new T(); }}
/* error :required: class
found: type parameter T
where T is a type-variable:
T extends Object declared in class GenericClass*/
according to my understanding, the array is created during compile-time
No, the array is created at runtime.
nd since we don't know the type of T at compile-time we cannot instantiate an array.
Correct.
But if T gets erased at compile-time and changes to Object then still why this error occurs ?
Because "it is erased at compile time and changes to Object" is oversimplified.
Also, generics and arrays don't play nice with each other. The problem is, where the generics part is erased, arrays do not work like that. You can do this:
String[] x = new String[10];
tellMeTheTypeOfMyArray(x);
void tellMeTheTypeOfMyArray(Object[] o) {
System.out.println("Your array's component type is: " + o.getClass().getComponentType());
}
This code will compile and work fine, without error, and prints:
Your array's component type is: java.lang.String
Contrast to generics where you cannot write such a method. You cannot possibly make this:
List<String> x = new ArrayList<String>();
tellMeTheTypeOfMyList(x);
void tellMeTheTypeOfMyList(List<?> o) {
System.out.println("Your list's component type is: " + ??????);
}
work. There's no java code possible here, nothing you can write in place of the ?????? to print String, because that information simply is not there at runtime anymore.
Imagine this code:
// This is written by Wahab today.
class Example<T> {
protected T[] arr;
Example() {
this.arr = new T[10];
}
}
and it worked like you wanted. Then I do:
// Written by me, a year later
class Uhoh extends Example<String> {
Uhoh() {
super();
}
void hmmm() {
System.out.println(this.arr.getComponentType());
}
}
I would obviously expect, nay, demand - that this prints java.lang.String, but it could not possibly do so. Because this is weird and confusing, java has a rule: If you compile your code and you do not see any warnings about generics problems (and did not #SuppressWarnings them away), then this kind of confusion is not likely to happen.
Allowing you to write new T[] and having that just be a silly way to write new Object[] is considered too far gone for this.
So how do I use arrays with generics types?
The same way java.util.ArrayList does it: Do not use generics here. Arrays should pretty much never have T types if you intend to create them inside the generic code. If you have a T[] anywhere in your codebase, then that means you should never be new-ing up anything for it - let the caller of your code do it for you. If you do want to new up new arrays yourself, don't use T, use Object[] as type, and cast to T where needed. This is literally how java's built-in ArrayList class works. Some excerpts copy-pasted straight from its source:
transient Object[] elementData; // non-private to simplify nested class access
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
#SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
Here's an example, again straight from ArrayList's sources (or rather, java.util.Collection defines this, and ArrayList inherits it), where you let the caller provide you with code to make arrays:
default <T> T[] toArray(IntFunction<T[]> generator) {
return toArray(generator.apply(0));
}
Here the caller provides a function that transforms an int into a T[] - it takes the concept of doing new String[10] and turns it into a function, that you then pass along to the toArray method which will then use it (feel free to ignore how it uses it here, it's a bit of a bizarre solution. It works, just - not sure you should be learning lessons about that part).
You use it like this:
List<String> listOfStrings = ...;
String[] convertedToArray = listOfStrings.toArray(String[]::new);
Java arrays know their component type at runtime. When you create an array, you must provide the component type at runtime. But in your GenericClass, it cannot do that because it does not know what T is at runtime. If it creates an Object[], that object will have the wrong runtime class, and that instance is not compatible with the type T[] if T is anything other than Object. You are correct that, within the class, nothing is immediately wrong. But if the claim that the variable is T[] is exposed to an outside scope which expects T to be a more specific type, it can cause a ClassCastException:
// Before type erasure
class GenericClass<T> {
T[] obj;
GenericClass() {
obj = new T[5]; // if hypothetically you could do this
}
T[] getObj() {
return obj;
}
}
class MyCode {
public static void main(String[] args) {
GenericClass<String> foo = new GenericClass<>();
String[] strings = foo.getObj(); // no casts needed; no warnings
}
}
// After type erasure
class GenericClass {
Object[] obj;
GenericClass() {
obj = new Object[5];
}
Object[] getObj() {
return obj;
}
}
class MyCode {
public static void main(String[] args) {
GenericClass foo = new GenericClass();
String[] strings = (String[]) foo.getObj(); // ClassCastException at runtime
}
}
This question already has answers here:
Dealing with an ArrayStoreException
(4 answers)
Closed 4 years ago.
Consider the following example, ignoring the reason one would want to do this:
private static class Original {
public String getValue() {
return "Foo";
}
}
private static class Wrapper extends Original {
private Original orig;
public Wrapper(Original orig) {
this.orig = orig;
}
#Override
public String getValue() {
return orig.getValue();
}
}
public static void test(Original... o) {
if (o != null && o.length > 0) {
for (int i = 0; i < o.length; i++) {
if (o[i] instanceof Wrapper) {
o[i] = ((Wrapper) o[i]).orig; // Throws java.lang.ArrayStoreException at runtime
}
}
}
}
public static void main(String[] args){
test(new Wrapper[] { // Explicitly create an array of subclass type
new Wrapper(new Original())
});
}
This example gives no warnings or errors at compile-time. It seems like the compiler decides that an Wrapper[] contains Wrapper instances, which effectively means that those are definitely instances of Original class. This is perfectly fine.
However, at runtime, the Wrapper[] instance is directly passed into the method. I have thought that it would be smart enough to tear down this array and re-create an instance of Original[] at runtime, but it seems like this is not the case.
Is this behavior ever documented somewhere (like JLS)? An ordinary programmer like me will always assume that I can manipulate that vararg parameter of Original... as if it is an Original[].
Yes, when a Wrapper is an Original, then also a Wrapper[] is an Original[] (it surprised me too when I realized it).
Your Wrapper is a subtype of Original since it exteds the Original class.
And yes, the subtype relationship between the array types may give rise to an ArrayStoreException if the called method tries to store an Original that is not a Wrapper into the passed array. But this is not checked at compile time. It is my understanding that this is exactly why we have the ArrayStoreException type since usually other attempts to store the wrong type into an array are caught at compile time. There is a nice brief example in the documentation of ArrayStoreException. That example also demonstrates that it hasn’t really got anything to do with varargs or method calls, its for all arrays.
The Java language was designed this way from version 1 (which is long before varargs were introduced, BTW). Thanks to Andy Turner for finding the Java Language Specification (JLS) reference: It is in section 4.10.3 Subtyping among Array Types:
If S and T are both reference types, then S[] >_1 T[] iff S >_1 T.
I have some code that I would write
GenericClass<Foo> foos = new GenericClass<>();
While a colleague would write it
GenericClass<Foo> foos = new GenericClass();
arguing that in this case the diamond operator adds nothing.
I'm aware that constructors that actually use arguments related to the generic type can cause a compile time error with <> instead of a run time error in the raw case. And that the compile time error is much better. (As outlined in this question)
I'm also quite aware that the compiler (and IDE) can generate warnings for the assignment of raw types to generics.
The question is instead for the case where there are no arguments, or no arguments related to the generic type. In that case, is there any way the constructed object GenericClass<Foo> foos can differ depending on which constructor was used, or does Javas type erasure guarantee they are identical?
For instantiations of two ArrayLists, one with the diamond operator at the end and one without...
List<Integer> fooList = new ArrayList<>();
List<Integer> barList = new ArrayList();
...the bytecode generated is identical.
LOCALVARIABLE fooList Ljava/util/List; L1 L4 1
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: java.util.List<java.lang.Integer>
LOCALVARIABLE barList Ljava/util/List; L2 L4 2
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: java.util.List<java.lang.Integer>
So there wouldn't any difference between the two as per the bytecode.
However, the compiler will generate an unchecked warning if you use the second approach. Hence, there's really no value in the second approach; all you're doing is generating a false positive unchecked warning with the compiler that adds to the noise of the project.
I've managed to demonstrate a scenario in which doing this is actively harmful. The formal name for this is heap pollution. This is not something that you want to occur in your code base, and any time you see this sort of invocation, it should be removed.
Consider this class which extends some functionality of ArrayList.
class Echo<T extends Number> extends ArrayList<T> {
public Echo() {
}
public Echo(Class<T> clazz) {
try {
this.add(clazz.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
System.out.println("YOU WON'T SEE ME THROWN");
System.exit(-127);
}
}
}
Seems innocuous enough; you can add an instance of whatever your type bound is.
However, if we're playing around with raw types...there can be some unfortunate side effects to doing so.
final Echo<? super Number> oops = new Echo(ArrayList.class);
oops.add(2);
oops.add(3);
System.out.println(oops);
This prints [[], 2, 3] instead of throwing any kind of exception. If we wanted to do an operation on all Integers in this list, we'd run into a ClassCastException, thanks to that delightful ArrayList.class invocation.
Of course, all of that could be avoided if the diamond operator were added, which would guarantee that we wouldn't have such a scenario on our hands.
Now, because we've introduced a raw type into the mix, Java can't perform type checking per JLS 4.12.2:
For example, the code:
List l = new ArrayList<Number>();
List<String> ls = l; // Unchecked warning
gives rise to a compile-time unchecked warning, because it is not
possible to ascertain, either at compile time (within the limits of
the compile-time type checking rules) or at run time, whether the
variable l does indeed refer to a List<String>.
The situation above is very similar; if we take a look at the first example we used, all we're doing is not adding an extra variable into the matter. The heap pollution occurs all the same.
List rawFooList = new ArrayList();
List<Integer> fooList = rawFooList;
So, while the byte code is identical (likely due to erasure), the fact remains that different or aberrant behavior can arise from a declaration like this.
Don't use raw types, mmkay?
The JLS is actually pretty clear on this point. http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.2
First it says "A generic class declaration defines a set of parameterized types (§4.5), one for each possible parameterization of the type parameter section by type arguments. All of these parameterized types share the same class at run time."
Then it gives us the code block
Vector<String> x = new Vector<String>();
Vector<Integer> y = new Vector<Integer>();
boolean b = x.getClass() == y.getClass();
and says that it "will result in the variable b holding the value true."
The test for instance equality (==) says that both x and y share exactly the same Class object.
Now do it with the diamond operator and without.
Vector<Integer> z = new Vector<>();
Vector<Integer> w = new Vector();
boolean c = z.getClass() == w.getClass();
boolean d = y.getClass() == z.getClass();
Again, c is true, and so is d.
So if, as I understand, you're asking whether there is some difference at runtime or in the bytecode between using the diamond and not, the answer is simple. There is no difference.
Whether it's better to use the diamond operator in this case is a matter of style and opinion.
P.S. Don't shoot the messenger. I would always use the diamond operator in this case. But that's just because I like what the compiler does for me in general w/r/t generics, and I don't want to fall into any bad habits.
P.P.S. Don't forget that this may be a temporary phenomenon. http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.8 warns us that "The use of raw types in code written after the introduction of generics into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types."
You may have problem with default constructor if your generic arguments are limited. For example, here's sloppy and incomplete implementation of the list of numbers which tracks the total sum:
public class NumberList<T extends Number> extends AbstractList<T> {
List<T> list = new ArrayList<>();
double sum = 0;
#Override
public void add(int index, T element) {
list.add(index, element);
sum += element.doubleValue();
}
#Override
public T remove(int index) {
T removed = list.remove(index);
sum -= removed.doubleValue();
return removed;
}
#Override
public T get(int index) {
return list.get(index);
}
#Override
public int size() {
return list.size();
}
public double getSum() {
return sum;
}
}
Omitting the generic arguments for default constructor may lead to ClassCastException in runtime:
List<String> list = new NumberList(); // compiles with warning and runs normally
list.add("test"); // throws CCE
However adding the diamond operator will produce a compile-time error:
List<String> list = new NumberList<>(); // error: incompatible types
list.add("test");
In your specific example: Yes, they are identical.
Generally: Beware, they may not be!
The reason is that different overloaded constructor/method may be invoked when raw type is used; it is not only that you get better type safety and avoid runtime ClassCastException.
Overloaded constructors
public class Main {
public static void main(String[] args) {
Integer anInteger = Integer.valueOf(1);
GenericClass<Integer> foosRaw = new GenericClass(anInteger);
GenericClass<Integer> foosDiamond = new GenericClass<>(anInteger);
}
private static class GenericClass<T> {
public GenericClass(Number number) {
System.out.println("Number");
}
public GenericClass(T t) {
System.out.println("Parameter");
}
}
}
Version with diamond invokes the different constructor; the output of the above program is:
Number
Parameter
Overloaded methods
public class Main {
public static void main(String[] args) {
method(new GenericClass());
method(new GenericClass<>());
}
private static void method(GenericClass<Integer> genericClass) {
System.out.println("First method");
}
private static void method(Object object) {
System.out.println("Second method");
}
private static class GenericClass<T> { }
}
Version with diamond invokes the different method; the output:
First method
Second method
This is not a complete answer - but does provide a few more details.
While you can not distinguish calls like
GenericClass<T> x1 = new GenericClass<>();
GenericClass<T> x2 = new GenericClass<T>();
GenericClass<T> x3 = new GenericClass();
There are tools that will allow you to distinguish between
GenericClass<T> x4 = new GenericClass<T>() { };
GenericClass<T> x5 = new GenericClass() { };
Note: While it looks like we're missing new GenericClass<>() { }, it is not currently valid Java.
The key being that type information about the generic parameters are stored for anonymous classes. In particular we can get to the generic parameters via
Type superclass = x.getClass().getGenericSuperclass();
Type tType = (superclass instanceof ParameterizedType) ?
((ParameterizedType) superclass).getActualTypeArguments()[0] :
null;
For x1, x2, and x3 tType will be an instance of TypeVariableImpl (the same instance in all three cases, which is not surprising as getClass() returns the same object for all three cases.
For x4 tType will be T.class
For x5 getGenericSuperclass() does not return an instance of ParameterizedType, but instead a Class (infact GenericClass.class)
We could then use this to determine whether our obect was constructed via (x1,x2 or x3) or x4 or x5.
I try to dive deeply into the Java Generics and I've come across a problem described by the following sample code.
public static void test(Object o) {
System.out.println("Hello Object!");
}
public static void test(Integer i) {
System.out.println("Hello Integer!");
}
public static <T> void test(Collection<T> col) {
for (T item : col) {
System.out.println(item.getClass().getSimpleName());
test(item);
}
}
public static void main (String[] args) throws java.lang.Exception
{
Collection<Integer> ints = new ArrayList<>();
ints.add(1);
test(ints);
}
The output of the sample is
Integer
Hello Object!
All the types are obviously known at compile time. As far as I understand, Java holds only a single compiled copy of each method (unlike C++) and because no other constraints are given about the parameter T, it's forced to call the generic Object implementation.
My question is - is there any way to call the "Hello Integer" method for integers without overloading the test method for Collection<Integer> and without using runtime type checking?
It cannot be done. Due to type erasure type Collection<T> will be resolved to type Collection<Object>. Erasure of Generic Methods
You can observe the same behavior without generics.
Object o = new Integer(5);
test(o); // will output "Hello Object!"
Overloaded methods are resolved using compile-time rather than run-time type information. See JLS: https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.9
No, you can't do what you ask for, for the reasons you gave. Type erasure means that the overload is resolved once for all type variables, and it has to resolve to Object.
I was learning Generics in Java and I came close to a very interesting piece of code. I know in Java it is illegal to add list of one type to another.
List<Integer> integerList = new ArrayList<Integer>();
List<String> stringList=integerList;
So in the second line I get a compile time error.
But if I create a generic method inside a class like this,
class GenericClass <E>{
void genericFunction(List<String> stringList) {
stringList.add("foo");
}
// some other code
}
And in the main class call the method with list of Integer I am not getting any error.
public class Main {
public static void main(String args[]) {
GenericClass genericClass=new GenericClass();
List<Integer> integerList= new ArrayList<Integer>();
integerList.add(100);
genericClass.genericFunction(integerList);
System.out.println(integerList.get(0));
System.out.println(integerList.get(1));
}
}
Output
100
foo
Why I am not getting any error?
You are not getting any compile time error because by using GenericClass<E> in a raw way :
GenericClass genericClass = new GenericClass();,
you are practically telling the compiler to disable generic type checkings because you don't care.
So the :
void genericFunction(List<String> stringList)
becomes
void genericFunction(List stringList) for the compiler.
You can try the following : GenericClass<?> genericClass , and you'll notice immediately that the compiler becomes aware of the generics bad use, and it will show you the error :
The method genericFunction(List<String>) in the type GenericClass<capture#1-of ?> is not applicable for the arguments (List<Integer>)
Also, if you try to get the class of the 2nd position object at runtime:
System.out.println(integerList.get(1).getClass());
, you'll get an error:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer.
You have mixed Generic with raw type. It will compile fine but at run-time it might fail because Generic information is lost at run-time.
Generic should be used to track such bugs at compile-time.
It's better explained at What is a raw type and why shouldn't we use it? in detail.
Warning: Type safety: The method genericFunction(List) belongs to the raw type GenericClass. References to generic type GenericClass<E> should be
parameterized.
If you have two methods with same name with different Generic type of List then it results into compile time error. The compiler is unable to resolve Generic type in case of method arguments that can be proved by below sample code.
Sample code: (compiler error - not a valid overloaded method)
void genericFunction(List<String> stringList){...}
void genericFunction(List<Integer> stringList){...}
Make some changes and try it again:
class GenericClass <E>{
void genericFunction(List<E> stringList) {
...
}
// some other code
}
...
GenericClass<String> genericClass=new GenericClass<String>(); // Genreric object
List<Integer> integerList= new ArrayList<Integer>();
integerList.add(100);
genericClass.genericFunction(integerList); // compile time error
Create methods in such a way
class GenericClass<E> {
private List<E> list = new ArrayList<E>();
public void addAll(List<E> newList) {
list.addAll(newList);
}
public void add(E e) {
list.add(e);
}
public E get(int index) {
return list.get(index);
}
// some other code
}
This happens, because (quite surprisingly for me) you have turned off generic type checking for the whole GenericClass class.
You need to be aware that you are, first of all, constructing a generic class without type argument here:
GenericClass genericClass = new GenericClass();
And appereantly, because of this, your following code:
class GenericClass<E> {
void genericFunction(List<String> stringList) {
stringList.add("foo");
}
// some other code
}
got refined to:
class GenericClass {
void genericFunction(List stringList) {
stringList.add("foo");
}
// some other code
}
Note that List also became a raw type, which is rather surprising to me.
You can find the full answer here, referencing the JLS: https://stackoverflow.com/a/662257/2057294 as explained by Jon Skeet.
I think it is fair that this happens (though not what you would expect), because if you decide to use raw types, it is assumed that you are using Java 4 or lower and have no access at all to generics either way, so it may as well not provide them for methods not involving the generic type from the class that got erased.
To add to the other answers.
Indeed you are mixing a raw (unparameterized) type with parameterized types which causes a type loss and an apparently correct argument passing to genericFunction when type safety checks should not allow you to.
The question remains on why the List<String> type is lost in an unparameterized GenericClass object. The reason the compiler "disables" type checking in your case is the type erasure mechanism, which says (in simple terms) your raw object belongs to a class with the following contents:
class GenericClass {
void genericFunction(List stringList) {
stringList.add("foo");
}
// some other code
}
Which as you see does not make any type checks whatsoever on the contents of the list being passed (it erases type parameters everywhere). Even more, type erasure also erases your parameterized type from the integerList variable, which makes it completely suitable as an argument for your genericFunction method.
Therefore, as others pointed out, it's always best to help Java maintain its safe type-checking mechanisms intact:
GenericClass<?> genericClass=new GenericClass<Integer>();
List<Integer> integerList= new ArrayList<Integer>();
integerList.add(100);
// no-no here
genericClass.genericFunction(integerList);
System.out.println(integerList.get(0));
System.out.println(integerList.get(1));
But then again if you do, the compiler does its job and goes bazooka on you.
Read more on type erasure here. It's a nice feature that allows generic type checking while still keeping backward compatibility to pre-generics Java versions.
class GenericClass <E>{
void genericFunction(List<String> stringList) {
stringList.add("foo");
}
// some other code
}
when you write just after the class name.It specifies the type parameters.Its a generic functionality.This introduces the type variable, E, that can be used anywhere inside the class. A type variable can be any non-primitive type you specify: any class type, any interface type, any array type, or even another type variable.