Generics type erasure in Java - java

Here's the code:
public class Main {
public static void main(String[] args) {
Gen<Integer> g = new Gen<Integer>(5);
System.out.println(g.getClass());
System.out.println(g.ob.getClass());
}
}
class Gen<T> {
T ob;
public Gen(T x) {
ob = x;
}
}
And here's the output
class Gen // This I understand
class java.lang.Integer // But if type erasure is happening, shouldn't this be java.lang.Object?
I get it that Type parameter T is erased at runtime, but then why is the type parameter of ob surviving at runtime?

Nope!
Consider this:
Object x = new Integer(1);
System.out.println(x.toString());
You'll get 1.
But shouldn't I get Object.toString()?
No. While x is a reference of type Object, the actual referent is an Integer, so at run-time, the Integer implementation of toString is called.
It is the same for getClass.

Type erasure is happening. Generics are a compile time type checking system. At run-time you still get the class (it is run-time type information). The linked Type erasure documentation says (in part)
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.
Your instance has a type, it's Object. But an Object reference can refer to any sub-class (which is every class) in Java. You get the type it refers to.

No matter what type the variable has, the return value of getClass() depends on the variable's contents. So, since you basically have a variable Object ob which contains an Integer (your int was converted to it at the time you provided it as that constructor's parameter), the output of ob.getClass() is class java.lang.Integer.
Also, about your question as to why getClass() remembers the type argument: it doesn't. All it does is determine the content's class. For example:
class Foo {
public Foo() {}
}
class Bar extends Foo {
public Bar() {}
}
class Baz<T> {
public T object;
public Baz(T object) { this.object = object; }
}
If you now run the following snippet...
public static void main(String... args) {
Baz<Foo> obj = new Baz(new Bar());
System.out.println(obj.object.getClass());
}
You will notice that the output is not class Foo, it's class Bar.

Because when compiled, class Gen has an Object ob; The generics disappear from the final product. The angle brackets only play a role at compile-time, during static type checking. It's something the compiler can do for you to give you better peace of mind, to assure you that you're using collections and other paramterized types correctly.
the actual object assigned to ob at runtime is an instance of the Integer class, and ob.getClass() serves the purpose to find out the actual class of the object referenced by the pointer -> hence you will see java.lang.Integer printed.
Remember, what comes out is effectively class Gen { Object ob; ... }

Since my first exposure to generics was with C# , it took time get a hold of what type erasure is in java.
But after better understanding java generics , i realized that in my question i'm mixing 2 separate topics : Generics and Reflection.
The main question was , why did the second call here
System.out.println(g.getClass());
System.out.println(g.ob.getClass());
returned java.lang.Integer instead of java.lang.Object.
Looking at the docs for getClass() , the answer becomes obvious
Returns the runtime class of this Object.
so, getClass() doesn't return the type of reference but the actual object being referred to by the reference.
For ex :
Object o = "abc";
System.out.println(o.getClass());
The output wouldn't be the type of the reference java.lang.Object but rather the actual type of the object java.lang.String.

Related

Casting an Object to an Interface

i have the following Classes and interfaces:
public interface if1 {}
public class ifTest implements if1 {}
public class HelloWorld {
public static void main(String[] args) {
//why does the following cast not work (Compiler error)
ifTest var3 = (if1) new ifTest();
}
}
Why does this cast not work? I get the following compile error: "Type mismatch: cannot convert from if1 to ifTest"
Inheritance denotes an is-a relationship. Any object of IfTest is-an If1. However, a reference of type If1 does not necessarily refer to an object of type IfTest.
In a textbook scenario, any Dog is an Animal but any Animal is not a Dog (it might be a zebra, elephant, deer, etc).
Casting takes the form:
A a = (B) c;
There are 2 steps involved:
You have any object c that is cast to type B.
That instance of B is then assigned to variable a (where A is a type that must be either equal to B or a super class of B for the compiler to be happy and for no ClassCastExceptions to occur at runtime.
Continuing the animal metaphor, you are casting Dog to an Animal in step 1, but then trying to assign that instance of an Animal to a variable of type Dog, which the compiler knows is not allowed.
The following would work because it casts IfTest to If1 and then assigning that If1 to IfTest.
if1 var3 = (if1) new ifTest();
EDIT As Lew points out in his comment below, this is what is referred to as a widening cast (because it goes from a specific type to a more generic, "wider" type. In this instance, the cast is not necessary because the compiler knows all references to objects of type IfTest by definition are also If1.
A more practical example would be if you had a reference to the interface and wanted to cast it to a IfTest. In this case, it's a "narrowing" cast because you're going from the generic type to a more specific, "narrower" type.
if1 var1 = (if1) new ifTest();
if (var1 instanceOf ifTest) {
ifTest var2 = (ifTest) var1;
}
Note the use of the instanceof to to ensure that var1 is in fact an instance of IfTest. While we know it is, in a real-world application If1 might have multiple subtypes so this will not always be true. The check prevents the cast from throwing a run-time ClassCassException.
TLDR: The important thing to know is what is inside the parenthesis must be a type the compiler knows can be assigned to the variable being assigned to.
Here you are trying to instantiate an interface. An interface is just like a template or something similar to a contract - the class which implements the interface must obey the contract and provide implementation of the methods.

Usage of Java generics when the type is known only at runtime

Consider the following code:
public class Generics {
C c; // initialized at runtime
public void testGenericsCall(Object o) {
c.myMethod(o);
}
}
class C<E> {
public void myMethod(E input) {
}
}
This is working, but I get warnings because the parametrized class C is used with a raw type. I cannot use a declaration like
C<String> c;
because the type of C is known only at runtime. I also cannot add a type parameter to the class Generics because I need to create objects of this class before knowing the type of C. The declaration
C<?> c;
or
C<? extends Object> c;
would be OK for the compiler, but then the method testGenericsCall does not compile ("actual argument java.lang.Object cannot be converted to capture#1 of ? by method invocation conversion")
What is the best way to deal with a situation like this?
EDIT: Note that when I actually (at runtime) create an instance of C, I know its type parameter, this part of the code is type-safe and working well. In the real code, I don't have a single "C" class, but a series of interrelated classes, and there the generics are definitely useful (even if in this simplified example this is not obvious - so please don't just tell me not to use generics :). I already have the compile-time type-safety, but not here, but between C and other classes (not shown here).
I see how in this case I cannot check the type parameter at compile time, that's why I tried to declare it C<?> c. Here I am just looking for the best way to bridge the generic and not-generic code without compiler warnings.
Because of type erasure, there's no way to use generics at runtime. You'll have to deal with your data type programmatically, by checking type or anything (reflection maybe).
You can do it. But through dirty tricks and reflection. Look at below code for example. Courtesy here:
class ParameterizedTest<T> {
/**
* #return the type parameter to our generic base class
*/
#SuppressWarnings("unchecked")
protected final Class<T> determineTypeParameter() {
Class<?> specificClass = this.getClass();
Type genericSuperclass = specificClass.getGenericSuperclass();
while (!(genericSuperclass instanceof ParameterizedType) && specificClass != ParameterizedTest.class) {
specificClass = specificClass.getSuperclass();
genericSuperclass = specificClass.getGenericSuperclass();
}
final ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
final Type firstTypeParameter = parameterizedType.getActualTypeArguments()[0];
return (Class<T>) firstTypeParameter;
}
}
//change the type of PrameterizedTest<Integer> to Parameterized<String> or something to display different output
public class Test extends ParameterizedTest<Integer>{
public static void main(String... args){
Test test = new Test();
System.out.println(test.determineTypeParameter());
}
}
Here on the runtime, you get the Type Parameter. So instead in your class, you will have to define a Class object which gets the class as explained above. Then using Class.newInstance you get a new Object. But you will have to manually handle type cast and so on.
The question is: Is all this worth it??
No according to me as most of it can be avoided by using bounds in generic types and interfacing to the bound type

Compilation error with == operator

I've isolated the error to this line:
string.getClass() == jojo.getClass()
Shouldn't this line create two Class objects and then check if they (as in the two references) point to the same object? Rather than returning a value of false, the code won't run.
public class Tester
{
public static void main(String[] args)
{
OreoJar jojo = new OreoJar(0);
OreoJar momo = new OreoJar(1);
String string = "Hello";
if (momo.getClass() == jojo.getClass())
{
System.out.println("Momo and jojo are of the same class");
}
if (string.getClass() == jojo.getClass())
{
System.out.println("String and jojo are of the same class");
}
}
}
public class OreoJar
{
int oreos;
public OreoJar(int oreos)
{
this.oreos = oreos;
}
public void count()
{
System.out.println(oreos + " oreos in this jar!");
}
}
This comment is kind of hidden and I think its worth mentioning since it makes the most sense to a beginner (such as myself)
-According to the JLS "It is a compile-time error if it is impossible to convert the type of either operand to the type of the other by a casting conversion" so two references of types A and B can be compared if, and only if, either A can be cast to B or B can be cast to A. – Patricia Shanahan
I agree OP should quote the compilation error.
Anyway the compilation error is quite obvious when anyone actually does a compilation.
The error is:
Tester.java:15: incomparable types: java.lang.Class<capture#125 of ? extends java.lang.String> and java.lang.Class<capture#29 of ? extends OreoJar>
if (string.getClass() == jojo.getClass()){
^
Reason seems obvious.
From Javadoc of Object.getClass():
The java.lang.Class object that represents the runtime class of the
object. The result is of type Class<? extends X> where X is the
erasure of the static type of the expression on which getClass is
called.
That means, an String instance is going to return a reference to Class<? extends String>, while an OreoJar instance is going to return reference to Class<? extends OreoJar>
The two types are simply not compatible, as the compiler knows that there is no chance that any type that extends String can be a type extends OreoJar. So comparison is going to cause compilation error.
A bit off topic but I think worth mentioning, you said:
Shouldn't this line create two Class objects and then check if they point to the same object
I think it is better to have clearer understanding. It is not going to "create" two Class objects. getClass() is going to return you a reference to Class object. And, it is always a reference that can point to an object, not object that point to object (it sounds weird too)
I think the reason it won't compile is due to the fact that Class has generic component. Try using momo.getClass().equals(jojo.getClass())
And you might also try comparing the canonical names of the classes for a similar effect: momo.getClass().getCanonicalName().equals(jojo.getClass().getCanonicalName())
getClass() returns an instance of a Class. getClass().getName() returns a string. The String.equals(otherString) method is the correct way to compare Strings for equality.

Why doesn't Class have a nice generic type in this case?

In this code, why can type not be declared as Class<? extends B>?
public class Foo<B> {
public void doSomething(B argument) {
Class<? extends Object> type = argument.getClass();
}
}
This problem is that Java's syntax doesn't allow getClass() to say it's returning a type which matches the class its defined in, and this is not a special case as far as the compiler is concerned. So you are forced to cast the result.
There are many cases where you would like to be able to specify this type, e.g. for chaining, so I would hope they include this feature one day.
You could write
Class<? extends this> getClass();
or
public this clone(); // must return a type of this class.
or
class ByteBuffer {
this order(ByteOrder order);
}
class MappedByteBuffer extends ByteBuffer {
}
// currently this won't work as ByteBuffer defines order()
MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0, fc.size())
.order(ByteOrder.nativeOrder());
This is all about generic type erasure. From here:
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. [at compile time]
So you cannot get Class of actual type of B but only ? or ? extends Object.
If your bounds will be turn into <B extends SomeClass> instead of <B> only then you can fetch Class object of type <? extends SomeClass>.
Object.getClass() is defined to return a Class, where T is the statically known type of the receiver (the object getClass() is called on). Take special note of the vertical bars, the erasure operator. The erasure of a type variable is the erasure of its leftmost bound. In your case that's the implicit bound Object. So you get back a Class, not a Class<? extends T>.
The right way to do it is,
abstract class AbstractExecutor<E> {
public void execute() throws Exception {
List<E> list = new ArrayList<E>();
Class<E> cls = (Class<E>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
E e = cls.getConstructor(String.class).newInstance("Gate");
list.add(e);
System.out.println(format(list));
}
// ...
}
Because the class of a given object is not guaranteed to be the same as the type it is stored in.
eg.
Object o = "some string";
Class<Object> clazz = o.getClass(); // actually Class<String>
By looking at the type you should expect the Class for Object, but in reality you get the class for String. What problem is this you might ask -- Object is a superclass of String, so String implements all the methods implemented by Object.
Problems
The problems are that when getting a Field the class will return the fields of the actual class and not the generic type. In addition, whilst Method is able to invoke the correct method if there is an overriding method in the given object, it is not able to do the reverse and find an implementation of the method that will work on the given object.
For instance, Object declares hashCode, so all objects have a hash code method. However, the following will produce a runtime exception:
Object.class.getMethod("hashCode").invoke("some string"); // works
String.class.getMethod("hashCode").invoke(new Object()); // fails
This is because the Method object for hashCode is expecting a String. It's expecting to generate a hash code from the sequence of characters, but the provided object does not have a char array for the method to work on, so it fails.
Meaning the following looks like it should work, but won't because the actual method returned by getMethod is the hash code method for String.
Object obj = "string";
Class<Object> clazz = obj.getClass();
clazz.getMethod("hashCode").invoke("another string");

Java generics T vs Object

I was wondering what is the difference between the following two method declarations:
public Object doSomething(Object obj) {....}
public <T> T doSomething(T t) {....}
Is there something you can/would do with one but not the other? I could not find this question elsewhere on this site.
Isolated from context - no difference. On both t and obj you can invoke only the methods of Object.
But with context - if you have a generic class:
MyClass<Foo> my = new MyClass<Foo>();
Foo foo = new Foo();
Then:
Foo newFoo = my.doSomething(foo);
Same code with object
Foo newFoo = (Foo) my.doSomething(foo);
Two advantages:
no need of casting (the compiler hides this from you)
compile time safety that works. If the Object version is used, you won't be sure that the method always returns Foo. If it returns Bar, you'll have a ClassCastException, at runtime.
The difference here is that in the first, we specify that the caller must pass an Object instance (any class), and it will get back another Object (any class, not necessarily of the same type).
In the second, the type returned will be the same type as that given when the class was defined.
Example ex = new Example<Integer>();
Here we specify what type T will be which allows us to enforce more constraints on a class or method. For example we can instantiate a LinkedList<Integer> or LinkedList<Example> and we know that when we call one of these methods, we'll get back an Integer or Example instance.
The main goal here is that the calling code can specify what type of objects a class will operate upon, instead of relying on type-casting to enforce this.
See Java Generics* from Oracle.
*Updated Link.
The difference is that with generic methods I don't need to cast and I get a compilation error when I do wrong:
public class App {
public static void main(String[] args) {
String s = process("vv");
String b = process(new Object()); // Compilation error
}
public static <T> T process(T val) {
return val;
}
}
Using object I always need to cast and I don't get any errors when I do wrong:
public class App {
public static void main(String[] args) {
String s = (String)process("vv");
String b = (String)process(new Object());
}
public static Object process(Object val) {
return val;
}
}
You don't need to do additional class casting. In first case you will always get an object of class java.lang.Object which you will need to cast to your class. In second case T will be replaced with the class defined in generic signature and no class casting will be needed.
At runtime, nothing. But at compile time the second will do type checking to make sure the type of the parameter and the type of the return value match (or are subtypes of) whatever type T resolves to (the first example also does type checking but every object is a subtype of Object so every type will be accepted).
T is a generic type. Meaning it can be substituted by any qualifying object at runtime. You may invoke such a method as follows:
String response = doSomething("hello world");
OR
MyObject response = doSomething(new MyObject());
OR
Integer response = doSomething(31);
As you can see, there is polymorphism here.
But if it is declared to return Object, you can't do this unless you type cast things.
in the first case it takes a parameter of any type e.g.string and return a type foo. In the second case it takes a parameter of type foo and returns an object of type foo.
There are few reasons that you can consider Generics over Object type in Java:
Generics is flexible and safe. At the same time, working with Object that requires type-casting is error-prone
Type Casting in Java is slow
ref : [1]: https://www.infoworld.com/article/2076555/java-performance-programming--part-2--the-cost-of-casting.html

Categories

Resources