Version 2

    Annotation driven equals and hashCode

    The following implementation of equals, hashcode and toString is using the concept of one or more business keys defined by annotations.The annotation @BusinessKey can be applied with an include/exclude filter on field or method level.

    Enumeration for include/exclude filter:

    public enum Method {
        ALL, NONE, EQUALS, HASH_CODE, TO_STRING
    }
    

     

    Business key annotation:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target( { ElementType.FIELD, ElementType.METHOD })
    public @interface BusinessKey {
        Method[] include() default Method.ALL;
        Method[] exclude() default Method.NONE;
    }
    

     

    Implementation of equals, hashCode and toString using cached reflection:

    import java.lang.reflect.AccessibleObject;
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.Hashtable;
    import java.util.List;
    import java.util.Map;
    
    import org.apache.commons.lang.builder.EqualsBuilder;
    import org.apache.commons.lang.builder.HashCodeBuilder;
    import org.apache.commons.lang.builder.ToStringBuilder;
    import org.apache.commons.lang.builder.ToStringStyle;
    
    public class BeanUtils {
        private static Map<String, List<AccessibleObject>> cache = 
              new Hashtable<String, List<AccessibleObject>>();
    
        private BeanUtils() {}
    
        public static boolean equals(Object obj1, Object obj2) {
            if (obj1 == obj2) {
                return true;
            }
    
            if (obj2 == null || obj2.getClass() != obj1.getClass()) {
                return false;
            }
    
            EqualsBuilder builder = new EqualsBuilder();
    
            for (AccessibleObject ao : getAccessibleObjects(obj1, 1)) {
                try {
                    if (ao instanceof Field) {
                        builder.append(((Field) ao).get(obj1), ((Field) ao).get(obj2));
                    } else {
                        builder.append(((java.lang.reflect.Method) ao).invoke(obj1, (Object[]) null), 
                            ((java.lang.reflect.Method) ao).invoke(obj2, (Object[]) null));
                    }
                } catch (Exception e) {}
            }
    
            return builder.isEquals();
        }
    
        public static int hashCode(Object obj) {
            HashCodeBuilder builder = new HashCodeBuilder();
    
            for (AccessibleObject ao : getAccessibleObjects(obj, 2)) {
                try {
                    if (ao instanceof Field) {
                        builder.append(((Field) ao).get(obj));
                    } else {
                        builder.append(((java.lang.reflect.Method) ao).invoke(obj, (Object[]) null));
                    }
                } catch (Exception e) {}
            }
    
            return builder.toHashCode();
        }
    
        public static String toString(Object obj) {
            ToStringBuilder builder = new ToStringBuilder(obj, ToStringStyle.SHORT_PREFIX_STYLE);
    
            for (AccessibleObject ao : getAccessibleObjects(obj, 4)) {
                try {
                    if (ao instanceof Field) {
                        builder.append(((Field) ao).getName(), ((Field) ao).get(obj));
                    } else {
                        builder.append(((java.lang.reflect.Method) ao).getName(), 
                            ((java.lang.reflect.Method) ao).invoke(obj, (Object[]) null));
                    }
                } catch (Exception e) {}
            }
    
            return builder.toString();
        }
    
        private static List<AccessibleObject> getAccessibleObjects(Object obj, int filter) {
            Class<?> clazz = obj.getClass();
    
            String name = clazz.getName() + filter;
    
            if (!cache.containsKey(name)) {
                List<AccessibleObject> aos = new ArrayList<AccessibleObject>();
    
                do {
                    Field[] fields = clazz.getDeclaredFields();
    
                    for (Field field : fields) {
                        BusinessKey bk = field.getAnnotation(BusinessKey.class);
                        if (bk != null && (filter(bk) & filter) == filter) {
                            field.setAccessible(true);
                            aos.add(field);
                        }
                    }
    
                    java.lang.reflect.Method[] methods = clazz.getDeclaredMethods();
    
                    for (java.lang.reflect.Method method : methods) {
                        BusinessKey bk = method.getAnnotation(BusinessKey.class);
                        if (bk != null && (filter(bk) & filter) == filter) {
                            method.setAccessible(true);
                            aos.add(method);
                        }
                    }
    
                    clazz = clazz.getSuperclass();
                } while (clazz != null);
    
                Collections.sort(aos, new AccessibleObjectComparator());
    
                cache.put(name, aos);
            }
    
            return cache.get(name);
        }
    
        private static int filter(BusinessKey bk) {
            int filter = 0;
    
            for (Method method : bk.include()) {
                switch (method) {
                case ALL:
                    filter = filter | 7;
                    break;
                case EQUALS:
                    filter = filter | 1;
                    break;
                case HASH_CODE:
                    filter = filter | 2;
                    break;
                case TO_STRING:
                    filter = filter | 4;
                    break;
                }
            }
    
            for (Method method : bk.exclude()) {
                switch (method) {
                case ALL:
                    filter -= filter & 7;
                    break;
                case EQUALS:
                    filter -= filter & 1;
                    break;
                case HASH_CODE:
                    filter -= filter & 2;
                    break;
                case TO_STRING:
                    filter -= filter & 4;
                    break;
                }
            }
    
            return filter;
        }
    
        private static class AccessibleObjectComparator implements Comparator<AccessibleObject> {
            public int compare(AccessibleObject o1, AccessibleObject o2) {
                boolean o1IsField = o1 instanceof Field;
                boolean o2IsField = o2 instanceof Field;
    
                if (!o1IsField && o2IsField) {
                    return 1;
                } else if (o1IsField && !o2IsField) {
                    return -1;
                }
    
                if (o1IsField) {
                    return ((Field) o1).getName().compareTo(((Field) o2).getName());
                } else {
                    return ((java.lang.reflect.Method) o1).getName()
                         .compareTo(((java.lang.reflect.Method) o2).getName());
                }
            }
        }
    }
    

     

    Example of usage in a JPA annotated bean:

    @Entity
    public class User {
        private Long id;
        private String username;
        private byte[] password;
        private Set<Role> roles = new TreeSet<Role>();
    
        protected User() {}
    
        public User(String username, String password) {
            this.username = username;
            setPassword(password);
        }
    
        @BusinessKey(include = Method.TO_STRING)
        @Id
        @GeneratedValue
        public Long getId() {
            return id;
        }
    
        protected void setId(Long id) {
            this.id = id;
        }
    
        @BusinessKey
        @Column(nullable = false, unique = true)
        public String getUsername() {
            return username;
        }
    
        public User setUsername(String username) {
            this.username = username;
            return this;
        }
    
        @BusinessKey
        @Column(length = 32, nullable = false)
        public byte[] getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            try {
                MessageDigest md = MessageDigest.getInstance("SHA-256");
                this.password = md.digest(password.getBytes());
            } catch (NoSuchAlgorithmException e) {}
        }
    
        @ManyToMany
        public Set<Role> getRoles() {
            return roles;
        }
    
        public void setRoles(Set<Role> roles) {
            this.roles = roles;
        }
        
        @Override
        public boolean equals(Object obj) {
            return BeanUtils.equals(this, obj);
        }
    
        @Override
        public int hashCode() {
            return BeanUtils.hashCode(this);
        }
    
        @Override
        public String toString() {
            return BeanUtils.toString(this);
        }    
    }
    

     

    If you don't mind to use a superclass in your beans the following superclass can be added as a convenience:

    public abstract class Bean {
        @Override
        public boolean equals(Object obj) {
            return BeanUtils.equals(this, obj);
        }
    
        @Override
        public int hashCode() {
            return BeanUtils.hashCode(this);
        }
    
        @Override
        public String toString() {
            return BeanUtils.toString(this);
        }
    }