Error in the equal method of generic Pair class

P

puzzlecracker

public class Pair <T, U>
{

@Override
public boolean equals( Object o )
{
if ( this == o )
{
return true;
}
if ( o == null || !(getClass().isInstance( o )) )
{
return false;
}
Pair<T, U> other = getClass().cast(o);
return (first == null? other.first == null :
first.equals( other.first ))
&& (second == null? other.second == null :
second.equals( other.second ));
}

}

This line gives a cast error during compilation: Pair<T, U> other =
getClass().cast(o);

Why can't we use a traditional Pair<T, U> other =(Pair<T, U> ) o; ?


Thanks
 
S

Stefan Ram

puzzlecracker said:
Pair<T, U> other = getClass().cast(o);
This line gives a cast error during compilation: Pair<T, U> other =
getClass().cast(o);

Type parameters are for compile-time type information.
There is no such static type information available here.
Therefore, do not use type parameters here.
Use »final Pair other =( Pair )other«.
 
T

Tom Anderson

public class Pair <T, U>
{

@Override
public boolean equals( Object o )
{
if ( this == o )
{
return true;
}
if ( o == null || !(getClass().isInstance( o )) )
{
return false;
}
Pair<T, U> other = getClass().cast(o);
return (first == null? other.first == null : first.equals( other.first ))
&& (second == null? other.second == null : second.equals( other.second ));
}

}

This line gives a cast error during compilation: Pair<T, U> other =
getClass().cast(o);

Why can't we use a traditional Pair<T, U> other =(Pair<T, U> ) o; ?

Because there's no way to actually check that cast. The way java does
generics is through a half-arsed approach called "type erasure", which
means all the stuff to do with generic types is done at compile time, and
none of the information survives to runtime. What looks like a
Pair<String, Date> to the compiler is a Pair<Object, Object> to the JVM.
Seriously. Java will only let you do casts it can actually check [1], so
it won't let you do this one.

So, what can you do?

You could cast to Pair<T, U>, suppress the warning, and hope that equals
never gets called with a different kind of pair. If it does, that cast
will still pass, but you'll get a random ClassCastException later on.

You could cast to Pair, without type parameters, and suppress the warning
about it being araw type.

You could cast to Pair<Object, Object>, and suppress the warning about the
unchecked cast, safe in the knowledge that this will always work.

Or, you could enter the voodoo wonderland of expert generics, and cast to
Pair<?, ?>. That means a Pair that could have any types as parameters, so
it compiles without warnings (at least, it does under Eclipse). And,
because you're never doing anything with its elements that requires them
to be of any specific type, it should work fine with the rest of your
code.

tom

[1] Apart from with arrays, kind of.
 
P

puzzlecracker

public class Pair <T, U>
{
   @Override
   public boolean equals( Object o )
   {
           if ( this == o )
           {
                   return true;
           }
           if ( o == null || !(getClass().isInstance( o )) )
           {
                   return false;
           }
           Pair<T, U> other = getClass().cast(o);
           return (first == null? other.first == null : first.equals( other.first ))
           && (second == null? other.second == null : second.equals( other.second ));
   }

This line gives a cast error during compilation: Pair<T, U> other =
getClass().cast(o);
Why can't we use a traditional  Pair<T, U> other =(Pair<T, U> ) o; ?

Because there's no way to actually check that cast. The way java does
generics is through a half-arsed approach called "type erasure", which
means all the stuff to do with generic types is done at compile time, and
none of the information survives to runtime. What looks like a
Pair<String, Date> to the compiler is a Pair<Object, Object> to the JVM.
Seriously. Java will only let you do casts it can actually check [1], so
it won't let you do this one.

So, what can you do?

You could cast to Pair<T, U>, suppress the warning, and hope that equals
never gets called with a different kind of pair. If it does, that cast
will still pass, but you'll get a random ClassCastException later on.

You could cast to Pair, without type parameters, and suppress the warning
about it being araw type.

You could cast to Pair<Object, Object>, and suppress the warning about the
unchecked cast, safe in the knowledge that this will always work.

Or, you could enter the voodoo wonderland of expert generics, and cast to
Pair<?, ?>. That means a Pair that could have any types as parameters, so
it compiles without warnings (at least, it does under Eclipse). And,
because you're never doing anything with its elements that requires them
to be of any specific type, it should work fine with the rest of your
code.

tom

[1] Apart from with arrays, kind of.

Hey would you exemplify in my equal method how to do that - Pair<?, ?>
in the context of the cast?

Thanks
 
P

puzzlecracker

I follow Josh Bloch's advice:

   @SuppressWarnings( "unchecked" ) // class check already prevented CCE
   final SomePair <T, U> other = getClass().cast(o);

You suppress warnings iff you can prove there will be no CCE, and you comment
the suppression as to how you prevented CCE.

I don't understand why we need final here, please explain.

Thanks
 
L

Lew

Tom said:
Because there's no way to actually check that cast. The way java does
generics is through a half-arsed approach called "type erasure", which
means all the stuff to do with generic types is done at compile time, and
none of the information survives to runtime. What looks like a
Pair<String, Date> to the compiler is a Pair<Object, Object> to the JVM.

Actually, it's a Pair <?, ?> to the JVM.
 
L

Lew

I don't understand why we need final here, please explain.

You don't, really, but it's a hint to the compiler that the reference
cannot change. This might allow certain optimizations that wouldn't
be provable for a non-final variable.

This is how my IDE generates equals() overrides. Whoever created that
template was a believer in locking down with 'final' variables that
cannot or should not change, a best practice.
 
T

Tom Anderson

Hey would you exemplify in my equal method how to do that - Pair<?, ?>
in the context of the cast?

public class Pair<T, U> {
public final T first ;
public final U second ;

public Pair(T first, U second) {
this.first = first ;
this.second = second ;
}
public boolean equals(Object obj) {
if (obj == this) return true ;
if (obj == null) return false ;
if (!(obj instanceof Pair)) return false ;
Pair<?, ?> p = (Pair<?, ?>)obj ;
return equals(first, p.first) && equals(second, p.second) ;

}
private static boolean equals(Object a, Object b) {
return (a != null) ? (a.equals(b)) : (b == null) ;
}

}

tom
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,012
Latest member
RoxanneDzm

Latest Threads

Top