Collection with both Set and List interface

Here’s something I wrote a while ago. I needed a collection that was ordered, but also guaranteed duplicates did not occur. So I made a class with both Set and List interfaces. It seems to work well.

I’m interested if there are better ways of doing this, maybe in the standard API or some library. I’d also be interested in general comments, if anyone cares to offer them.

This is a fairly simple implementation, designed to meet my immediate needs. There’s probably lots of ways it could be made more fancy.

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

/**
 * Unholy spawn of set and list!
 * 
 * <p>@code HashSetList} is similar to {@code LinkedHashSet}.  Objects
 * inserted can be retrieved in a predicable order.  Unlike
 * {@code LinkedHashSet}, this class implements more of the {@code List}
 * API, including a reverse iterator and a subList() method.
 * </p>
 * 
 * <p><b>Very lightly tested!</b>  Caveat emptor.</p>
 * 
 * @author Brenden Towey
 * @param <T> The type parameter of the {@code HashSetList}.
 */
public class HashSetList<T> implements Set<T>, List<T>
{
   private final HashSet<T> set = new HashSet<>();
   private final ArrayList<T> list = new ArrayList<>();

   @Override
   public boolean containsAll(
           Collection<?> c )
   {
      return set.containsAll( c );
   }

   @Override
   public int size()
   {
      return list.size();
   }

   @Override
   public boolean isEmpty()
   {
      return list.isEmpty();
   }

   @Override
   public boolean contains( Object o )
   {
      return set.contains( o );
   }

   @Override
   public int indexOf( Object o )
   {
      return list.indexOf( o );
   }

   @Override
   public int lastIndexOf( Object o )
   {
      return list.lastIndexOf( o );
   }

//   @Override
//   public Object clone()
//   {
//      try {
//         HashSetList copy = (HashSetList) super.clone();
//         return copy;
//      } catch( CloneNotSupportedException ex ) {
//         throw new AssertionError( "This should NOT happen.", ex );
//      }
//   }

   @Override
   public Object[] toArray()
   {
      return list.toArray();
   }

   @Override
   public <T> T[] toArray( T[] a )
   {
      return list.toArray( a );
   }

   @Override
   public T get( int index )
   {
      return list.get( index );
   }

   /**
    * Throws {@code UnsupportedOperationException}.
    * 
    * @param index
    * @param element 
    */
   @Override
   public T set( int index, T element )
   {
      throw new UnsupportedOperationException();
//      return list.set( index, element );
   }

   @Override
   public boolean add( T e )
   {
      if( set.add( e ) ) {
         return list.add( e ); // always returns true
      }
      return false;
   }

   /**
    * Throws {@code UnsupportedOperationException}.
    * 
    * @param index
    * @param element 
    */
   @Override
   public void add( int index, T element )
   {
      throw new UnsupportedOperationException();
//      list.add( index, element );
   }

   /**
    * Throws {@code UnsupportedOperationException}.
    * 
    * @param index
    */
   @Override
   public T remove( int index )
   {
      throw new UnsupportedOperationException();
//      return list.remove( index );
   }

   @Override
   public boolean remove( Object o )
   {
      if( set.remove( o ) )
         return list.remove( o );
      else
         return false;
   }

   @Override
   public void clear()
   {
      set.clear();
      list.clear();
   }

   /**
    * Adds all elements in the {@code Collection c} to this
    * {@code HashSetList}.  This method is implemented very simply
    * and likely to be quite slow.  Caveat emptor.
    * 
    * @param c {@code Collection} of elements to remove.
    * @return @{code true} if changed.
    */
   @Override
   public boolean addAll(
           Collection<? extends T> c )
   {
      boolean change = false;
      for( T x : c )
         change |= this.add( x );
      return change;
   }

   /**
    * Throws {@code UnsupportedOperationException}.
    * 
    * @param index
    * @param element 
    */
   @Override
   public boolean addAll( int index,
           Collection<? extends T> c )
   {
      throw new UnsupportedOperationException();
//      return list.addAll( index, c );
   }

   /**
    * Removes all elements in the {@code Collection c} from this
    * {@code HashSetList}.  This method is implemented very simply
    * and likely to be quite slow.  Caveat emptor.
    * 
    * @param c {@code Collection} of elements to remove.
    * @return @{code true} if changed.
    */
   @Override
   public boolean removeAll(
           Collection<?> c )
   {
      boolean change = false;
      for( Object o : c )
         change |= this.remove( o );
      return change;
   }

   /**
    * Retains all elements in the {@code Collection c} which also in this
    * {@code HashSetList}.  (In other words it computes the intersection
    * of {@code c} and this {@code HashSetList}.
    * This method is implemented very simply
    * and likely to be quite slow.  Caveat emptor.
    * 
    * @param c {@code Collection} of elements to remove.
    * @return @{code true} if changed.
    */
   @Override
   public boolean retainAll(
           Collection<?> c )
   {
      boolean change = set.retainAll( c );
      change |= list.retainAll( c );
      return change;
   }

   @Override
   public ListIterator<T> listIterator( int index )
   {
      return list.listIterator( index );
   }

   @Override
   public ListIterator<T> listIterator()
   {
      return list.listIterator();
   }

   @Override
   public Iterator<T> iterator()
   {
      return list.iterator();
   }

   @Override
   public List<T> subList( int fromIndex, int toIndex )
   {
      return list.subList( fromIndex, toIndex );
   }

}

Answer

There is a whole bunch of throws UnsupportedOperationException in your code. More specifically, everything that has to do with specific indexes seems to be unsupported.

All this makes me wonder: Should you really implement the List interface? In my opinion, you should not. Because essentially, those List-specific methods are not supported.

I think you should consider dropping that class entirely and use LinkedHashSet or TreeSet, which I think should be sufficient for you.

I think what you are looking for is LinkedHashSet which is a Set that preserves the insertion order of elements that are added to it.

Attribution
Source : Link , Question Author : markspace , Answer Author : Simon Forsberg

Leave a Comment