【问题标题】:Generics and cast泛型和演员表
【发布时间】:2016-06-08 08:59:25
【问题描述】:

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

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”声明为一个点,它仍然有效:

    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)”

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

谢谢

【问题讨论】:

  • 你指的是哪个Point类?
  • java.awt.Point ,但我不确定这是否重要。
  • 如果编译器知道强制转换何时有效,则无需强制转换。根据定义进行强制转换发生在运行时。

标签: java generics reflection


【解决方案1】:

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

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

【讨论】:

  • 谢谢。不幸的是,我不能将扩展用于我想做的事情。我想我需要一些解决方法来验证参数化类型,但是使用类型擦除会很痛苦......
  • 为什么不能使用extends?检查上面的代码,对你不起作用?
  • 我可以输入任何类型的对象。像 String、Long... 等... 我不能为每个都有一个超类。而且我无法预测这个类可以处理哪个对象。
  • 根据你所说的,这意味着你有一个非常通用的类,它应该处理任何类型的方法参数和任何返回的类型。您能做的最好的事情就是记录抛出了哪种异常,并让这个类的客户在他们认为合适的时候处理它们。我建议声明每个单独抛出的异常(但不是 RuntimeException 子类)并记录 ClassCastException 的可能性。因为它是一个 RuntimeException,所以你不应该将它添加到 throw 子句中。
【解决方案2】:

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

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

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

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

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

    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】:

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

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

    【讨论】:

      【解决方案4】:

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

      希望这对某人有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-07-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-15
        • 2017-02-17
        • 1970-01-01
        相关资源
        最近更新 更多