【问题标题】:Writing contains() for a generic collection为泛型集合编写 contains()
【发布时间】:2011-06-26 21:30:48
【问题描述】:

我正在编写一个 java 中的跳过列表类作为练习。我编写了一个名为SkipListInternal<E> 的类,其中包含实际的跳过列表。我还创建了一个名为SkipListSet<E> 的包装类,它实现了SortedSet<E> 接口并包含SkipListInternal<E> 的一个实例。

除此之外,SkipListInternal<E> 包含一个方法 E find(E e),如果存在则返回等于 e 的元素,否则返回 null。

在编写 boolean contains(Object o)(通过 SortedSet<E> 继承自 Collection<E>)方法时,我注意到它的参数是 Object 而不是 E。我打算做这样的事情,但由于类型擦除而无法实现:

public class SkipListSet<E> implements SortedSet<E>{
   private SkipListInternal<E> skiplist;

   ...

   public boolean contains(Object o){
       if (!(o instanceof E)) return false;
       return skiplist.find((E) o) != null;
   }

   ...

}

既然不能这样,那我该怎么做呢?

【问题讨论】:

    标签: java generics collections


    【解决方案1】:

    由于contains()来自java.util.Collection,我们应该遵循Collection.contains()合同。因为抛出ClassCastException 是一种可选行为,所以当转换失败时在代码中返回false 是正确的。所以我认为你的实现符合合同。

            /**
             * Returns true if this collection contains the specified element.
             * More formally, returns true if and only if this collection
             * contains at least one element e such that
             * (o==null ? e==null : o.equals(e)).
             *
             * @param o element whose presence in this collection is to be tested
             * @return <tt>true</tt> if this collection contains the specified
             *         element
             * @throws ClassCastException if the type of the specified element
             *         is incompatible with this collection (optional)
             * @throws NullPointerException if the specified element is null and this
             *         collection does not permit null elements (optional)
             */
             boolean contains(Object o);
    

    【讨论】:

    • +1 :由于合同规定如果给定对象的类型不正确,则可能会抛出 ClassCastException,因此我只需将对象转换为泛型类型 E。
    • @JBNizet:如果我有一个Animal 的集合,我想问一个Cat 的集合是否包含其中的每个项目,那应该没有问题。当被问及它是否包含Fido 时,简单地回答“不,它不包含”似乎比抛出异常更合乎逻辑。
    【解决方案2】:

    @Joaschim 注释对于标准集合是正确的,但是如果您想要一个已检查的集合,我建议您检查可以添加的内容,而不是针对无效类型的查找进行优化(除非您想抛出异常)您可以做一些事情喜欢。

    public class SkipListSet<E> implements SortedSet<E>{
       private final Class<E> eClass;
       private final SkipListInternal<E> skiplist;
    
       ...
       public boolean add(Object o) {
           if(!eClass.isInstanceOf(o))
        // OR
           if(eClass != o.getClass())
               throw new IllegalArgumentException("Type "+o.getClass()+" is not a "+eClass);
           skiplist.add(o);
       }
    
       public boolean contains(Object o){
           // if (!(o instanceof E)) return false; // don't need to optmise for this.
           return skiplist.find((E) o) != null;
       }
    
       ...
    
    }
    

    顺便说一句:Java 有一个内置的线程安全 ConcurrentSkipListSetConcurrentSkipListMap 你可能会觉得阅读源代码很有趣。 ;)

    【讨论】:

    • 感谢有关内置类的提示!我已经设法完全错过了那些。
    【解决方案3】:

    为了使有序集合实现工作,集合的元素必须有一个排序。这可能是“自然的”(即元素实现了Comparable)或“强加的”(通过在集合构造期间使用显式的Comparator)。

    所以,第一件事是,为了提高效率,您可能宁愿使用为集合元素定义的排序(毕竟,您正在实现SortedSet!)而不是contains 中的equals。我假设,你已经在你的内部SkipListInternal&lt;E&gt; 中使用了一个排序——你是如何在SortedSet 中只给equals 维护Sorted 的?

    contains 在接口中实际上被声明为contains(Object key) 的事实真的很不幸。我愿意,TreeMap 实现的作用(这是 TreeSet 的底层容器,集合框架中的标准 SortedSet):

    if (comparator != null)
        return getEntryUsingComparator(key);
    if (key == null)
        throw new NullPointerException();
    Comparable<? super K> k = (Comparable<? super K>) key;
    

    即强制转换,假设使用您的集合的客户端应用程序行为正常。

    【讨论】:

      【解决方案4】:

      严格来说这样的实现是错误的。

      原因是即使对象不是E 类型,它仍然可以在equals() 调用上返回true

      假设您有这样的课程:

      public class FakeString {
        private final String value;
      
        public FakeString(String value) {
          if (value == null) {
            throw new IllegalArgumentException();
          }
          this.value = value;
        }
      
        public int hashCode() {
          return value.hashCode();
        }
      
        public boolean equals(Object o) {
          return value.equals(o);
        }
      }
      

      然后这段代码将打印true:

      List<String> strings = Arrays.asList("foo", "bar", "baz");
      System.out.println(strings.contains(new FakeString("bar")));
      

      澄清一下:这种行为是有意的,这也是contains() 采用Object 而不是E 的原因。顺便说一句,remove() 也是如此。

      【讨论】:

      • 这真的是预期的行为吗? equals 的规则要求实现是对称的,但您的 FakeString 违反了该规则。并且所有内置的 SortedSet 实现(TreeSet,来自 java.util.concurrent 的跳过列表实现)都使用 Comparator/Comparable 并且根本不调用 equals
      • @Dirk,同样的问题也适用于 Comparator/Comparable。
      • @Dirk:TreeSet 文档甚至声称它“[..] 未能遵守Set 接口的一般约定。”
      • @Dirk: 虽然我的样本违反了equals() 合同,但很容易用PersonDataPersonStub 构造一个样本,其中equals() 构造被阻止,你仍然可以删除一个PersonData 来自 List&lt;PersonData&gt; 的对象,使用 PersonStub 作为参数。
      • 朱普。假装我最初的评论被“撤回”(保留作为参考,虽然:-)无论如何:OP想要实现SortedSet。由于您的答案是当前最喜欢的...也许您可以添加几句关于在 contains 方法中使用 Comparator/Comparable 而不是 equals 的句子,因为如果 O( log n) 是可能的,因为选择的数据结构可能是值得的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-08
      • 1970-01-01
      • 2020-06-14
      • 2015-05-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多