[英]Java generics - Type casting issue
我有一个泛型方法,它接受要更新的类的类和字段。 例如:
class A {
private int a;
private int b;
}
class B {
private int c;
private int d;
}
在运行时,如果我们将类类型传递为“A.class”并将fieldstoBeUpdated传递为“b”,那么访问该特定类的字段的getter / setter的最佳方法是什么,以便我们可以修改字段。
public <T> void find(T clazz, List<String> fieldsToBeUpdated) {
List<T> collectionList = findAll((Class<T>) clazz);
collectionList.parallelStream().forEach(p -> {
if (clazz instanceof A) {
fieldsToBeUpdated.parallelStream().forEach(classFieldName -> {
switch(classFieldName) {
case "a":((A)p).setA(10);
break;
case "b":((A)p).setB(20);
break;
}
});
}
if (clazz instanceof B) {
fieldsToBeUpdated.parallelStream().forEach(classFieldName -> {
switch(classFieldName) {
case "c":((B)p).setC(30);
break;
case "d":((B)p).setD(40);
break;
}
});
}
});
}
我已经写了上面的代码来实现相同的目的。
但问题是我有30个这样的类作为参数传递给这个泛型方法,该类的字段列表要更新/修改。
编写30个if语句检查类类型然后键入将对象强制转换为该类,这不是正确的实现。
有没有更好的方法来实现同样的目标?
提前致谢。
您的A
和B
类似乎都提供set/getCreatedTime
和set/getUpdatedTime
方法。
如果其他28个左右的类也提供了这些(如你的问题所暗示的那样),那么只需要一个包含这些方法的通用接口,就可以实现所有类。
然后,您可以将方法的泛型类型绑定到该接口,并放弃所有instanceof
语句和后续显式转换。
唯一的缺点是,如果List
中的字段名称与传递给方法的具体类不相关。
如果要强制执行此操作,可以使用对象上的反射来发现字段是否按名称存在。 然后,您可以通过记录警告(或您认为适当的任何机制)轻松处理任何缺失的字段。
注意
正如thijs-steel所提到的,如果你的“时间”方法共享相同的实现,你可以让你的30个类扩展一个只实现“时间”方法的公共抽象父类。
或者,您可以使用default
方法,因为您显然使用的是Java 8。
例
interface I {
// assuming parameters and return types here
public void setCreatedTime(ZonedDateTime z);
public void setUpdatedTime(ZonedDateTime z);
public ZonedDateTime getCreatedTime();
public ZonedDateTime getUpdatedTime();
}
// A, B etc. all implement I
public <T extends I> void find(T object, List<String> fieldsToBeUpdated) {
fieldsToBeUpdated
.parallelStream()
.forEach(
field -> {
switch(field) {
case "a": {
try {
object.getClass().getDeclaredField("a");
// we're good
object.setCreatedTime(...);
}
catch (NoSuchFieldException e) {
// TODO something
}
break;
}
// ...
}
});
}
更新
如果您的类根本不共享任何公共字段,您可能希望完全改变整个方法。
您可能希望使用继承并在每个类中拥有自己的find
方法实现,而不是采用“一个通用方法适合所有”逻辑实现范例。
这将允许在每个实现中使用较小的switch
语句,并在default
情况下执行错误处理。
您还可以通过仍然使用一个带有T extends Findable
的find
方法(其中Findable
声明“time”方法,现在是find
方法)来概括行为,并且只需在给定的T
对象上调用find
。
甚至可以在Findable
和Timed
之间单独关注,让你的类实现它们。
我先提取一个接口:
public interface TimeManipulator {
public void setCreatedTime(long createdTime);
public long getCreatedTime();
public void setUpdatedTime(long createdTime);
public long getUpdatedTime();
}
并将其应用于类:
class A implements TimeManipulator {
...
}
class B implements TimeManipulator {
...
}
然后你需要做的就是将T
绑定到这个接口:
public <T extends TimeManipulator> void find(T p, List<String> fieldsToBeUpdated) {
fieldsToBeUpdated.parallelStream().forEach(field -> {
switch(field) {
case "a":
case "c":
p.setCreatedTime(TimeUtils.toGMT(p.getCreatedTime(), ZoneOffset.of("+05:30")));
break;
case "b":
case "d":p.setUpdatedTime(TimeUtils.toGMT(p.getUpdatedTime(), ZoneOffset.of("+05:30")));
break;
}
});
}
使用@Mena给出的建议,我想出了一个使用反射API的解决方案。
我正在遍历fieldsToBeUpdated参数列表,并且在每次迭代中,我正在使用下面的一行来检查对象中是否存在该字段(作为参数传递):
如果存在,则返回字段对象null。
null != clazz.getDeclaredField(field)
以下是整个实现逻辑:
public <T> void find(Class clazz, List<String> fieldsToBeUpdated) {
List<T> collectionList = db.findAll((Class<T>) clazz);
if (CollectionUtils.isNotEmpty(collectionList)) {
collectionList.stream().forEach(p -> {
fieldsToBeUpdated.stream().forEach(field -> {
Date date = null;
try {
if (null != clazz.getDeclaredField(field)) {
Field f = clazz.getDeclaredField(field);
f.setAccessible(true);
date = (Date) f.get(p);
}
} catch (NoSuchFieldException|SecurityException|IllegalArgumentException|IllegalAccessException e) {
e.printStackTrace();
}
});
});
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.