【问题标题】:Consuming different objects than defined from a generic collection使用不同于泛型集合定义的对象
【发布时间】:2021-06-30 13:41:59
【问题描述】:

我昨天在回答Applying functions of various types to a value 的问题时发现了一些很奇怪的东西。

我可以将集合定义为O 类型,即使它的实际类型是T。 这可能是因为我使用的是原始类型。

然而,最令人惊讶的部分是我能够从这个List<FileInputStream> 集合中消费,尽管很明显它是List<Integer>

下面是有问题的代码

public static void main(String[] args) {
    String value = "a string with numb3r5";
    Function<String, List<String>> fn1 = List::of;
    Function<List<String>, String> fn2 = x -> x.get(0);
    Function<String, List<Integer>> fn3 = x -> List.of(x.length());

    InputConverter<String> converter = new InputConverter<>(value);
    List<FileInputStream> ints = converter.convertBy(fn1, fn2, fn3);

    System.out.println("ints = " + ints);
    System.out.println("ints.get(0) = " + ints.get(0));
    System.out.println("ints.get(0).getClass() = " + ints.get(0).getClass());
}

public static class InputConverter<T> {
    private final T src;

    public InputConverter(T src) {
        this.src = src;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    public <R> R convertBy(Function... functions) {
        Function functionsChain = Function.identity();

        for (Function function : functions) {
            functionsChain = functionsChain.andThen(function);
        }

        return (R) functionsChain.apply(src);
    }
}

这是结果

ints = [21]
ints.get(0) = 21
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.io.FileInputStream (java.lang.Integer and java.io.FileInputStream are in module java.base of loader 'bootstrap')
    at Scratch.main(scratch_8.java:18)

为什么可以从这个集合中消费?

我注意到以下内容在使用时不会引发异常

System.out.println("String.valueOf(ints.get(0)) = " + String.valueOf(ints.get(0)));
System.out.println("((Object) ints.get(0)).toString() = " + ((Object) ints.get(0)).toString());

但是下面的情况

System.out.println("ints.get(0).toString() = " + ints.get(0).toString());

【问题讨论】:

  • 你的问题到底是什么?
  • @Turing85 我刚刚编辑它以在末尾添加一个问题。
  • "为什么可以从这个集合中消费*" 因为 + 是为 (String, Object) 对定义的(即列表元素的确切类型确实没关系)。但是,如果我们在假定为FileInputStream 的情况下调用列表元素上的某个方法,则它的类型必须由类型转换来保证。
  • 好的,但是当我尝试ints.get(0).toString() 时,我得到一个ClassCastException,即使它是来自Object 的方法
  • ... 因为您访问列表元素 作为实际 FileInputStream (这是列表的泛型类型断言您,请记住:调用的方法由参数的 static 类型确定)并且断言必须由类型转换强制执行。这就是为什么如果我们将 System.out.println("ints.get(0).getClass() = " + ints.get(0).getClass()); 替换为 Object o = ints.get(0); System.out.println("ints.get(0).getClass() = " + o.getClass()); (Ideone demo),原始程序可以工作的原因。

标签: java generics type-inference


【解决方案1】:

InputConverter#convertBy 使用原始类型 Function 声明作为其输入,其返回类型使用泛型类型推断 &lt;R&gt; 因此它将解析为分配的类型,在您的情况下它将是 List&lt;FileInputStream&gt;

话虽如此,您可以将转换后的类型,即InputConverter#convertBy 的结果分配给您想要的任何类型:List&lt;FileInputStream&gt;List&lt;HashSet&lt;FileInputStream&gt;&gt;List&lt;Charset&gt;...只要它与运行时兼容转换类型的结果(最后一个函数结果),您仍然不会收到任何错误,直到在运行时,JVM 将对集合擦除类型执行 implicit cast 在您的情况下为 FileInputStream 和会因为实际类型为Integer 而失败。

以下语句不会失败,因为您将集合元素向上转换为Object 超类型并从java.lang.Object 类型调用#toString 方法(您在访问任何成员之前转换变量/方法):

System.out.println("((Object) ints.get(0)).toString() = " + ((Object) ints.get(0)).toString());

下面的语句实际上与上一个相似:

System.out.println("String.valueOf(ints.get(0)) = " + String.valueOf(ints.get(0)));

给定Object#valueOf方法实现如下:

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

另一方面,下面的语句将失败,因为在访问 #toString 成员之前,ints.get(0) 将被强制转换为已擦除的运行时类型 FileInputStream

System.out.println("ints.get(0).toString() = " + ints.get(0).toString());

你可以这样想:

System.out.println("ints.get(0).toString() = " + ((FileInputStream) ints.get(0)).toString());

【讨论】:

    【解决方案2】:

    泛型不是 java 类的二进制表示的一部分。泛型是通过强制转换实现的。

    “在幕后,编译器替换了 Crate 中对 T 的所有引用 与对象。换句话说,在代码编译之后,你的泛型 实际上只是对象类型。 ” 摘自:珍妮·博亚尔斯基。 “OCP Oracle Certified Professional Java SE 11 Developer Complete Study 指导”。苹果图书。

    这意味着编译器将确保 convertBy 返回 List,但无法确认所有类型。但编译器将替换: ints.get(0).toString()((FileInputStream) ints.get(0)).toString()) 以备不时之需。

    类似的例子,带有错误泛型参数的转换列表会产生同样的异常。

        List list = List.of("text");
        List<Integer> numbers = (List<Integer>) list;
        System.out.println(numbers.get(0).longValue());
    

    结论: 如果我们想拥有类型安全的代码,我们必须确保我们不使用原始类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-31
      • 1970-01-01
      • 2017-06-13
      • 2020-12-27
      相关资源
      最近更新 更多