Reputation: 2663
I need to write some generic solution to find out what properties in two objects have changed and return the changed properties (not the value).
class Student {
public String name;
public Address address;
public int age;
}
class Address {
public String hno;
public String street;
public int pin;
}
public class Test {
public static void main(String... arg) {
Student s1 = new Student();
s1.name = "Krishna";
s1.age = 30;
s1.address = new Address();
s1.address.hno = "2-2-22";
s1.address.street = "somewhere";
s1.address.pin = 123;
Student s2 = new Student();
s2.name = "Krishna";
s2.age = 20;
s2.address = new Address();
s2.address.hno = "1-1-11";
s2.address.street = "nowhere";
s2.address.pin = 123;
List<String> = difference(s1, s2);
// expected result
// Student.age
// Student.address.hno
// Student.address.street
}
}
Can anyone please suggest some solution?
PS
Overriding equals/hashcode is not an option for me.
I have written the below code but I am not sure how to identify if a type is complex (for example Address)
private static List<String> difference(Student s1, Student s2) throws IllegalArgumentException, IllegalAccessException {
for(Field field: Student.class.getDeclaredFields()) {
System.out.println(field.getName() + " " +field.get(s1).equals(field.get(s2)));
}
return null;
}
Upvotes: 14
Views: 32944
Reputation: 101
I would like to add
Awgtek's method is good but doesn't work with Enum field
I added this code before isBaseType(value1.getClass())
if (value1 instanceof Enum && value2 instanceof Enum ) {
if (!value1.equals(value2) {
changedProperties.add(parent + "." + field.getName());
continue;
}
}
I also advise adding LocalDate.class
in BASE_TYPES
And if you want fields from superclass compare too.
You need change s1.getClass().getDeclaredFields()
-> getAllFields(s1.getClass())
private static List<Field> getAllFields(Class currentClass) {
final List<Field> fields = new ArrayList<>(Arrays.asList(currentClass.getDeclaredFields()));
final Class superclass = currentClass.getSuperclass();
if (superclass != Object.class) {
fields.addAll(getAllFields(superclass));
}
return fields;
}
Upvotes: 2
Reputation: 1829
This is an enhancement of Dumbo's difference
method, using recursion to check all nested complex fields.
private static void difference(Object s1, Object s2, List<String> changedProperties, String parent) throws IllegalAccessException {
for (Field field : s1.getClass().getDeclaredFields()) {
if (parent == null) {
parent = s1.getClass().getSimpleName();
}
field.setAccessible(true);
Object value1 = field.get(s1);
Object value2 = field.get(s2);
if (value1 == null && value2 == null) {
continue;
}
if (value1 == null || value2 == null) {
changedProperties.add(parent + "." + field.getName());
} else {
if (isBaseType(value1.getClass())) {
if (!Objects.equals(value1, value2)) {
changedProperties.add(parent + "." + field.getName());
}
} else {
difference(value1, value2, changedProperties, parent + "." + field.getName());
}
}
}
}
private static final Set<Class> BASE_TYPES = new HashSet(Arrays.asList(
String.class, Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class));
public static boolean isBaseType(Class clazz) {
return BASE_TYPES.contains(clazz);
}
Sample usage (assumes the referenced model objects have getters/setters):
public static void main(String[] args) throws IllegalAccessException {
Student s1 = new Student();
s1.setName("Krishna");
s1.setAge(30);
Address address = new Address();
s1.setAddress(address);
s1.getAddress().setHno("2-2-22");
s1.getAddress().setStreet("somewhere");
s1.getAddress().setPin(123);
Student s2 = new Student();
s2.setName("Krishna");
s2.setAge(20);
address = new Address();
s2.setAddress(address);
s2.getAddress().setHno("1-1-11");
s2.getAddress().setStreet("nowhere");
s2.getAddress().setPin(123);
List<String> changedProperties = new ArrayList<>();
difference(s1, s2, changedProperties, null);
System.out.println("changedProperties = " + changedProperties);
// expected result
// Student.age
// Student.address.hno
// Student.address.street
}
Result:
changedProperties = [Student.address.hno, Student.address.street, Student.age]
Primitive adapting checking adapted from https://stackoverflow.com/a/711226/1527469
Upvotes: 12
Reputation: 1827
I think this is the method you searching for:
private static List<String> difference(Student s1, Student s2) throws IllegalAccessException {
List<String> changedProperties = new ArrayList<>();
for (Field field : s1.getClass().getDeclaredFields()) {
// You might want to set modifier to public first (if it is not public yet)
field.setAccessible(true);
Object value1 = field.get(s1);
Object value2 = field.get(s2);
if (value1 != null && value2 != null) {
System.out.println(field.getName() + "=" + value1);
System.out.println(field.getName() + "=" + value2);
if (!Objects.equals(value1, value2)) {
changedProperties.add(field.getName());
}
}
}
return changedProperties;
}
Upvotes: 14
Reputation: 7907
Although it's an old question, I would like to add an idea:-
It will give you the names of fields that have changed, the before and after values of those fields, etc.
I admit this is not the best way because it has overhead of marshalling and running XML comparison; and the need to add a couple of libraries. But it might be better than writing field by field comparison (specially deep comparison) and reflection for complex model objects.
Upvotes: 2
Reputation: 54148
1. A solution that match your requirement :
//Add this in Student Class
public static List<String> difference(Student s1, Student s2) {
List<String> res = new ArrayList<>();
if (!s1.name.equalsIgnoreCase(s2.name)) res.add("name");
if (s1.age != s2.age) res.add("age");
res.addAll(Address.difference(s1.address, s2.address));
return res;
}
//Add this in Address Class
public static List<String> difference(Address s1, Address s2) {
List<String> res = new ArrayList<>();
if (!s1.hno.equalsIgnoreCase(s2.hno)) res.add("adress.hno");
if (!s1.street.equalsIgnoreCase(s2.street)) res.add("adress.street");
if (s1.pin != s2.pin) res.add("pin");
return res;
}
And this will print : [age, adress.hno, adress.street]
correct but not easy of more attributs
List<String> list = Student.difference(s1, s2);
System.out.println(list);
2. A version using reflection would result in :
//Add this method in Test Class
private static List<String> difference(Student s1, Student s2) throws IllegalAccessException {
List<String> res = new ArrayList<>();
for (Field f : s1.getClass().getFields())
if (!f.get(s1).equals(f.get(s2)))
res.add(f.getName());
return res;
}
And this will print : [address, age]
not all correct but no change if more attributs
List<String> list = difference(s1, s2);
System.out.println(list);
3. Tips :
constructor
with all the attributs
, don't set them aftergetters
and setters
equals
method in bothclass Student {
private String name; private Address address; private int age;
public Student (String n, Address ad, int a){name = n; address = ad; age=a;}
}
class Address {
private String hno; private String street; private int pin;
public Address (String a, String s, int p){hno = a; street = s; pin=p;}
}
//To use in your main of Test (or elsewhere) like this
Student s1 = new Student("Krishna", new Address("2-2-22", "somewhere", 123), 30);
Student s2 = new Student("Krishna", new Address("1-1-11", "nowhere", 123), 20);
Upvotes: -1