具有多个实例但具有唯一对象标识的已排序集合

时间:2013-08-30 13:36:17

标签: java generics collections

我正在寻找实现Collection接口的东西,我可以在其中添加同一对象的多个实例(基于给定的Comparator),但是该集合不能包含两次相同的对象标识(基于==运算符)。必须对集合进行排序,并且我必须能够删除一个特定元素(基于==运算符)。

换句话说,它必须满足以下测试用例:

public MyCollection<E> implements Collection<E>
{ ... }

public class MyCollectionTest extends TestCase
{
   static class MyComparator implements Comparator<MyInterface>
   {
      @Override
      public int compare(MyInterface pO1, MyInterface pO2)
      {
         // the return type of getCategory() is just an enum.
         return pO1.getCategory().ordinal() - pO2.getCategory().ordinal();
      }
   }

   public void testAdd()
   {
      MyCollection<MyInterface> sut = new MyCollection<MyInterface>(new MyComparator());
      MyInterface svc1 = EasyMock.createNiceMock(MyInterface.class);
      MyInterface svc2 = EasyMock.createNiceMock(MyInterface.class);
      EasyMock.expect(svc1.getCategory()).andStubReturn(Category.CAR);
      EasyMock.expect(svc2.getCategory()).andStubReturn(Category.VAN);
      EasyMock.replay(svc1, svc2);
      sut.add(svc1);
      sut.add(svc2);
      assertEquals(2, sut.size());
      assertSame(svc1, sut.last());
      assertSame(svc2, sut.first());
   }

   public void testAddDouble()
   {
      MyCollection<MyInterface> sut = new MyCollection<MyInterface>(new MyComparator());
      MyInterface svc1 = EasyMock.createNiceMock(MyInterface.class);
      EasyMock.expect(svc1.getCategory()).andStubReturn(Category.CAR);
      sut.add(svc1);
      sut.add(svc1);
      assertEquals(1, sut.size());
   }

   public void testRemove()
   {
      MyCollection<MyInterface> sut = new MyCollection<MyInterface>(new MyComparator());
      MyInterface svc1 = EasyMock.createNiceMock(MyInterface.class);
      MyInterface svc2 = EasyMock.createNiceMock(MyInterface.class);
      EasyMock.expect(svc1.getCategory()).andStubReturn(Category.VAN);
      EasyMock.expect(svc2.getCategory()).andStubReturn(Category.VAN);
      EasyMock.replay(svc1, svc2);
      sut.add(svc1);
      sut.add(svc2);
      assertEquals(2, sut.size());
      sut.remove(svc1);
      assertEquals(1, sut.size());
   }
}

任何帮助?

谢谢!

3 个答案:

答案 0 :(得分:1)

编辑:其实我认为这可以用new TreeSet<>(Ordering.natural().thenComparing(Ordering.arbitrary()))(与番石榴的Ordering

解决

我之前使用MultiSet的建议不起作用。这是一个使用标准库的实现,使用Patricia的一些想法:

public class IdentityTreeSet<T extends Comparable> extends AbstractCollection<T> 
{

    private static final Object PRESENT = new Object(); 

    private SortedMap<T, IdentityHashMap<T, Object>> values = new TreeMap<T, IdentityHashMap<T, Object>>();

    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>()
        {
            Iterator<IdentityHashMap<T, Object>> outerIterator = values.values().iterator();
            IdentityHashMap<T, Object> currentMap = new IdentityHashMap();
            Iterator<T> innerIterator = currentMap.keySet().iterator();

            @Override
            public boolean hasNext()
            {
                return innerIterator.hasNext() || outerIterator.hasNext();
            }

            @Override
            public T next()
            {
                if (innerIterator.hasNext())
                {
                    return innerIterator.next();
                }
                else
                {
                    currentMap = outerIterator.next();
                    innerIterator = currentMap.keySet().iterator();
                    return next();
                }
            }

            @Override
            public void remove()
            {
                innerIterator.remove();
                if (currentMap.isEmpty())
                {
                    outerIterator.remove();
                }
            }

        };
    }

    @Override
    public int size()
    {
        int i = 0;
        for (T t: this)
        {
            i++;
        }
        return i;
    }

    @Override
    public boolean add(T e)
    {
        IdentityHashMap<T, Object> entrySet = values.get(e);

        if (entrySet == null)
        {
            IdentityHashMap<T, Object> newList = new IdentityHashMap<T, Object>();
            newList.put(e, PRESENT);
            values.put(e, newList);
            return true;
        }
        else
        {
            if (entrySet.containsKey(e))
            {
                return false;
            }
            entrySet.put(e, PRESENT);
            return true;
        }
    }

    @Override
    public boolean remove(Object o)
    {
        IdentityHashMap<T, Object> entrySet = values.get(o);

        if (entrySet == null)
        {
            return false;
        }
        else
        {
            if (! entrySet.containsKey(o))
            {
                return false;
            }
            else
            {
                entrySet.remove(o);
                if (entrySet.isEmpty())
                {
                    values.remove(o);
                }
                return true;
            }
        }
    }

}

如果IdentityHashMap<T, Object>被包装到新的类IdentityHashSet<T>中,这会更干净,但是这个代码已经有点像StackOverflow的答案了,所以我将把它留作练习为了读者。

请注意,Collection.remove的文档是以equals编写的,因此此类不能严格遵守Collection合同,如果作为Collection传递给代码,则可能会导致错误控制。

答案 1 :(得分:1)

如果现有的集合没有完全符合您的要求,请考虑以下策略:

定义一个类,其公共方法完全符合您的需要,不多也不少。

使用现有的集合来实现类来处理繁忙的工作,但是控制代码可以强制执行您的需求。

例如,您的类可能有一个TreeSet,其每个元素都是底层类的非空IdentityHashMap。 TreeSet比较器将从每个IdentityHashMap中提取一个元素并返回比较它们的结果。

要删除元素,首先检查删除它是否会清空其IdentityHashMap(它存在,并且设置大小为1)。如果是这样,请从TreeSet中删除IdentityHashMap。如果没有,请从IdentityHashMap中删除该元素。

这只是一个大纲,需要填写很多细节。重点是根据您编写的课程中已经存在的内容构建您想要的内容。

答案 2 :(得分:0)

关于你问题的这一部分“但是集合不能包含两倍相同的对象标识(基于==运算符)”,如果你想要两个对象都等于等于==运算符,您需要阅读有关实例控制的对象(基本上是散列自己的实例的对象,并返回相同的缓存对象,而不是在请求的对象已经存在时创建一个新对象。)