3

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?

asked Feb 24, 2016 at 17:51
8
  • If you could clarify your code, that would help a lot. Make sure to tell us exactly what you're trying to get. I don't see an object named field, or a method named get Commented Feb 24, 2016 at 17:54
  • stackoverflow.com/questions/17485297/… Commented Feb 24, 2016 at 17:56
  • The rule is the same as for top-level classes: You need an instance of the class whose (non-static) field value you're trying to read. Meaning, you need an existing instance of Foo.Bar.Abc. Commented Feb 24, 2016 at 17:57
  • @VGR the problem is how can I navigate through the top-level class instance and get the specific instance I need programmatically? Commented Feb 24, 2016 at 17:59
  • Unless you are planning to instantiate the inner class yourself, you don't need to. Presumably the instance was created elsewhere, just like any other Java object; and like any other Java object, the program can have any number of references to it. Commented Feb 24, 2016 at 18:01

1 Answer 1

5

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).

answered Feb 24, 2016 at 18:55
3
  • Amazing job. Way more elegant than what I was trying to do. Commented 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? Commented Oct 9, 2017 at 20:35
  • I don't even remember writing it by now, but changing annotatedValues.add(currentValue) to annotatedValues.add(field) would probably be a good start. You might need to store references to both field and root 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. Commented Oct 10, 2017 at 8:18

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.