您“只是”需要使用递归搜索,但这样扫描时需要担心很多事情,因为您不知道这种结构可能有多深和复杂。
还有一些关于扫描 Java 类型(如列表和映射)的决定,将来对此类内部的反射访问可能会受到限制,因此最好在可能的情况下跳过反射 - 如果字段是列表/设置/可迭代类型然后只需使用普通的 java 代码对其进行迭代。但是,您要么需要检查给定列表是否为简单的知名类型,如 ArrayList,要么记住您的扫描代码可能有一些副作用,例如,如果您要遍历从某些数据流中读取的某些资源。
另外要记住的是循环引用,因为在扫描时您可能最终会遇到您已经在其中的一个对象,并引入无限循环。为了防止这种情况,我们可以将已扫描的对象添加到 Set 或 IdentitySet - 取决于您是否关心实际的循环引用,或者您还想跳过扫描不同实例的相同对象。
还有一件事 - 实现某种过滤器可能是个好主意,如果您最终会遇到一些 java 内部对象,则停止扫描,例如在使用反射扫描某种并发集合时,您可能最终会扫描内部使用的一些内部对象实现它。
在解释了几个更大的问题之后,我希望您考虑一下,如果您真的想要这样的解决方案,也许它是 xyproblem 并且您实际上正在尝试实现的更好的解决方案。
因此,您需要根据自己的需要和可以做出的假设调整此代码,但您可以以此为例:
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();
}
}
}
将其拆分为几个方法是个好主意,但这只是概念证明,您仍应根据需要对其进行调整,但它适用于您的示例案例:
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);
它会找到 6 个 X 类的实例。 (包括myAClass本身)