September 23, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Java Needs to Get a Pair (and a Triple...)

  • April 1, 2009
  • By Dick Wall
  • Send Email »
  • More Articles »

The Need for a Pair

The pattern above is one I use a lot as a struct replacement in Java API calls now, but it is still an overhead to create these classes if all you want to do is return two typed objects—something that is, in my experience, really common (many finder algorithms can be made much more efficient by simply returning a pair of related instead of one, for a key,value pair that will be added to a map).

Something that seems to be low hanging fruit for such a situation, but which is still mysteriously missing from the Java SE standard distribution, is a genericized Pair class. Take a look at how you could build it from the pattern above.

Firstly, the values would be more general than name and dob. The most universal would seem to be fields named first and second:

public class Pair {
    public final String first;
    public final Date second;

    public Pair(String first, Date second) {
        this.first = first;
        this.second = second;
    }
}

So far so good. You now have a general class for returning pairs of Strings and Dates, but not other types. Bring on the Generics:

public class Pair<A, B> {
    public final A first;
    public final B second;

    public Pair(A first, B second) {
        this.first = first;
        this.second = second;
    }
}

This is much better. There is no need to worry about wildcards for something that is just a quick way of representing a couple of return types. This class can now be used for general type pairs, for example:

public static Pair<String, Date> lookupBySSN(String ssn) {
    // find the person in the DB....
    return new Pair(person.getName(), new Date(person.getDOB().getTime()));
}

and to use it:

Pair<String, Date> personNameDOB = SSNLookup.lookupBySSN("123-45-6789");
System.out.println(personNameDOB.first);
System.out.println(personNameDOB.second);

You aren't finished yet with the Pair class though. Some of the things you need to consider if this thing is going to be truly universal:

  • You don't want someone extending the Pair class and changing what it does - that might break the original intent of the class.
  • The new Pair() is okay, but it is a little clumsy looking. You can do better than that.
  • Pair is pretty useful just for returning values, but with a little effort it could, for example, be used as the key in a map.
  • It would be nice to have a pretty form of the Pair string representation for debugging or other toString() usage.
  • Finally, perhaps (and this is debatable), it would be nice to allow the Pair and contained objects to be serializable assuming the contents are serializable too.

So, let's see how that changes the pair implementation up:

public final class Pair<A,B> implements Serializable {

    private static final long serialVersionUID = 1L;  // shouldn't 
                                                      // need to change

    public final A first;
    public final B second;

    private Pair (A first, B second) {
        this.first = first;
        this.second = second;
    }

    public static <A,B> Pair<A,B> of (A first, B second) {
       return new Pair<A,B>(first,second);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Pair other = (Pair) obj;
        if (this.first != other.first && 
                (this.first == null || !this.first.equals(other.first))) {
            return false;
        }
        if (this.second != other.second && 
                (this.second == null || !this.second.equals(other.second))) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 37 * hash + (this.first != null ? 
                              this.first.hashCode() : 0);
        hash = 37 * hash + (this.second != null ? this.second.hashCode() : 0);
        return hash;
    }

    @Override
    public String toString () {
        return String.format("Pair[%s,%s]", first,second);
    }
}

You have made the constructor private, but provided a static of() method, which I think reads more nicely:

return Pair.of(person.getName(), new Date(person.getDOB().getTime()));

You have made the Pair class final, so no one will override it and change the original intent of the class. It might seem strict, but with something that is intended for wide usage, it is sensible to be quite defensive about things like this. If someone wants Pair to work differently, they should write their own implementation, and justify it to their peers.

The equals() and hashCode() methods mean that this class can be used for more purposes than just returning values. They can used, for example, as the key to a map. A recommendation here, for any kind of return object using this pattern, is to let your IDE create the equals and hashCode methods for you - these are simple implementations but even here there are some things to remember about the contract with hashCode and equals, for example null checks and different types (see the excellent description in Josh Bloch's Effective Java 2nd ed. for the definitive writeup on the subject). IDEs tend to have boilerplate insertions with the best practices already defined, so I just had NetBeans create these for me. Perhaps they could be streamlined a little, but these implementations are safe. I did remove the generic signature from the NetBeans equals() implementation though, as it is not necessary and could create confusion.

The toString() override just prints a pretty form of the pair, like "Pair[Fred Jones,Sun Mar 22 12:55:44 PDT 2009]". This is particularly useful for, say, debugging popups.

The class now implements Serializable. I believe that this is the point where the choices become more dubious, but since collections are serializable, Pair should be as well in my opinion. If the classes used in the Pair are not themselves serializable, that is no worse than classes in collections not being serializable, and Pair should not be the thing that prevents serialization used in a class.





Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel