我尝试编写一个带有参数名称的类,并且可以返回给定对象的相应参数。目前,我的班级看起来像这样:
public class ParamValue<T> {
private String paramName;
@SuppressWarnings("unchecked")
public T getValue(Object obj) throws Exception {
Class<?> c = obj.getClass();
Field param = c.getDeclaredField(paramName);
boolean isAccessible = param.isAccessible();
param.setAccessible(true);
Object value = param.get(obj);
param.setAccessible(isAccessible);
return (T) value;
}
// get set ...
}
现在,假设我们有一个带有简单Long参数的对象:
public class ExampleClass {
private Long value;
// get set ...
}
我们可以这样做以取回长值:
ExampleClass ec = new ExampleClass();
ec.setValue(12L);
ParamValue<Long> pvString = new ParamValue<>();
pvString.setParamName("value");
// print 12
System.out.println(pvString.getValue(ec));
现在,如果我将“ParamValue”声明为Point,它仍然有效:
ExampleClass ec = new ExampleClass();
ec.setValue(12L);
ParamValue<Point> pvPoint = new ParamValue<>();
pvPoint.setParamName("value");
// print 12
System.out.println(pvPoint.getValue(ec));
但是,由于Point不能转换为Long,我预计会有一些异常,比如ClassCastException。
我知道java编译器在编译时会做一些类型的擦除,但我认为编译器会自动尝试转换为Point,并且失败,输出到“pvPoint.getValue(ec)”
有人可以解释这是如何工作的吗?
由于
答案 0 :(得分:2)
ClassCastException是一个RuntimeException,这意味着它只会在运行时而不是在编译时抛出。由于您的值引用Object value = param.get(obj);
是Object类型,因此所有类都扩展了Object,因此应该允许转换为类型。您的getValue方法接受任何Object作为参数,因此在编译时,无论您声明的参数化类型如何,都将接受任何类。
如果你想要类型安全,你可以将方法的参数声明为<? extends T>
,然后只允许T和扩展它的类进行方法调用。
您也可以像这样修改方法:
public <T> T getValue(Class<T> returnType, Object obj) throws Exception {
Class<?> c = obj.getClass();
Field param = c.getDeclaredField(paramName);
boolean isAccessible = param.isAccessible();
param.setAccessible(true);
Object value = param.get(obj);
param.setAccessible(isAccessible);
return returnType.cast(value);
}
在这种情况下,无需对您的班级进行参数化。您甚至可以将上述方法中的return语句修改为:
if (returnType.isInstance(value)) {
return returnType.cast(value);
} else {
// could be replaced with a return null
throw new ClassCastException("Exception message");
}
答案 1 :(得分:1)
您正在禁止编译器警告您正在进行未经检查的强制转换,因此编译器不会捕获它。并且在运行时,类型信息不可用。
如果您希望您的代码实际抛出ClassCastException,您可以添加一个存储实际目标类的字段。这也可以删除@SuppressWarnings
注释。结果类将如下所示:
class ParamValue<T> {
private final Class<T> clazz;
private final String paramName;
ParamValue(Class<T> clazz, String paramName)
{
this.clazz = clazz;
this.paramName = paramName;
}
public T getValue(Object obj) throws Exception {
Class<?> c = obj.getClass();
Field param = c.getDeclaredField(paramName);
boolean isAccessible = param.isAccessible();
param.setAccessible(true);
Object value = param.get(obj);
param.setAccessible(isAccessible);
return clazz.cast(value);
}
}
,可以像这样调用:
ExampleClass ec = new ExampleClass();
ec.setValue(12L);
ParamValue<Point> pvPoint = new ParamValue<>(Point.class, "value");
// print 12
System.out.println(pvPoint.getValue(ec));
答案 2 :(得分:1)
演员(T)
在运行时没有做任何事情。它只会让编译器平静下来。
方法PrintStream.println
有多个重载,其中一个重载接受Object
参数。因此,代码中不会执行强制转换。
在这些情况下,您会看到ClassCastException
:
将值存储在所需类型的变量中:
final Point value = pvPoint.getValue(ec); // CCE
System.out.println(value);
为Class<T>
提供ParamValue
实例以进行运行时检查:
public class ParamValue<T> {
private final Class<T> clazz;
public ParamValue(Class<T> clazz) {
this.clazz = clazz;
}
public T getValue(Object obj) throws Exception {
Class<?> c = obj.getClass();
Field param = c.getDeclaredField(paramName);
boolean isAccessible = param.isAccessible();
param.setAccessible(true);
Object value = param.get(obj);
param.setAccessible(isAccessible);
return clazz.cast(value); // instead of (T) value
}
// get set ...
}
然后:
ParamValue<Point> pvPoint = new ParamValue<>(Point.class);
pvPoint.setParamName("value");
System.out.println(pvPoint.getValue(ec)); // CCE
答案 3 :(得分:0)
我找到了一件奇特的东西,以防有人进来。 如果我像你那样扩展“ParamValue”类:
public class ParamPoint extends ParamValue<Point> {
}
类型擦除仅适用于部分,没有异常抛出,但仍然可以测试这样的类型:
ParamValue<Point> pvPoint = new ParamPoint();
ParameterizedType superclassType = (ParameterizedType) pvPoint.getClass().getGenericSuperclass();
Type paramType = superclassType.getActualTypeArguments()[0];
// print true
System.out.println(paramType.equals(Point.class));
希望这有助于某人。