Maybe Java Generics Really Are Useless
So in March I thought I had found a pretty cool way to get around type erasure in Java 5 generics. If you overload methods with generic types, the resolution of which overloaded method to call happens at compile-time, so the compiler will hard-code the correct method to call. Or so I thought. This led me to think that I could do something cool like write a base class to handle all the correct operator equals() operations without requiring a runtime type-check. I describe this in detail in my blog posting titled "Overcoming Java 5 Generics Type Erasure with Method Overloading".
In that posting I basically posited that given:
class MyClass
{
public boolean equals( T o ) {}
public boolean equals( Object o) {}
}
MyClass myString = new ...
myString.equals( "someString" );
myString.equals( new Object() );
I thought that I could now assume that the proper equals() would always get called, because the compiler would at compile-time figure out the appropriate equals() and hard-code that into the bytecode, thus avoiding type erasure. In other words, I assumed that the "T" would get replaced with String in the definition of MyClass, and thus when I called equals() the compiler would hard-wire a call the correct overloaded version at compile-time.
Turns out I was mostly wrong, because the compiler only knows about the correct type in the scope in which the correct type is specified. In other words, if you say "MyClass<> myclass", then within the immediate scope of 'myclass' the compiler knows what type 'myclass' is and will call the appropriate overloaded type. That's why I got tricked into thinking this would always work.
This led me to think that I could send in MyClass to a generically-typed object, and then when the object called "equals()" the appropriate overloaded version of MyClass's equals() would get called. So if I had:
class GenericClass
{
public void doSomething( T a ) {
a.equals( "someString" );
}
}
// Code snippet
GenericClass o = new ....;
MyClass myclass = new ....;
o.doSomething( myclass );
I thought that within doSomething() the proper equals would get called in this situation, since the compiler would know that myclass is of generic type "String". Unfortunately this is not the case. Once the scope passed into the doSomething method, the compiler "forgot" about the generic type of myclass, and instead just defaulted to "Object". So instead of calling equals(T) the compiler coded in equals(Object).
This makes sense in a way, because the compiler can only see the generic type at the boundary level where it is specified. Once it enters a block of code that treats the code generically it can't keep track of the type anymore, not if it wants to allow the block of code to be pre-compiled and distributed in binary form.
Put into another way, the decision of which overloaded method to call is always made at compile-time. So if one block of code can be compiled separately from another block of code, then it's not possible for the compiler to figure out retroactively which overloaded method to call given the constraints of Java 5 generics, so it has to default to the lowest-common-denominator, which is usually Object.
Man, Java 5 generics really aren't all that useful...
In that posting I basically posited that given:
class MyClass
{
public boolean equals( T o ) {}
public boolean equals( Object o) {}
}
MyClass
myString.equals( "someString" );
myString.equals( new Object() );
I thought that I could now assume that the proper equals() would always get called, because the compiler would at compile-time figure out the appropriate equals() and hard-code that into the bytecode, thus avoiding type erasure. In other words, I assumed that the "T" would get replaced with String in the definition of MyClass, and thus when I called equals() the compiler would hard-wire a call the correct overloaded version at compile-time.
Turns out I was mostly wrong, because the compiler only knows about the correct type in the scope in which the correct type is specified. In other words, if you say "MyClass<> myclass", then within the immediate scope of 'myclass' the compiler knows what type 'myclass' is and will call the appropriate overloaded type. That's why I got tricked into thinking this would always work.
This led me to think that I could send in MyClass to a generically-typed object, and then when the object called "equals()" the appropriate overloaded version of MyClass's equals() would get called. So if I had:
class GenericClass
{
public void doSomething( T a ) {
a.equals( "someString" );
}
}
// Code snippet
GenericClass
MyClass
o.doSomething( myclass );
I thought that within doSomething() the proper equals would get called in this situation, since the compiler would know that myclass is of generic type "String". Unfortunately this is not the case. Once the scope passed into the doSomething method, the compiler "forgot" about the generic type of myclass, and instead just defaulted to "Object". So instead of calling equals(T) the compiler coded in equals(Object).
This makes sense in a way, because the compiler can only see the generic type at the boundary level where it is specified. Once it enters a block of code that treats the code generically it can't keep track of the type anymore, not if it wants to allow the block of code to be pre-compiled and distributed in binary form.
Put into another way, the decision of which overloaded method to call is always made at compile-time. So if one block of code can be compiled separately from another block of code, then it's not possible for the compiler to figure out retroactively which overloaded method to call given the constraints of Java 5 generics, so it has to default to the lowest-common-denominator, which is usually Object.
Man, Java 5 generics really aren't all that useful...

0 Comments:
Post a Comment
Links to this post:
Create a Link
<< Home