Generic pair class

2019-01-09 06:51发布

问题:

Just attempting this question I found in a past exam paper so that I can prepare for an upcoming Java examination.

Provide a generic class Pair for representing pairs of things. The class should provide a constructor, a method for getting the first member of the pair, a method for getting the second member of the pair, a method for setting the first member of the pair, a method for setting the second member of the pair. The class should be parameterised over two types one for the first member and one for the second member of the pair.

Is this a correct implementation for this question ?

public class Pair<firstThing, secondThing>{
   private firstThing first;//first member of pair
   private secondThing second;//second member of pair

   public Pair(firstThing first, secondThing second){
     this.first = first;
     this.second = second;
   }

   public void setFirst(firstThing first){
    this.first = first;
   }

   public void setSecond(secondThing second) {
     this.second = second;
   }

   public thing getFirst() {
     return this.first;
   }

   public thing getSecond() {
     return this.second;
   }
}

回答1:

Almost. I'd write it like this:

public class Pair<F, S> {
    private F first; //first member of pair
    private S second; //second member of pair

    public Pair(F first, S second) {
        this.first = first;
        this.second = second;
    }

    public void setFirst(F first) {
        this.first = first;
    }

    public void setSecond(S second) {
        this.second = second;
    }

    public F getFirst() {
        return first;
    }

    public S getSecond() {
        return second;
    }
}

Edit: I agree with @karmakaze's comment. The code should skip the setters and make first and second final to keep it immutable.



回答2:

The need for a Pair class usually crops up in larger projects - I'm about to (re)implement one for the current project (as previous implementations are not accessible).

Generally I make it an immutable POJO, with a convenience function to create instances. For example:

public class Pair<T,U>
{
    public final T first;
    public final U second;
    public static <T,U> Pair<T,U> of(T first, U second);
}

So that the end-user can write:

return Pair.of (a, b);

and

Pair<A,B> p = someThing ();
doSomething (p.first);
doSomethingElse (p.second);

As mentioned above, the Pair class should also implement hashCode(), equals(), optional-but-useful toString(), as possibly clone() and compareTo() for use where these are supported by T and U - though extra work is required to describe how these contracts are supported by the Pair class.



回答3:

You can look to implementation of standard Java classes AbstractMap.SimpleEntry and AbstractMap.SimpleImmutableEntry. It is pretty easy to google sources:

  • http://www.docjar.com/html/api/java/util/AbstractMap.java.html
  • http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/AbstractMap.java


回答4:

Here is an implementation from the Android SDK

/**
 * Container to ease passing around a tuple of two objects. This object provides a sensible
 * implementation of equals(), returning true if equals() is true on each of the contained
 * objects.
 */
public class Pair<F, S> {
    public final F first;
    public final S second;

    /**
     * Constructor for a Pair.
     *
     * @param first the first object in the Pair
     * @param second the second object in the pair
     */
    public Pair(F first, S second) {
        this.first = first;
        this.second = second;
    }

    /**
     * Checks the two objects for equality by delegating to their respective
     * {@link Object#equals(Object)} methods.
     *
     * @param o the {@link Pair} to which this one is to be checked for equality
     * @return true if the underlying objects of the Pair are both considered
     *         equal
     */
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Pair)) {
            return false;
        }
        Pair<?, ?> p = (Pair<?, ?>) o;
        return Objects.equal(p.first, first) && Objects.equal(p.second, second);
    }

    /**
     * Compute a hash code using the hash codes of the underlying objects
     *
     * @return a hashcode of the Pair
     */
    @Override
    public int hashCode() {
        return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
    }

    /**
     * Convenience method for creating an appropriately typed pair.
     * @param a the first object in the Pair
     * @param b the second object in the pair
     * @return a Pair that is templatized with the types of a and b
     */
    public static <A, B> Pair <A, B> create(A a, B b) {
        return new Pair<A, B>(a, b);
    }
}


回答5:

I think No. Quote:

"the class should be parameterised over two types..."

I think they are expecting in terms of :

public class Pair<ThingA, ThingB>


回答6:

Usually a generic Pair type has two generic type parameters, not one - so you could have (say) a Pair<String, Integer>. That's typically more useful, IMO.

I would also suggest that you think about a more conventional name for your type parameter than "thing". For example, you might use Pair<A, B> or Pair<T, U>.



回答7:

Getters are broken

public thing getFirst() {
  return thing.first;
}

public thing getSecond() {
  return thing.second;
}

thing should be replaced with this



回答8:

After editing, it looks good.

However, you really should implement the hashCode and equals methods, so that two pairs containing the same objects will be equal to each other, and can be used as keys in a HashMap. And toString if you're feeling generous. These methods are not required to fulfil the requirements you've been given, but they are things a good programmer would add.



回答9:

The class should be parameterised over two types one for the first member and one for the second member of the pair.

You have only one parameter.

you need something like Pair<F,S> and use F where you use thing for first and S where thing for second.



回答10:

No. Have you tried coding it to see if it works?

You seem to have missed this part of the requirement:

The class should be parameterised over two types one for the first member and one for the second member of the pair.

Which means the class should probably be defined as something more like:

public class Pair<T1, T2>

and the other methods updated accordingly. (By the way, I've used T1 and T2 to refer to the types as by convention a short - 1 or 2 char - identifier is used).

Also,

return thing.first;

and

return thing.second;

are not going to work, as in your example, thing is a type, not an object. Think about what you want to return here. Do you even need to call a method?

Once you've made your changes, code it and either write a unit test or a simple test-harness to check if it works.



回答11:

Apache Commons Lang has a generic pair implementation

https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/tuple/Pair.html



回答12:

thing is a Type Variable in an unsual notation - we usually use one uppercase latter (like T). Then: a type variable does not have any methods, so your getters won't compile.

Quick improvement: replace all thing with T

Quick fix for getters:

public T getFirst() {
 return first;
}

public T getSecond() {
 return second;
}

One requirement was to allow two different types for the pair members. So the class signature should look like:

public Pair<S,T> {
  private S first;
  private T second;
  //...
}


回答13:

My version of Pair. This also handles compares. PS : Most of the code is taken from AOSP.

package util;

import java.util.Objects;

public class Pair<F extends Comparable<F>, S extends Comparable<S>>
  implements Comparable<Pair<F, S>> {

    public final F first;
    public final S second;

    /**
     * Constructor for a Pair.
     *
     * @param first  the first object in the Pair
     * @param second the second object in the pair
     */
    public Pair(F first, S second) {
        this.first = first;
        this.second = second;
    }

    /**
     * Checks the two objects for equality by delegating to their respective
     * {@link Object#equals(Object)} methods.
     *
     * @param o the {@link Pair} to which this one is to be checked for equality
     * @return true if the underlying objects of the Pair are both considered
     * equal
     */
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Pair)) {
            return false;
        }
        Pair<?, ?> p = (Pair<?, ?>) o;
        return Objects.equals(p.first, first) && Objects.equals(p.second, second);
    }

    /**
     * Compute a hash code using the hash codes of the underlying objects
     *
     * @return a hashcode of the Pair
     */
    @Override
    public int hashCode() {
        return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
    }

    /**
     * Convenience method for creating an appropriately typed pair.
     *
     * @param a the first object in the Pair
     * @param b the second object in the pair
     * @return a Pair that is templatized with the types of a and b
     */
    public static <A extends Comparable<A>, B extends Comparable<B>> Pair<A, B> create(A a, B b) {
        return new Pair<>(a, b);
    }

    @Override
    public int compareTo(Pair<F, S> that) {
        int cmp = this.first.compareTo(that.first);
        if (cmp == 0)
            cmp = this.second.compareTo(that.second);
        return cmp;
    }
}


回答14:

I implemented something similar but with static builder and chained setters

public class Pair<R, L> {

private R left;
private L right;

public static <K,V> Pair<K, V> of(K k, V v) {
    return new Pair<K,V>(k, v);
}

public Pair() {}

public Pair(R key, L value) {
    this.left(key);
    this.right(value);
}

public R left() {
    return left;
}

public Pair<R, L> left(R key) {
    this.left = key;
    return this;
}

public L right() {
    return right;
}

public Pair<R, L> right(L value) {
    this.right = value;
    return this;
}
}