Как создать двумерный массив из потока в Java 8?

У меня есть такой текстовый файл:

ids.txt

1000
999
745
123
...

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

Object[][] data = new Object[][] { //
     { new Integer(1000) }, //
     { new Integer(999) }, //
     { new Integer(745) }, //
     { new Integer(123) }, //
     ...
};

Вот код, который я написал:

File idsFile = ... ;
try (Stream<String> idsStream = Files.lines(idsFile.toPath(), StandardCharsets.US_ASCII)) {
    Object[][] ids = idsStream
       .filter(s -> s.trim().length() > 0)
       .toArray(size -> new Object[size][]);

    // Process ids array here...
}

При запуске этого кода возникает исключение:

java.lang.ArrayStoreException: null
at java.lang.System.arraycopy(Native Method) ~[na:1.8.0_45]
at java.util.stream.SpinedBuffer.copyInto(Unknown Source) ~[na:1.8.0_45]
at java.util.stream.Nodes$SpinedNodeBuilder.copyInto(Unknown Source) ~[na:1.8.0_45]
at java.util.stream.SpinedBuffer.asArray(Unknown Source) ~[na:1.8.0_45]
at java.util.stream.Nodes$SpinedNodeBuilder.asArray(Unknown Source) ~[na:1.8.0_45]
at java.util.stream.ReferencePipeline.toArray(Unknown Source) ~[na:1.8.0_45]
... 

Как разрешить это исключение?


person Stephan    schedule 13.06.2015    source источник
comment
Почему new Integer(1000)? И вам нужен [][], где во внутреннем массиве всегда есть length == 1?   -  person Boris the Spider    schedule 13.06.2015
comment
@BoristheSpider Идентификаторы передаются сторонней библиотеке, которая принимает только Object[][] в качестве входных данных.   -  person Stephan    schedule 13.06.2015


Ответы (2)


Учитывая Stream<String>, вы можете разобрать каждый элемент на int и обернуть его в Object[], используя:

 strings
        .filter(s -> s.trim().length() > 0)
        .map(Integer::parseInt)
        .map(i -> new Object[]{i})

Теперь, чтобы превратить этот результат в Object[][], вы можете просто сделать:

Object[][] result = strings
        .filter(s -> s.trim().length() > 0)
        .map(Integer::parseInt)
        .map(i -> new Object[]{i})
        .toArray(Object[][]::new);

Для ввода:

final Stream<String> strings = Stream.of("1000", "999", "745", "123");

Выход:

[[1000], [999], [745], [123]]
person Boris the Spider    schedule 13.06.2015
comment
Если вы подозреваете, что строки могут иногда иметь пробелы до и после, то лучше добавить отдельный шаг trim: strings.map(String::trim).filter(s -> !s.isEmpty()). Также я бы использовал Integer::valueOf вместо Integer::parseInt, поскольку он лучше соответствует желаемому типу (Integer, а не int). Хотя, наверное, дело вкуса. - person Tagir Valeev; 13.06.2015
comment
@TagirValeev Почему лучше добавить отдельный шаг обрезки? - person Stephan; 13.06.2015
comment
@Stephan: таким образом вы можете анализировать файлы, в которых есть пробелы после чисел (например, "1 \n2\n3 \n"). В настоящее время у вас будет NumberFormatException в таких файлах. - person Tagir Valeev; 13.06.2015
comment
Как вы думаете, есть ли польза в этих двух map операциях вместо того, чтобы просто сказать .map(s -> new Object[]{Integer.parseInt(s)})? - person Holger; 15.06.2015
comment
@ Холгер, правда, не знаю. Мне нравится разделять каждое логическое преобразование на отдельный map шаг - я думаю, что это делает код более читаемым. Я полагаю, это просто вопрос личных предпочтений - я сомневаюсь, что это сильно повлияет на производительность. - person Boris the Spider; 15.06.2015

Ваша последняя строка, вероятно, должна быть size -> new Object[size], но вам нужно будет предоставить массивы целых чисел размера один, и вам также нужно будет проанализировать строки на целые числа.

Предлагаю следующее:

try (Stream<String> idsStream = Files.lines(idsFile.toPath(), StandardCharsets.US_ASCII)) {
    Object[][] ids = idsStream
       .map(String::trim)
       .filter(s -> !s.isEmpty())
       .map(Integer::valueOf)
       .map(i -> new Integer[] { i })
       .toArray(Object[][]::new);

    // Process ids array here...
}
person assylias    schedule 13.06.2015