Java泛型中擦除的概念是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/313584/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 13:02:00  来源:igfitidea点击:

What is the concept of erasure in generics in Java?

javagenerics

提问by Tushu

What is the concept of erasure in generics in Java?

Java泛型中擦除的概念是什么?

采纳答案by Jon Skeet

It's basically the way that generics are implemented in Java via compiler trickery. The compiled generic code actuallyjust uses java.lang.Objectwherever you talk about T(or some other type parameter) - and there's some metadata to tell the compiler that it really is a generic type.

这基本上是通过编译器技巧在 Java 中实现泛型的方式。编译后的泛型代码实际上只是java.lang.Object在你谈论的任何地方使用T(或其他一些类型参数) - 并且有一些元数据告诉编译器它确实是一个泛型类型。

When you compile some code against a generic type or method, the compiler works out what you really mean (i.e. what the type argument for Tis) and verifies at compiletime that you're doing the right thing, but the emitted code again just talks in terms of java.lang.Object- the compiler generates extra casts where necessary. At execution time, a List<String>and a List<Date>are exactly the same; the extra type information has been erasedby the compiler.

当您针对泛型类型或方法编译一些代码时,编译器会计算出您真正的意思(即类型参数T是什么)并在编译时验证您在做正确的事情,但发出的代码再次只是说话就java.lang.Object- 编译器在必要时生成额外的强制转换而言。在执行时,aList<String>和aList<Date>完全一样;额外的类型信息已被编译器删除

Compare this with, say, C#, where the information is retained at execution time, allowing code to contain expressions such as typeof(T)which is the equivalent to T.class- except that the latter is invalid. (There are further differences between .NET generics and Java generics, mind you.) Type erasure is the source of many of the "odd" warning/error messages when dealing with Java generics.

将其与 C# 进行比较,其中信息在执行时保留,允许代码包含诸如typeof(T)which 等价于T.class- 的表达式,但后者无效。(请注意,.NET 泛型和 Java 泛型之间还有进一步的区别。)在处理 Java 泛型时,类型擦除是许多“奇怪”警告/错误消息的来源。

Other resources:

其他资源:

回答by Andrew Kennan

As I understand it (being a .NETguy) the JVMhas no concept of generics, so the compiler replaces type parameters with Object and performs all the casting for you.

据我了解(作为.NET人员)JVM没有泛型的概念,因此编译器用 Object 替换类型参数并为您执行所有转换。

This means that Java generics are nothing but syntax sugar and don't offer any performance improvement for value types that require boxing/unboxing when passed by reference.

这意味着 Java 泛型只不过是语法糖,并且不会为通过引用传递时需要装箱/拆箱的值类型提供任何性能改进。

回答by VonC

To complete the already very complete Jon Skeet's answer, you have to realize the concept of type erasurederives from a need of compatibility with previous versions of Java.

要完成已经非常完整的 Jon Skeet 的答案,您必须意识到类型擦除的概念源于与以前版本的 Java 兼容的需要。

Initially presented at EclipseCon 2007 (no longer available), the compatibility included those points:

最初在 EclipseCon 2007 上展示(不再可用),兼容性包括以下几点:

  • Source compatibility (Nice to have...)
  • Binary compatibility (Must have!)
  • Migration compatibility
    • Existing programs must continue to work
    • Existing libraries must be able to use generic types
    • Must have!
  • 源兼容性(很高兴拥有...)
  • 二进制兼容性(必须有!)
  • 迁移兼容性
    • 现有的程序必须继续工作
    • 现有库必须能够使用泛型类型
    • 一定有!

Original answer:

原答案:

Hence:

因此:

new ArrayList<String>() => new ArrayList()

There are propositions for a greater reification. Reify being "Regard an abstract concept as real", where language constructs should be concepts, not just syntactic sugar.

有一个更大的具体化的提议。Reify 是“将抽象概念视为真实的”,其中语言结构应该是概念,而不仅仅是语法糖。

I should also mention the checkCollectionmethod of Java 6, which returns a dynamically typesafe view of the specified collection. Any attempt to insert an element of the wrong type will result in an immediate ClassCastException.

我还应该提到checkCollectionJava 6的方法,它返回指定集合的​​动态类型安全视图。任何插入错误类型元素的尝试都将导致立即出现ClassCastException.

The generics mechanism in the language provides compile-time (static) type checking, but it is possible to defeat this mechanism with unchecked casts.

该语言中的泛型机制提供了编译时(静态)类型检查,但可以使用未经检查的强制转换来破坏这种机制

Usually this is not a problem, as the compiler issues warnings on all such unchecked operations.

通常这不是问题,因为编译器会针对所有此类未经检查的操作发出警告。

There are, however, times when static type checking alone is not sufficient, like:

但是,有时仅靠静态类型检查是不够的,例如:

  • when a collection is passed to a third-party library and it is imperative that the library code not corrupt the collection by inserting an element of the wrong type.
  • a program fails with a ClassCastException, indicating that an incorrectly typed element was put into a parameterized collection. Unfortunately, the exception can occur at any time after the erroneous element is inserted, so it typically provides little or no information as to the real source of the problem.
  • 当集合传递给第三方库时,库代码必须不通过插入错误类型的元素来破坏集合。
  • 程序失败并显示ClassCastException,表明将错误类型的元素放入参数化集合中。不幸的是,在插入错误元素后的任何时间都可能发生异常,因此它通常很少或根本不提供有关问题真正来源的信息。


Update July 2012, almost four years later:

近四年后的 2012 年 7 月更新:

It is now (2012) detailed in "API Migration Compatibility Rules (Signature Test)"

现在(2012)在“ API迁移兼容性规则(签名测试)”中有详细说明

The Java programming language implements generics using erasure, which ensures that legacy and generic versions usually generate identical class files, except for some auxiliary information about types. Binary compatibility is not broken because it is possible to replace a legacy class file with a generic class file without changing or recompiling any client code.

To facilitate interfacing with non-generic legacy code, it is also possible to use the erasure of a parameterized type as a type. Such a type is called a raw type(Java Language Specification 3/4.8). Allowing the raw type also ensures backward compatibility for source code.

According to this, the following versions of the java.util.Iteratorclass are both binary and source code backward compatible:

Java 编程语言使用擦除来实现泛型,这确保了遗留和泛型版本通常生成相同的类文件,除了一些关于类型的辅助信息。二进制兼容性不会被破坏,因为可以用通用类文件替换遗留类文件,而无需更改或重新编译任何客户端代码。

为了方便与非泛型遗留代码的接口,还可以使用参数化类型的擦除作为类型。这种类型称为原始类型Java Language Specification 3/4.8)。允许原始类型还确保了源代码的向后兼容性。

据此,java.util.Iterator该类的以下版本是二进制和源代码向后兼容的:

Class java.util.Iterator as it is defined in Java SE version 1.4:

public interface Iterator {
    boolean hasNext();
    Object next();
    void remove();
}

Class java.util.Iterator as it is defined in Java SE version 5.0:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

回答by Daniel Spiewak

Complementing the already-complemented Jon Skeet answer...

补充已经补充的 Jon Skeet 答案......

It has been mentioned that implementing generics through erasure leads to some annoying limitations (e.g. no new T[42]). It has also been mentioned that the primary reason for doing things this way was backwards compatibility in the bytecode. This is also (mostly) true. The bytecode generated -target 1.5 is somewhat different from just de-sugared casting -target 1.4. Technically, it's even possible (through immense trickery) to gain access to generic type instantiations at runtime, proving that there really is something in the bytecode.

已经提到通过擦除实现泛型会导致一些恼人的限制(例如 no new T[42])。也有人提到,这样做的主要原因是字节码的向后兼容性。这也是(大部分)正确的。生成的字节码 -target 1.5 与脱糖的铸造 -target 1.4 有所不同。从技术上讲,甚至有可能(通过巨大的技巧)在运行时访问泛型类型实例,证明字节码中确实存在某些内容。

The more interesting point (which has not been raised) is that implementing generics using erasure offers quite a bit more flexibility in what the high-level type system can accomplish. A good example of this would be Scala's JVM implementation vs CLR. On the JVM, it is possible to implement higher-kinds directly due to the fact that the JVM itself imposes no restrictions on generic types (since these "types" are effectively absent). This contrasts with the CLR, which has runtime knowledge of parameter instantiations. Because of this, the CLR itself must have some concept of how generics should be used, nullifying attempts to extend the system with unanticipated rules. As a result, Scala's higher-kinds on the CLR are implemented using a weird form of erasure emulated within the compiler itself, making them not-entirely-compatible with plain-old .NET generics.

更有趣的一点(尚未提出)是使用擦除实现泛型在高级类型系统可以完成的事情上提供了更多的灵活性。一个很好的例子是 Scala 的 JVM 实现与 CLR。在 JVM 上,由于 JVM 本身对泛型类型没有限制(因为这些“类型”实际上不存在),因此可以直接实现更高类型。这与 CLR 形成对比,后者具有参数实例化的运行时知识。正因为如此,CLR 本身必须有一些关于应该如何使用泛型的概念,从而使使用意外规则扩展系统的尝试无效。因此,Scala 在 CLR 上的高级类型是使用在编译器本身中模拟的一种奇怪的擦除形式来实现的,

Erasure may be inconvenient when you want to do naughty things at runtime, but it does offer the most flexibility to the compiler writers. I'm guessing that's part of why it's not going away any time soon.

当您想在运行时做一些讨厌的事情时,擦除可能不方便,但它确实为编译器编写者提供了最大的灵活性。我猜这就是为什么它不会很快消失的部分原因。

回答by jigawot

Just as a side-note, it is an interesting exercise to actually see what the compiler is doing when it performs erasure -- makes the whole concept a little easier to grasp. There is a special flag you can pass the compiler to output java files that have had the generics erased and casts inserted. An example:

顺便提一下,实际查看编译器在执行擦除时正在做什么是一个有趣的练习——使整个概念更容易掌握。有一个特殊的标志,您可以将编译器传递给输出已删除泛型并插入强制转换的 Java 文件。一个例子:

javac -XD-printflat -d output_dir SomeFile.java

The -printflatis the flag that gets handed off to the compiler that generates the files. (The -XDpart is what tells javacto hand it to the executable jar that actually does the compiling rather than just javac, but I digress...) The -d output_diris necessary because the compiler needs some place to put the new .java files.

-printflat是传递给生成文件的编译器的标志。(这-XD部分是告诉javac将它交给实际进行编译的可执行 jar 而不仅仅是javac,但我离题了......)这-d output_dir是必要的,因为编译器需要一些地方来放置新的 .java 文件。

This, of course, does more than just erasure; all of the automatic stuff the compiler does gets done here. For example, default constructors are also inserted, the new foreach-style forloops are expanded to regular forloops, etc. It is nice to see the little things that are happening automagically.

当然,这不仅仅是擦除;编译器所做的所有自动操作都在这里完成。例如,还插入了默认构造函数,将新的 foreach 样式for循环扩展为常规for循环等。很高兴看到自动发生的小事情。

回答by Parag

Erasure, literally means that the type information which is present in the source code is erased from the compiled bytecode. Let us understand this with some code.

擦除,字面意思是将源代码中存在的类型信息从编译后的字节码中擦除。让我们用一些代码来理解这一点。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class GenericsErasure {
    public static void main(String args[]) {
        List<String> list = new ArrayList<String>();
        list.add("Hello");
        Iterator<String> iter = list.iterator();
        while(iter.hasNext()) {
            String s = iter.next();
            System.out.println(s);
        }
    }
}

If you compile this code and then decompile it with a Java decompiler, you will get something like this. Notice that the decompiled code contains no trace of the type information present in the original source code.

如果你编译这段代码,然后用 Java 反编译器反编译它,你会得到这样的东西。请注意,反编译的代码不包含原始源代码中存在的类型信息的痕迹。

import java.io.PrintStream;
import java.util.*;

public class GenericsErasure
{

    public GenericsErasure()
    {
    }

    public static void main(String args[])
    {
        List list = new ArrayList();
        list.add("Hello");
        String s;
        for(Iterator iter = list.iterator(); iter.hasNext(); System.out.println(s))
            s = (String)iter.next();

    }
} 

回答by snr

There are good explanations. I only add an example to show how the type erasure work with a decompiler.

有很好的解释。我只添加了一个例子来展示类型擦除如何与反编译器一起工作。

Original class,

原班,

import java.util.ArrayList;
import java.util.List;


public class S<T> {

    T obj; 

    S(T o) {
        obj = o;
    }

    T getob() {
        return obj;
    }

    public static void main(String args[]) {
        List<String> list = new ArrayList<>();
        list.add("Hello");

        // for-each
        for(String s : list) {
            String temp = s;
            System.out.println(temp);
        }

        // stream
        list.forEach(System.out::println);
    }
}

Decompiled code from its bytecode,

从字节码中反编译代码,

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.Consumer;

public class S {

   Object obj;


   S(Object var1) {
      this.obj = var1;
   }

   Object getob() {
      return this.obj;
   }

   public static void main(String[] var0) {

   ArrayList var1 = new ArrayList();
   var1.add("Hello");


   // for-each
   Iterator iterator = var1.iterator();

   while (iterator.hasNext()) {
         String string;
         String string2 = string = (String)iterator.next();
         System.out.println(string2);
   }


   // stream
   PrintStream printStream = System.out;
   Objects.requireNonNull(printStream);
   var1.forEach(printStream::println);


   }
}