05/06/2021

[Java] Pair class of generic types

Something I've always found weird is that Java does not offer a Pair class representing a couple of objects. Sure there are many offerings around and technically a Pair class IS offered, but still uses the key,value naming which is strange. The closes standard offering we have is Map.Entry which still uses the key,value naming.

I've read some philosophical theories that you should not use a generic Pair since getFirst and getSecond are not meaningful names and should instead write your own pair classes with relevant names, however I cannot fully get behind this as for a similar reasoning also key,value is not relevant naming: is the key the ID of this value person, or is the key a character and the value the number of times we found it in a string?

Anyway, I needed to use pairs and decided to write my own class, the only challenge is how to correctly implement the equals method since we're handling generic types which also means our fields might be null.

Here is the code I came up with:

 package com.blogspot.groglogs.structures;  
   
 import java.util.Objects;  
   
 /**  
  * Defines a Pair of objects.  
  * @param <T> type of the first object.  
  * @param <E> type of the second object.  
  */  
 public class Pair<T, E> {  
   
   private T first;  
   private E second;  
   
   public Pair(T first, E second){  
     this.first = first;  
     this.second = second;  
   }  
   
   public T getFirst(){  
     return this.first;  
   }  
   
   public E getSecond(){  
     return this.second;  
   }  
   
   @Override  
   public int hashCode() {  
     return Objects.hash(this.first, this.second);  
   }  
   
   @Override  
   public boolean equals(Object o) {  
     if(this == o){  
       return true;  
     }  
   
     if(!(o instanceof Pair)){  
       return false;  
     }  
   
     //the other pair could be typed differently, we accept it since the equals later will factor these types in  
     Pair<?,?> other = (Pair<?,?>)o;  
   
     //Objects.equals logic is: this.first == null ? other.first == null : this.first.equals(other.first)  
     //which guarantees null safety AND factors in the typechecking by using the relevant equals logic of our field types  
     return Objects.equals(this.first, other.first) && Objects.equals(this.second, other.second);  
   }  
 }  
   

And here are some tests:

 package com.blogspot.groglogs.test.structures;  
   
 import com.blogspot.groglogs.structures.Pair;  
 import org.junit.Test;  
   
 import static org.junit.Assert.assertEquals;  
 import static org.junit.Assert.assertNotEquals;  
   
 public class PairTests {  
   
   @Test  
   public void nullEmpty() {  
     Pair<Integer, Integer> p1 = null;  
     Pair<Integer, Integer> p2 = null;  
   
     assertEquals("null pairs are equal", p1, p2);  
   
     p1 = new Pair<>(null, null);  
     p2 = new Pair<>(null, null);  
   
     assertEquals("pairs of nulls are equal", p1, p2);  
   
     p1 = null;  
     Pair<Integer, String> p3 = null;  
   
     assertEquals("null pairs of different types are equal", p1, p3);  
   
     p1 = new Pair<>(null, null);  
     p3 = new Pair<>(null, null);  
   
     assertEquals("pairs of nulls of different types are equal", p1, p3);  
   }  
   
   @Test  
   public void oneNotnullOrEmpty() {  
     Pair<Integer, Integer> p1 = new Pair<>(1, 2);  
     Pair<Integer, Integer> p2 = null;  
   
     assertNotEquals("one pair is not equal to null pair", p1, p2);  
   
     Pair<Integer, String> p3 = null;  
   
     assertNotEquals("one pair is not equal to null pair of different type", p1, p3);  
   }  
   
   @Test  
   public void twoPairsOfSameTypes() {  
     Pair<Integer, Integer> p1 = new Pair<>(1, 2);  
     Pair<Integer, Integer> p2 = new Pair<>(1, 2);  
   
     assertEquals("two pairs of same type with same values are equal", p1, p2);  
   
     p2 = new Pair<>(1, 3);  
   
     assertNotEquals("two pairs of same type with different values are not equal", p1, p2);  
   }  
   
   @Test  
   public void twoPairsOfDifferentTypes() {  
     Pair<Integer, Integer> p1 = new Pair<>(1, 2);  
     Pair<Integer, String> p2 = new Pair<>(1, "test");  
   
     assertNotEquals("two pairs of different types are not equal", p1, p2);  
   
     Pair<Integer, Double> p3 = new Pair<>(1, 10.0);  
   
     assertNotEquals("two pairs of different compatible types are not equal", p1, p3);  
   
     String s = "test";  
   
     assertNotEquals("one pair is not equal to a completely different type", p1, s);  
   }  
 }  
   

No comments:

Post a Comment

With great power comes great responsibility