Overcoming Type Erasure with Method Overloading in Java 5 Generics
I wrote a set of classes intended to represent a DatabaseID, and I wanted to make use of generics so that the appropriate overloaded equals() would be called. What I ended up doing was perhaps figuring out a way around type erasure. I've stripped everything down to make it relevant for this post:
// Base class that does most of the work, abstract.
public abstract class DatabaseID <>
{
protected
int
id;
protected
DatabaseID ( int newID )
{
id = newID;
}
// Overloaded equals(), specific to the parameterized type
public
boolean
equals ( ConcreteID otherID )
{
System.out.println( "Overloaded equals() called." );
return id == otherID.id;
}
// Overridden equals() that simply compares Objects
public
boolean
equals ( Object o )
{
System.out.println( "Object equals() called." );
return false;
}
}
// Concrete subclass of DatabaseID representing an OrderID
public class OrderID extends DatabaseID<>
{
// Contains nothing but a constructor to initialize its value
public
OrderID ( int newID )
{
super( newID );
}
}
// Concrete subclass of DatabaseID representing a UserID
public class UserID extends DatabaseID<>
{
// Contains nothing but a constructor to initialize its value
public
UserID ( int newID )
{
super( newID );
}
}
// The test class
public class Test
{
public static
void
main ( String[] argv )
{
OrderID o = new OrderID( 1 );
UserID u1 = new UserID( 1 );
UserID u2 = new UserID( 1 );
u1.equals( u2 ); // Calls overloaded equals()
o.equals( u2 ); // Calls equals( Object )
}
}
What surprised me was that I had expected type erasure to cause both of these calls to resolve to the overloaded equals(). Since I had defined the overloaded equals to take, the overloaded equals will ultimately erase to equals( DatabaseID ). Since both ID's are DatabaseID objects, I had expected that that version of equals() is the one that would have been called.
But if you run the code, you'll see that in the first case, where two UserID objects are being compared, the overloaded equals() is being called. But in the second case, where an OrderID object is being compared to a UserID object, the "normal" equals() that compares regular Objects is called.
At first I wondered what was going on, and then I realized that it's the compiler that chooses which overloaded method is called. The compiler is the one that looks at the types of the objects and determines which version of a method should get executed. Because this is done by the compiler, the compiler is still able to see the un-erased types, and thus in the second case it knows that two different types of objects are being compared, and chooses the equals( Object ) version. This can be confirmed by looking at the byte code of the Test class and seeing that in the first case, the compiler has inserted a call to the overloaded equals() method, but in the second case it has inserted a call to the regular equals() method.
So, method overloading is one case where you can use generics and type erasure won't come into play.
// Base class that does most of the work, abstract.
public abstract class DatabaseID <>
{
protected
int
id;
protected
DatabaseID ( int newID )
{
id = newID;
}
// Overloaded equals(), specific to the parameterized type
public
boolean
equals ( ConcreteID otherID )
{
System.out.println( "Overloaded equals() called." );
return id == otherID.id;
}
// Overridden equals() that simply compares Objects
public
boolean
equals ( Object o )
{
System.out.println( "Object equals() called." );
return false;
}
}
// Concrete subclass of DatabaseID representing an OrderID
public class OrderID extends DatabaseID<>
{
// Contains nothing but a constructor to initialize its value
public
OrderID ( int newID )
{
super( newID );
}
}
// Concrete subclass of DatabaseID representing a UserID
public class UserID extends DatabaseID<>
{
// Contains nothing but a constructor to initialize its value
public
UserID ( int newID )
{
super( newID );
}
}
// The test class
public class Test
{
public static
void
main ( String[] argv )
{
OrderID o = new OrderID( 1 );
UserID u1 = new UserID( 1 );
UserID u2 = new UserID( 1 );
u1.equals( u2 ); // Calls overloaded equals()
o.equals( u2 ); // Calls equals( Object )
}
}
What surprised me was that I had expected type erasure to cause both of these calls to resolve to the overloaded equals(). Since I had defined the overloaded equals to take
But if you run the code, you'll see that in the first case, where two UserID objects are being compared, the overloaded equals() is being called. But in the second case, where an OrderID object is being compared to a UserID object, the "normal" equals() that compares regular Objects is called.
At first I wondered what was going on, and then I realized that it's the compiler that chooses which overloaded method is called. The compiler is the one that looks at the types of the objects and determines which version of a method should get executed. Because this is done by the compiler, the compiler is still able to see the un-erased types, and thus in the second case it knows that two different types of objects are being compared, and chooses the equals( Object ) version. This can be confirmed by looking at the byte code of the Test class and seeing that in the first case, the compiler has inserted a call to the overloaded equals() method, but in the second case it has inserted a call to the regular equals() method.
So, method overloading is one case where you can use generics and type erasure won't come into play.

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