如何实现通用接口的方法?
How to implements a method of a generic interface?
我有这个界面:
public interface ParsableDTO<T> {
public <T> T parse(ResultSet rs) throws SQLException;
}
在某种 dto classes 中实现,此方法在另一个 class:
public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table,
Class<T> dto_class) {
List<T> rtn_lst = new ArrayList<T>();
ResultSet rs = doQueryWithReturn(StringQueryComposer
.createLikeSelectQuery(table, null, null, null, true));
try {
while(rs.next()) {
rtn_lst.add(T.parse(rs)); //WRONG, CAN'T ACCESS TO parse(...) OF ParsableDTO<T>
}
rs.close();
} catch (SQLException e) {
System.err.println("Can't parse DTO from "
+ table + " at " + dateformat.format(new Date()));
System.err.println("\nError on " + e.getClass().getName()
+ ": " + e.getMessage());
e.printStackTrace();
}
return rtn_lst;
}
如何访问可以解析特定T
接口的方法parse(ResultSet rs)
?是否有更好的工作方法 and/or 来做到这一点?
从 parse()
方法中删除 <T>
。
它隐藏了接口声明的 T
。
就目前而言,parse()
是 ParsableDTO
上的一个实例方法,因此您需要一个 T
类型的实例(例如 dto_class
的实例)来访问方法。例如:
T t = dto_class.newInstance();
rtn_lst.add(t.parse(rs));
我认为它作为实例方法也是正确的 - 如果 ParsableDTO
的子类是静态的,您将无法调用该方法的不同版本。
此外,顺便说一句,这看起来很奇怪:<T extends ParsableDTO<T>>
。
这表明 parse()
将返回扩展 ParsableDTO
的实例。如果这不是故意的,最好有两个通用类型:
public <T, P extends ParsableDTO<T>> List<T> getParsableDTOs(String table,
Class<P> dto_class) {
...
P p = dto_class.newInstance();
rtn_lst.add(p.parse(rs));
并同意之前关于接口及其方法有两个 <T>
声明的评论。它编译正常,但提示 parse()
返回的类型可能与 ParsableDTO<T>
中声明的 T
不同。
您正试图在泛型上调用非静态方法,该泛型在编译时被删除。即使该方法是静态的,编译器也不允许这样做(因为在这种情况下 T 是 ParseableDTO
,而不是具体实现)。
相反,假设你在 Java 8,我会这样做:
@FunctionalInterface
public interface RowMapper<T> {
T mapRow(ResultSet rs) throws SQLException;
}
然后:
public <T> List<T> getParsableDTOs(String table, RowMapper<T> mapper) {
try (ResultSet rs = doQueryWithReturn(StringQueryComposer
.createLikeSelectQuery(table, null, null, null, true))) {
List<T> rtn_lst = new ArrayList<T>();
while(rs.next()) {
rtn_lst.add(mapper.mapRow(rs));
}
return rtn_lst;
} catch (SQLException e) {
// ...
}
return rtn_lst;
}
接口RowMapper
源自现有框架,例如JDBC Template。
这个想法是为了分离关注点:DTO 不会被 JDBC 相关方法污染(例如:映射或解析,但我建议您避免使用 parse
名称,因为您不是在此处解析 SQL ResultSet
),您甚至可以将映射保留在 DAO 中(lambda 使其更易于实现)。
用 JDBC 污染 DTO 可能会有问题,因为 client/caller 可能没有有效的 ResultSet
传递给 parse
。更糟糕的是:在较新的 JDK (9++) 中,ResultSet
接口位于 java.sql
模块中,该模块可能不可用(如果考虑 Web 服务,客户端不需要 JDBC。
附带说明,从 Java 7 开始,您可以使用 try-with-resource 和 ResultSet
自动关闭它更安全的方法:在您的实施中,如果没有错误,您只会关闭 ResultSet
。
如果你被 Java 6 困住了,你应该使用以下成语:
ResultSet rs = null;
try {
rs = ...; // obtain rs
// do whatever
} finally {
if (null != rs) {rs.close();}
}
无法在泛型类型 T
上调用静态方法是 type erasure 的 side-effect。类型擦除意味着通用类型信息在编译后从 Java 字节码中移除或擦除。执行此过程是为了保持使用 Java 5(其中引入了泛型)之前的代码编写的向后兼容性。最初,我们在 Java 5 及更高版本中使用的许多泛型类型都是简单的 classes。例如,List
只是一个普通的 class,它包含 Object
个实例并且需要显式转换以确保 type-safety:
List myList = new List();
myList.add(new Foo());
Foo foo = (Foo) myList.get(0);
在 Java 5 中引入泛型后,其中许多 classes 升级为泛型 classes。例如,List
现在变成了 List<T>
,其中 T
是列表中元素的类型。这允许编译器执行静态 (compile-time) 类型检查并消除了执行显式转换的需要。例如,上面的代码片段使用泛型简化为以下内容:
List<Foo> myList = new List<Foo>();
myList.add(new Foo());
Foo foo = myList.get(0);
这种通用方法有两个主要好处:(1) 消除了繁琐和不守规矩的强制转换,(2) 编译器可以确保 compile-time 我们不会混合类型或执行不安全的操作。例如,以下将是非法的并且会在编译期间导致错误:
List<Foo> myList = new List<Foo>();
myList.add(new Bar()); // Illegal: cannot use Bar where Foo is expected
虽然泛型在类型安全方面有很大帮助,但将它们包含在 Java 中有破坏现有代码的风险。例如,在没有任何泛型类型信息的情况下创建 List
对象应该仍然有效(这称为将其用作原始类型)。因此,编译后的通用 Java 代码必须仍然等同于 non-generic 代码。换句话说,泛型的引入不应影响编译器生成的字节码,因为这会破坏现有的 non-generic 代码。
因此,决定只在编译时和编译前处理泛型。这意味着编译器使用泛型类型信息来确保类型安全,但是一旦 Java 源代码被编译,这个泛型类型信息就会被删除。如果我们查看您问题中方法生成的字节码,就可以验证这一点。例如,假设我们将该方法放在一个名为 Parser
的 class 中,并将该方法简化为以下内容:
public class Parser {
public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, Class<T> clazz) {
T dto = null;
List<T> list = new ArrayList<>();
list.add(dto);
return list;
}
}
如果我们编译此 class 并使用 javap -c Parser.class
检查其字节码,我们将看到以下内容:
Compiled from "Parser.java"
public class var.Parser {
public var.Parser();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public <T extends var.ParsableDTO<T>> java.util.List<T> getParsableDTOs(java.lang.String, java.lang.Class<T>);
Code:
0: aconst_null
1: astore_3
2: new #18 // class java/util/ArrayList
5: dup
6: invokespecial #20 // Method java/util/ArrayList."<init>":()V
9: astore 4
11: aload 4
13: aload_3
14: invokeinterface #21, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
19: pop
20: aload 4
22: areturn
}
行 14: invokeinterface #21, 2
表示我们使用 Object
参数在 List
上调用了 add
,即使我们源代码中参数的实际类型是 T
。由于泛型不能影响编译器生成的字节码,编译器将泛型类型替换为 Object
(这使得泛型类型为 T
non-reifiable),然后,如果需要,执行转换回对象的预期类型。例如,如果我们编译如下:
public class Parser {
public void doSomething() {
List<Foo> foos = new ArrayList<>();
foos.add(new Foo());
Foo myFoo = foos.get(0);
}
}
我们得到以下字节码:
public class var.Parser {
public var.Parser();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public void doSomething();
Code:
0: new #15 // class java/util/ArrayList
3: dup
4: invokespecial #17 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: new #18 // class var/Foo
12: dup
13: invokespecial #20 // Method Foo."<init>":()V
16: invokeinterface #21, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
21: pop
22: aload_1
23: iconst_0
24: invokeinterface #27, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
29: checkcast #18 // class Foo
32: astore_2
33: return
}
行 29: checkcast #18
显示编译器添加了一条指令来检查我们从 List
(使用 get(0)
)收到的 Object
是否可以转换为Foo
。换句话说,我们从 List
收到的 Object
实际上是运行时的 Foo
。
那么这个因素如何影响您的问题?在 Java 中进行诸如 T.parse(rs)
的调用是无效的,因为编译器无法在运行时知道 class 调用静态方法 parse
的内容,因为泛型类型信息在运行时丢失。这也限制了我们创建 T
(即 new T();
)类型的对象。
这个难题很常见,实际上可以在 Java 库中找到。例如,每个 Collection
对象都有两个方法可以将 Collection
转换为数组:Object[] toArray()
和 <T> T[] toArray(T[] a)
。后者允许客户端提供预期类型的数组。这在运行时为 Collection
提供了足够的类型信息来创建和 return 预期(相同)类型的数组 T
。例如,如果我们查看 AbstractCollection
的 JDK 9 源代码
public <T> T[] toArray(T[] a) {
// ...
T[] r = a.length >= size ? a :
(T[])java.lang.reflect.Array
.newInstance(a.getClass().getComponentType(), size);
// ...
}
我们看到该方法能够使用反射创建类型为 T
的新数组,但这需要使用对象 a
。本质上,提供 a
以便该方法可以在运行时确定 T
的实际类型(询问对象 a
,"What type are you?")。如果我们不能提供 T[]
参数,则必须使用 Object[] toArray()
方法,它只能创建一个 Object[]
(同样来自 AbstractCollection
源代码):
public Object[] toArray() {
Object[] r = new Object[size()];
// ...
}
toArray(T[])
使用的解决方案对于您的情况来说是一个合理的解决方案,但有一些非常重要的差异使其成为一个糟糕的解决方案。在 toArray(T[])
情况下使用反射是可以接受的,因为创建数组是 Java 中的标准化过程(因为数组不是 user-defined classes,而是标准化的 classes,很像 String
)。因此,构建过程(例如提供哪些参数)是已知的 先验 和标准化。在调用类型的静态方法的情况下,我们不知道事实上,静态方法将存在于提供的类型中(即没有实现接口的等价物来确保静态方法存在方法)。
相反,最常见的约定是提供一个函数,该函数可用于将请求的参数(在本例中为 ResultSet
)映射到 T
对象。例如,您的 getParsableDTOs
方法的签名将变为:
public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, Function<ResultSet, T> mapper) {
/* ... */
}
mapper
参数只是一个Function<ResultSet, T>
,这意味着它消耗了一个ResultSet
并产生了一个T
。这是最通用的方式,因为任何接受 ResultSet
对象并产生 T
对象的 Function
都可以使用。我们也可以为此创建一个特定的接口:
@FunctionalInterface
public interface RowMapper<T> {
public T mapRow(ResultSet rs);
}
并将方法签名更改为以下内容:
public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, RowMapper<T> mapper) {
/* ... */
}
因此,获取您的代码并将非法调用(对 T
的静态调用)替换为映射器函数,我们最终得到:
public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, RowMapper<T> mapper) {
List<T> rtn_lst = new ArrayList<T>();
ResultSet rs = doQueryWithReturn(StringQueryComposer
.createLikeSelectQuery(table, null, null, null, true));
try {
while(rs.next()) {
rtn_lst.add(mapper.mapRow(rs)); // <--- Map value using our mapper function
}
rs.close();
} catch (SQLException e) {
System.err.println("Can't parse DTO from "
+ table + " at " + dateformat.format(new Date()));
System.err.println("\nError on " + e.getClass().getName()
+ ": " + e.getMessage());
e.printStackTrace();
}
return rtn_lst;
}
此外,因为我们使用 @FunctionalInterface
作为 getParsableDTOs
的参数,我们可以使用 lambda 函数将 ResultSet
映射到 T
,如:
Parser parser = new Parser();
parser.getParsableDTOs("FOO_TABLE", rs -> { return new Foo(); });
您只需更改 getParsableDTOs 的方法签名以使用 ParsableDTO<T>
而不是 Class<T>
。在你的 while 循环中做
rtn_lst.add(dto_class.parse(rs));
我有这个界面:
public interface ParsableDTO<T> {
public <T> T parse(ResultSet rs) throws SQLException;
}
在某种 dto classes 中实现,此方法在另一个 class:
public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table,
Class<T> dto_class) {
List<T> rtn_lst = new ArrayList<T>();
ResultSet rs = doQueryWithReturn(StringQueryComposer
.createLikeSelectQuery(table, null, null, null, true));
try {
while(rs.next()) {
rtn_lst.add(T.parse(rs)); //WRONG, CAN'T ACCESS TO parse(...) OF ParsableDTO<T>
}
rs.close();
} catch (SQLException e) {
System.err.println("Can't parse DTO from "
+ table + " at " + dateformat.format(new Date()));
System.err.println("\nError on " + e.getClass().getName()
+ ": " + e.getMessage());
e.printStackTrace();
}
return rtn_lst;
}
如何访问可以解析特定T
接口的方法parse(ResultSet rs)
?是否有更好的工作方法 and/or 来做到这一点?
从 parse()
方法中删除 <T>
。
它隐藏了接口声明的 T
。
就目前而言,parse()
是 ParsableDTO
上的一个实例方法,因此您需要一个 T
类型的实例(例如 dto_class
的实例)来访问方法。例如:
T t = dto_class.newInstance();
rtn_lst.add(t.parse(rs));
我认为它作为实例方法也是正确的 - 如果 ParsableDTO
的子类是静态的,您将无法调用该方法的不同版本。
此外,顺便说一句,这看起来很奇怪:<T extends ParsableDTO<T>>
。
这表明 parse()
将返回扩展 ParsableDTO
的实例。如果这不是故意的,最好有两个通用类型:
public <T, P extends ParsableDTO<T>> List<T> getParsableDTOs(String table,
Class<P> dto_class) {
...
P p = dto_class.newInstance();
rtn_lst.add(p.parse(rs));
并同意之前关于接口及其方法有两个 <T>
声明的评论。它编译正常,但提示 parse()
返回的类型可能与 ParsableDTO<T>
中声明的 T
不同。
您正试图在泛型上调用非静态方法,该泛型在编译时被删除。即使该方法是静态的,编译器也不允许这样做(因为在这种情况下 T 是 ParseableDTO
,而不是具体实现)。
相反,假设你在 Java 8,我会这样做:
@FunctionalInterface
public interface RowMapper<T> {
T mapRow(ResultSet rs) throws SQLException;
}
然后:
public <T> List<T> getParsableDTOs(String table, RowMapper<T> mapper) {
try (ResultSet rs = doQueryWithReturn(StringQueryComposer
.createLikeSelectQuery(table, null, null, null, true))) {
List<T> rtn_lst = new ArrayList<T>();
while(rs.next()) {
rtn_lst.add(mapper.mapRow(rs));
}
return rtn_lst;
} catch (SQLException e) {
// ...
}
return rtn_lst;
}
接口RowMapper
源自现有框架,例如JDBC Template。
这个想法是为了分离关注点:DTO 不会被 JDBC 相关方法污染(例如:映射或解析,但我建议您避免使用 parse
名称,因为您不是在此处解析 SQL ResultSet
),您甚至可以将映射保留在 DAO 中(lambda 使其更易于实现)。
用 JDBC 污染 DTO 可能会有问题,因为 client/caller 可能没有有效的 ResultSet
传递给 parse
。更糟糕的是:在较新的 JDK (9++) 中,ResultSet
接口位于 java.sql
模块中,该模块可能不可用(如果考虑 Web 服务,客户端不需要 JDBC。
附带说明,从 Java 7 开始,您可以使用 try-with-resource 和 ResultSet
自动关闭它更安全的方法:在您的实施中,如果没有错误,您只会关闭 ResultSet
。
如果你被 Java 6 困住了,你应该使用以下成语:
ResultSet rs = null;
try {
rs = ...; // obtain rs
// do whatever
} finally {
if (null != rs) {rs.close();}
}
无法在泛型类型 T
上调用静态方法是 type erasure 的 side-effect。类型擦除意味着通用类型信息在编译后从 Java 字节码中移除或擦除。执行此过程是为了保持使用 Java 5(其中引入了泛型)之前的代码编写的向后兼容性。最初,我们在 Java 5 及更高版本中使用的许多泛型类型都是简单的 classes。例如,List
只是一个普通的 class,它包含 Object
个实例并且需要显式转换以确保 type-safety:
List myList = new List();
myList.add(new Foo());
Foo foo = (Foo) myList.get(0);
在 Java 5 中引入泛型后,其中许多 classes 升级为泛型 classes。例如,List
现在变成了 List<T>
,其中 T
是列表中元素的类型。这允许编译器执行静态 (compile-time) 类型检查并消除了执行显式转换的需要。例如,上面的代码片段使用泛型简化为以下内容:
List<Foo> myList = new List<Foo>();
myList.add(new Foo());
Foo foo = myList.get(0);
这种通用方法有两个主要好处:(1) 消除了繁琐和不守规矩的强制转换,(2) 编译器可以确保 compile-time 我们不会混合类型或执行不安全的操作。例如,以下将是非法的并且会在编译期间导致错误:
List<Foo> myList = new List<Foo>();
myList.add(new Bar()); // Illegal: cannot use Bar where Foo is expected
虽然泛型在类型安全方面有很大帮助,但将它们包含在 Java 中有破坏现有代码的风险。例如,在没有任何泛型类型信息的情况下创建 List
对象应该仍然有效(这称为将其用作原始类型)。因此,编译后的通用 Java 代码必须仍然等同于 non-generic 代码。换句话说,泛型的引入不应影响编译器生成的字节码,因为这会破坏现有的 non-generic 代码。
因此,决定只在编译时和编译前处理泛型。这意味着编译器使用泛型类型信息来确保类型安全,但是一旦 Java 源代码被编译,这个泛型类型信息就会被删除。如果我们查看您问题中方法生成的字节码,就可以验证这一点。例如,假设我们将该方法放在一个名为 Parser
的 class 中,并将该方法简化为以下内容:
public class Parser {
public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, Class<T> clazz) {
T dto = null;
List<T> list = new ArrayList<>();
list.add(dto);
return list;
}
}
如果我们编译此 class 并使用 javap -c Parser.class
检查其字节码,我们将看到以下内容:
Compiled from "Parser.java"
public class var.Parser {
public var.Parser();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public <T extends var.ParsableDTO<T>> java.util.List<T> getParsableDTOs(java.lang.String, java.lang.Class<T>);
Code:
0: aconst_null
1: astore_3
2: new #18 // class java/util/ArrayList
5: dup
6: invokespecial #20 // Method java/util/ArrayList."<init>":()V
9: astore 4
11: aload 4
13: aload_3
14: invokeinterface #21, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
19: pop
20: aload 4
22: areturn
}
行 14: invokeinterface #21, 2
表示我们使用 Object
参数在 List
上调用了 add
,即使我们源代码中参数的实际类型是 T
。由于泛型不能影响编译器生成的字节码,编译器将泛型类型替换为 Object
(这使得泛型类型为 T
non-reifiable),然后,如果需要,执行转换回对象的预期类型。例如,如果我们编译如下:
public class Parser {
public void doSomething() {
List<Foo> foos = new ArrayList<>();
foos.add(new Foo());
Foo myFoo = foos.get(0);
}
}
我们得到以下字节码:
public class var.Parser {
public var.Parser();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public void doSomething();
Code:
0: new #15 // class java/util/ArrayList
3: dup
4: invokespecial #17 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: new #18 // class var/Foo
12: dup
13: invokespecial #20 // Method Foo."<init>":()V
16: invokeinterface #21, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
21: pop
22: aload_1
23: iconst_0
24: invokeinterface #27, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
29: checkcast #18 // class Foo
32: astore_2
33: return
}
行 29: checkcast #18
显示编译器添加了一条指令来检查我们从 List
(使用 get(0)
)收到的 Object
是否可以转换为Foo
。换句话说,我们从 List
收到的 Object
实际上是运行时的 Foo
。
那么这个因素如何影响您的问题?在 Java 中进行诸如 T.parse(rs)
的调用是无效的,因为编译器无法在运行时知道 class 调用静态方法 parse
的内容,因为泛型类型信息在运行时丢失。这也限制了我们创建 T
(即 new T();
)类型的对象。
这个难题很常见,实际上可以在 Java 库中找到。例如,每个 Collection
对象都有两个方法可以将 Collection
转换为数组:Object[] toArray()
和 <T> T[] toArray(T[] a)
。后者允许客户端提供预期类型的数组。这在运行时为 Collection
提供了足够的类型信息来创建和 return 预期(相同)类型的数组 T
。例如,如果我们查看 AbstractCollection
public <T> T[] toArray(T[] a) {
// ...
T[] r = a.length >= size ? a :
(T[])java.lang.reflect.Array
.newInstance(a.getClass().getComponentType(), size);
// ...
}
我们看到该方法能够使用反射创建类型为 T
的新数组,但这需要使用对象 a
。本质上,提供 a
以便该方法可以在运行时确定 T
的实际类型(询问对象 a
,"What type are you?")。如果我们不能提供 T[]
参数,则必须使用 Object[] toArray()
方法,它只能创建一个 Object[]
(同样来自 AbstractCollection
源代码):
public Object[] toArray() {
Object[] r = new Object[size()];
// ...
}
toArray(T[])
使用的解决方案对于您的情况来说是一个合理的解决方案,但有一些非常重要的差异使其成为一个糟糕的解决方案。在 toArray(T[])
情况下使用反射是可以接受的,因为创建数组是 Java 中的标准化过程(因为数组不是 user-defined classes,而是标准化的 classes,很像 String
)。因此,构建过程(例如提供哪些参数)是已知的 先验 和标准化。在调用类型的静态方法的情况下,我们不知道事实上,静态方法将存在于提供的类型中(即没有实现接口的等价物来确保静态方法存在方法)。
相反,最常见的约定是提供一个函数,该函数可用于将请求的参数(在本例中为 ResultSet
)映射到 T
对象。例如,您的 getParsableDTOs
方法的签名将变为:
public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, Function<ResultSet, T> mapper) {
/* ... */
}
mapper
参数只是一个Function<ResultSet, T>
,这意味着它消耗了一个ResultSet
并产生了一个T
。这是最通用的方式,因为任何接受 ResultSet
对象并产生 T
对象的 Function
都可以使用。我们也可以为此创建一个特定的接口:
@FunctionalInterface
public interface RowMapper<T> {
public T mapRow(ResultSet rs);
}
并将方法签名更改为以下内容:
public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, RowMapper<T> mapper) {
/* ... */
}
因此,获取您的代码并将非法调用(对 T
的静态调用)替换为映射器函数,我们最终得到:
public <T extends ParsableDTO<T>> List<T> getParsableDTOs(String table, RowMapper<T> mapper) {
List<T> rtn_lst = new ArrayList<T>();
ResultSet rs = doQueryWithReturn(StringQueryComposer
.createLikeSelectQuery(table, null, null, null, true));
try {
while(rs.next()) {
rtn_lst.add(mapper.mapRow(rs)); // <--- Map value using our mapper function
}
rs.close();
} catch (SQLException e) {
System.err.println("Can't parse DTO from "
+ table + " at " + dateformat.format(new Date()));
System.err.println("\nError on " + e.getClass().getName()
+ ": " + e.getMessage());
e.printStackTrace();
}
return rtn_lst;
}
此外,因为我们使用 @FunctionalInterface
作为 getParsableDTOs
的参数,我们可以使用 lambda 函数将 ResultSet
映射到 T
,如:
Parser parser = new Parser();
parser.getParsableDTOs("FOO_TABLE", rs -> { return new Foo(); });
您只需更改 getParsableDTOs 的方法签名以使用 ParsableDTO<T>
而不是 Class<T>
。在你的 while 循环中做
rtn_lst.add(dto_class.parse(rs));