You "just" need to use recursive search, but there is a lot of things to worry about when scanning like that, as you don't know how deep and complicated this structure might be.
There are also decision to make about scanning java types like lists and maps, in future reflective access to internals of such classes might be restricted, so it is good idea to skip reflections when possible - and if field is of list/set/iterable type then just iterate over it using normal java code. But then you either need to check that given list is of simple well-known type like ArrayList or remember that your scanning code might have some side-effects, like if you would iterate over some resource that reads from some data stream.
Additional thing to remember about are cyclic references, as when scanning you might end up in one of objects you were already in, and introduce endless loop. To prevent this we can just add already scanned objects to Set or to IdentitySet - depending if you care about actual cyclic references or you also want to skip scanning same objects that are different instances.
And another thing - it might be good idea to implement some kind of filter, to stop scanning if you will end up in some java internal objects, like while scanning some kind of concurrent collection using reflections you might end up scanning some internal objects used inside implementation of it.
And after explaining few bigger issues I would want you to think if you really want such solution, maybe it is xyproblem and there is better solution that what you are actually trying too achieve.
So you will need to adjust this code to your needs and assumptions you can make, but you can use this as an example:
private static <T> void scanInstance(Object objectToScan, Class<T> lookingFor, Set<? super Object> scanned, Collection<? super T> results) {
if (objectToScan == null) {
return;
}
if (! scanned.add(objectToScan)) { // to prevent any endless scan loops
return;
}
// you might need some extra code if you want to correctly support scanning for primitive types
if (lookingFor.isInstance(objectToScan)) {
results.add(lookingFor.cast(objectToScan));
// either return or continue to scan of target object might contains references to other objects of this type
}
// we won't find anything intresting in Strings, and it is pretty popular type
if (objectToScan instanceof String) {
return;
}
// basic support for popular java types to prevent scanning too much of java internals in most common cases, but might cause
// side-effects in some cases
else if (objectToScan instanceof Iterable) {
((Iterable<?>) objectToScan).forEach(obj -> scanInstance(obj, lookingFor, scanned, results));
}
else if (objectToScan instanceof Map) {
((Map<?, ?>) objectToScan).forEach((key, value) -> {
scanInstance(key, lookingFor, scanned, results);
scanInstance(value, lookingFor, scanned, results);
});
}
// remember about arrays, if you want to support primitive types remember to use Array class instead.
else if (objectToScan instanceof Object[]) {
int length = Array.getLength(objectToScan);
for (int i = 0; i < length; i++) {
scanInstance(Array.get(objectToScan, i), lookingFor, scanned, results);
}
}
else if (objectToScan.getClass().isArray()) {
return; // primitive array
}
else {
Class<?> currentClass = objectToScan.getClass();
while (currentClass != Object.class) {
for (Field declaredField : currentClass.getDeclaredFields()) {
// skip static fields
if (Modifier.isStatic(declaredField.getModifiers())) {
continue;
}
// skip primitives, to prevent wrapping of "int" to "Integer" and then trying to scan its "value" field and loop endlessly.
if (declaredField.getType().isPrimitive()) {
return;
}
if (! declaredField.trySetAccessible()) {
// either throw error, skip, or use more black magic like Unsafe class to make field accessible anyways.
continue; // I will just skip it, it's probably some internal one.
}
try {
scanInstance(declaredField.get(objectToScan), lookingFor, scanned, results);
}
catch (IllegalAccessException ignored) {
continue;
}
}
currentClass = currentClass.getSuperclass();
}
}
}
It would be good idea to split this to few methods, but this is just proof of concept, you still should adjust this to your needs, but it works in your example case:
List<X> instances = new ArrayList<>(); // either use list or set or identity set, depending if you want to see duplicates of data/instances
scanInstance(myAClass, X.class, Collections.newSetFromMap(new IdentityHashMap<>()), instances);
System.out.println(instances);
And it will find 6 instances of X class. (including myAClass
itself)