Java 如何解决泛型实例化问题?

Java 如何解决泛型实例化问题?,java,parsing,generics,Java,Parsing,Generics,我正在为3个文件(如“城市id”、“国家id”、“地区id”、“名称”)制作一个CSV解析器,我面临一个通用的实例化问题。有什么办法可以解决它并坚持干燥?(我知道答案可能是在构造函数中取T,但我不知道如何在我的情况下正确使用它) 公共静态列表csvParcer(字符串文件路径){ List cities=new ArrayList(); 弦线; 字符串[]分隔线; try(BufferedReader=new BufferedReader(new FileReader(filePath))){

我正在为3个文件(如“城市id”、“国家id”、“地区id”、“名称”)制作一个CSV解析器,我面临一个通用的实例化问题。有什么办法可以解决它并坚持干燥?(我知道答案可能是在构造函数中取T,但我不知道如何在我的情况下正确使用它)

公共静态列表csvParcer(字符串文件路径){
List cities=new ArrayList();
弦线;
字符串[]分隔线;
try(BufferedReader=new BufferedReader(new FileReader(filePath))){
reader.readLine();
而((line=reader.readLine())!=null){
dividedLine=line.replace(“\”,“”)。replace(“;”,“”)。split(“”);
添加(新的T(分割线[0]、分割线[1]、分割线[2]、分割线[3]);
}
回归城市;
}捕获(IOEX异常){
}
返回null;
}

这是我能想到的最干燥的版本,使用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());
}
公共静态列表parseCsvFile(字符串文件路径,函数映射器){
返回Files.lines(新文件(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]);
});
List foos=parseCsvFile(“foos.csv”,列->{
返回新的Foo(列[0]、列[1]、列[2]、列[3]);
});

这是我能想到的最干燥的版本,使用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());
}
公共静态列表parseCsvFile(字符串文件路径,函数映射器){
返回Files.lines(新文件(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]);
});
List foos=parseCsvFile(“foos.csv”,列->{
返回新的Foo(列[0]、列[1]、列[2]、列[3]);
});
为什么它不起作用 在Java中不起作用的是一个泛型给定类的
new t(…)
实例化。您只能使用带有特定类名的
new
关键字

在运行时,您的
csvParcer()
方法甚至不知道哪个类用于
t
,对于JVM,
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;
}
带反射的解 一种方法是在方法中添加一个参数,命名要实例化的类:

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);
    }
}
公共静态列表csvParcer(字符串文件路径,类tClazz){
List cities=new ArrayList();
弦线;
字符串[]分隔线;
try(BufferedReader=new BufferedReader(new FileReader(filePath))){
reader.readLine();
而((line=reader.readLine())!=null){
dividedLine=line.replace(“\”,“”)。replace(“;”,“”)。split(“”);
构造函数myConstructor=tClazz.getConstructor(String.class,String.class,String.class);
T object=myConstructor.newInstance(分割线[0]、分割线[1]、分割线[2]、分割线[3]);
城市。添加(对象);
}
回归城市;
}捕获(例外情况除外){
抛出新的运行时异常(“错误读取”+文件路径,ex);
}
}
[顺便说一下,如果我的方法无法正确读取和解析文件,我将错误处理更改为引发异常,因为这是告诉调用方他无法获得结果的首选方法。]

缺点是浪费了运行时性能(与读取CSV文本文件相比不明显),并且如果所需的类没有一个只接受四个字符串的公共构造函数,则不会出现编译时错误

工厂对象的解决方案 这就是Leo已经提出的方法,您传入一个封装实例创建的对象——一个“工厂”对象,并且您需要为每个不同的T类(您希望从CVS阅读器获得)提供一个。Leo使用优雅的Java-8 streams编码风格重写了您的示例,但也可以使用经典风格,更接近您的原始想法。首先,我们需要为工厂提供一个接口:

public interface TFactory<T> {
    T create(String arg0, String arg1, String arg2, String arg3);
}
公共接口工厂{
T创建(字符串arg0、字符串arg1、字符串arg2、字符串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);
    }
}
公共静态列表csvParcer(字符串文件路径,TFactory工厂){
List cities=new ArrayList();
弦线;
字符串[]分隔线;
try(BufferedReader=new BufferedReader(new FileReader(filePath))){
reader.readLine();
而((line=reader.readLine())!=null){
dividedLine=line.replace(“\”,“”)。replace(“;”,“”)。split(“”);
T object=factory.create(dividedLine[0]、dividedLine[1]、dividedLine[2]、dividedLine[3]);
城市。添加(对象);
}
回归城市;
}捕获(例外情况除外){
抛出新的运行时异常(“错误读取”+文件路径,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);
} 
private void示例(){
TFactory cityFactory=新的TFactory(){
@凌驾
创建公共城市(字符串arg0、字符串arg1、字符串arg2、字符串arg3){
返回新城市(arg0、arg1、arg2、arg3);
}
};
列出城市=csvParcer(“C:\\temp\\cities.csv”,cityFactory);
} 
与使用String[]数组相比,使用四个显式字符串参数会使代码更加冗长,但会增加编译时的安全性。

为什么不起作用 在Java中不起作用的是一个泛型给定类的
new t(…)
实例化。您只能使用带有特定类名的
new
关键字

在运行时,您的
csvParcer()
方法甚至不知道
t
使用了哪个类,对于JVM,
t
将是r