修改了Collections.copy

时间:2015-02-16 11:23:39

标签: java generics

Collections.copy定义为:

public static <T> void copy(List<? super T> dest, List<? extends T> src)

我理解它是如何工作的,没有任何问题。我尝试用另一种方式展示相同的:

public static <T, E extends T> void copy(List<T> dest, List<E> src){
        for(E e: src){
            dest.add(e);
        }//ignore implementation. it has flaws. The focus is declaration of copy
    }

这个想法很简单。对于从源读取的任何类型,目标类型应为其超类型。所以现在你可以这样做:

copy(new ArrayList<Number>(), new ArrayList<Number>());
copy(new ArrayList<Number>(), new ArrayList<Integer>());
copy(new ArrayList<Object>(), new ArrayList<Number>());
copy(new ArrayList<Object>(), new ArrayList<Double>());

看起来不错。但与实际Collections.copy相比,上面是否有任何缺陷?从类型信息的角度来看,实际上超出上述地方的任何地方?

4 个答案:

答案 0 :(得分:3)

我认为原件更好的一个地方是简洁。特别是从代码生成的角度来看。

如果我通过完整声明调用原始函数,我可以

Collections.<Object>copy(new ArrayList<Object>(), new ArrayList<Number>());

就我而言,它将是:

MyClass.<Object, Number> copy(new ArrayList<Object>(), new ArrayList<Number>());

所以它更简洁一点。

答案 1 :(得分:2)

就绝对信息类型而言,我认为没有区别(但我很乐意被证明是错误的)。实际上,如果静态方法的主体只是对Collections.copy的调用,它会编译并运行正常。经过大量的思考和实验后,有几个关键点:

  • Collections.copy的签名在概念上比您建议的签名要清晰得多。它举例说明了PECS principle(另见Josh Bloch的 Effective Java )。
  • 查看Collections.copy的源代码(OpenJDK 7),我发现类型参数T实际上从未直接使用过。您的版本创建了相同的类型边界(源必须生成dest的子类型),但没有这样一个明确的含义,即通配符边界会遇到一些明确定义的“中间类型”。
  • 也许最重要的是,你的版本被认为是等价的唯一原因是因为它是一个静态方法,而静态方法一般不会充分利用泛型的力量(尽管这是没有理由不使用那里最好的语法)。以下示例通过执行与实例方法类似的操作来使其非常清楚。

考虑一个复制参数化类型的类TypeCopier<T>

public class TypeCopier<T> {

  void copyType(List<? super T> dest, List<? extends T> src) {
    // copy
  }

  public static void main(String[] args) {
    TypeCopier<Number> copier = new TypeCopier<>();
    copier.copyType(new ArrayList<Object>(), new ArrayList<Integer>());
  }
}

请注意关于第一个示例的一些事项:

  1. 使用单个参数
  2. 指定类型Number很容易
  3. 编译
  4. 它允许destsrc分别是Number的任何超类型和子类型
  5. 现在尝试使用您建议的签名创建类似的类:

    public class TypeCopierBad<T, E extends T> {
    
      void copyType(List<T> dest, List<E> src) {
        // copy
      }
    
      public static void main(String[] args) {
        TypeCopierBad<Object, Number> copier = new TypeCopierBad<>();
        copier.copyType(new ArrayList<Object>(), new ArrayList<Integer>());
      }
    }
    

    请注意关于第二个例子的一些事项:

    1. 需要两个类型参数才能尝试指定与上面相同的内容(并且尝试失败,我们将在下面看到)
    2. 它无法编译,因为src不完全是Number类型
    3. 与前一个示例相比,它更加严格地约束了destsrc的内容

答案 2 :(得分:1)

看起来你的声明符合 PECS 原则的要求,所以我认为这只是风格,其中Collections.copy是更常见的版本。

答案 3 :(得分:0)

假设两个ArrayLists中都有对象。然后迭代源并将每个对象从源添加到目标。问题是,从源获取并添加到目标的每个对象都将指向相同的内存位置。 例如, 说

source {x, y}
destination {x, y}.

在这种情况下,源和目标中的x将指向相同的内存位置