преобразование потока Java в массив в соответствии с контрактом Collection.toArray (T [] array) - PullRequest
0 голосов
/ 13 ноября 2018

Все, пожалуйста, на самом деле прочитайте этот вопрос, прежде чем вы увидите "поток" и "массив" и просто предположите , что вопрос является дубликатом. Я знаю как преобразовать поток в массив. Этот вопрос очень специфичен: как выполнить договор Collection.toArray(T[] array). Все эти люди говорят, что это дубликат: другие ответы повторно используют существующий массив, если он достаточно большой? Помечают ли они элемент null, если существующий массив слишком большой? (Нет и нет.)

Я знаю, что интерфейс Java Stream<> предоставляет несколько способов преобразования потока в массив. Но метод Collection.toArray(T[] array) немного отличается. Он имеет несколько умных (на тот момент) требований, в том числе:

  • Если массив, который вы передаете, достаточно большой, то массив, который вы передаете, должен использоваться; в противном случае должен быть создан новый.
  • Если массив больше необходимого, после последнего элемента необходимо добавить null.

Так что, если моя Collection<T> реализация извлекает свои значения из некоторого Stream<FooBar> (со стратегией конвертации, которая преобразуется в T, как я могу преобразовать мой поток в массив, требуемый для Collection.toArray(T[] array)?

Без долгих раздумий, похоже, я должен сделать это:

@Override
public <T> T[] toArray(T[] array) {
  try (final Stream<FooBar> stream = getStream()) {
    T[] result = stream.map(converter::toT).toArray(length ->
        (T[])Array.newInstance(array.getClass(), length));
    if(result.length <= array.length) {
      System.arraycopy(result, 0, array, 0, result.length);
      if(result.length < array.length) {
        array[result.length] = null;
      }
      result = array;
    }
  }
  return result;
}

Но есть ли более краткий способ сделать это? Есть ли способ, которым я мог бы передать поток непосредственно в данный массив, если это возможно? И обеспечивает ли API Stream<> что-то вроде этого: создание массива, как ожидает API Collection<>.toArray(T[] array)?

1 Ответ

0 голосов
/ 14 ноября 2018

Очень рекомендуемое чтение - статья Массивы Древних .

Короче говоря, вопреки интуиции, передача массива предварительно заданного размера в метод Collections.toArray(T[]) оказываетсябыть менее эффективным, чем передача массива нулевого размера, который служит только для определения типа результата, но позволяет коллекции распределять массив результатов.

Именно поэтому новый default метод Java 11 <T> T[] toArray​(IntFunction<T[]> generator) не использует функцию для выделения массива размера коллекции, а для выделения массива нулевого размера для передачи <T> T[] toArray​(T[] a).

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

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

Вы даже можете использовать такую ​​реализацию, когда не реализуете коллекцию, например,

public <T> T[] toArray(T[] array) {
    try(final Stream<FooBar> stream = getStream()) {
        return new AbstractCollection<XYZ>() {
            public Iterator<XYZ> iterator() {
                return stream.map(converter::toT).iterator();
            }
            public int size() { return 0; } // don't know beforehand
        }.toArray(array);
    }
}

Вы должны заменить XYZ с типом возврата метода converter.toT(FooBar).

Что приводит к большему вопросу, как converter::toT должен преобразовываться в правильный тип, фактически не зная, что такое T.

...