不明白Arrays.copyOf的源代码

时间:2015-04-07 15:02:19

标签: java arrays generics

我无法理解Arrays.copyOf的源代码。

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}
  1. 这行检查是什么?

    (Object)newType == (Object)Object[].class
    
  2. (T[]) new Object[newLength](T[]) Array.newInstance(newType.getComponentType(), newLength)之间有何区别?为什么Array.newInstance对两种情况都不够好?

  3. 以下这一行编译,但在运行时崩溃(正如预期的那样)。我什么时候应该使用这种方法?

    Integer[] nums = Arrays.copyOf(new String[]{"a", "b"}, 2, Integer[].class) 
    

5 个答案:

答案 0 :(得分:21)

  

这行检查是什么? (Object)newType == (Object)Object[].class

它正在检查简单的相等性(可能是为了进行微优化,但稍后会更多)。

异常投射是必要的,因为Class<Object[]>Object[].class的类型)和Class<? extends T[]>是无法比较的类型。基本上,为了与==进行相等的比较以进行编译,其中一方必须是另一方的子类型或超类型。

即。我们做不到:

// doesn't compile
// this expression can never evaluate to true
(new Integer(0) == new Float(0f))

泛型类型的规则有点复杂,有些情况下比较不能编译,但它仍然可以评估为真。

尽管Class<Object[]>是所有对象数组类型的超类型,但Class<? extends T[]>不是Object[]的超类型的原因是Java generics are invariant没有通配符。

进行比较的另一种方法是:

(newType == (Class<? extends Object[]>)Object[].class)
  

(T[]) new Object[newLength](T[]) Array.newInstance(newType.getComponentType(), newLength)之间有什么区别?

  • new Object[...]以正常方式创建一个数组,这是一种静态知道的类型。请注意,代码刚检查T[]Object[]
  • Array.newInstance(...)使用反射动态创建传入的Class类型的数组。
  

为什么Array.newInstance对两种情况都不够好?

使用反射的操作是generally slower而不是非反射对应物。

reflection tutorial说:

  

由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能低于非反射操作,应避免在对性能敏感的应用程序中频繁调用的代码段中。

Java SE充满了像这样的微优化。 SE的作者试图从中榨取所有东西。

但在这种情况下我不会担心性能损失:newInstance and copyOfHotSpot intrinsics。这意味着理想情况下对这些方法的调用将被特定于机器的程序集替换。有趣的是,我进行了一些测试,发现new Object[...]Array.newInstance(...)之间的区别可以忽略不计。问题中的代码可能是一个遗留物,虽然它可能仍然适用于装备较差的JVM。

也可以在具有严格安全性的某些上下文(例如applet)中禁用反射,但通常不适用于普通的桌面应用程序。

  

我什么时候应该使用这种方法?

通常,您可能永远不会使用此重载。只有在您想要更改数组的类型时,此重载才有用。

  • 加宽:

    Object[] a = Arrays.copyOf(
        new String[] { "hello", "world" }, 3, Object[].class);
    a[2] = Character.valueOf('!');
    System.out.println(Arrays.toString(a));
    
  • 变窄:

    String[] a = Arrays.copyOf(
        new Object[] { "hello", "world" }, 2, String[].class);
    System.out.println(String.join(" ", a));
    

使用Arrays.copyOf(T[], int)更为典型。

答案 1 :(得分:14)

  
      
  1. 这行检查是什么?
  2.   
(Object)newType == (Object)Object[].class

检查变量newType是否包含对表示类型java.lang.Class的{​​{1}}实例的引用。演员是不需要的。

  
      
  1. Object[](T[]) new Object[newLength]之间有何区别?为什么(T[]) Array.newInstance(newType.getComponentType(), newLength)对两种情况都不够好?
  2.   

据我所知,Array.newInstance 可以在两种情况下使用,但非反射普通数组构造可能会快一点。因此,我认为出于性能原因,Array.newInstance()被称为特殊情况,但我不知道这种情况是否经常运行以使优化变得重要。

  
      
  1. 以下行编译,但在运行时崩溃(正如预期的那样)。我什么时候应该使用这种方法?
  2.   
Object[]

当您需要将数组复制到具有可能不同(但兼容)的元素类型的数组时,尤其是当元素类型不是静态知道时,您应该使用它。如果您知道您希望副本具有与原始副本相同的元素类型,那么使用原始数组的Integer[] nums = Arrays.copyOf(new String[]{"a", "b"}, 2, Integer[].class) 方法会更容易。

答案 2 :(得分:5)

  1. 检查newType是否为Objects数组:

    Object[] a1 = new Object[100]; -- array of Objects
    
    String[] a2 = new String[100]; -- array of Strings
    
  2. 为什么这样做?因为新的Object [n]比Array.newInstance

    1. Array.newInstance(Class<?> componentType, int... dimensions)创建由第一个参数定义的类型数组,例如String.class - &gt; String[]。请注意,String[].class.getComponentType()会返回String.class

    2. 你不能那样使用它,但它可以像这样

      Integer[] nums = Arrays.copyOf(new Object[]{1, 2}, 2, Integer[].class);
      
    3. 在这种情况下,它仅取决于元素的实际类型,例如

        Arrays.copyOf(new Object[]{1L, 2}, 2, Integer[].class);
      

      将失败,除Integer[]

      外,您无法在Integer内写任何内容

答案 3 :(得分:5)

首先,该行中的演员

((Object)newType == (Object)Object[].class)

是非常需要的。删除它们将导致编译错误:

incomparable types: Class<CAP#1> and Class<Object[]>
 where CAP#1 is a fresh type-variable:
  CAP#1 extends T[] from capture of ? extends T[]

现在回答你的问题 这行检查的内容是什么?

它只是验证给定数组是否属于对象类型,这是您的其他问题的答案的一部分 为什么Array.newInstance对于这两种情况都不够好?

在第一种情况下,我们已经知道数组是Object类型,因此调用newInstance方法来检索正确的类型是没有意义的,这只会导致性能下降。

至于你的最后一个例子,

Integer[] nums = Arrays.copyOf(new String[]{"a", "b"}, 2, Integer[].class) 

它确实编译,这是真的。因为该方法的给定参数都是valids。它肯定会在运行时失败;什么是转换的预期输出&#34; a&#34;到Integer类型?

现在,何时使用copyOf?当你已经知道这两种类型,并且已经知道它们一起有效时。

它的主要用途是返回一个副本,但用[null / default values]截断或填充到原始数组。

答案 4 :(得分:1)

让我试着回答这个问题:

要回答您的第一个问题,请检查newType类型是否与数组中的类型相同。两者都将类型向上转换为Object类型。也就是说,它试图查看数组的父类型是否是对象。 See this SO question on Upcasting and Downcasting。我猜测它不是因为要检查类型安全性。即使Java中的所有对象都是derive from objects as a superclass.

注意到

会很有帮助
 T[] copy = ((Object)newType == (Object)Object[].class)
    ? (T[]) new Object[newLength]
    : (T[]) Array.newInstance(newType.getComponentType(), newLength);

实际上是一行。即它实际上是一个if-else条件。

result = (condition) ? (doThisIfTrue) : (elseDoThisIfFalse)

Simple example here

所以基本上这条线与:

相同
T[] copy;
Boolean condition = ((Object)newType == (Object)Object[].class)
if(condition) 
     copy = (T[]) new Object[newLength];
else 
     copy = (T[]) Array.newInstance(newType.getComponentType(), newLength);

在Array.newInstance中创建新实例的原因可能是性能选择,如果程序总是必须创建新实例,那么它比直接初始化通用Object数组并复制东西更昂贵。

Arrays.copyOf将创建一个新数组(引用旧数组),但使用较新的长度并使用空对象填充未使用的位置。 This is what it does on an array of ints,其中用零填充未使用的索引。

Arrays.CopyOf用于提供对象的浅表副本,即它引用旧项目,但是在新数组中。 This SO question has more info on it.