Почему это не работает
То, что не работает в 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 [], но обеспечивает дополнительную безопасность во время компиляции.