Java 泛型:当变量为 null 时检查类型

问题描述 投票:0回答:1

我在处理泛型类型的可能值时遇到问题。我想要一个 TimeTreeNodeDTO 应该能够处理两种类型的 TimeTree。具有 int 值的时间树和具有 double 值的时间树。为此,我添加了一个通用 T(扩展 Number 以毫无问题地处理 Inger 和 Double)。

public class TimeTreeNodeDTO<T extends Number>
{

    // ....

    private T value;

    private List<TimeTreeNodeDTO<T>> children;

    public TimeTreeNodeDTO(PeriodType periodType, List<TimeTreeNodeDTO<T>> children) {
        this.periodType = periodType;
        this.value = calculateValue(children);
        this.children = children;

    }

    private T calculateValue(List<TimeTreeNodeDTO<T>> children) {
        if(children == null || children.isEmpty())
        {
            return null;
        }

        if (value instanceof Integer) {
            int sum = children.stream()
                    .mapToInt(node -> node.getValue().intValue())
                    .sum();
            return (T) Integer.valueOf(sum);
        } else if (value instanceof Double) {
            double sum = children.stream()
                    .mapToDouble(node -> node.getValue().doubleValue())
                    .sum();
            return (T) Double.valueOf(sum);
        } else {
            throw new IllegalArgumentException("Unsupported type for value : " + value.getClass().getName());
        }
    }

    // ....

}

到目前为止我认为很好(也许可以有更好的解决方案)。当我尝试实例化这种情况时

TimeTreeNodeDTO<Integer> wn1 = new TimeTreeNodeDTO<>(PeriodType.WEEK, List.of(
                new TimeTreeNodeDTO<>(PeriodType.DAY,1),
                new TimeTreeNodeDTO<>(PeriodType.DAY,2),
                new TimeTreeNodeDTO<>(PeriodType.DAY,3),
                new TimeTreeNodeDTO<>(PeriodType.DAY,4),
                new TimeTreeNodeDTO<>(PeriodType.DAY,5),
                new TimeTreeNodeDTO<>(PeriodType.DAY,6),
                new TimeTreeNodeDTO<>(PeriodType.DAY,7)));
java.lang.NullPointerException
    at artelys.crystal.pdm.opus.timetreelib.dto.TimeTreeNodeDTO.calculateValue(TimeTreeNodeDTO.java:65)
    at artelys.crystal.pdm.opus.timetreelib.dto.TimeTreeNodeDTO.<init>(TimeTreeNodeDTO.java:33)
    at artelys.crystal.pdm.opus.timetreelib.TestTimeTreeDTO.setUp(TestTimeTreeDTO.java:29)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptLifecycleMethod(TimeoutExtension.java:126)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptBeforeEachMethod(TimeoutExtension.java:76)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeMethodInExtensionContext(ClassBasedTestDescriptor.java:506)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$synthesizeBeforeEachMethodAdapter$21(ClassBasedTestDescriptor.java:491)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeEachMethods$3(TestMethodTestDescriptor.java:171)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeMethodsOrCallbacksUntilExceptionOccurs$6(TestMethodTestDescriptor.java:199)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(TestMethodTestDescriptor.java:199)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeEachMethods(TestMethodTestDescriptor.java:168)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
    at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
    at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)

在 TimeTreeNodeDTO 类中运行此行时,我基本上遇到了 NullPointerException

throw new IllegalArgumentException("Unsupported type for value : " + value.getClass().getName());

发生这种情况是因为 value 为空。我知道 is null,我想在这个方法中计算该值,但因为不是 Integer 或 Double,所以它直接进入 else 语句。有没有办法检查 T 值的类型,即使该值为 null?

我尝试将 T 值“初始化”为 0,但我不能,因为它是泛型。我不知道我还能做什么。

我不知道也许这是一种不好的方法,可以通过其他方式解决。

希望你能帮助我:)

java spring generics
1个回答
0
投票

这是完全不可能的;泛型是编译器想象出来的。它的存在是为了将事物结合在一起。例如,给出以下代码:

public static Object nullCheck(Object o) {
  if (o == null) throw new NullPointerException();
  return o;
}

// to use:

class Example {
  private String name;

  public void setName(String name) {
    this.name = nullCheck(name);
  }
}

这当然很好。然而,这不会编译 - 而 code 保证你的

nullCheck
方法要么返回你给它的相同的东西(所以,那个“东西”与你给它的类型相同),要么抛出,那就是只是在代码中。从设计上来说,Java 假设代码不是一次性编写就不再更改的。 signatures(名称、参数类型、返回类型等)是您不应该轻易更改的内容(然后需要重新编译各种代码),但您可以很好地更改主体。

这就是为什么泛型存在的原因,是为了链接事物。这确实有效:

public static <T> T nullCheck(T o) {
  if (o == null) throw new NullPointerException();
  return o;
}

作为类文件,上面的内容与早期版本“完全相同”,除了类文件知道泛型,但 JVM (java.exe) 实际上不知道泛型是什么。这就像对它的评论。

相反,这里发生的事情完全是编译时的事情:编译器在这里做了两件事:

它知道两个 T 是链接在一起的:传递给
    nullCheck
  • 的参数类型和返回类型是相同的类型。
    因此它知道它可以默默地注入一个强制转换;因此 
  • this.name = nullCheck(name)
  • 可以工作,因为编译器会像你写的那样编译
    this.name = (String) nullCheck(name)
    编译器知道需要强制执行此“保证”(arg 和返回类型相同);如果您尝试编写 
  • nullCheck
  • 方法的实现,而编译器无法保证这一点,它将拒绝编译它。
    
    
  • 但这就是结束了。

那些类型参数没有具体化。

new ArrayList<String>()

new ArrayList<Integer>()
的执行是
100% 相同
- 该 arraylist 不知道泛型是什么。 你的错误

您在

value

方法中使用

calculateValue(List<..> children)
。你不应该这样做;你应该使用
child.value
(其中 child 是该列表中的某个项目)。
解决方案

这里你真的不想要一个非常丑陋的解决方案,那就是你将

Class<T>

作为这个东西的构造函数的一部分传递,因此现在你知道了。这真的很难看 -

j.l.Class
和泛型并不完全相同:基元可以表示为
j.l.Class
对象(
Class<?> x = int.class;
工作正常),但泛型不能(
List<int> foo;
是编译器错误)。泛型可以嵌套(
List<Set<String>>
很好),但这在
j.l.Class
实例中不起作用 - 这些实例不能携带泛型,所以你不能比
Set.class
更进一步 - 你不能存储那)。即使
here
你认为你不会遇到它,必须显式传递类型意味着你的调用者必须重复自己,这是令人讨厌的。不要这样做。 更好的解决办法就是不举报。您需要在代码中的大 if 子句批处理上使用另一个

else

:如果子项的

value
不是 Integer,也不是 Double,但也不是
null
,您可以提及类型。如果是
null
,那么这不是“我不支持该类型”的问题 - 不,问题是:“不知何故,您设法创建了一个值为
null
的节点,但根本不支持该节点”。
    

© www.soinside.com 2019 - 2024. All rights reserved.