Currently I have classes with several nested classes inside is, like:
public class Foo {
private Bar bar;
//getter & setter
public class Bar {
private Abc abc;
//getter & setter
public class Abc {
@RequiredParam
private String property;
//getter & setter
}
}
}
I am trying to get the value of the fields but I am having a hard time how to achieve this.
So far I have:
public static boolean isValid(Object paramClazz) throws Exception {
List<Class> classes = new ArrayList<>();
getClasses(classes, paramClazz.getClass());
for (Class clazz : classes) {
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(RequiredParam.class)) {
field.setAccessible(true);
//how to get the value? field.get(paramClazz) doesn't work
}
}
}
return true;
}
private static void getClasses(List<Class> classes, Class<?> clazz) {
if (clazz.getDeclaredClasses().length > 0) {
for (Class<?> c : clazz.getDeclaredClasses()) {
getClasses(classes, c);
}
}
classes.add(clazz);
}
My goal is to the able to check if the field annotated with @RequiredParam is not null, so I have the method isValid()
which will received an object and should be able to check all fields (even the ones inside nested classes) and see if any is missing.
The problem is when I try to call field.get()
and I don't know which object I am supposed to pass to this method. Passing the highest level object won't work, because I need somehow to pass only the Abc object to the method.
How can I get the correct object to pass to the field.get()
call, considering I can have more or less nested levels in my classes?
1 Answer 1
This is an example code that scans all fields of an object recursively until it finds values of all annotated fields:
public static Collection<Object> getAnnotatedValues(final Object root) throws ReflectiveOperationException {
return getAnnotatedValues(root, new HashSet<>());
}
private static Collection<Object> getAnnotatedValues(final Object root, final Set<Object> inspected)
throws ReflectiveOperationException {
final List<Object> annotatedValues = new ArrayList<>();
if (inspected.contains(root)) { // Prevents stack overflow.
return Collections.EMPTY_LIST;
}
inspected.add(root);
for (final Field field : gatherFields(root.getClass())) {
field.setAccessible(true);
final Object currentValue = field.get(root);
field.setAccessible(false);
if (field.isAnnotationPresent(RequiredParam.class)) {
// Found required value, search finished:
annotatedValues.add(currentValue);
if (currentValue != null) {
inspected.add(currentValue);
}
} else if (currentValue != null) {
// Searching for annotated fields in nested classes:
annotatedValues.addAll(getAnnotatedValues(currentValue, inspected));
}
}
return annotatedValues;
}
private static Iterable<Field> gatherFields(Class<?> fromClass) {
// Finds ALL fields, even the ones from super classes.
final List<Field> fields = new ArrayList<>();
while (fromClass != null) {
fields.addAll(Arrays.asList(fromClass.getDeclaredFields()));
fromClass = fromClass.getSuperclass();
}
return fields;
}
You might have implemented something similar, but had trouble getting to the last nested class. This is because Field
instance is just a description of a class and (unless the field is static) it needs an actual instance the be able to extract a value. From Field#get(Object)
method docs:
If the underlying field is a static field, the obj argument is ignored; it may be null.
Otherwise, the underlying field is an instance field. If the specified obj argument is null, the method throws a NullPointerException. If the specified object is not an instance of the class or interface declaring the underlying field, the method throws an IllegalArgumentException.
If you did not initiate fields in your class structure, you would have a hard time extracting a value - even if you found annotated fields, you would still need an instance of the class to extract them. For example, given these classes:
public class Foo {
private final Bar bar = new Bar();
public class Bar {
private final Abc abc = new Abc();
public class Abc {
@RequiredParam private final String property = "Result.";
}
}
@Retention(RetentionPolicy.RUNTIME)
public static @interface RequiredParam {}
}
...getAnnotatedValues(new Foo())
returns collection containing "Result."
. You can easily modify the methods to fit your needs (for example, return true
as soon as the first valid annotated field is found or simply return false if the collection is empty/contains nulls).
-
Amazing job. Way more elegant than what I was trying to do.dambros– dambros02/24/2016 22:23:43Commented Feb 24, 2016 at 22:23
-
I am trying to tweak this to return the fields themselves but not sure where to start. Any suggestions?Half_Duplex– Half_Duplex10/09/2017 20:35:02Commented Oct 9, 2017 at 20:35
-
I don't even remember writing it by now, but changing
annotatedValues.add(currentValue)
toannotatedValues.add(field)
would probably be a good start. You might need to store references to bothfield
androot
though, a small data class could be a good idea. If you post what you're trying to achieve and what you've already tried as a separate question, I'll try my best to find some time and answer.Czyzby– Czyzby10/10/2017 08:18:56Commented Oct 10, 2017 at 8:18
Foo.Bar.Abc
.