Как я могу решить общую проблему создания экземпляров? - PullRequest
0 голосов
/ 20 сентября 2018

Я делаю парсер CSV для 3 файлов (например, "city_id"; "country_id"; "region_id"; "name"), и я столкнулся с общей проблемой создания экземпляров.Есть ли способ, как я могу решить это и придерживаться СУХОЙ?(Я видел, что ответом может быть использование T в конструкторе, но я не понимаю, как правильно использовать его в моей ситуации).

public static <T> List<T> csvParcer(String filePath) {
    List<T> cities = new ArrayList<>();
    String line;
    String[] dividedLine;
    try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
        reader.readLine();
        while ((line = reader.readLine()) != null) {
            dividedLine = line.replace("\"", "").replace(";", " ").split(" ");
            cities.add(new T(dividedLine[0], dividedLine[1], dividedLine[2], dividedLine[3]));
        }
        return cities;
    } catch (IOException ex) {
    }
    return null;
}

Ответы [ 2 ]

0 голосов
/ 20 сентября 2018

Почему это не работает

То, что не работает в Java, - это new T(...) создание экземпляров обобщенно заданного класса.Вы можете использовать только ключевое слово new с определенным именем класса.

Во время выполнения ваш метод csvParcer() даже не знает, какой класс использовался для T, для JVM, T будет заменен на Object.Таким образом, у вашего метода нет возможности узнать, какой класс создать.Вам нужно передать что-то в ваш метод, что позволит вам создать экземпляр класса, который вы хотите для данной ситуации.

Решение с отражением

Один из подходов заключается в добавлении параметра в ваш метод с именем классаВы хотите создать экземпляр:

public static <T> List<T> csvParcer(String filePath, Class<T> tClazz) {
    List<T> cities = new ArrayList<>();
    String line;
    String[] dividedLine;
    try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
        reader.readLine();
        while ((line = reader.readLine()) != null) {
            dividedLine = line.replace("\"", "").replace(";", " ").split(" ");

            Constructor<T> myConstructor = tClazz.getConstructor(String.class, String.class, String.class);
            T object = myConstructor.newInstance(dividedLine[0], dividedLine[1], dividedLine[2], dividedLine[3]);

            cities.add(object);
        }
        return cities;
    } catch (Exception ex) {
        throw new RuntimeException("Error reading " + filePath, ex);
    }
}

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

Недостатком является то, что вы теряете производительность во время выполнения (не заметно по сравнению с чтением текстового файла CSV), и вы не получаете ошибок во время компиляции, если класс выNeed не имеет общедоступного конструктора, который принимает ровно четыре строки.

Решение с фабричными объектами

Это подход, который уже предложил Лео, вы передаете объект, который инкапсулирует создание экземпляра - a«фабричный» объект, и он вам нужен для каждого отдельного T-класса, который вы хотите получить от вашего CVS-ридера.Лео переписал ваш пример, используя элегантный стиль кодирования потоков Java-8, но это также возможно в классическом стиле, ближе к вашей первоначальной идее.Для начала нам нужен интерфейс для фабрики:

public interface TFactory<T> {
    T create(String arg0, String arg1, String arg2, String arg3);
}

Метод парсера выглядит следующим образом:

public static <T> List<T> csvParcer(String filePath, TFactory<T> factory) {
    List<T> cities = new ArrayList<>();
    String line;
    String[] dividedLine;
    try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
        reader.readLine();
        while ((line = reader.readLine()) != null) {
            dividedLine = line.replace("\"", "").replace(";", " ").split(" ");

            T object = factory.create(dividedLine[0], dividedLine[1], dividedLine[2], dividedLine[3]);

            cities.add(object);
        }
        return cities;
    } catch (Exception ex) {
        throw new RuntimeException("Error reading " + filePath, ex);
    }
}

И вы используете его, как в этом примере:

private void example() {
    TFactory<City> cityFactory = new TFactory<City>() {
        @Override
        public City create(String arg0, String arg1, String arg2, String arg3) {
            return new City(arg0, arg1, arg2, arg3);
        }
    };
    List<City> cities = csvParcer("C:\\temp\\cities.csv", cityFactory);
} 

Наличие четырех явных аргументов String делает код более многословным, чем использование массива String [], но обеспечивает дополнительную безопасность во время компиляции.

0 голосов
/ 20 сентября 2018

Вот самая сухая версия, которую я могу придумать, используя Java 8.

public static <T> List<T> parseCsvFile(String filePath, Function<String[], T> mapper) {
    return Files.lines(new File(filePath).toPath())
                .map(s -> s.replace("\"", "").split(";"))
                .map(mapper)
                .collect(Collectors.toList());
}

Используйте вот так.

List<Foo> foos = parseCsvFile("foos.csv", columns -> {
    return new Foo(columns[0], columns[1], columns[2], columns[3]);
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...