Java. Считать из файла массивы разной длины в ArrayList
Есть csv файл. В файле список химических элементов, который выглядит примерно так:
Element,Number,Symbol,Weight,Boil,Melt,Density Vapour,Fusion, Aluminum,13,Al,26.98,2723.16,933.16,2700,284.34,10.68, Silicon,14,Si,28.09,2953.16,1683.16,2330,170.02,46.48, Phosphorus,15,P,30.98, Sulfur,16,S,32.06,717.76,392.16,2070,12.60,1.42, Chlorine,17,Cl,35.45,238.46,172.16,1560,10.22,3.22,
То есть, массивы, которые я буду отсюда считывать - разной длины. Из за этого возникает ошибка:
Exception in thread "main" java.lang.ExceptionInInitializerError at chemistry.Chemistry.allElements(Chemistry.java:21) at chemistry.Chemistry.main(Chemistry.java:30) Caused by: java.lang.ArrayIndexOutOfBoundsException: 5 at chemistry.ChemicalElement.(ChemicalElement.java:35) at chemistry.ChemicalElementDAO.(ChemicalElementDAO.java:34) ... 2 more
Ошибка возникает из за того, что считываемые в ArrayList массивы - разной длины. Если считываемые массивы одинаковые, ошибки не возникает, все работает.
Мой код: Конструктор
public ChemicalElement(String element, int number, String symbol, double[]vals){
this.element = element;
this.number = number;
this.symbol = symbol;
this.weight = vals[0];
this.boil = vals[1];
this.melt = vals[2];
this.density = vals[3];
this.vapour = vals[4];
this.fusion = vals[5];
}
Код, который считывает данные:
public static List<ChemicalElement> chemicalElements = new ArrayList();
static{
try {
Scanner scanner = new Scanner(new FileReader("elements.csv"));
String title = scanner.nextLine();
while(scanner.hasNext()){
String[] line = scanner.nextLine().split(",");
String element = line[0];
int numb = Integer.parseInt(line[1]);
String symbol = line[2];
// this creates a proper sized array.
double[] values = new double[line.length-3];
for (int i = 0; i < values.length; i++) {
values[i] = Double.parseDouble(line[i+3]);
}
ChemicalElement chemicalElement = new ChemicalElement(element,numb,symbol,values);
chemicalElements.add(chemicalElement);
}
//(String element, int number, String symbol, double weight, double boil, double melt, double density, double vapour, double fusion)
} catch (FileNotFoundException ex) {
System.out.println(ex.getMessage());
}
}
Я думала, что если вместо последних double элементов добавить в конструктор массив, и варьировать количество элементов в массиве, то это поможет. Но нет. Может быть вы сможете подсказать какие-то варианты?
Ответы (2 шт):
вы создаёте массив в зависимости от количества считанных
double[] values = new double[line.length-3];
а используете фиксированную
this.weight = vals[0];
this.boil = vals[1];
this.melt = vals[2];
this.density = vals[3];
this.vapour = vals[4];
this.fusion = vals[5];
оттуда и ошибка
опирайтесь от длины массива и инициализируйте поля в зависимости что имеете
if (val.length > 0) this.weight = vals[0];
if (val.length > 1) this.boil = vals[1];
if (val.length > 2) this.melt = vals[2];
if (val.length > 3) this.density = vals[3];
if (val.length > 4) this.vapour = vals[4];
if (val.length > 5) this.fusion = vals[5];
Мне не нравится такое решение причем по очень многим причинам. Самое главное - я предпочел бы универсальный парсер, т.е. он должен уметь читать любой csv-файл, преобразовывая его в список из любых экземпляров класса и при этом модель не должна иметь никаких спец.методов, в т.ч. конструкторов. Огромным минусом является то, что вы преобразуете все в массив, при этом игнорируете первую строку csv-файла с заголовками. В реальной разработке я бы предпочитал использовать для чтения csv библиотеку, а не делать это руками, но для учебного примера это не обязательно.
Модель выглядит так:
@lombok.Data
@lombok.NoArgsConstructor
public class ChemicalElement {
private String element;
private Integer number;
private String symbol;
private Double weight;
private Double boil;
private Double melt;
private Double density;
private Double vapour;
private Double fusion;
}
Если вы не знаете, что такое lombok, то поясню, зачем нужны эти аннотации: Data добавляет все геттеры, сеттеры, а также equls, hashcode, toString, а NoArgsConstructor - конструктор без аргументов.
И теперь сам парсер:
import com.google.gson.Gson;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Parser {
private Gson GSON = new Gson();
private final String delimiter;
public Parser(String delimiter){
this.delimiter = delimiter;
}
public Parser(){
this.delimiter = ";";
}
public static void main(String[] args) throws Exception {
Parser parser = new Parser(",");
List<ChemicalElement> result = parser.readFile("elements.csv", ChemicalElement.class);
System.out.println(result);
}
private List<ChemicalElement> readFile(String fileName) throws IOException {
try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
return stream
.skip(1)
.map(line -> new ChemicalElement(line.split(",")))
.collect(Collectors.toList());
}
}
public <T> List<T> readFile(final String fileName, final Class<T> clazz) throws Exception {
final List<T> valuesList = new ArrayList<>();
Scanner scanner = new Scanner(new File(fileName));
String[] title = scanner.nextLine().split(delimiter);
while (scanner.hasNext()) {
final String[] value = scanner.nextLine().split(delimiter);
final Map<String, Object> values = new LinkedHashMap(value.length);
for (int k = 0; k < value.length; k++) values.put(title[k], value[k]);
final T obj = (T) GSON.fromJson(GSON.toJson(values), clazz.newInstance().getClass());
valuesList.add(obj);
}
return valuesList;
}
}
Ваш csv-файл содержал ошибку в заголовке, кроме того, в данном случае необходимо, чтобы имена полей совпадали с заголовками, поэтому все заголовки я исправил и написал с маленькой буквы.
element,number,symbol,weight,boil,melt,density,vapour,fusion,
Aluminum,13,Al,26.98,2723.16,933.16,2700,284.34,10.68,
Silicon,14,Si,28.09,2953.16,1683.16,2330,170.02,46.48,
Phosphorus,15,P,30.98,
Sulfur,16,S,32.06,717.76,392.16,2070,12.60,1.42,
Chlorine,17,Cl,35.45,238.46,172.16,1560,10.22,3.22,
Я использую сериализатор gson для создания объектов, ссылка на репозиторий: https://mvnrepository.com/artifact/com.google.code.gson/gson/2.8.6
Вот и все. В парсере есть метод main, в нем видно, что помимо имени файла передается ChemicalElement.class. Именно список экземпляров этого класса вы получите. Главное не забывать простое правило - имена полей вашего класса должны совпадать с заголовками файла. Теперь вы можете из любого csv - файла получить коллекцию обеъктов, используя один и тот же парсер, добавив лишь модель (класс с полями) и ничего больше.
В случае с вашим примером решение на много проще. Метод парсера выглядит так:
private static List<ChemicalElement> readFile(String fileName) throws IOException {
try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
return stream
.skip(1)
.map(line -> new ChemicalElement(line.split(",")))
.collect(Collectors.toList());
}
}
а модель так:
@lombok.Data
@lombok.NoArgsConstructor
public class ChemicalElement {
private String element;
private Integer number;
private String symbol;
private Double weight;
private Double boil;
private Double melt;
private Double density;
private Double vapour;
private Double fusion;
public ChemicalElement(String[] vals) {
try {
this.element = vals[0];
this.number = parseInteger(vals[1]);
this.symbol = vals[2];
this.weight = parseDouble(vals[3]);
this.boil = parseDouble(vals[4]);
this.melt = parseDouble(vals[5]);
this.density = parseDouble(vals[6]);
this.vapour = parseDouble(vals[7]);
this.fusion = parseDouble(vals[8]);
} catch (ArrayIndexOutOfBoundsException e) {}
}
private static Double parseDouble(String value) {
try {
return Double.valueOf(value);
} catch (NumberFormatException | NullPointerException e) {
return null;
}
}
private static Integer parseInteger(String value) {
try {
return Integer.valueOf(value);
} catch (NumberFormatException | NullPointerException e) {
return null;
}
}
}
Мне показалось проще отловить исключение, чем написать целый набор условных операторов. Плюс методы для безопасного распарсивания String в Double и Integer явно не повредят.