I have this code from the book Thinking in Java,where Bruce indicates the warning on calling the method set(). The code is as follows:
package testPackage;
class GenericBase<T> {
private T element;
public void set(T arg) { arg = element; }
public T get() { return element; }
}
class Derived1<T> extends GenericBase<T> {}
class Derived2 extends GenericBase {} // No warning
// class Derived3 extends GenericBase<?> {}
// Strange error:
// unexpected type found : ?
// required: class or interface without bounds
public class ErasureAndInheritance {
#SuppressWarnings("unchecked")
public static void main(String[] args) {
Derived2 d2 = new Derived2();
Object obj = d2.get();
d2.set(obj); // Warning here!
}
}
If I remove the annotation, I get the following warning:
Type safety: The method set(Object) belongs to the raw type GenericBase. References to generic type GenericBase should be parameterized
My question is, Why is the warning displayed on the set() method? And can somebody please explain what this warning means?
Btw, I am completely new to Java Generics and although I read other questions on Generics, I am still somewhat confused on Erasure.
From the Java doc here
Generics were introduced to the Java language to provide tighter type
checks at compile time and to support generic programming. To
implement generics, the Java compiler applies type erasure to:
Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode,
therefore, contains only ordinary classes, interfaces, and methods.
Insert type casts if necessary to preserve type safety.
Generate bridge methods to preserve polymorphism in extended generic types.
Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.
Simple: Every generic is a Object after the compile process, it just adds the casting for you, and if you did something wrong it will generate a compiler error.
About the unchecked annotation, it's used when the compiler can't be sure that what are you doing is correct (in fact, you use the raw type which is the same thing as GenericBase<Object>)
You have the warning since the set methods except the type T but since you are using a raw type, it don't know what T is, and generate this warning to let you know that you are using a raw type (bad thing).
You can use the annotations to say: "I know it's a raw type, but i know it's legal and i know what i'm doing".
Mixing raw types and generic types is possible due to the way that generics are implemented, as i said above, after the compiler process the T becomes Object and it means that a raw object like GenericBase is the same thing like GenericBase<T>.
The reason that it doesn't asks for any casting with generics, is because it will add the casts for you (and you can be sure that the code will always work in runtime without worry about a possible ClassCastException).
Related
I read about Java's type erasure on Oracle's website.
When does type erasure occur? At compile time or runtime? When the class is loaded? When the class is instantiated?
A lot of sites (including the official tutorial mentioned above) say type erasure occurs at compile time. If the type information is completely removed at compile time, how does the JDK check type compatibility when a method using generics is invoked with no type information or wrong type information?
Consider the following example: Say class A has a method, empty(Box<? extends Number> b). We compile A.java and get the class file A.class.
public class A {
public static void empty(Box<? extends Number> b) {}
}
public class Box<T> {}
Now we create another class B which invokes the method empty with a non-parameterized argument (raw type): empty(new Box()). If we compile B.java with A.class in the classpath, javac is smart enough to raise a warning. So A.class has some type information stored in it.
public class B {
public static void invoke() {
// java: unchecked method invocation:
// method empty in class A is applied to given types
// required: Box<? extends java.lang.Number>
// found: Box
// java: unchecked conversion
// required: Box<? extends java.lang.Number>
// found: Box
A.empty(new Box());
}
}
My guess would be that type erasure occurs when the class is loaded, but it is just a guess. So when does it happen?
Type erasure applies to the use of generics. There's definitely metadata in the class file to say whether or not a method/type is generic, and what the constraints are etc. But when generics are used, they're converted into compile-time checks and execution-time casts. So this code:
List<String> list = new ArrayList<String>();
list.add("Hi");
String x = list.get(0);
is compiled into
List list = new ArrayList();
list.add("Hi");
String x = (String) list.get(0);
At execution time there's no way of finding out that T=String for the list object - that information is gone.
... but the List<T> interface itself still advertises itself as being generic.
EDIT: Just to clarify, the compiler does retain the information about the variable being a List<String> - but you still can't find out that T=String for the list object itself.
The compiler is responsible for understanding Generics at compile time. The compiler is also responsible for throwing away this "understanding" of generic classes, in a process we call type erasure. All happens at compile time.
Note: Contrary to beliefs of majority of Java developers, it is possible to keep compile-time type information and retrieve this information at runtime, despite in a very restricted way. In other words: Java does provide reified generics in a very restricted way.
Regarding type erasure
Notice that, at compile-time, the compiler has full type information available but this information is intentionally dropped in general when the byte code is generated, in a process known as type erasure. This is done this way due to compatibility issues: The intention of language designers was providing full source code compatibility and full byte code compatibility between versions of the platform. If it were implemented differently, you would have to recompile your legacy applications when migrating to newer versions of the platform. The way it was done, all method signatures are preserved (source code compatibility) and you don't need to recompile anything (binary compatibility).
Regarding reified generics in Java
If you need to keep compile-time type information, you need to employ anonymous classes.
The point is: in the very special case of anonymous classes, it is possible to retrieve full compile-time type information at runtime which, in other words means: reified generics. This means that the compiler does not throw away type information when anonymous classes are involved; this information is kept in the generated binary code and the runtime system allows you to retrieve this information.
I've written an article about this subject:
https://rgomes.info/using-typetokens-to-retrieve-generic-parameters/
A note about the technique described in the article above is that the technique is obscure for majority of developers. Despite it works and works well, most developers feel confused or uncomfortable with the technique. If you have a shared code base or plan to release your code to the public, I do not recommend the above technique. On the other hand, if you are the sole user of your code, you can take advantage of the power this technique delivers to you.
Sample code
The article above has links to sample code.
If you have a field that is a generic type, its type parameters are compiled into the class.
If you have a method that takes or returns a generic type, those type parameters are compiled into the class.
This information is what the compiler uses to tell you that you can't pass a Box<String> to the empty(Box<T extends Number>) method.
The API is complicated, but you can inspect this type information through the reflection API with methods like getGenericParameterTypes, getGenericReturnType, and, for fields, getGenericType.
If you have code that uses a generic type, the compiler inserts casts as needed (in the caller) to check types. The generic objects themselves are just the raw type; the parameterized type is "erased". So, when you create a new Box<Integer>(), there is no information about the Integer class in the Box object.
Angelika Langer's FAQ is the best reference I've seen for Java Generics.
Generics in Java Language is a really good guide on this topic.
Generics are implemented by Java
compiler as a front-end conversion
called erasure. You can (almost) think
of it as a source-to-source
translation, whereby the generic
version of loophole() is converted to
the non-generic version.
So, it's at compile time. The JVM will never know which ArrayList you used.
I'd also recommend Mr. Skeet's answer on What is the concept of erasure in generics in Java?
Type erasure occurs at compile time. What type erasure means is that it will forget about the generic type, not about every type. Besides, there will still be metadata about the types being generic. For example
Box<String> b = new Box<String>();
String x = b.getDefault();
is converted to
Box b = new Box();
String x = (String) b.getDefault();
at compile time. You may get warnings not because the compiler knows about what type is the generic of, but on the contrary, because it doesn't know enough so it cannot guarantee type safety.
Additionally, the compiler does retain the type information about the parameters on a method call, which you can retrieve via reflection.
This guide is the best I've found on the subject.
The term "type erasure" is not really the correct description of Java's problem with generics.
Type erasure is not per se a bad thing, indeed it is very necessary for performance and is often used in several languages like C++, Haskell, D.
Before you disgust, please recall the correct definition of type erasure from Wikipedia
What is type erasure?
type erasure refers to the load-time process by which explicit type annotations are removed from a program, before it is executed at run-time
Type erasure means to throw away type tags created at design time or inferred type tags at compile time such that the compiled program in binary code does not contain any type tags.
And this is the case for every programming language compiling to binary code except in some cases where you need runtime tags. These exceptions include for instance all existential types (Java Reference Types which are subtypeable, Any Type in many languages, Union Types).
The reason for type erasure is that programs get transformed to a language which is in some kind uni-typed (binary language only allowing bits) as types are abstractions only and assert a structure for its values and the appropriate semantics to handle them.
So this is in return, a normal natural thing.
Java's problem is different and caused to how it reifies.
The often made statements about Java does not have reified generics is also wrong.
Java does reify, but in a wrong way due to backward compatibility.
What is reification?
From Wikipedia
Reification is the process by which an abstract idea about a computer program is turned into an explicit data model or other object created in a programming language.
Reification means to convert something abstract (Parametric Type) into something concrete (Concrete Type) by specialization.
We illustrate this by a simple example:
An ArrayList with definition:
ArrayList<T>
{
T[] elems;
...//methods
}
is an abstraction, in detail a type constructor, which gets "reified" when specialized with a concrete type, say Integer:
ArrayList<Integer>
{
Integer[] elems;
}
where ArrayList<Integer> is really a type.
But this is exactly the thing what Java does not!!!, instead they reify constantly abstract types with their bounds, i.e. producing the same concrete type independent of the parameters passed in for specialization:
ArrayList
{
Object[] elems;
}
which is here reified with the implicit bound Object (ArrayList<T extends Object> == ArrayList<T>).
Despite that it makes generic arrays unusable and cause some strange errors for raw types:
List<String> l= List.<String>of("h","s");
List lRaw=l
l.add(new Object())
String s=l.get(2) //Cast Exception
it causes a lot of ambiguities as
void function(ArrayList<Integer> list){}
void function(ArrayList<Float> list){}
void function(ArrayList<String> list){}
refer to the same function:
void function(ArrayList list)
and therefore generic method overloading can't be used in Java.
I've encountered with type erasure in Android. In production we use gradle with minify option. After minification I've got fatal exception. I've made simple function to show inheritance chain of my object:
public static void printSuperclasses(Class clazz) {
Type superClass = clazz.getGenericSuperclass();
Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName()));
Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString()));
while (superClass != null && clazz != null) {
clazz = clazz.getSuperclass();
superClass = clazz.getGenericSuperclass();
Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName()));
Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString()));
}
}
And there is two results of this function:
Not minified code:
D/Reflection: this class: com.example.App.UsersList
D/Reflection: superClass: com.example.App.SortedListWrapper<com.example.App.Models.User>
D/Reflection: this class: com.example.App.SortedListWrapper
D/Reflection: superClass: android.support.v7.util.SortedList$Callback<T>
D/Reflection: this class: android.support.v7.util.SortedList$Callback
D/Reflection: superClass: class java.lang.Object
D/Reflection: this class: java.lang.Object
D/Reflection: superClass: null
Minified code:
D/Reflection: this class: com.example.App.UsersList
D/Reflection: superClass: class com.example.App.SortedListWrapper
D/Reflection: this class: com.example.App.SortedListWrapper
D/Reflection: superClass: class android.support.v7.g.e
D/Reflection: this class: android.support.v7.g.e
D/Reflection: superClass: class java.lang.Object
D/Reflection: this class: java.lang.Object
D/Reflection: superClass: null
So, in minified code actual parametrized classes are replaced with raw classes types without any type information.
As a solution for my project i removed all reflection calls and replced them with explicit params types passed in function arguments.
I read about Java's type erasure on Oracle's website.
When does type erasure occur? At compile time or runtime? When the class is loaded? When the class is instantiated?
A lot of sites (including the official tutorial mentioned above) say type erasure occurs at compile time. If the type information is completely removed at compile time, how does the JDK check type compatibility when a method using generics is invoked with no type information or wrong type information?
Consider the following example: Say class A has a method, empty(Box<? extends Number> b). We compile A.java and get the class file A.class.
public class A {
public static void empty(Box<? extends Number> b) {}
}
public class Box<T> {}
Now we create another class B which invokes the method empty with a non-parameterized argument (raw type): empty(new Box()). If we compile B.java with A.class in the classpath, javac is smart enough to raise a warning. So A.class has some type information stored in it.
public class B {
public static void invoke() {
// java: unchecked method invocation:
// method empty in class A is applied to given types
// required: Box<? extends java.lang.Number>
// found: Box
// java: unchecked conversion
// required: Box<? extends java.lang.Number>
// found: Box
A.empty(new Box());
}
}
My guess would be that type erasure occurs when the class is loaded, but it is just a guess. So when does it happen?
Type erasure applies to the use of generics. There's definitely metadata in the class file to say whether or not a method/type is generic, and what the constraints are etc. But when generics are used, they're converted into compile-time checks and execution-time casts. So this code:
List<String> list = new ArrayList<String>();
list.add("Hi");
String x = list.get(0);
is compiled into
List list = new ArrayList();
list.add("Hi");
String x = (String) list.get(0);
At execution time there's no way of finding out that T=String for the list object - that information is gone.
... but the List<T> interface itself still advertises itself as being generic.
EDIT: Just to clarify, the compiler does retain the information about the variable being a List<String> - but you still can't find out that T=String for the list object itself.
The compiler is responsible for understanding Generics at compile time. The compiler is also responsible for throwing away this "understanding" of generic classes, in a process we call type erasure. All happens at compile time.
Note: Contrary to beliefs of majority of Java developers, it is possible to keep compile-time type information and retrieve this information at runtime, despite in a very restricted way. In other words: Java does provide reified generics in a very restricted way.
Regarding type erasure
Notice that, at compile-time, the compiler has full type information available but this information is intentionally dropped in general when the byte code is generated, in a process known as type erasure. This is done this way due to compatibility issues: The intention of language designers was providing full source code compatibility and full byte code compatibility between versions of the platform. If it were implemented differently, you would have to recompile your legacy applications when migrating to newer versions of the platform. The way it was done, all method signatures are preserved (source code compatibility) and you don't need to recompile anything (binary compatibility).
Regarding reified generics in Java
If you need to keep compile-time type information, you need to employ anonymous classes.
The point is: in the very special case of anonymous classes, it is possible to retrieve full compile-time type information at runtime which, in other words means: reified generics. This means that the compiler does not throw away type information when anonymous classes are involved; this information is kept in the generated binary code and the runtime system allows you to retrieve this information.
I've written an article about this subject:
https://rgomes.info/using-typetokens-to-retrieve-generic-parameters/
A note about the technique described in the article above is that the technique is obscure for majority of developers. Despite it works and works well, most developers feel confused or uncomfortable with the technique. If you have a shared code base or plan to release your code to the public, I do not recommend the above technique. On the other hand, if you are the sole user of your code, you can take advantage of the power this technique delivers to you.
Sample code
The article above has links to sample code.
If you have a field that is a generic type, its type parameters are compiled into the class.
If you have a method that takes or returns a generic type, those type parameters are compiled into the class.
This information is what the compiler uses to tell you that you can't pass a Box<String> to the empty(Box<T extends Number>) method.
The API is complicated, but you can inspect this type information through the reflection API with methods like getGenericParameterTypes, getGenericReturnType, and, for fields, getGenericType.
If you have code that uses a generic type, the compiler inserts casts as needed (in the caller) to check types. The generic objects themselves are just the raw type; the parameterized type is "erased". So, when you create a new Box<Integer>(), there is no information about the Integer class in the Box object.
Angelika Langer's FAQ is the best reference I've seen for Java Generics.
Generics in Java Language is a really good guide on this topic.
Generics are implemented by Java
compiler as a front-end conversion
called erasure. You can (almost) think
of it as a source-to-source
translation, whereby the generic
version of loophole() is converted to
the non-generic version.
So, it's at compile time. The JVM will never know which ArrayList you used.
I'd also recommend Mr. Skeet's answer on What is the concept of erasure in generics in Java?
Type erasure occurs at compile time. What type erasure means is that it will forget about the generic type, not about every type. Besides, there will still be metadata about the types being generic. For example
Box<String> b = new Box<String>();
String x = b.getDefault();
is converted to
Box b = new Box();
String x = (String) b.getDefault();
at compile time. You may get warnings not because the compiler knows about what type is the generic of, but on the contrary, because it doesn't know enough so it cannot guarantee type safety.
Additionally, the compiler does retain the type information about the parameters on a method call, which you can retrieve via reflection.
This guide is the best I've found on the subject.
The term "type erasure" is not really the correct description of Java's problem with generics.
Type erasure is not per se a bad thing, indeed it is very necessary for performance and is often used in several languages like C++, Haskell, D.
Before you disgust, please recall the correct definition of type erasure from Wikipedia
What is type erasure?
type erasure refers to the load-time process by which explicit type annotations are removed from a program, before it is executed at run-time
Type erasure means to throw away type tags created at design time or inferred type tags at compile time such that the compiled program in binary code does not contain any type tags.
And this is the case for every programming language compiling to binary code except in some cases where you need runtime tags. These exceptions include for instance all existential types (Java Reference Types which are subtypeable, Any Type in many languages, Union Types).
The reason for type erasure is that programs get transformed to a language which is in some kind uni-typed (binary language only allowing bits) as types are abstractions only and assert a structure for its values and the appropriate semantics to handle them.
So this is in return, a normal natural thing.
Java's problem is different and caused to how it reifies.
The often made statements about Java does not have reified generics is also wrong.
Java does reify, but in a wrong way due to backward compatibility.
What is reification?
From Wikipedia
Reification is the process by which an abstract idea about a computer program is turned into an explicit data model or other object created in a programming language.
Reification means to convert something abstract (Parametric Type) into something concrete (Concrete Type) by specialization.
We illustrate this by a simple example:
An ArrayList with definition:
ArrayList<T>
{
T[] elems;
...//methods
}
is an abstraction, in detail a type constructor, which gets "reified" when specialized with a concrete type, say Integer:
ArrayList<Integer>
{
Integer[] elems;
}
where ArrayList<Integer> is really a type.
But this is exactly the thing what Java does not!!!, instead they reify constantly abstract types with their bounds, i.e. producing the same concrete type independent of the parameters passed in for specialization:
ArrayList
{
Object[] elems;
}
which is here reified with the implicit bound Object (ArrayList<T extends Object> == ArrayList<T>).
Despite that it makes generic arrays unusable and cause some strange errors for raw types:
List<String> l= List.<String>of("h","s");
List lRaw=l
l.add(new Object())
String s=l.get(2) //Cast Exception
it causes a lot of ambiguities as
void function(ArrayList<Integer> list){}
void function(ArrayList<Float> list){}
void function(ArrayList<String> list){}
refer to the same function:
void function(ArrayList list)
and therefore generic method overloading can't be used in Java.
I've encountered with type erasure in Android. In production we use gradle with minify option. After minification I've got fatal exception. I've made simple function to show inheritance chain of my object:
public static void printSuperclasses(Class clazz) {
Type superClass = clazz.getGenericSuperclass();
Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName()));
Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString()));
while (superClass != null && clazz != null) {
clazz = clazz.getSuperclass();
superClass = clazz.getGenericSuperclass();
Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName()));
Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString()));
}
}
And there is two results of this function:
Not minified code:
D/Reflection: this class: com.example.App.UsersList
D/Reflection: superClass: com.example.App.SortedListWrapper<com.example.App.Models.User>
D/Reflection: this class: com.example.App.SortedListWrapper
D/Reflection: superClass: android.support.v7.util.SortedList$Callback<T>
D/Reflection: this class: android.support.v7.util.SortedList$Callback
D/Reflection: superClass: class java.lang.Object
D/Reflection: this class: java.lang.Object
D/Reflection: superClass: null
Minified code:
D/Reflection: this class: com.example.App.UsersList
D/Reflection: superClass: class com.example.App.SortedListWrapper
D/Reflection: this class: com.example.App.SortedListWrapper
D/Reflection: superClass: class android.support.v7.g.e
D/Reflection: this class: android.support.v7.g.e
D/Reflection: superClass: class java.lang.Object
D/Reflection: this class: java.lang.Object
D/Reflection: superClass: null
So, in minified code actual parametrized classes are replaced with raw classes types without any type information.
As a solution for my project i removed all reflection calls and replced them with explicit params types passed in function arguments.
I have come across a very strange compilation error. At first I thought it was a bug in IntelliJ, but now I've tried with Eclipse and see the same thing.
I have the following interface:
public interface PluginDriver<T extends Plugin> {
PluginBuilder<T> getBuilder();
Set<PluginParam> getParams();
Set<String> getStrings();
}
where Plugin is an abstract class:
public abstract class Plugin
and we also have a POJO as follows:
public class PluginParam { ... }
Now I have a DriverManager to return an instance of a PluginDriver, and here I will try to use that:
PluginDriver driver = DriverManager.getDriver(transformer.getPlugin());
PluginBuilder builder = driver.getBuilder();
for (PluginParam o : driver.getParams()) {
//type mismatch - cannot convert from element type Object to PluginParam
}
for (String s : driver.getStrings()) {
//type mismatch: cannot convert from element type Object to String
}
The for loop commands don't compile, for reasons that I don't understand. The interface specifies concrete return types for those methods, not type T.
Now, if I change the following line:
PluginDriver<Plugin> driver = DriverManager.getDriver(transformer.getPlugin());
everything suddenly plays nicely.
Similarly, if I remove the type arguments from the interface, it will work.
I could understand that the getBuilder() method might not work if we hadn't correctly typed the PluginDriver.
But how could it possibly complain about the getStrings() method?
==EDIT
This is not a duplicate of Combining Raw Types and Generic Methods, since that question refers to a method with typed parameters. The method I'm asking about is untyped.
The JLS says,
"The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C."
http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.8
When you declared a variable of the raw interface type, you forced the member methods to have raw types also. That's what screwed up the class cast.
The documentation rules.
Here:
PluginDriver driver = DriverManager.getDriver(transformer.getPlugin());
You are creating a raw type. Bad idea.
Can lead to all sorts of problems - see here.
Thus the answer is simple: never do that! When you are using generic types, then make sure that you have a "generic" on (one/both) sides of your declaration/definition!
NB: This is not a duplicate of the question I have already linked to below. I obviously read that question/answer first before posting and did not have my question answered in any form.
This linked question does go into more detail explaining why the generic Class exists. However I don't get an answer specifically to the benefits of Class in my situation.
What does the generic nature of the class Class<T> mean? What is T?
I've written a utility method that accepts a parameter 'cl' of type Class and performs logic by using cl.isInstance(objectInstance) method.
However I've seen example code that declares parameters using the generic wildcard Class<?>.
Why not just use Class without the generic wildcard? Can't Class represent all possible class types including generics? What is the benefit, if any of using Class<?> in my situation?
The accepted answer in an existing related question (see below) does not actually provide a useful answer.
What does Class<?> mean in Java?
The main difference lies in the (self-)documentation of the code to the reader. A variable declared as Class<?> says: “the actual type represented by this Class instance is unknown or not representable at compile-time and I know that”. In contrast the type Class says: “I’m not using Generics here”, perhaps, because you don’t know that Class is generic, or you are a bit sloppy, didn’t understand the difference between Class<?> and Class, or this is very old pre-Generics code.
This has consequences for the code. E.g.
Class<?> unknownType=Object.class;
Class<String> string=unknownType;
produces a compile-time error as you are assigning an explicitly unknown type to a variable declaring a known type.
In contrast
Class nonGenericType=Object.class;
Class<String> string=nonGenericType;
will only produce a (suppressible) warning as you are performing a non-generic, aka unchecked, operation.
Regarding what you can do with a Class object, besides assignments, there is no difference, as, when you use it to create a new instance, the compile-time type of the returned reference will be the most abstract type there is, java.lang.Object, in both cases. Had Class methods receiving arguments related to the type parameter, a difference showed up as you can’t invoke such methods for an unknown type, Class<?> (it would be the same as trying to insert an element into a List<?>) while you could invoke such a method unchecked on a raw Class instance. But since there are no such methods, there’s no difference in functionality between Class<?> and Class.
Still, you should always use Class<?> to be sure that no accidental unchecked operations, like the assignment shown above, happen. If using Class doesn’t produce compiler warnings, you should check how to (re-)enable them. If the compiler silently ignores raw types or unchecked operations, there might be other problems, with other types than Class, hiding somewhere.
The difference between the wildcard type <?> and the raw type in this particular scenario is only whether the compiler will warn you or not. Otherwise they're equivalent, so if for some reason you don't wouldn't want to use the <?> syntax and you didn't care about compiler warnings, you could use the raw type without any problems.
Netbeans not complaining about the raw type is not correct behaviour, and my Eclipse will complain when using a raw Class.
The Class object has distinct usage patterns, which affect whether the type will be a concrete type (seen in method parameters as Class<T> clazz) or the wildcard Class<?>.
The most common form seen in the API is the concrete type, since it allows you to use newInstance() (primarily) in a type-safe way (making all Class<T> objects automatically type-safe factories), such as the following:
public static void List<T> fill(Class<T> clazz, int size) {
List<T> l = new ArrayList<T>();
for(int i = 0;i < size; i++)
l.add(clazz.newInstance());
return l;
}
So Class<T> is useful, but what about Class<?>? Well, not so much. As indicated at the beginning, it's just required for syntax compliance. The alternative would be to use a concrete T type redundantly.
public void foo(Class<?> clazz) {
// Do something non-typed, we don't have a type
}
vs.
public <T> void foo(Class<T> clazz) {
// Do something non-typed, even though we have a type T
}
I read about Java's type erasure on Oracle's website.
When does type erasure occur? At compile time or runtime? When the class is loaded? When the class is instantiated?
A lot of sites (including the official tutorial mentioned above) say type erasure occurs at compile time. If the type information is completely removed at compile time, how does the JDK check type compatibility when a method using generics is invoked with no type information or wrong type information?
Consider the following example: Say class A has a method, empty(Box<? extends Number> b). We compile A.java and get the class file A.class.
public class A {
public static void empty(Box<? extends Number> b) {}
}
public class Box<T> {}
Now we create another class B which invokes the method empty with a non-parameterized argument (raw type): empty(new Box()). If we compile B.java with A.class in the classpath, javac is smart enough to raise a warning. So A.class has some type information stored in it.
public class B {
public static void invoke() {
// java: unchecked method invocation:
// method empty in class A is applied to given types
// required: Box<? extends java.lang.Number>
// found: Box
// java: unchecked conversion
// required: Box<? extends java.lang.Number>
// found: Box
A.empty(new Box());
}
}
My guess would be that type erasure occurs when the class is loaded, but it is just a guess. So when does it happen?
Type erasure applies to the use of generics. There's definitely metadata in the class file to say whether or not a method/type is generic, and what the constraints are etc. But when generics are used, they're converted into compile-time checks and execution-time casts. So this code:
List<String> list = new ArrayList<String>();
list.add("Hi");
String x = list.get(0);
is compiled into
List list = new ArrayList();
list.add("Hi");
String x = (String) list.get(0);
At execution time there's no way of finding out that T=String for the list object - that information is gone.
... but the List<T> interface itself still advertises itself as being generic.
EDIT: Just to clarify, the compiler does retain the information about the variable being a List<String> - but you still can't find out that T=String for the list object itself.
The compiler is responsible for understanding Generics at compile time. The compiler is also responsible for throwing away this "understanding" of generic classes, in a process we call type erasure. All happens at compile time.
Note: Contrary to beliefs of majority of Java developers, it is possible to keep compile-time type information and retrieve this information at runtime, despite in a very restricted way. In other words: Java does provide reified generics in a very restricted way.
Regarding type erasure
Notice that, at compile-time, the compiler has full type information available but this information is intentionally dropped in general when the byte code is generated, in a process known as type erasure. This is done this way due to compatibility issues: The intention of language designers was providing full source code compatibility and full byte code compatibility between versions of the platform. If it were implemented differently, you would have to recompile your legacy applications when migrating to newer versions of the platform. The way it was done, all method signatures are preserved (source code compatibility) and you don't need to recompile anything (binary compatibility).
Regarding reified generics in Java
If you need to keep compile-time type information, you need to employ anonymous classes.
The point is: in the very special case of anonymous classes, it is possible to retrieve full compile-time type information at runtime which, in other words means: reified generics. This means that the compiler does not throw away type information when anonymous classes are involved; this information is kept in the generated binary code and the runtime system allows you to retrieve this information.
I've written an article about this subject:
https://rgomes.info/using-typetokens-to-retrieve-generic-parameters/
A note about the technique described in the article above is that the technique is obscure for majority of developers. Despite it works and works well, most developers feel confused or uncomfortable with the technique. If you have a shared code base or plan to release your code to the public, I do not recommend the above technique. On the other hand, if you are the sole user of your code, you can take advantage of the power this technique delivers to you.
Sample code
The article above has links to sample code.
If you have a field that is a generic type, its type parameters are compiled into the class.
If you have a method that takes or returns a generic type, those type parameters are compiled into the class.
This information is what the compiler uses to tell you that you can't pass a Box<String> to the empty(Box<T extends Number>) method.
The API is complicated, but you can inspect this type information through the reflection API with methods like getGenericParameterTypes, getGenericReturnType, and, for fields, getGenericType.
If you have code that uses a generic type, the compiler inserts casts as needed (in the caller) to check types. The generic objects themselves are just the raw type; the parameterized type is "erased". So, when you create a new Box<Integer>(), there is no information about the Integer class in the Box object.
Angelika Langer's FAQ is the best reference I've seen for Java Generics.
Generics in Java Language is a really good guide on this topic.
Generics are implemented by Java
compiler as a front-end conversion
called erasure. You can (almost) think
of it as a source-to-source
translation, whereby the generic
version of loophole() is converted to
the non-generic version.
So, it's at compile time. The JVM will never know which ArrayList you used.
I'd also recommend Mr. Skeet's answer on What is the concept of erasure in generics in Java?
Type erasure occurs at compile time. What type erasure means is that it will forget about the generic type, not about every type. Besides, there will still be metadata about the types being generic. For example
Box<String> b = new Box<String>();
String x = b.getDefault();
is converted to
Box b = new Box();
String x = (String) b.getDefault();
at compile time. You may get warnings not because the compiler knows about what type is the generic of, but on the contrary, because it doesn't know enough so it cannot guarantee type safety.
Additionally, the compiler does retain the type information about the parameters on a method call, which you can retrieve via reflection.
This guide is the best I've found on the subject.
The term "type erasure" is not really the correct description of Java's problem with generics.
Type erasure is not per se a bad thing, indeed it is very necessary for performance and is often used in several languages like C++, Haskell, D.
Before you disgust, please recall the correct definition of type erasure from Wikipedia
What is type erasure?
type erasure refers to the load-time process by which explicit type annotations are removed from a program, before it is executed at run-time
Type erasure means to throw away type tags created at design time or inferred type tags at compile time such that the compiled program in binary code does not contain any type tags.
And this is the case for every programming language compiling to binary code except in some cases where you need runtime tags. These exceptions include for instance all existential types (Java Reference Types which are subtypeable, Any Type in many languages, Union Types).
The reason for type erasure is that programs get transformed to a language which is in some kind uni-typed (binary language only allowing bits) as types are abstractions only and assert a structure for its values and the appropriate semantics to handle them.
So this is in return, a normal natural thing.
Java's problem is different and caused to how it reifies.
The often made statements about Java does not have reified generics is also wrong.
Java does reify, but in a wrong way due to backward compatibility.
What is reification?
From Wikipedia
Reification is the process by which an abstract idea about a computer program is turned into an explicit data model or other object created in a programming language.
Reification means to convert something abstract (Parametric Type) into something concrete (Concrete Type) by specialization.
We illustrate this by a simple example:
An ArrayList with definition:
ArrayList<T>
{
T[] elems;
...//methods
}
is an abstraction, in detail a type constructor, which gets "reified" when specialized with a concrete type, say Integer:
ArrayList<Integer>
{
Integer[] elems;
}
where ArrayList<Integer> is really a type.
But this is exactly the thing what Java does not!!!, instead they reify constantly abstract types with their bounds, i.e. producing the same concrete type independent of the parameters passed in for specialization:
ArrayList
{
Object[] elems;
}
which is here reified with the implicit bound Object (ArrayList<T extends Object> == ArrayList<T>).
Despite that it makes generic arrays unusable and cause some strange errors for raw types:
List<String> l= List.<String>of("h","s");
List lRaw=l
l.add(new Object())
String s=l.get(2) //Cast Exception
it causes a lot of ambiguities as
void function(ArrayList<Integer> list){}
void function(ArrayList<Float> list){}
void function(ArrayList<String> list){}
refer to the same function:
void function(ArrayList list)
and therefore generic method overloading can't be used in Java.
I've encountered with type erasure in Android. In production we use gradle with minify option. After minification I've got fatal exception. I've made simple function to show inheritance chain of my object:
public static void printSuperclasses(Class clazz) {
Type superClass = clazz.getGenericSuperclass();
Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName()));
Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString()));
while (superClass != null && clazz != null) {
clazz = clazz.getSuperclass();
superClass = clazz.getGenericSuperclass();
Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName()));
Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString()));
}
}
And there is two results of this function:
Not minified code:
D/Reflection: this class: com.example.App.UsersList
D/Reflection: superClass: com.example.App.SortedListWrapper<com.example.App.Models.User>
D/Reflection: this class: com.example.App.SortedListWrapper
D/Reflection: superClass: android.support.v7.util.SortedList$Callback<T>
D/Reflection: this class: android.support.v7.util.SortedList$Callback
D/Reflection: superClass: class java.lang.Object
D/Reflection: this class: java.lang.Object
D/Reflection: superClass: null
Minified code:
D/Reflection: this class: com.example.App.UsersList
D/Reflection: superClass: class com.example.App.SortedListWrapper
D/Reflection: this class: com.example.App.SortedListWrapper
D/Reflection: superClass: class android.support.v7.g.e
D/Reflection: this class: android.support.v7.g.e
D/Reflection: superClass: class java.lang.Object
D/Reflection: this class: java.lang.Object
D/Reflection: superClass: null
So, in minified code actual parametrized classes are replaced with raw classes types without any type information.
As a solution for my project i removed all reflection calls and replced them with explicit params types passed in function arguments.