Using reflection on Java classes to access all field, methods, and so on:
Is there a standardized order of these elements (which is specified in some standard)?
Of course, I could check it empirically, but I need to know if it's always
the same.
EDIT:
I waited for the question: What I need the order for ;)
Long story short: I have JAXB-annotated classes, and want no
represent these classes visually. While the order of XML attributes
is neither relevant for the XML
standard, nor for JAXB, I want to have a certain order the XML attributes for the
visual representation.
For example: start comes after end. This hurts one's intuition.
According to the documentation:
getFields()
Returns an array containing Field objects reflecting all the accessible public fields of the class or interface represented by this Class object. The elements in the array returned are not sorted and are not in any particular order. This method returns an array of length 0 if the class or interface has no accessible public fields, or if it represents an array class, a primitive type, or void.
getMethods()
Returns an array containing Method objects reflecting all the public member methods of the class or interface represented by this Class object, including those declared by the class or interface and those inherited from superclasses and superinterfaces. Array classes return all the (public) member methods inherited from the Object class. The elements in the array returned are not sorted and are not in any particular order. This method returns an array of length 0 if this Class object represents a class or interface that has no public member methods, or if this Class object represents a primitive type or void.
A sample for my annotation based idea.
public class FiledOrder {
#Retention(RetentionPolicy.RUNTIME)
public #interface Order {
int value();
}
public class SomeClass {
#Order(value=2)
public int field1;
#Order(value=1)
public int field2;
// no annotation
public int field3;
#Order(value=1)
public void start() { }
#Order(value=2)
public void end() { }
}
/**
* #param args
*/
public static void main(String[] args) {
Field[] fields = SomeClass.class.getFields();
Arrays.sort(fields, new Comparator<Field>() {
#Override
public int compare(Field o1, Field o2) {
Order or1 = o1.getAnnotation(Order.class);
Order or2 = o2.getAnnotation(Order.class);
// nulls last
if (or1 != null && or2 != null) {
return or1.value() - or2.value();
} else
if (or1 != null && or2 == null) {
return -1;
} else
if (or1 == null && or2 != null) {
return 1;
}
return o1.getName().compareTo(o2.getName());
}
});
for (Field f : fields) {
System.out.println(f.getName());
}
Method[] methods = SomeClass.class.getMethods();
Arrays.sort(methods, new Comparator<Method>() {
#Override
public int compare(Method o1, Method o2) {
Order or1 = o1.getAnnotation(Order.class);
Order or2 = o2.getAnnotation(Order.class);
// nulls last
if (or1 != null && or2 != null) {
return or1.value() - or2.value();
} else
if (or1 != null && or2 == null) {
return -1;
} else
if (or1 == null && or2 != null) {
return 1;
}
return o1.getName().compareTo(o2.getName());
}
});
for (Method m : methods) {
System.out.println(m.getName());
}
}
}
Even though getFields() and getMethods() return results in no particular order, you can add the elements in the returned arrays to collections, and provide your own Comparator to sort them however you want.
In this example, I'm just sorting the fields and methods based on the alphabetical order of their names - but you could sort them based on declaring class, modifiers, return types, etc. by providing the required logic in the respective Comparator.
public void PrintClassData(Class c) {
Field[] fieldArray = c.getFields();
Method[] methodArray = c.getMethods();
SortedSet<Field> fields = new TreeSet<Field>(new FieldComparator());
fields.addAll(Arrays.asList(fieldArray));
SortedSet<Method> methods = new TreeSet<Method>(new MethodComparator());
methods.addAll(Arrays.asList(methodArray));
StringBuffer b = new StringBuffer("All About ");
b.append(c.getName());
b.append("\nFields:\n");
for(Field f : fields) {
b.append("\t");
b.append(Modifier.toString(f.getModifiers()));
b.append(" ");
b.append(f.getType());
b.append(" ");
b.append(f.getName());
b.append("\n");
}
b.append("\nMethods:\n");
for (Method m : methods) {
b.append("\t");
b.append(Modifier.toString(m.getModifiers()));
b.append(" ");
b.append(m.getReturnType());
b.append(" ");
b.append(m.getName());
b.append("( ");
for (Class param : m.getParameterTypes()) {
b.append(param.getName());
b.append(", ");
}
b.deleteCharAt(b.lastIndexOf(","));
b.append(")\n");
}
System.out.println(b.toString());
}
private static class FieldComparator implements Comparator<Field> {
public int compare(Field f1, Field f2) {
return (f1.getName().compareTo(f2.getName()));
}
}
private static class MethodComparator implements Comparator<Method> {
public int compare(Method m1, Method m2) {
return (m1.getName().compareTo(m2.getName()));
}
}
Related
Is there a nice way to iterate over object fields using reflection?
The main problem is in object can be another object therefore it's needed to iterate over another object's properties too.
For example I have AllInclusiveDetails object
public class AllInclusiveDetails {
#JsonProperty("renters_business")
private String rentersBusiness;
#Valid
#NotNull
#JsonProperty("main_property_owner_details")
private ShortCustomer mainPropertyOwnerDetails;
#Valid
#NotNull
#JsonProperty("main_tenant_details")
private ShortCustomer mainTenantDetails;
}
And ShortCustomer is
public class ShortCustomer {
#NotNull
#Positive
private Long id;
#NotEmpty
#JsonProperty("full_name")
private String fullName;
#JsonProperty("organization_number")
private String organizationNumber;
#PastOrPresent
private LocalDate birthdate;
}
I want to iterate over AllInclusiveDetails object fields using reflection and if there is another object in it , I want to iterate over that object fields too.
The main purpose is to track if value of the same field in two different object are equal or not and if not to save old value and the new one.
Does this satisfy your requirement:
for(Field field : AllInclusiveDetails.class.getDeclaredFields()) {
if(field.getType()== ShortCustomer.class) {
//Do your logic here
}
}
Here's a way to get all the fields of a class and a method to recursively use reflection to compare fields. Play around with main to test it, it will not work correctly for Objects that are logically equivalent but not the same in memory.
// Gathers all fields of this class, including those in superclasses, regardless of visibility
public static List<Field> getAllFields(Class<?> klass) {
List<Field> fields = new ArrayList<>();
for (Class<?> k = klass; k != null; k = k.getSuperclass()) {
fields.addAll(Arrays.asList(k.getDeclaredFields()));
}
return fields;
}
// Uses reflection and recursion to deep compare two objects.
// If the sub-fields and sub-arrays are not deeply equal this will return false.
// This will cause problems with data structures that may be logically equivalent
// but not have the same structure in memory, HashMaps and Sets come to mind
//
// Also might perform illegal reflective access which gets a warning from the JVM
// WARNING: Illegal reflective access ... to field java.util.LinkedList.size
// WARNING: Please consider reporting this to the maintainers ...
// WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
// WARNING: All illegal access operations will be denied in a future release
public static <T> boolean reflexiveEquals(T o1, T o2) {
return reflexiveEquals(o1, o2, new HashSet<>(), new HashSet<>());
}
private static <T> boolean reflexiveEquals(T o1, T o2, Set<Object> o1Refs, Set<Object> o2Refs) {
if (o1 == o2) {
// exact same object or both are null
return true;
}
if (o1 == null || o2 == null) {
// one is null but the other is not
System.err.println(o1 + " != " + o2);
return false;
}
Class<?> type = o1.getClass();
if (type != o2.getClass()) {
// not the exact same class therefore not equal
// you could treat this differently if you want
System.err.println(type + " != " + o2.getClass());
return false;
}
if (PRIMITIVE_WRAPPERS.contains(type)) {
// if it's a primitive wrapper then compare plainly
boolean result = Objects.equals(o1, o2);
if (!result) {
System.err.println("Objects.equals: " + o1 + " : " + o2);
}
return result;
}
// before descending, make sure there wont be an infinite loop
// if this object appeared in the reference chain before
// then it is currently being compared lower in the stack,
// return true to let it finish it's comparison
if (o1Refs.contains(o1) || o2Refs.contains(o2)) {
return true;
}
try {
// keep track of the objects that have been descended into
o1Refs.add(o1);
o2Refs.add(o2);
if (type.isArray()) {
// if its an array, compare all elements
try {
Object[] a1 = (Object[]) o1;
Object[] a2 = (Object[]) o2;
// only comparable field besides elements
if (a1.length != a2.length) {
System.err.println("Array length diff");
return false;
}
for (int i = 0; i < a1.length; i++) {
if (!reflexiveEquals(a1[i], a2[i], o1Refs, o2Refs)) {
return false;
}
}
return true;
} catch (Exception e) {
return false;
}
}
// otherwise its some other object so compare all fields
// moving up the super-classes as well
for (Class<?> k = type; k != null; k = k.getSuperclass()) {
for (Field f : k.getDeclaredFields()) {
try {
f.setAccessible(true);
if (!reflexiveEquals(f.get(o1), f.get(o2), o1Refs, o2Refs)) {
return false;
}
} catch (IllegalArgumentException | IllegalAccessException e) {
return false;
}
}
}
return true;
} finally {
// remove the references since their compare is complete
o1Refs.remove(o1);
o2Refs.remove(o2);
}
}
private static final Set<Class<?>> PRIMITIVE_WRAPPERS = getPrimitiveWrapperClasses();
private static final Set<Class<?>> getPrimitiveWrapperClasses() {
Set<Class<?>> set = new HashSet<>();
set.add(Boolean.class);
set.add(Character.class);
set.add(Byte.class);
set.add(Short.class);
set.add(Integer.class);
set.add(Long.class);
set.add(Float.class);
set.add(Double.class);
set.add(Void.class);
return set;
}
public static class AllInclusiveDetails {
private String rentersBusiness;
private ShortCustomer mainPropertyOwnerDetails;
private ShortCustomer mainTenantDetails;
private ShortCustomer[] arr;
private List<ShortCustomer> list;
}
public static class ShortCustomer {
private Long id;
private String fullName;
private String organizationNumber;
private LocalDate birthdate;
}
public static void main(String[] args) {
AllInclusiveDetails aids1 = new AllInclusiveDetails();
aids1.rentersBusiness = "Business";
aids1.mainTenantDetails = new ShortCustomer();
aids1.mainTenantDetails.id = 1L;
aids1.mainTenantDetails.fullName = "John Doe";
aids1.arr = new ShortCustomer[] {
aids1.mainTenantDetails,
aids1.mainPropertyOwnerDetails };
aids1.list = new LinkedList<>(Arrays.asList(aids1.arr));
AllInclusiveDetails aids2 = new AllInclusiveDetails();
aids2.rentersBusiness = "Business";
aids2.mainTenantDetails = new ShortCustomer();
aids2.mainTenantDetails.id = 1L;
aids2.mainTenantDetails.fullName = "John Doe";
aids2.arr = new ShortCustomer[] {
aids2.mainTenantDetails,
aids2.mainPropertyOwnerDetails };
aids2.list = new LinkedList<>(Arrays.asList(aids2.arr));
System.out.println(reflexiveEquals(aids1, aids2));
}
I am trying to override the mentioned methods for my HashSet:
Set<MyObject> myObjectSet = new HashSet<MyObject>();
MyObject:
public class MyObject implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int number;
Map<String,String> myMap;
public MyObject(String name, int number, Map<String,String> myMap) {
this.name = name;
this.number = number;
this.myMap = myMap;
}
[...]
}
How do I override the hashcode(), equals() and compareTo() method?
Currently I have the following:
public int hashCode () {
return id.hashCode();
}
// override the equals method.
public boolean equals(MyObject s) {
return id.equals(s.id);
}
// override compareTo
public int compareTo(MyObject s) {
return id.compareTo(s.id);
}
I read that comparing by id is not enough this is object is a persistent entity for the DB (see here).
The name and number aren't unique across all objects of this type.
So how should I override it?
Do I also need to compare the hashMap inside it?
I am confused. The only unique thing about the object is the the map myMap which gets populated later in the lifecycle.
How do I check for its equality?
Based on all the responses I have changed the methods to the following
#Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyComplexObj myComplexObj = (MyComplexObj) o;
return myMap != null ? myMap.equals(myComplexObj.myMap) : myComplexObj.myMap == null;
}
#Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
public int compareTo(MyComplexObj o) {
return myMap.compareTo(o.getMyMap()));
}
This fails at the compareTo method, "this method is undefined for the type Map
The basic question here is "How can you determine if two objects are equal to each other?"
This is a simple question for simple objects. However, it becomes increasingly difficult with even slightly more complex objects.
As stated in the original question:
The only unique thing about the object is the the map myMap which gets populated later in the lifecycle.
Given two instances of the type MyObject, the member variables myMap must be compared with each other. This map is of type Map<String, String>. A few questions immediately come to mind:
How do the keys & values define equality?
(does a key=value pair need to be compared as a unit?)
(or should only the values be compared to each other?)
How does the order of the keys in the map affect equality?
(should keys in the list be sorted, so that A-B-C is equivalent to B-C-A?)
(or does 1-2-3 mean something different than 3-2-1?)
Does upper/lower case make any different to the equality of the values?
Will these objects ever be stored in some kind of Java HashSet or Java TreeSet?
(do you need to store the same object several times in the same collection?)
(or should objects with equal hashcodes only be stored once?)
Will these objects ever require sorting as part of a list or Java Collection?
How should the comparison function arrange non-equal objects in a list?
(how should key order determine if an object will come earlier or later in a list?)
(how should values determine order, especially if several values are different?)
Answers to each of these questions will vary between applications. In order to keep this applicable to a general audience, the following assumptions are being made:
To maintain a deterministic comparison, keys will be sorted
Values will be considered to be case-sensitive
Keys and values are inseparable, and will be compared as a unit
The Map will be flattened into a single String, so results can be compared easily
The beauty of using equals(), hashCode(), and compareTo() is that once hashCode() is implemented properly, the other functions can be defined based on hashCode().
Considering all of that, we have the following implementation:
#Override
public boolean equals(final Object o)
{
if (o instanceof MyObject)
{
return (0 == this.compareTo(((MyObject) o)));
}
return false;
}
#Override
public int hashCode()
{
return getKeyValuePairs(this.myMap).hashCode();
}
// Return a negative integer, zero, or a positive integer
// if this object is less than, equal to, or greater than the other object
public int compareTo(final MyObject o)
{
return this.hashCode() - o.hashCode();
}
// The Map is flattened into a single String for comparison
private static String getKeyValuePairs(final Map<String, String> m)
{
final StringBuilder kvPairs = new StringBuilder();
final String kvSeparator = "=";
final String liSeparator = "^";
if (null != m)
{
final List<String> keys = new ArrayList<>(m.keySet());
Collections.sort(keys);
for (final String key : keys)
{
final String value = m.get(key);
kvPairs.append(liSeparator);
kvPairs.append(key);
kvPairs.append(kvSeparator);
kvPairs.append(null == value ? "" : value);
}
}
return 0 == kvPairs.length() ? "" : kvPairs.substring(liSeparator.length());
}
All the critical work is being done inside of hashCode(). For sorting, the compareTo() function only needs to return a negative/zero/positive number -- a simple hashCode() diff. And the equals() function only needs to return true/false -- a simple check that compareTo() equals zero.
For further reading, there is a famous dialogue by Lewis Carroll on the foundations of logic, which touches on the basic question of equality:
https://en.wikipedia.org/wiki/What_the_Tortoise_Said_to_Achilles
And, in regard to even simple grammatical constructs, there is a fine example of two "equal" sentences at the start of chapter 6, "Pig and Pepper", from Alice in Wonderland:
The Fish-Footman began by producing from under his arm a great letter, and this he handed over to the other, saying, in a solemn tone, "For the Duchess. An invitation from the Queen to play croquet." The Frog-Footman repeated, in the same solemn tone, "From the Queen. An invitation for the Duchess to play croquet." Then they both bowed low and their curls got entangled together.
compareTo() is relevant to sorting. It has no relevance to a HashSet or HashMap.
A properly working equals() and hashCode() are vital for members of hash-based collections. Read their specifications in the Javadoc for Object.
Possibly the definitive recommendations for implementing these are in Joshua Bloch's Effective Java. I recommend reading the relevant chapter -- it's easily Google-able. There's no point in trying to paraphrase it all here.
One thing that may have escaped your notice, is that your field myMap has a working equals() and hashCode() of its own, so you don't have to do anything special with it. If you can guarantee that none of the fields are null, a reasonable hashCode() would be (following Bloch's system):
public int hashCode() {
int result = 44; // arbitrarily chosen
result = 31 * result + (int) (id ^ (id >>> 32));
result = 31 * result + name.hashCode();
result = 31 * result + number;
result = 31 * result + myMap.hashCode();
return result;
}
(You'll need more code if any of these could be null)
Pretty much all IDEs will automatically generate both equals() and hashcode(), using all the fields in the class. They'll use something very similar to Bloch's recommendations. Hunt around the UI. You'll find it.
Another alternative is to use Apache ReflectionUtils, which allows you to simply use:
#Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
#Override
public boolean equals(final Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
This works out which fields to use at runtime, and applies Bloch's methods.
This is what intellij default option gives
import java.util.Map;
public class MyObject {
String name;
int number;
Map<String,String> myMap;
#Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyObject myObject = (MyObject) o;
if (number != myObject.number) return false;
if (name != null ? !name.equals(myObject.name) : myObject.name != null) return false;
return myMap != null ? myMap.equals(myObject.myMap) : myObject.myMap == null;
}
#Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + number;
result = 31 * result + (myMap != null ? myMap.hashCode() : 0);
return result;
}
}
But, since you said
The only unique thing about the object is the the map myMap which gets
populated later in the lifecycle.
I would just keep myMap and skip both name and number (But this begs the question, why would you include a redundant data- name and number in all the elements of your collection?)
Then it becomes
import java.util.Map;
public class MyObject {
String name;
int number;
Map<String,String> myMap;
#Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyObject myObject = (MyObject) o;
return myMap != null ? myMap.equals(myObject.myMap) : myObject.myMap == null;
}
#Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
}
Keep in mind that, there are other ways too for the equals and hashcode methods. For example, Here are the options that intelliJ gives for code generation
To Answer Further question about CompareTo
Unlike Equals and Hashcode, here is no contract exist between compareTo and any other behaviors. You don't really need to do anything with compareTo until you want to make use of it for say, sorting. To read more about CompareTo Why should a Java class implement comparable?
If you want to make myMap implements comparable, and any other methods that you want, create decorator that implement comparable interface and delegate all other methods to enclosing myMap instance.
public class ComparableMap implements Map<String, String>, Comparable<Map<String, String>> {
private final Map<String, String> map;
public ComparableMap(Map<String, String> map) {
this.map = map;
}
#Override
public int compareTo(Map<String, String> o) {
int result = 0;
//your implementation based on values on map on you consider one map bigger, less or as same as another
return result;
}
#Override
public boolean equals(Object obj) {
return map.equals(obj);
}
#Override
public int hashCode() {
return map.hashCode();
}
// map implementation methods
#Override
public int size() {
return map.size();
}
#Override
public boolean isEmpty() {
return map.isEmpty();
}
#Override
public boolean containsKey(Object key) {
return map.containsKey(key);
}
#Override
public boolean containsValue(Object value) {
return map.containsValue(value);
}
#Override
public String get(Object key) {
return map.get(key);
}
#Override
public String put(String key, String value) {
return map.put(key, value);
}
#Override
public String remove(Object key) {
return map.remove(key);
}
#Override
public void putAll(Map<? extends String, ? extends String> m) {
map.putAll(m);
}
#Override
public void clear() {
map.clear();
}
#Override
public Set<String> keySet() {
return map.keySet();
}
#Override
public Collection<String> values() {
return map.values();
}
#Override
public Set<Entry<String, String>> entrySet() {
return map.entrySet();
}
}
You may use this map in anywhere where you use myMap
public class MyObject implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int number;
ComparableMap myMap;
public MyObject(String name, int number, Map<String, String> myMap) {
this.name = name;
this.number = number;
this.myMap = new ComparablemyMap(myMap);
}
#Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyComplexObj myComplexObj = (MyComplexObj) o;
return myMap != null ? myMap.equals(myComplexObj.myMap) : myComplexObj.myMap == null;
}
#Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
public int compareTo(MyComplexObj o) {
return myMap.compareTo(o.getMyMap())); //now it works
}
}
I have a UserProfile class which contains user's data as shown below:
class UserProfile {
private String userId;
private String displayName;
private String loginId;
private String role;
private String orgId;
private String email;
private String contactNumber;
private Integer age;
private String address;
// few more fields ...
// getter and setter
}
I need to count non null fields to show how much percentage of the profile has been filled by the user. Also there are few fields which I do not want to consider in percentage calculation like: userId, loginId and displayName.
Simple way would be to use multiple If statements to get the non null field count but it would involve lot of boiler plate code and there is another class Organization for which I need to show completion percentage as well. So I created a utility function as show below:
public static <T, U> int getNotNullFieldCount(T t,
List<Function<? super T, ? extends U>> functionList) {
int count = 0;
for (Function<? super T, ? extends U> function : functionList) {
count += Optional.of(t).map(obj -> function.apply(t) != null ? 1 : 0).get();
}
return count;
}
And then I call this function as shown below:
List<Function<? super UserProfile, ? extends Object>> functionList = new ArrayList<>();
functionList.add(UserProfile::getAge);
functionList.add(UserProfile::getAddress);
functionList.add(UserProfile::getEmail);
functionList.add(UserProfile::getContactNumber);
System.out.println(getNotNullFieldCount(userProfile, functionList));
My question is, is this the best way I could count not null fields or I could improve it further. Please suggest.
You can simply a lot your code by creating a Stream over the given list of functions:
public static <T> long getNonNullFieldCount(T t, List<Function<? super T, ?>> functionList) {
return functionList.stream().map(f -> f.apply(t)).filter(Objects::nonNull).count();
}
This will return the count of non-null fields returned by each function. Each function is mapped to the result of applying it to the given object and null fields are filtered out with the predicate Objects::nonNull.
I wrote a utility class to get the total count of readable properties and the count of non null values in an object. The completion percentage can be calculated based on these.
It should work pretty well with inherited properties, nested properties, (multi-dimensional) iterables and maps.
I couldn't include the tests as well in here, because of the character limit, but here's the utility class:
import lombok.*;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class PropertyCountUtils {
/***
* See {#link #getReadablePropertyValueCount(Object, Set)}.
*/
public static PropertyValueCount getReadablePropertyValueCount(#NonNull Object object) {
return getReadablePropertyValueCount(object, null);
}
/**
* Counts the properties of the given object, including inherited and nested properties,
* returning the total property count and the count of properties with assigned values.
*
* <p>
* Properties with assigned values have a value meeting all conditions below:
* <ul>
* <li>different from null</li>
* <li>different from an empty iterable or an empty map</li>
* <li>different from an iterable containing only null values</li>
* <li>different from a map containing only null values.</li>
* </ul>
* For multidimensional Iterables and Maps, these conditions apply to each dimension.
* </p>
*
* #param object The object to inspect. It should not be null.
* #param ignoredProperties The properties to ignore or null.
* For nested properties, use dot as a separator: "property1.nestedProperty.nestedProperty2"
* #return A pair of `assignedValueCount` (properties with assigned value) and `totalCount` (total property count).
*/
public static PropertyValueCount getReadablePropertyValueCount(
#NonNull Object object, Set<String> ignoredProperties) {
PropertyValueCount countHolder = new PropertyValueCount();
processReadablePropertyValueCount(countHolder, object, ignoredProperties, null);
return countHolder;
}
/***
* #return true if the object had at least one non-null property value or no readable properties.
* <p>
* If the object is an instance of String, for example, it would have no readable nested properties.
* Also, if the object is an instance of some class for which all nested properties are ignored,
* the method would return true, since the object itself has a non-null value,
* but the caller decided to ignore all properties.
* </p>
*/
#SneakyThrows
private static boolean processReadablePropertyValueCount(
PropertyValueCount countHolder, #NonNull Object object, Set<String> ignoredProperties, String parentPath) {
boolean objectHasAssignedProperties = false;
boolean objectHasNoReadableProperties = true;
List<Field> fields = getAllDeclaredFields(object.getClass());
for (Field field : fields) {
String fieldPath = buildFieldPath(parentPath, field);
Method readMethod = getReadMethod(object.getClass(), ignoredProperties, field, fieldPath);
if (readMethod == null) {
continue;
}
countHolder.setTotalCount(countHolder.getTotalCount() + 1);
objectHasNoReadableProperties = false;
Object value = readMethod.invoke(object);
if (value == null || isCollectionWithoutAnyNonNullValue(value)) {
// no assigned value, so we'll just count the total available properties
int readablePropertyValueCount = getReadablePropertyCount(
readMethod.getGenericReturnType(), ignoredProperties, fieldPath);
countHolder.setTotalCount(countHolder.getTotalCount() + readablePropertyValueCount);
} else if (value instanceof Iterable<?> iterable) {
processPropertyValueCountInIterable(countHolder, ignoredProperties, fieldPath, iterable);
} else if (value instanceof Map<?, ?> map) {
processPropertyValueCountInIterable(countHolder, ignoredProperties, fieldPath, map.values());
} else {
countHolder.setAssignedValueCount(countHolder.getAssignedValueCount() + 1);
// process properties of nested object
processReadablePropertyValueCount(countHolder, value, ignoredProperties, fieldPath);
objectHasAssignedProperties = true;
}
}
return objectHasAssignedProperties || objectHasNoReadableProperties;
}
private static void processPropertyValueCountInIterable(
PropertyValueCount countHolder, Set<String> ignoredProperties, String fieldPath, Iterable<?> iterable) {
boolean iterableHasNonNullValues = false;
// process properties of each item in the iterable
for (Object value : iterable) {
if (value != null) {
// check if the current iterable item is also an iterable itself
Optional<Iterable<?>> nestedIterable = getProcessableCollection(value);
if (nestedIterable.isPresent()) {
processPropertyValueCountInIterable(countHolder, ignoredProperties, fieldPath, nestedIterable.get());
} else {
iterableHasNonNullValues = processReadablePropertyValueCount(
countHolder, value, ignoredProperties, fieldPath);
}
}
}
// consider the iterable as having an assigned value only if it contains at least one non-null value
if (iterableHasNonNullValues) {
countHolder.setAssignedValueCount(countHolder.getAssignedValueCount() + 1);
}
}
#SneakyThrows
private static int getReadablePropertyCount(
#NonNull Type inspectedType, Set<String> ignoredProperties, String parentPath) {
int totalReadablePropertyCount = 0;
Class<?> inspectedClass = getTargetClassFromGenericType(inspectedType);
List<Field> fields = getAllDeclaredFields(inspectedClass);
for (Field field : fields) {
String fieldPath = buildFieldPath(parentPath, field);
Method readMethod = getReadMethod(inspectedClass, ignoredProperties, field, fieldPath);
if (readMethod != null) {
totalReadablePropertyCount++;
Class<?> returnType = getTargetClassFromGenericType(readMethod.getGenericReturnType());
// process properties of nested class, avoiding infinite loops
if (!hasCircularTypeReference(inspectedClass, returnType)) {
int readablePropertyValueCount = getReadablePropertyCount(
returnType, ignoredProperties, fieldPath);
totalReadablePropertyCount += readablePropertyValueCount;
}
}
}
return totalReadablePropertyCount;
}
// In case the object being analyzed is of parameterized type,
// we want to count the properties in the class of the parameter, not of the container.
private static Class<?> getTargetClassFromGenericType(Type type) {
if (type instanceof ParameterizedType parameterizedType) {
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments.length > 0) {
// Inspect the last parameter type.
// For example, lists would only have one parameter type,
// but in the case of maps we would inspect the parameter representing the entry value, not the entry key.
Type inspectedTypeArgument = actualTypeArguments[actualTypeArguments.length - 1];
return inspectedTypeArgument instanceof ParameterizedType ?
getTargetClassFromGenericType(inspectedTypeArgument) :
(Class<?>) inspectedTypeArgument;
}
}
return type instanceof Class<?> ? (Class<?>) type : type.getClass();
}
private static List<Field> getAllDeclaredFields(#NonNull Class<?> inspectedClass) {
List<Field> fields = new ArrayList<>();
Collections.addAll(fields, inspectedClass.getDeclaredFields());
Class<?> superClass = inspectedClass.getSuperclass();
while (superClass != null) {
Collections.addAll(fields, superClass.getDeclaredFields());
superClass = superClass.getSuperclass();
}
return fields;
}
private static Method getReadMethod(#NonNull Class<?> inspectedClass, Set<String> ignoredProperties, Field field, String fieldPath) {
if (ignoredProperties != null && ignoredProperties.contains(fieldPath)) {
return null;
}
PropertyDescriptor propertyDescriptor;
try {
propertyDescriptor = new PropertyDescriptor(field.getName(), inspectedClass);
} catch (IntrospectionException e) {
// statement reached when the field doesn't have a getter
return null;
}
return propertyDescriptor.getReadMethod();
}
private static boolean hasCircularTypeReference(Class<?> propertyContainerClass, Class<?> propertyType) {
return propertyContainerClass.isAssignableFrom(propertyType);
}
private static String buildFieldPath(String parentPath, Field field) {
return parentPath == null ? field.getName() : parentPath + "." + field.getName();
}
private static boolean isCollectionWithoutAnyNonNullValue(Object value) {
Stream<?> stream = null;
if (value instanceof Iterable<?> iterable) {
stream = StreamSupport.stream(iterable.spliterator(), false);
} else if (value instanceof Map<?, ?> map) {
stream = map.values().stream();
}
return stream != null &&
stream.noneMatch(item -> item != null && !isCollectionWithoutAnyNonNullValue(item));
}
private static Optional<Iterable<?>> getProcessableCollection(Object value) {
if (value instanceof Iterable<?> iterable) {
return Optional.of(iterable);
} else if (value instanceof Map<?, ?> map) {
return Optional.of(map.values());
}
return Optional.empty();
}
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public static class PropertyValueCount {
private int assignedValueCount;
private int totalCount;
}
}
The completion percentage can be calculated like this:
PropertyCountUtils.PropertyValueCount propertyValueCount = getReadablePropertyValueCount(profile);
BigDecimal profileCompletionPercentage = BigDecimal.valueOf(propertyValueCount.getNonNullValueCount())
.multiply(BigDecimal.valueOf(100))
.divide(BigDecimal.valueOf(propertyValueCount.getTotalCount()), 2, RoundingMode.UP)
.stripTrailingZeros();
Is there any esthetic way of retrieving an enum value based on two other enum types? What I'd like to do is to get a state for two enum arguments like is following example:
public enum State{
ONE_STATE,
SECOND_STATE;
THIRD_STATE;
public static State getState(Direction dir, Type type) {
if (dir.equals(Direction.LEFT) && type.equals(Type.BIG)) {
return ONE_STATE;
}
else if (dir.equals(Direction.RIGHT) && type.equals(Type.SMALL)) {
return SECOND_STATE;
}
else if (dir.equals(Direction.UP) && type.equals(Type.SMALL)) {
return FIRST_STATE;
}
return THIRD_STATE;
}
}
Of course this will word but my intention is to make something clearer since number of such possibilities will grow in time.
Why not use a lookup table ? An array of rows - each row containing the 2 inputs and one output.
LEFT, BIG, ONE_STATE
RIGHT, SMALL, SECOND_STATE
etc. and provide a method to look up and return the default if a lookup fails.
Failing that, you could investigate double dispatch
Enums can have fields, you could try something like:
public enum State {
ONE_STATE(Direction.LEFT, Type.BIG),
...
Direction d;
Type t;
private State(Direction d, Type t) {
...
}
public static State getState(Direction d, Type t) {
for (State s : State.values()) {
if (s.d == d && s.t == t) {
return s;
}
}
return null;
}
}
How about putting the Direction and Type as members of the enum, something like this:
public enum State {
ONE_STATE(Direction.LEFT, Type.BIG),
SECOND_STATE(Direction.RIGHT, Type.SMALL);
THIRD_STATE(Direction.UP, Type.SMALL);
private Direction direction;
private Type type;
private State(Direction direction, Type type) {
this.direction = direction;
this.type = type;
}
public static State getState(Direction dir, Type type) {
for (State state : values()) {
if (state.direction == dir && state.type == type) {
return state;
}
}
return THIRD_STATE;
}
}
Note that this will not work if there is more than one different combination for each enum. In that case, you will need to use some sort of lookup table as suggested by another poster. For example, you could use a Map<Pair<Direction, Type>, State>. (Java doesn't have a Pair<T, U> class, but you can easily make one or find one in lots of different libraries.)
You could use switches instead of ifs but that won't make the code shorter. It will have the advantage of being clearer and also, depending on your IDE, you can have it gives you error when the switch is missing cases.
Have the enum with the most items be the outer switch and other the inner switch.
Adding fields to your State enum will work only if there's only one combination of Direction and Type that "reach" that State.
switch(dir) {
case LEFT:
{
switch(type) {
case BIG:
...
Use a nested EnummMap. The "Effective Java" book (Item 33) explains why this is the best solution.
// Using a nested EnumMap to associate data with enum pairs
public enum Phase {
SOLID, LIQUID, GAS;
public enum Transition {
MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID),
SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);
final Phase src;
final Phase dst;
Transition(Phase src, Phase dst) {
this.src = src;
this.dst = dst;
}
// Initialize the phase transition map
private static final Map<Phase, Map<Phase,Transition>> m =
new EnumMap<Phase, Map<Phase,Transition>>(Phase.class);
static {
for (Phase p : Phase.values())
m.put(p,new EnumMap<Phase,Transition>(Phase.class));
for (Transition trans : Transition.values())
m.get(trans.src).put(trans.dst, trans);
}
public static Transition from(Phase src, Phase dst) {
return m.get(src).get(dst);
}
}
}
You can encapsulate DIRECTION and TYPE in a concrete class(say EnumGroup) . And create a Hashmap that contains key as the object of EnumGroup and value as the value of enum State. In this way we could save the state value for multiple combination of DIRECTION and STATE value . We can also have two different EnumGroup having same state value. Here is the code Demo.
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
class EnumGroup
{
Direction direction;
Type type;
private EnumGroup(){}//To prevent parmeterless construction
public EnumGroup(Direction direction , Type type)
{
if (direction==null || type == null)
{
throw new IllegalStateException("null is not allowed");
}
this.direction = direction;
this.type = type;
}
#Override
public int hashCode()
{
return direction.toString().hashCode() + type.toString().hashCode() ;
}
#Override
public boolean equals(final Object other)
{
if (other == null || !(other instanceof EnumGroup))
{
return false;
}
if (this == other)
{
return true;
}
EnumGroup temp = (EnumGroup)other;
if (temp.type == this.type && temp.direction == this.direction)
{
return true;
}
return false;
}
}
enum Direction
{
LEFT,RIGHT,DOWN,UP,ACCROSS;
}
enum Type
{
BIG,SMALL,MEDIUM;
}
enum State
{
ONE_STATE,FIRST_STATE,SECOND_STATE,THIRD_STATE;
private static final Map<EnumGroup,State> map = new HashMap<EnumGroup,State>();
static
{
map.put(new EnumGroup(Direction.LEFT,Type.BIG),ONE_STATE);
map.put(new EnumGroup(Direction.RIGHT,Type.SMALL),SECOND_STATE);
map.put(new EnumGroup(Direction.UP,Type.SMALL),FIRST_STATE);
/*
.
.
.
*/
}
public static State getState(EnumGroup eGroup)
{
State state = map.get(eGroup);
return state == null ? THIRD_STATE : state;
}
}
public class EnumValueByArgument
{
public static void main(String st[])
{
ArrayList<EnumGroup> list = new ArrayList<EnumGroup>();
for (Direction direction : Direction.values())
{
for (Type type : Type.values() )
{
list.add(new EnumGroup(direction,type));
}
}
for (EnumGroup eGroup : list )
{
System.out.println(State.getState(eGroup));
}
}
}
This question already has answers here:
Using Comparable for multiple dynamic fields of VO in java
(7 answers)
Closed 8 years ago.
I have a List of Java objects that I want to sort according to more than one field.
public class graduationCeremony {
String campus;
String faculty;
String building;
}
Is it possible to use a Comparator or the Comparable interface to sort the list according to multiple fields? All the examples I have seen sort according to only one field. In other words, one can sort by 'campus' OR 'faculty' OR 'building'. I want to sort by 'campus', then 'faculty', then 'building' (as it exists in SQL: ORDER BY campus, faculty, building)
I think this question has been asked before, but I don't understand the accepted answer. Can someone expand or illustrate this answer?
Your Comparator would look like this:
public class GraduationCeremonyComparator implements Comparator<GraduationCeremony> {
public int compare(GraduationCeremony o1, GraduationCeremony o2) {
int value1 = o1.campus.compareTo(o2.campus);
if (value1 == 0) {
int value2 = o1.faculty.compareTo(o2.faculty);
if (value2 == 0) {
return o1.building.compareTo(o2.building);
} else {
return value2;
}
}
return value1;
}
}
Basically it continues comparing each successive attribute of your class whenever the compared attributes so far are equal (== 0).
Yes, you absolutely can do this. For example:
public class PersonComparator implements Comparator<Person>
{
public int compare(Person p1, Person p2)
{
// Assume no nulls, and simple ordinal comparisons
// First by campus - stop if this gives a result.
int campusResult = p1.getCampus().compareTo(p2.getCampus());
if (campusResult != 0)
{
return campusResult;
}
// Next by faculty
int facultyResult = p1.getFaculty().compareTo(p2.getFaculty());
if (facultyResult != 0)
{
return facultyResult;
}
// Finally by building
return p1.getBuilding().compareTo(p2.getBuilding());
}
}
Basically you're saying, "If I can tell which one comes first just by looking at the campus (before they come from different campuses, and the campus is the most important field) then I'll just return that result. Otherwise, I'll continue on to compare faculties. Again, stop if that's enough to tell them apart. Otherwise, (if the campus and faculty are the same for both people) just use the result of comparing them by building."
If you know in advance which fields to use to make the comparison, then other people gave right answers.
What you may be interested in is to sort your collection in case you don't know at compile-time which criteria to apply.
Imagine you have a program dealing with cities:
protected Set<City> cities;
(...)
Field temperatureField = City.class.getDeclaredField("temperature");
Field numberOfInhabitantsField = City.class.getDeclaredField("numberOfInhabitants");
Field rainfallField = City.class.getDeclaredField("rainfall");
program.showCitiesSortBy(temperatureField, numberOfInhabitantsField, rainfallField);
(...)
public void showCitiesSortBy(Field... fields) {
List<City> sortedCities = new ArrayList<City>(cities);
Collections.sort(sortedCities, new City.CityMultiComparator(fields));
for (City city : sortedCities) {
System.out.println(city.toString());
}
}
where you can replace hard-coded field names by field names deduced from a user request in your program.
In this example, City.CityMultiComparator<City> is a static nested class of class City implementing Comparator:
public static class CityMultiComparator implements Comparator<City> {
protected List<Field> fields;
public CityMultiComparator(Field... orderedFields) {
fields = new ArrayList<Field>();
for (Field field : orderedFields) {
fields.add(field);
}
}
#Override
public int compare(City cityA, City cityB) {
Integer score = 0;
Boolean continueComparison = true;
Iterator itFields = fields.iterator();
while (itFields.hasNext() && continueComparison) {
Field field = itFields.next();
Integer currentScore = 0;
if (field.getName().equalsIgnoreCase("temperature")) {
currentScore = cityA.getTemperature().compareTo(cityB.getTemperature());
} else if (field.getName().equalsIgnoreCase("numberOfInhabitants")) {
currentScore = cityA.getNumberOfInhabitants().compareTo(cityB.getNumberOfInhabitants());
} else if (field.getName().equalsIgnoreCase("rainfall")) {
currentScore = cityA.getRainfall().compareTo(cityB.getRainfall());
}
if (currentScore != 0) {
continueComparison = false;
}
score = currentScore;
}
return score;
}
}
You may want to add an extra layer of precision, to specify, for each field, whether sorting should be ascendant or descendant. I guess a solution is to replace Field objects by objects of a class you could call SortedField, containing a Field object, plus another field meaning ascendant or descendant.
Hope this Helps:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
class Person implements Comparable {
String firstName, lastName;
public Person(String f, String l) {
this.firstName = f;
this.lastName = l;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String toString() {
return "[ firstname=" + firstName + ",lastname=" + lastName + "]";
}
public int compareTo(Object obj) {
Person emp = (Person) obj;
int deptComp = firstName.compareTo(emp.getFirstName());
return ((deptComp == 0) ? lastName.compareTo(emp.getLastName()) : deptComp);
}
public boolean equals(Object obj) {
if (!(obj instanceof Person)) {
return false;
}
Person emp = (Person) obj;
return firstName.equals(emp.getFirstName()) && lastName.equals(emp.getLastName());
}
}
class PersonComparator implements Comparator<Person> {
public int compare(Person emp1, Person emp2) {
int nameComp = emp1.getLastName().compareTo(emp2.getLastName());
return ((nameComp == 0) ? emp1.getFirstName().compareTo(emp2.getFirstName()) : nameComp);
}
}
public class Main {
public static void main(String args[]) {
ArrayList<Person> names = new ArrayList<Person>();
names.add(new Person("E", "T"));
names.add(new Person("A", "G"));
names.add(new Person("B", "H"));
names.add(new Person("C", "J"));
Iterator iter1 = names.iterator();
while (iter1.hasNext()) {
System.out.println(iter1.next());
}
Collections.sort(names, new PersonComparator());
Iterator iter2 = names.iterator();
while (iter2.hasNext()) {
System.out.println(iter2.next());
}
}
}
You just need to have your class inherit from Comparable.
then implement the compareTo method the way you like.
You have to write your own compareTo() method that has the Java code needed to perform the comparison.
If we wanted for example to compare two public fields, campus, then faculty, we might do something like:
int compareTo(GraduationCeremony gc)
{
int c = this.campus.compareTo(gc.campus);
if( c != 0 )
{
//sort by campus if we can
return c;
}
else
{
//campus equal, so sort by faculty
return this.faculty.compareTo(gc.faculty);
}
}
This is simplified but hopefully gives you an idea. Consult the Comparable and Comparator docs for more info.