Java泛型 - 类型铸造问题
我有一个通用的方法接受类类型和该类的字段进行更新。 对于前:Java泛型 - 类型铸造问题
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语句来检查类的类型,然后将类型转换为该类的对象。
有没有更好的方法来实现?
在此先感谢。
使用@Mena给出的建议,我想出了一个使用反射API的解决方案。
我通过fieldsToBeUpdated参数的列表,并在每一次迭代中(作为参数传递)我检查如果该字段是存在于对象使用下面的片线的迭代:
这将返回字段对象如果存在,否则为空。
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();
}
});
});
}
}
您的A
和B
类似乎都提供了set/getCreatedTime
和set/getUpdatedTime
方法。
如果其他28个左右的类提供了这些(就像你的问题所暗示的那样),那么只需要有一个以这些方法为特征的通用接口,以供所有类实现。
然后,您可以将方法的通用类型绑定到该接口,并放弃所有instanceof
语句和随后的显式转换。
唯一的缺点是如果在List
中有一个字段名称与传递给该方法的具体类不相关。
如果要强制执行此操作,可以对对象使用反射来发现字段是否按名称存在。然后,您可以轻松处理任何遗漏的警告字段(或您认为适用的任何机制)。
注意
正如thijs-steel提到的,如果你的“时间”的方法共享相同的实现,你可以有你的30个班扩展了一个公共抽象父,只有实现了“时间”的方法。
或者,你因为你清楚地使用Java 8
实例可以使用default
方法
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
的情况下执行错误处理。
你也仍然还是有find
方法以推广行为的T extends Findable
(其中Findable
宣布了“时间”的方法和现在find
方法),并简单地调用指定的T
对象find
。
甚至在Findable
和Timed
之间分开关注,并让你的类同时实现。
而不是一个接口,实际的类继承可能会更好 –
@ThijsSteel确实如果“时间”方法有一个共同的实现。 – Mena
这30个班都没有类似的领域。如示例中所述,这些类将具有不同的字段。 –
我想提取接口第一:
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;
}
});
}
作为一个侧面说明,这看起来并不像一个良好的使用parallelStream'的'。你没有那么多的字段需要更新,所以性能可能会更差,而你的'forEach' lambda [修改共享状态时没有任何线程安全性](https://docs.oracle.com/javase/tutorial/必需/并发/ memconsist.html)。 – Radiodef