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();
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 have this problem by this moment, I got a class with two methods with the same erasure:
public class VO implements Map<String,String> , Serializable{
public synchronized String put (Object key, Object value){
if (key == null)
return null;
if (value == null)
return remove(key);
String stringKey = key.toString();
String stringValue = value.toString();
if (value instanceof Boolean)
stringValue = ((Boolean)value) ? "Y" : "N";
return put(stringKey,stringValue);
}
#Override
public synchronized String put (String key, String value)
{
if (key == null)
return null;
if (value == null)
return remove(key);
//
int index = m_keys.indexOf(key);
if (index != -1)
return m_values.set(index, value);
m_keys.add(key);
m_values.add(value);
return null;
} // put
}
Both methods are used by this app a lot, I can't change the name of any and I can't delete put(Object key, Object value) method, any ideas how I can fix this?
#Aris_Kortex well firts of all, VO is used a LOT, to create the UImodel of this application and the constructor of VO is this:
public VO (Map<String,String> map)
{
this();
Iterator<String> it = map.keySet().iterator();
while (it.hasNext())
{
Object key = it.next().toString();
Object value = map.get(key);
put (key, value);
}
} // VO
/** Base SerVersion */
private static final long serialVersionUID = 8683452581122892189L;
/** Keys */
private ArrayList<String> m_keys;
/** Values */
private ArrayList<String> m_values;
/** Embedded VOs */
protected ArrayList<VO> p_vos = null;
then i am affraid i can't measure the impact of changing something here...i need to create a package of this proyect that i refactoriced because before was a mess..but maven throw me that error...
I want to have an Enum like this:
public enum Type {
STRING, INTEGER, BOOLEAN, LIST(Type);
Type t;
Type() { this.t = this; )
Type(Type t) { this.t = t; }
}
Such that I can enter various Types for LIST, like being able to call Type.LIST(STRING). Is this possible in Java?
enums are limited, you can't have an unknown amount of entries. So you can't have LIST(LIST(LIST(LIST(...))) as a separate Type enum. You'll need a class, but that doesn't mean you have to instantiate lots of objects necessarily:
It may be premature optimization, but you can use a flyweight pattern to ensure that you can't get more than one instance of a Type:
package com.example;
public final class Type {
public enum LeafType {
STRING,
INTEGER,
BOOLEAN
}
//Gives you the familiar enum syntax
public static final Type STRING = new Type(LeafType.STRING);
public static final Type INTEGER = new Type(LeafType.INTEGER);
public static final Type BOOLEAN = new Type(LeafType.BOOLEAN);
private final LeafType leafType;
private final Type listType;
private final Object lock = new Object();
// This is the cache that prevents creation of multiple instances
private Type listOfMeType;
private Type(LeafType leafType) {
if (leafType == null) throw new RuntimeException("X");
this.leafType = leafType;
listType = null;
}
private Type(Type type) {
leafType = null;
listType = type;
}
/**
* Get the type that represents a list of this type
*/
public Type list() {
synchronized (lock) {
if (listOfMeType == null) {
listOfMeType = new Type(this);
}
return listOfMeType;
}
}
public boolean isList() {
return listType != null;
}
/**
* If this type is a list, will return what type of list it is
*/
public Type getListType() {
if (!isList()) {
throw new RuntimeException("Not a list");
}
return listType;
}
/**
* If this type is a leaf, will return what type of leaf it is
*/
public LeafType getLeafType() {
if (isList()) {
throw new RuntimeException("Not a leaf");
}
return leafType;
}
#Override
public String toString() {
if (isList()) {
return "LIST(" + getListType() + ")";
}
return getLeafType().toString();
}
}
Usage:
Simple type:
Type string = Type.STRING;
List:
Type stringList = Type.STRING.list();
List of list:
Type stringListList = Type.STRING.list().list();
And you can never get in the situation where you have two instances of Type that describe the same type, e.g.:
Type t1 = Type.BOOLEAN.list().list().list();
Type t2 = Type.BOOLEAN.list().list().list();
System.out.println(t1 == t2 ? "Same instance" : "Not same instance");
I added toString for debugging:
Type listListListInt = Type.INTEGER.list().list().list();
System.out.println(listListListInt);
Gives:
LIST(LIST(LIST(INTEGER)))
Is there a way in Java to have a map where the type parameter of a value is tied to the type parameter of a key? What I want to write is something like the following:
public class Foo {
// This declaration won't compile - what should it be?
private static Map<Class<T>, T> defaultValues;
// These two methods are just fine
public static <T> void setDefaultValue(Class<T> clazz, T value) {
defaultValues.put(clazz, value);
}
public static <T> T getDefaultValue(Class<T> clazz) {
return defaultValues.get(clazz);
}
}
That is, I can store any default value against a Class object, provided the value's type matches that of the Class object. I don't see why this shouldn't be allowed since I can ensure when setting/getting values that the types are correct.
EDIT: Thanks to cletus for his answer. I don't actually need the type parameters on the map itself since I can ensure consistency in the methods which get/set values, even if it means using some slightly ugly casts.
You're not trying to implement Joshua Bloch's typesafe hetereogeneous container pattern are you? Basically:
public class Favorites {
private Map<Class<?>, Object> favorites =
new HashMap<Class<?>, Object>();
public <T> void setFavorite(Class<T> klass, T thing) {
favorites.put(klass, thing);
}
public <T> T getFavorite(Class<T> klass) {
return klass.cast(favorites.get(klass));
}
public static void main(String[] args) {
Favorites f = new Favorites();
f.setFavorite(String.class, "Java");
f.setFavorite(Integer.class, 0xcafebabe);
String s = f.getFavorite(String.class);
int i = f.getFavorite(Integer.class);
}
}
From Effective Java (2nd edition) and this presentation.
The question and the answers made me come up with this solution: Type-safe object map. Here is the code. Test case:
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class TypedMapTest {
private final static TypedMapKey<String> KEY1 = new TypedMapKey<String>( "key1" );
private final static TypedMapKey<List<String>> KEY2 = new TypedMapKey<List<String>>( "key2" );
#Test
public void testGet() throws Exception {
TypedMap map = new TypedMap();
map.set( KEY1, null );
assertNull( map.get( KEY1 ) );
String expected = "Hallo";
map.set( KEY1, expected );
String value = map.get( KEY1 );
assertEquals( expected, value );
map.set( KEY2, null );
assertNull( map.get( KEY2 ) );
List<String> list = new ArrayList<String> ();
map.set( KEY2, list );
List<String> valueList = map.get( KEY2 );
assertEquals( list, valueList );
}
}
This is the Key class. Note that the type T is never used in this class! It's purely for the purpose of type casting when reading the value out of the map. The field key only gives the key a name.
public class TypedMapKey<T> {
private String key;
public TypedMapKey( String key ) {
this.key = key;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( key == null ) ? 0 : key.hashCode() );
return result;
}
#Override
public boolean equals( Object obj ) {
if( this == obj ) {
return true;
}
if( obj == null ) {
return false;
}
if( getClass() != obj.getClass() ) {
return false;
}
TypedMapKey<?> other = (TypedMapKey<?>) obj;
if( key == null ) {
if( other.key != null ) {
return false;
}
} else if( !key.equals( other.key ) ) {
return false;
}
return true;
}
#Override
public String toString() {
return key;
}
}
TypedMap.java:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class TypedMap implements Map<Object, Object> {
private Map<Object, Object> delegate;
public TypedMap( Map<Object, Object> delegate ) {
this.delegate = delegate;
}
public TypedMap() {
this.delegate = new HashMap<Object, Object>();
}
#SuppressWarnings( "unchecked" )
public <T> T get( TypedMapKey<T> key ) {
return (T) delegate.get( key );
}
#SuppressWarnings( "unchecked" )
public <T> T remove( TypedMapKey<T> key ) {
return (T) delegate.remove( key );
}
public <T> void set( TypedMapKey<T> key, T value ) {
delegate.put( key, value );
}
// --- Only calls to delegates below
public void clear() {
delegate.clear();
}
public boolean containsKey( Object key ) {
return delegate.containsKey( key );
}
public boolean containsValue( Object value ) {
return delegate.containsValue( value );
}
public Set<java.util.Map.Entry<Object, Object>> entrySet() {
return delegate.entrySet();
}
public boolean equals( Object o ) {
return delegate.equals( o );
}
public Object get( Object key ) {
return delegate.get( key );
}
public int hashCode() {
return delegate.hashCode();
}
public boolean isEmpty() {
return delegate.isEmpty();
}
public Set<Object> keySet() {
return delegate.keySet();
}
public Object put( Object key, Object value ) {
return delegate.put( key, value );
}
public void putAll( Map<? extends Object, ? extends Object> m ) {
delegate.putAll( m );
}
public Object remove( Object key ) {
return delegate.remove( key );
}
public int size() {
return delegate.size();
}
public Collection<Object> values() {
return delegate.values();
}
}
No, you can't do it directly. You'll need to write a wrapper class around Map<Class, Object> to enforce that Object will be instanceof Class.
It's possible to create a class which stores a map of type safe key to a value, and cast when necessary. The cast in get method is safe, as after using new Key<CharSequence>(), it's not possible to safely cast it to Key<String> or Key<Object>, so the type system enforces the correct usage of a class.
The Key class needs to be final, as otherwise an user could override equals and cause type-unsafety if two elements with different types were to be equal. Alternatively, it's possible to override equals to be final if you want to use inheritance despite the issues with it.
public final class TypeMap {
private final Map<Key<?>, Object> m = new HashMap<>();
public <T> T get(Key<? extends T> key) {
// Safe, as it's not possible to safely change the Key generic type,
// hash map cannot be accessed by an user, and this class being final
// to prevent serialization attacks.
#SuppressWarnings("unchecked")
T value = (T) m.get(key);
return value;
}
public <T> void put(Key<? super T> key, T value) {
m.put(key, value);
}
public static final class Key<T> {
}
}
You can use below 2 classes, Map class: GenericMap, Map-Key class: GenericKey
For example:
// Create a key includine Type definition
public static final GenericKey<HttpServletRequest> REQUEST = new GenericKey<>(HttpServletRequest.class, "HttpRequestKey");
public void example(HttpServletRequest requestToSave)
{
GenericMap map = new GenericMap();
// Saving value
map.put(REQUEST, requestToSave);
// Getting value
HttpServletRequest request = map.get(REQUEST);
}
Advantages
It forces the user to put and get correct types by compilation error
It's doing casing for you inside
Generic Key helps to avoid write the class type each time you calling put(..) or get
No typo mistakes, like if key is 'String' type
GenericMap
public class GenericMap
{
private Map<String, Object> storageMap;
protected GenericMap()
{
storageMap = new HashMap<String, Object>();
}
public <T> T get(GenericKey<T> key)
{
Object value = storageMap.get(key.getKey());
if (value == null)
{
return null;
}
return key.getClassType().cast(value);
}
/**
* #param key GenericKey object with generic type - T (it can be any type)
* #param object value to put in the map, the type of 'object' mast be - T
*/
public <T> void put(GenericKey<T> key, T object)
{
T castedObject = key.getClassType().cast(object);
storageMap.put(key.getKey(), castedObject);
}
#Override
public String toString()
{
return storageMap.toString();
}
}
GenericKey
public class GenericKey<T>
{
private Class<T> classType;
private String key;
#SuppressWarnings("unused")
private GenericKey()
{
}
public GenericKey(Class<T> iClassType, String iKey)
{
this.classType = iClassType;
this.key = iKey;
}
public Class<T> getClassType()
{
return classType;
}
public String getKey()
{
return key;
}
#Override
public String toString()
{
return "[classType=" + classType + ", key=" + key + "]";
}
}
T as a type must be defined generically in the class instance. The following example works:
public class Test<T> {
private Map<Class<T>, T> defaultValues;
public void setDefaultValue(Class<T> clazz, T value) {
defaultValues.put(clazz, value);
}
public T getDefaultValue(Class<T> clazz) {
return defaultValues.get(clazz);
}
}
Alternatively, you can use Paul Tomblin's answer, and wrap the Map with your own object which will enforce this type of generics.
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()));
}
}