daniel098
daniel098

Reputation: 99

Generic classes in java and equals() method: type safety

I want to override equals() method in generic class and to do that I must cast Object to my generic type Pair.

I added @SuppressWarnings("unchecked") to "mute" the warnings, but the problem is still there. Methods getType() and getClass() aren't working with generic types too, so using T.getType() is out of the question.

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

    public Pair(T _first, U _second)
    {
        first = _first;
        second = _second;
    }

    public boolean equals(Object obj) {
         if (this == obj)
           return true;
         if (obj == null)
           return false;
         if (getClass() != obj.getClass())
           return false;

        //Problem ahead!!!
         Pair<T, U> other = (Pair<T, U>) obj;

                   ...
    }
}

Is there any general good practice or "trick" to do that correctly and safely?

Upvotes: 7

Views: 1608

Answers (6)

Fahad Iqbal
Fahad Iqbal

Reputation: 17

package com.journaldev.generics;

public class GenericsMethods {

//Java Generic Method
public static <T> boolean isEqual(GenericsType<T> g1, GenericsType<T> g2){
    return g1.get().equals(g2.get());
}

public static void main(String args[]){
    GenericsType<String> g1 = new GenericsType<>();
    g1.set("Test");

    GenericsType<String> g2 = new GenericsType<>();
    g2.set("Test");

    boolean isEqual = GenericsMethods.<String>isEqual(g1, g2);
    //above statement can be written simply as
    isEqual = GenericsMethods.isEqual(g1, g2);
    //This feature, known as type inference, allows you to invoke a generic method as an ordinary method, without specifying a type between angle brackets.
    //Compiler will infer the type that is needed
}
}

Upvotes: -2

Popeye
Popeye

Reputation: 12093

You could write your equals method like this:

@Override
public boolean equals(Object object) {
    boolean equal = false;

    if(this == object){
        equal = true;
    } else if(object instanceof Pair<?, ?>) {
        // Check that object is an instance of Pair<?, ?>, this will also null check.
        // Then just case object to Pair<?, ?> like.
        Pair<?, ?> pair = (Pair<?, ?>) object;

        if(((this.first == null && pair.first == null) || (this.first != null && this.first.equals(pair.first))) &&
                ((this.second == null && pair.second == null) || (this.second != null && this.second.equals(pair.second)))){
            equal = true;
        }
    }
    return equal;

The ? between the <> is kind of a wildcard, it's actually classed as the unbounded wildcard; which means the type of class has not been specified.

The object instanceof Pair<?, ?> will check two things, first it will check that the object is not null, so creates null safety for you and then it will check that the object is of type Pair<?, ?>.

You can read about wildcards here

As per ntalbs if you are overriding equals don't forget to override hashCode as well.

@Override
public int hashCode() {
    final int prime = 31;
    int result = super.hashcode;

    result = result * prime + (this.first == null ? 0 : this.first.hashCode());
    result = result * prime + (this.second == null ? 0 : this.second.hashCode());

    return result;
}

Why do I have to override hashCode when I override equals?

You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object.hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.

Upvotes: 4

ntalbs
ntalbs

Reputation: 29448

As the generic type is erased in compile time, you cannot check the type in runtime. So you shoud cast it to Pair<?,?>.

If you use JDK 7 or later, you can also use Objects.equals(..) in your equals method. This will simplify the code as you don't need to worry about if first or second is null.

Pair<?, ?> pair = (Pair<?, ?>) obj;
return Objects.equals(first, pair.first) &&
  Objects.equals(second, pair.second);

Also, don't forget to implement hashCode if you added equals method in your class.

@Override
public int hashCode() {
  return Objects.hash(first, second);
}

Upvotes: 1

Dimitris
Dimitris

Reputation: 589

You can use instanceof to check for the type of your object and then cast it safely.

if(obj instanceof Pair){
    Pair other = (Pair) obj;
}

Upvotes: 1

jbx
jbx

Reputation: 22148

First of all keep in mind that generics are only active at compile time. They are an extra layer of compile time checking to ensure you do not misuse containers or functions that can take different types.

Also remember that equals() has always been there since the first release of Java, while Generics were introduced in Java 1.5. So it is natural that for this specific method you will have some type casting to do.

After casting the object you will anyway be checking each individual element of the Pair, so if they are of a different type, they will fail the equality check too.

Upvotes: 1

Sweeper
Sweeper

Reputation: 271625

You can cast obj to a Pair<?, ?> and call equals on first and second:

Pair<?, ?> other = (Pair<?, ?>) obj;
return other.first.equals(first) && other.first.equals(second);

This way the type checks will be handled by T.equals and U.equals, whatever T and U are.

Upvotes: 6

Related Questions