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