【问题标题】:Finding the most specific class in Java在 Java 中查找最具体的类
【发布时间】:2017-07-23 15:37:39
【问题描述】:

我尝试为我的特定目的编写某种异常处理程序。 我有一个班级清单。比方说:

List<Class<? extends Throwable>> list = new LinkedList<>();
list.add(RuntimeException.class);
list.add(IllegalArgumentException.class);

现在,我想做:

public Class<? extends Throwable> findMostSpecific(Class<? extends Throwable> type) {
     for (....) {
         if (... WHAT TO PUT HERE ? .... ) {
             return ....
         }
     }
}

这个方法必须找到给定类型的最具体的类。所以如果我通过了:

  • IllegalArgumentException,它必须找到 IllegalArgumentException(不是 RTE)。
  • RuntimeException,它必须找到RuntimeException
  • IllegalStateException,它也必须找到RuntimeException(因为IllegalStateException不在列表中)
  • 如果我添加CustomException extends IllegalArgumentException并传递它,它必须返回IllegalArgumentException(不是RTE并且CustomException不在列表中,所以IllegalArgumentException是最具体的)

那么如何找到给定实例的最具体类型呢?有可能吗?

【问题讨论】:

  • 只使用代表类对象。通过type.class 访问它,然后您可以使用Class#getName 之类的方法和类似的东西。从那里您还可以检查Class#isInstance(Object o) 之类的内容,或者调用方法或使用构造函数创建实例等。这是Java-Doc of Class。请注意,此类对象将始终在 real 类上工作,而不是受限视图。因此,您会直接收到您正在搜索的内容。

标签: java reflection instanceof


【解决方案1】:

基于流的解决方案也可以包括两个简单的步骤:

  • 根据元素是否可从给定类中分配来过滤流
  • 计算剩余流的最大值

如下图:

import java.util.Arrays;
import java.util.List;

public class MostSpecificClassFinder
{
    public static void main(String[] args)
    {
        List<Class<?>> classes = Arrays.asList(
            RuntimeException.class, 
            IllegalArgumentException.class
        );

        System.out.println(findMostSpecific(classes, IllegalArgumentException.class));
        System.out.println(findMostSpecific(classes, RuntimeException.class));
        System.out.println(findMostSpecific(classes, IllegalStateException.class));
        System.out.println(findMostSpecific(classes, CustomException.class));
    }

    public static Class<?> findMostSpecific(List<Class<?>> classes, Class<?> type) {
        return classes.stream()
            .filter(c -> c.isAssignableFrom(type))
            .max((c0, c1) -> c0.isAssignableFrom(c1) ? -1 : c1.isAssignableFrom(c0) ? 1 : 0)
            .get();
    }

}

class CustomException extends IllegalArgumentException 
{

}

【讨论】:

  • 非常好,但你认为它比前面的例子更好吗?只是问:)
  • @Mariusz.v7 诚然,我发现当前基于流的解决方案......“有点奇怪”,至少可以说:我看不出有理由通过他们的 @ 来比较这些类987654322@。除此之外,以再次对流进行操作的递归调用结束流看起来很奇怪......
【解决方案2】:

在这里试一试:

public Class<? extends Throwable> findMostSpecific(Class<? extends Throwable> type) {
     // we'll keep a reference to the most specific one here
     Class<? extends Throwable> mostSpecific = null;
     // here we iterate over your list of types
     for (Class<? extends Throwable> tType : list) {
         // well not even a subtype of tType so ignore it
         if (!tType.isAssignableFrom(type)) {
             continue;
         }

         if (mostSpecific == null || mostSpecific.isAssignableFrom(tType)) {
             mostSpecific = tType;
         }
     }
     return mostSpecific;
}

【讨论】:

  • 我在原始问题中又增加了一项要求。我得检查一下它是否能在这种情况下工作
  • 你的意思是你的自定义类?应该仍然有效,因为您的自定义类不在列表中,并且 IllegalArgumentExceptionRuntimeException 更具体。
  • 它有效。第二个答案也有效,但因为你是第一个回答我会接受你的人
【解决方案3】:

在这种情况下,您可以使用Class.getCanonicalName() 在运行时检测当前类。考虑以下使用 Java Stream API 来找到最佳候选者的示例测试用例:

import org.junit.Test;

import java.util.LinkedList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

public class ReflectionTest {

    @Test
    public void test() {

        assertThat(findMostSpecific(IllegalArgumentException.class)).isEqualTo(IllegalArgumentException.class);

        assertThat(findMostSpecific(RuntimeException.class)).isEqualTo(RuntimeException.class);

        assertThat(findMostSpecific(IllegalStateException.class)).isEqualTo(RuntimeException.class);

        assertThat(findMostSpecific(IllegalStateException.class)).isEqualTo(RuntimeException.class);

        assertThat(findMostSpecific(CustomException.class)).isEqualTo(IllegalArgumentException.class);
    }

    public Class<? extends Throwable> findMostSpecific(Class<? extends Throwable> type) {
        List<Class<? extends Throwable>> list = new LinkedList<>();
        list.add(RuntimeException.class);
        list.add(IllegalArgumentException.class);

        return list.stream()
                .peek(e -> System.out.println("e.getClass() == " + e.getClass()))
                .filter(e -> type.getCanonicalName().equals(e.getCanonicalName()))
                .findAny()
                .orElseGet(() -> findMostSpecific((Class<? extends Throwable>) type.getSuperclass()));
    }

    public static class CustomException extends IllegalArgumentException {}
}

我特意添加了.peek(e -&gt; System.out.println("e.getClass() == " + e.getClass())),这样你就可以看到我们在这里获得了编译时信息:

e.getClass() == class java.lang.Class
e.getClass() == class java.lang.Class
e.getClass() == class java.lang.Class

Class.getCanonicalName() 从另一方面返回运行时类的规范名称。 .orElseGet() 使用父类名称检查最特定的类,因此对于 IllegalStateException.class,您将按预期获得 RuntimeException.class。当然,这只是一个可以改进和改进的示例代码(例如,不应为每个方法调用实例化带有类的列表)。希望对你有帮助。

【讨论】:

  • 不错的尝试,我在原始问题中添加了一项要求,我认为在这种情况下它不会起作用。我一会儿试试。
  • 它将完全按照您的预期工作 - 如果找不到类,则为父类调用 findMostSpecific。所以CustomException 将返回IllegalArgumentException 而不是RuntimeException。我已经更新了代码示例。
  • 两个答案都有效。可惜我不能同时接受他们两个。迈克尔罗斯更快,所以我会接受他的回答。并为您 +1。谢谢!
  • 有什么特别的理由来比较getCanonicalName而不是直接比较类实例吗?
  • 比较规范名称并不是在运行时比较类的好方法。有些类甚至没有规范名称(匿名、本地),getCanonicalName() 返回null,甚至可以由具有相同名称的不同类加载器加载两个类。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-26
  • 1970-01-01
  • 2017-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多