繁体   English   中英

泛型和演员表

[英]Generics and cast

我尝试编写一个类,该类采用参数名称并可以返回给定对象的相应参数。 目前,我的班级是这样的:

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 ...
}

我们可以这样做来取回 long 值:

    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)”的输出

有人可以解释这是如何工作的吗?

谢谢

ClassCastException 是一个 RuntimeException,这意味着它只会在运行时抛出,而不是在编译时抛出。 由于您的价值参考Object value = param.get(obj); 是 Object 类型,因为所有类都扩展了 Object,所以应该允许您进行类型转换。 您的 getValue 方法接受任何 Object 作为参数,因此在编译时,任何类都将被接受,无论您声明的参数化类型如何。

如果你想要类型安全,你可以将方法的参数声明为<? extends T> <? 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");          
    }

您正在抑制编译器警告您正在执行未经检查的强制转换,因此编译器不会捕获它。 并且在运行时,类型信息不可用。

如果您希望您的代码实际抛出 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));

演员(T)在运行时不做任何事情。 它只会让编译器平静下来。

PrintStream.println方法有多个重载,其中之一接受Object参数。 因此,您的代码中不会执行任何强制转换。

在这些情况下,您会看到ClassCastException

  1. 将值存储在所需类型的变量中:

     final Point value = pvPoint.getValue(ec); // CCE System.out.println(value);
  2. ParamValue提供Class<T>实例以进行运行时检查:

     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

我发现了一个奇特的东西,以防有人进来。 如果我像这样扩展“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));

希望这有助于某人。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM