.toArray(новый MyClass [0]) или .toArray(новый MyClass [myList.size()])?

Предполагая, что у меня есть ArrayList

ArrayList<MyClass> myList;

И я хочу вызвать toArray, есть ли причина использования для использования

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

над

MyClass[] arr = myList.toArray(new MyClass[0]);

?

Я предпочитаю второй стиль, поскольку он менее подробный, и я предположил, что компилятор удостоверится, что пустой массив действительно не создается, но мне было интересно, правда ли это.

Конечно, в 99% случаев это не имеет никакого значения так или иначе, но я бы хотел сохранить постоянный стиль между моим нормальным кодом и моими оптимизированными внутренними циклами...

Ответ 1

Начиная с ArrayList в Java 5, массив будет заполнен уже, если он имеет нужный размер (или больше). Следовательно,

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

создаст один объект массива, залейте его и верните в "arr". С другой стороны,

MyClass[] arr = myList.toArray(new MyClass[0]);

создаст два массива. Второй - массив MyClass с длиной 0. Таким образом, существует объект для объекта, который будет немедленно выброшен. Что касается исходного кода, то компилятор /JIT не может его оптимизировать, чтобы он не был создан. Кроме того, использование объекта с нулевой длиной приводит к литью (-ам) в методе toArray().

См. источник ArrayList.toArray():

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Использовать первый метод, чтобы создать только один объект и избежать (неявных, но все же дорогих) отливок.

Ответ 2

Противоинтуитивно, самая быстрая версия в Hotspot 8:

MyClass[] arr = myList.toArray(new MyClass[0]);

Я выполнил микро-тест, используя jmh, результаты и код ниже, показывая, что версия с пустым массивом последовательно превосходит версию с заданным массивом. Обратите внимание: если вы можете повторно использовать существующий массив правильного размера, результат может быть другим.

Результаты тестов (оценка в микросекундах, меньше = лучше):

Benchmark                      (n)  Mode  Samples    Score   Error  Units
c.a.p.SO29378922.preSize         1  avgt       30    0.025 ▒ 0.001  us/op
c.a.p.SO29378922.preSize       100  avgt       30    0.155 ▒ 0.004  us/op
c.a.p.SO29378922.preSize      1000  avgt       30    1.512 ▒ 0.031  us/op
c.a.p.SO29378922.preSize      5000  avgt       30    6.884 ▒ 0.130  us/op
c.a.p.SO29378922.preSize     10000  avgt       30   13.147 ▒ 0.199  us/op
c.a.p.SO29378922.preSize    100000  avgt       30  159.977 ▒ 5.292  us/op
c.a.p.SO29378922.resize          1  avgt       30    0.019 ▒ 0.000  us/op
c.a.p.SO29378922.resize        100  avgt       30    0.133 ▒ 0.003  us/op
c.a.p.SO29378922.resize       1000  avgt       30    1.075 ▒ 0.022  us/op
c.a.p.SO29378922.resize       5000  avgt       30    5.318 ▒ 0.121  us/op
c.a.p.SO29378922.resize      10000  avgt       30   10.652 ▒ 0.227  us/op
c.a.p.SO29378922.resize     100000  avgt       30  139.692 ▒ 8.957  us/op

Для справки, код:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
public class SO29378922 {
  @Param({"1", "100", "1000", "5000", "10000", "100000"}) int n;
  private final List<Integer> list = new ArrayList<>();
  @Setup public void populateList() {
    for (int i = 0; i < n; i++) list.add(0);
  }
  @Benchmark public Integer[] preSize() {
    return list.toArray(new Integer[n]);
  }
  @Benchmark public Integer[] resize() {
    return list.toArray(new Integer[0]);
  }
}

Ответ 3

Современные JVM оптимизируют конструкцию отражающих массивов в этом случае, поэтому разница в производительности крошечная. Именование коллекции дважды в таком шаблоне кода не является отличной идеей, поэтому я бы избежал первого метода. Другим преимуществом второго является то, что он работает с синхронизированными и параллельными коллекциями. Если вы хотите сделать оптимизацию, повторно используйте пустой массив (пустые массивы неизменяемы и могут быть разделены) или используйте профайлер (!).

Ответ 4

Первый случай более эффективен.

Это потому, что во втором случае:

MyClass[] arr = myList.toArray(new MyClass[0]);

среда выполнения фактически создает пустой массив (с нулевым размером), а затем внутри метода toArray создается другой массив для соответствия фактическим данным. Это создание выполняется с использованием отражения, используя следующий код (взятый из jdk1.5.0_10):

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.
    newInstance(a.getClass().getComponentType(), size);
System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Используя первую форму, вы избегаете создания второго массива, а также избегаете кода отражения.

Ответ 5

toArray проверяет, что переданный массив имеет нужный размер (то есть достаточно большой, чтобы соответствовать элементам из вашего списка), и если это так, использует это. Следовательно, если размер массива при условии, что он меньше требуемого, новый массив будет рефлексивно создан.

В вашем случае массив нулевого размера является неизменным, поэтому его можно безопасно поднять до статической конечной переменной, что может сделать ваш код немного более чистым, что позволяет избежать создания массива при каждом вызове. Новый массив будет создан внутри метода в любом случае, поэтому он оптимизирует читаемость.

Возможно, более быстрая версия - передать массив правильного размера, но если вы не сможете доказать, что этот код является узким местом производительности, предпочитайте читаемость производительности во время выполнения, пока не будет доказано обратное.

Ответ 6

Использование 'toArray' с массивом правильного размера будет лучше работать, поскольку альтернатива создаст сначала массив нулевого размера, а затем массив правильного размера. Однако, как вы говорите, разница, вероятно, будет незначительной.

Также обратите внимание, что компилятор javac не выполняет никакой оптимизации. В эти дни все оптимизации выполняются компиляторами JIT/HotSpot во время выполнения. Я не знаю никаких оптимизаций вокруг "toArray" в любых JVM.

Таким образом, ответ на ваш вопрос во многом зависит от стиля, но ради обеспечения последовательности должен быть частью любых стандартов кодирования, которые вы придерживаетесь (независимо от того, задокументированы они или нет).

Ответ 7

Используйте первый случай, потому что он проще и обеспечивает более чистый код. Причина этого заключается в том, что основной метод работы метода ToArray заключается в выполнении операции копирования, которая является O (n). Неизменяемая память не имеет большого значения. Управление такими объектами осуществляется очень эффективно, и оно будет расширяться до нужного размера.

Не оптимизируйте слишком много, пока не убедитесь, что что-то является узким местом в вашем коде. Если вы тратите слишком много времени на оптимизацию, вы просто теряете время. Я уверен, что есть много других вещей, которые вы можете оптимизировать, поэтому я бы сказал, что используйте один из них. Если вы хотите, чтобы читаемость и меньшая многословность принимались первой. Если вы не возражаете против дополнительного кода и уменьшения ясности, используйте последний.

Ответ 8

Второй - слегка читаемый, но там так мало улучшения, что его не стоит. Первый метод выполняется быстрее, без каких-либо недостатков во время выполнения, так что я использую. Но я пишу это вторым способом, потому что быстрее его набирать. Затем моя IDE помещает его в качестве предупреждения и предлагает исправить его. С одним нажатием клавиши он преобразует код из второго типа в первый.

Ответ 9

пример кода для целого числа:

Integer[] arr = myList.toArray(new integer[0]);