Java:反射、通用类型和未检查的转换

Java: Reflection, Generic Types, and Unchecked Casts

我的 class 将获得一个对象 class。然后我使用反射遍历 class 的声明字段并在每个字段上注册一个 ChangeListener Property base class.

原始的 'createChangeListener' 方法如下所示:

private void createChangeListener(Property property) {
    property.addListener(new ChangeListener() {
        @Override
        public void changed(ObservableValue observable, Object oldValue, Object newValue) {                        
            Foo.this.propertyChanged(observable);
        }
    });
}

但是,这产生了不需要的警告:

warning: [unchecked] unchecked call to addListener(ChangeListener<? super T>) as a member of the raw type ObservableValue
    property.addListener(new ChangeListener() {
        where T is a type-variable:
    T extends Object declared in interface ObservableValue

不要被劝阻,我为我的 Property 参数和 ChangeListener:

提供了一个通用类型
private void createChangeListener(Property<Object> property) {
    property.addListener(new ChangeListener<Object>() {
        @Override
        public void changed(ObservableValue observable, Object oldValue, Object newValue) {                        
            Foo.this.propertyChanged(observable);
        }
    });
}

...直到现在才被告知我只是将我的问题转移到反射源。下面的代码现在已修改为从其原始 Property w/o 转换为 Property<Object> 通用类型:

if (Property.class.isAssignableFrom(field.getType())) {
    createChangeListener((Property<Object>)(field.get(model)));
}

这个以前没有警告的代码现在正在产生偏头:

warning: [unchecked] unchecked cast
    createChangeListener((Property<Object>)(field.get(model)));
required: Property<Object>
found:    Object

问题:

将您的字段值转换为 Property<Object> 您明确表示 Property 通用参数是 Object,这可能是错误的,编译器无法帮助您。例如,该字段的真实类型是 Property<String> 您可以执行以下操作:

Property<Object> p = (Property<Object>)(field.get(model)); // compiler warning, runtime ok
p.setValue(new Object()); // runtime ok, but now you're probably stored incompatible value

// somewhere much later in completely different piece of code
String value = this.propertyField.getValue(); // sudden ClassCastException

因此编译器会警告您转换为 Property<Object> 您假设编译器无法证明并且您实际上可能具有不同的类型,这可能导致将对象置于不正确的状态,这可能会在某个地方破坏您的程序稍后。

在您的特定情况下,您不想说 "I know that generic argument type is an Object"。你想说的是"I don't care about generic argument type"。对于这种情况,有一个特殊的结构 <?>。您可以在这里使用它:

void createChangeListener(Property<?> property) {
    property.addListener(new ChangeListener<Object>() {
        @Override
        public void changed(ObservableValue<?> observable, 
                            Object oldValue, Object newValue) {                        
            Foo.this.propertyChanged(observable);
        }
    });
}

并且您可以在没有任何警告的情况下安全地转换为 Property<?>。但是现在您根本无法调用 setValue,但似乎您仍然不需要。

您需要提高泛型技能。

与其尝试通过 casts 来解决它,不如尝试通过自己编写 generic 代码来解决它:

private <T> void createChangeListener(Property<T> property) {
  property.addListener(new ChangeListener<T>() {
    @Override
    public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {                        
        Foo.this.propertyChanged(observable);
    }
  });
}

在这里,编译器可以帮助您进行类型检查。它知道 oldValue 和 newValue 将具有特定类型 T,并且可以检查您是否做出了错误的假设。

现在,由于 addListener(ChangeListener<? super T>) 也将接受 超类型 的侦听器(<? super T>!),以下也应该没问题:

private void createChangeListener(Property<?> property) {
  property.addListener(new ChangeListener<Object>() {
    @Override
    public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) {                        
        Foo.this.propertyChanged(observable);
    }
  });
}

编译器应该能够验证此代码类型安全

确保您了解 <?><Object><T><? super T><? extends T> 之间的区别,并尝试学习如何在你自己的代码。总是更喜欢 更广泛的 版本(extendssuper 可以改变一切!查看 Java 集合的示例)。

在上面的示例中,super 意味着您可以将 Object 侦听器附加到 Property<String>,因为它是 String 的超类型。