效率:通用数组与对象数组

时间:2018-04-07 17:36:52

标签: java performance generics

假设您必须经常调用操作T get(int),它从底层数组返回一个对象。基本上,这可以通过两种方式实现:

class GenericArray<T> {
    final T[] underlying;
    GenericArray(Class<T> clazz, int length) {
        underlying = (T[]) Array.newInstance(clazz, length);
    }
    T get(int i) { return underlying[i]; }
}

class ObjectArray<T> {
    final Object[] underlying;
    ObjectArray(int length) {
        underlying = new Object[length];
    }
    T get(int i) { return (T) underlying[i]; }
}

第一个是使用反射,因此在创建时会更慢。第二个是使用向下转换,这引入了一些开销。由于运行时的泛型类型擦除,必须有一些隐式的转换机制。

这是真的,这两个在get(i)

方面是相同的

2 个答案:

答案 0 :(得分:8)

让我们检查一下字节码:

Compiled from "ObjectArray.java"
class lines.ObjectArray<T> {
  final java.lang.Object[] underlying;

  lines.ObjectArray(int);
    Code:
       0: aload_0
       1: invokespecial #10                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iload_1
       6: anewarray     #3                  // class java/lang/Object
       9: putfield      #13                 // Field underlying:[Ljava/lang/Object;
      12: return

  T get(int);
    Code:
       0: aload_0
       1: getfield      #13                 // Field underlying:[Ljava/lang/Object;
       4: iload_1
       5: aaload
       6: areturn
}


Compiled from "GenericArray.java"
class lines.GenericArray<T> {
  final T[] underlying;

  lines.GenericArray(java.lang.Class<T>, int);
    Code:
       0: aload_0
       1: invokespecial #13                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: aload_1
       6: iload_2
       7: invokestatic  #16                 // Method java/lang/reflect/Array.newInstance:(Ljava/lang/Class;I)Ljava/lang/Object;
      10: checkcast     #22                 // class "[Ljava/lang/Object;"
      13: putfield      #23                 // Field underlying:[Ljava/lang/Object;
      16: return

  T get(int);
    Code:
       0: aload_0
       1: getfield      #23                 // Field underlying:[Ljava/lang/Object;
       4: iload_1
       5: aaload
       6: areturn
}

如您所见,get的字节码相同。

答案 1 :(得分:1)

This answer证明两种get方法是等价的。这是一个字节码免费答案,解释我如何理解这个主题。请记住,Java中的泛型是使用类型擦除实现的。宽松地说,这意味着TObject取代,并且在必要时插入了强制转换(实际上并不总是Object - 如果你写class Foo<T extends Number> { ... },那么T在类的主体内被Number)替换。

这意味着您的ObjectArray类的代码会转换为类似

的代码
class ObjectArray {
    final Object[] underlying;

    ObjectArray(int length) {
        underlying = new Object[length];
    }

    Object get(int i) { 
        return underlying[i]; 
    }
}

请注意get方法中没有强制转换。仅需要(T)来编译代码。它在运行时没有任何影响,并且永远不会抛出ClassCastException

另一个类的代码转换成这样的代码:

class GenericArray {
    final Object[] underlying;

    GenericArray(Class clazz, int length) {
        underlying = (Object[]) Array.newInstance(clazz, length);
    }

    Object get(int i) { 
        return underlying[i]; 
    }
}

所以get方法是等价的。这两个类之间的唯一区别是反射用于生成数组,因此如果您尝试存储错误类型的对象,则会产生ArrayStoreException。因为只有当你通过使用原始类型滥用泛型时才会发生这种情况,所以在大多数情况下可能不值得使用反射。