我需要比较两个对象(同一个类的实例)中的几十个字段,并在存在差异时进行一些记录和更新。元代码看起来像这样:
if (a.getfield1 != b.getfield1)
log(a.getfield1 is different than b.getfield1)
b.field1 = a.field1
if (a.getfield2!= b.getfield2)
log(a.getfield2 is different than b.getfield2)
b.field2 = a.field2
...
if (a.getfieldn!= b.getfieldn)
log(a.getfieldn is different than b.getfieldn)
b.fieldn = a.fieldn
所有比较的代码非常简洁,我想以某种方式使它更紧凑。如果我有一个方法可以作为参数方法调用setter和getter,并为所有字段调用它,那将是很好的,但不幸的是,这对于java来说是不可能的。
我提出了三个选项,每个选项都有自己的缺点。
1。使用反射API找出getter和setter
丑陋,如果字段名称发生变化,可能会导致运行时错误
2。将字段更改为public并直接操作它们而不使用getter和setter
丑陋也会将类的实现暴露给外部世界
3。让包含类(实体)进行比较,更新更改的字段并返回日志消息
实体不应参与业务逻辑
所有字段都是字符串类型,如果需要,我可以修改拥有字段的类的代码。
编辑:课堂上有一些字段不得比较。
答案 0 :(得分:16)
使用Annotations。
如果您标记需要比较的字段(无论它们是否为私有字段,您仍然不会丢失封装,然后获取这些字段并进行比较。它可能如下所示:
在需要比较的班级中:
@ComparableField
private String field1;
@ComparableField
private String field2;
private String field_nocomparable;
在外部课堂上:
public <T> void compare(T t, T t2) throws IllegalArgumentException,
IllegalAccessException {
Field[] fields = t.getClass().getDeclaredFields();
if (fields != null) {
for (Field field : fields) {
if (field.isAnnotationPresent(ComparableField.class)) {
field.setAccessible(true);
if ( (field.get(t)).equals(field.get(t2)) )
System.out.println("equals");
field.setAccessible(false);
}
}
}
}
代码未经过测试,但如果有帮助请告诉我。
答案 1 :(得分:4)
JavaBeans API旨在帮助内省。自Java版本1.2以来,它已经以某种形式存在,并且自版本1.4以来一直非常有用。
演示代码,用于比较两个bean中的属性列表:
public static void compareBeans(PrintStream log,
Object bean1, Object bean2, String... propertyNames)
throws IntrospectionException,
IllegalAccessException, InvocationTargetException {
Set<String> names = new HashSet<String>(Arrays
.asList(propertyNames));
BeanInfo beanInfo = Introspector.getBeanInfo(bean1
.getClass());
for (PropertyDescriptor prop : beanInfo
.getPropertyDescriptors()) {
if (names.remove(prop.getName())) {
Method getter = prop.getReadMethod();
Object value1 = getter.invoke(bean1);
Object value2 = getter.invoke(bean2);
if (value1 == value2
|| (value1 != null && value1.equals(value2))) {
continue;
}
log.format("%s: %s is different than %s%n", prop
.getName(), "" + value1, "" + value2);
Method setter = prop.getWriteMethod();
setter.invoke(bean2, value2);
}
}
if (names.size() > 0) {
throw new IllegalArgumentException("" + names);
}
}
示例调用:
compareBeans(System.out, bean1, bean2, "foo", "bar");
如果你去注释路线,考虑转储反射并用编译时annotation processor或其他代码生成器生成比较代码。
答案 2 :(得分:2)
我会选择选项1,但我会使用getClass().getDeclaredFields()
来访问字段而不是使用名称。
public void compareAndUpdate(MyClass other) throws IllegalAccessException {
for (Field field : getClass().getDeclaredFields()) {
if (field.getType() == String.class) {
Object thisValue = field.get(this);
Object otherValue = field.get(other);
// if necessary check for null
if (!thisValue.equals(otherValue)) {
log(field.getName() + ": " + thisValue + " <> " + otherValue);
field.set(other, thisValue);
}
}
}
}
这里有一些限制(如果我是对的):
答案 3 :(得分:1)
这可能也不太好,但它比你提出的两种替代方案中的任何一种都更不邪恶(恕我直言)。
如何提供一个获取数字索引字段的getter / setter对,然后让getter / setter将索引字段取消引用到相关的成员变量?
即:
public class MyClass {
public void setMember(int index, String value) {
switch (index) {
...
}
}
public String getMember(int index) {
...
}
static public String getMemberName(int index) {
...
}
}
然后在你的外部课程中:
public void compareAndUpdate(MyClass a, MyClass b) {
for (int i = 0; i < a.getMemberCount(); ++i) {
String sa = a.getMember();
String sb = b.getMember();
if (!sa.equals(sb)) {
Log.v("compare", a.getMemberName(i));
b.setMember(i, sa);
}
}
}
这至少可以让你将所有重要的逻辑保留在正在检查的类中。
答案 4 :(得分:1)
虽然选项1可能很难看,但它可以完成工作。选项2甚至更加丑陋,并打开您无法想象的漏洞代码。即使您最终排除了选项1,我也建议您保留现有代码而不是选项2。
说完这些之后,如果您不想将此作为静态列表传递给方法,则可以使用反射来获取类的字段名称列表。假设您要比较所有字段,则可以循环动态创建比较。
如果不是这种情况,并且您比较的字符串只是某些字段,则可以进一步检查字段并仅隔离String
类型的字段,然后继续进行比较。 / p>
希望这有帮助,
Yuval = 8 - )
答案 5 :(得分:1)
因为
所有字段都是字符串类型,如果需要,我可以修改拥有字段的类的代码。
你可以尝试这个课程:
public class BigEntity {
private final Map<String, String> data;
public LongEntity() {
data = new HashMap<String, String>();
}
public String getFIELD1() {
return data.get(FIELD1);
}
public String getFIELD2() {
return data.get(FIELD2);
}
/* blah blah */
public void cloneAndLogDiffs(BigEntity other) {
for (String field : fields) {
String a = this.get(field);
String b = other.get(field);
if (!a.equals(b)) {
System.out.println("diff " + field);
other.set(field, this.get(field));
}
}
}
private String get(String field) {
String value = data.get(field);
if (value == null) {
value = "";
}
return value;
}
private void set(String field, String value) {
data.put(field, value);
}
@Override
public String toString() {
return data.toString();
}
魔法代码:
private static final String FIELD1 = "field1";
private static final String FIELD2 = "field2";
private static final String FIELD3 = "field3";
private static final String FIELD4 = "field4";
private static final String FIELDN = "fieldN";
private static final List<String> fields;
static {
fields = new LinkedList<String>();
for (Field field : LongEntity.class.getDeclaredFields()) {
if (field.getType() != String.class) {
continue;
}
if (!Modifier.isStatic(field.getModifiers())) {
continue;
}
fields.add(field.getName().toLowerCase());
}
}
这个班有几个好处:
答案 6 :(得分:0)
我也会提出一个类似于Alnitak的解决方案。
如果在比较时需要迭代字段,为什么不省去单独的字段,并将数据放入数组,HashMap或类似的适当的东西。
然后您可以通过编程方式访问它们,比较它们等。如果需要处理不同的字段,通过不同的方式进行比较,您可以为值创建适当的辅助类,从而实现接口。
然后你可以做
valueMap.get("myobject").compareAndChange(valueMap.get("myotherobject")
或类似的东西......
答案 7 :(得分:0)
一个广泛的想法:
创建一个新类,其对象采用以下参数:要比较的第一个类,要比较的第二个类,以及getter和amp的列表。对象的setter方法名称,其中只包含感兴趣的方法。
您可以使用反射查询对象的类,并从中查询其可用的方法。假设参数列表中的每个getter方法都包含在类的可用方法中,您应该能够调用该方法来获取用于比较的值。
粗略勾勒出类似的东西(道歉,如果它不是超级完美......不是我的主要语言):
public class MyComparator
{
//NOTE: Class a is the one that will get the value if different
//NOTE: getters and setters arrays must correspond exactly in this example
public static void CompareMyStuff(Object a, Object b, String[] getters, String[] setters)
{
Class a_class = a.getClass();
Class b_class = b.getClass();
//the GetNamesFrom... static methods are defined elsewhere in this class
String[] a_method_names = GetNamesFromMethods(a_class.getMethods());
String[] b_method_names = GetNamesFromMethods(b_class.getMethods());
String[] a_field_names = GetNamesFromFields(a_class.getFields());
//for relative brevity...
Class[] empty_class_arr = new Class[] {};
Object[] empty_obj_arr = new Object[] {};
for (int i = 0; i < getters.length; i++)
{
String getter_name = getter[i];
String setter_name = setter[i];
//NOTE: the ArrayContainsString static method defined elsewhere...
//ensure all matches up well...
if (ArrayContainsString(a_method_names, getter_name) &&
ArrayContainsString(b_method_names, getter_name) &&
ArrayContainsString(a_field_names, setter_name)
{
//get the values from the getter methods
String val_a = a_class.getMethod(getter_name, empty_class_arr).invoke(a, empty_obj_arr);
String val_b = b_class.getMethod(getter_name, empty_class_arr).invoke(b, empty_obj_arr);
if (val_a != val_b)
{
//LOG HERE
//set the value
a_class.getField(setter_name).set(a, val_b);
}
}
else
{
//do something here - bad names for getters and/or setters
}
}
}
}
答案 8 :(得分:0)
你说你现在有所有这些领域的吸气剂和制定者吗?好的,然后将基础数据从一堆单个字段更改为数组。更改所有getter和setter以访问该数组。我会为索引创建常量标记,而不是使用数字来实现长期可维护性。还要创建一个并行的标志数组,指示应处理哪些字段。然后创建一个使用索引的通用getter / setter对,以及compare标志的getter。像这样:
public class SomeClass
{
final static int NUM_VALUES=3;
final static int FOO=0, BAR=1, PLUGH=2;
String[] values=new String[NUM_VALUES];
static boolean[] wantCompared={true, false, true};
public String getFoo()
{
return values[FOO];
}
public void setFoo(String foo)
{
values[FOO]=foo;
}
... etc ...
public int getValueCount()
{
return NUM_VALUES;
}
public String getValue(int x)
{
return values[x];
}
public void setValue(int x, String value)
{
values[x]=value;
}
public boolean getWantCompared(int x)
{
return wantCompared[x];
}
}
public class CompareClass
{
public void compare(SomeClass sc1, SomeClass sc2)
{
int z=sc1.getValueCount();
for (int x=0;x<z;++x)
{
if (!sc1.getWantCompared[x])
continue;
String sc1Value=sc1.getValue(x);
String sc2Value=sc2.getValue(x);
if (!sc1Value.equals(sc2Value)
{
writeLog(x, sc1Value, sc2Value);
sc2.setValue(x, sc1Value);
}
}
}
}
我刚刚把它写在了我的头顶,我还没有测试过,所以它们可能是代码中的错误,但我认为这个概念应该有效。
由于您已经拥有getter和setter,因此使用此类的任何其他代码应继续保持不变。如果没有其他代码使用此类,则抛弃现有的getter和setter,只使用该数组执行所有操作。
答案 9 :(得分:-1)
我正在编写一个名为jComparison(https://github.com/mmirwaldt/jcomparison)的框架,它找到两个java对象,字符串,映射和集合之间的差异(和相似之处)。 有许多演示向您展示如何使用它。 然而,这是一个alpha版本atm,我还没有决定我想选择哪个许可证。
注意:我是该框架的作者。
下的演示它显示了一个虚构类Person和Address的示例。 演示的输出是:
Similarities:
private final net.mirwaldt.jcomparison.core.object.Person$Sex net.mirwaldt.jcomparison.core.object.Person.sex:
MALE
Differences:
private final double net.mirwaldt.jcomparison.core.object.Person.moneyInPocket:
ImmutableDoublePair{leftDouble=35.12, rightDouble=148.96}
private final int net.mirwaldt.jcomparison.core.object.Person.age:
ImmutableIntPair{leftInt=32, rightInt=45}
Comparisons:
name:
personA : 'Mich[a]el'
personB : 'Mich[]el'
Features:
Feature of personA only : '{WRISTWATCH_BRAND=CASIO}'
Feature of personB only : '{TATTOO_TEXT=Mum}'
personA and personB have different features : '{SKIN_COLOR=ImmutablePair [leftValue= white, rightValue= black]}'
personA and personB have similar features: '{HAIR_COLOR=brown}'
Leisure activities:
Leisure activities of personA only : '[Piano]'
Leisure activities of personB only : '[Tennis, Jogging]'
personA and personB have similar leisureActivities: '[Swimming]'
Address:
Similarities:
private final int net.mirwaldt.jcomparison.core.object.Address.zipCode:
81245
private final net.mirwaldt.jcomparison.core.object.Address$Country net.mirwaldt.jcomparison.core.object.Address.country:
GERMANY
private final java.lang.String net.mirwaldt.jcomparison.core.object.Address.streetName:
August-Exter-Str.
private final java.lang.String net.mirwaldt.jcomparison.core.object.Address.city:
Munich
Differences:
private final int net.mirwaldt.jcomparison.core.object.Address.houseNumber:
ImmutableIntPair{leftInt=10, rightInt=12}