# 与传统代码互操作

> 原文： [https://docs.oracle.com/javase/tutorial/extra/generics/legacy.html](https://docs.oracle.com/javase/tutorial/extra/generics/legacy.html)

到目前为止，我们所有的例子都假设了一个理想化的世界，每个人都在使用最新版本的 Java 编程语言，它支持泛型。

唉，实际情况并非如此。在早期版本的语言中已经编写了数百行代码，并且它们不会在一夜之间全部转换。

稍后，在[转换遗留代码使用泛型](convert.html)部分中，我们将解决将旧代码转换为使用泛型的问题。在本节中，我们将关注一个更简单的问题：遗留代码和通用代码如何互操作？这个问题有两个部分：使用通用代码中的遗留代码和使用遗留代码中的通用代码。

## 在通用代码中使用旧代码

如何使用旧代码，同时仍然在自己的代码中享受泛型的好处？

例如，假设您要使用包`com.Example.widgets`。 Example.com 的人们推出了一个库存控制系统，其亮点如下所示：

```
package com.Example.widgets;

public interface Part {...}

public class Inventory {
    /**
     * Adds a new Assembly to the inventory database.
     * The assembly is given the name name, and 
     * consists of a set parts specified by parts. 
     * All elements of the collection parts
     * must support the Part interface.
     **/ 
    public static void addAssembly(String name, Collection parts) {...}
    public static Assembly getAssembly(String name) {...}
}

public interface Assembly {
    // Returns a collection of Parts
    Collection getParts();
}

```

现在，您想要添加使用上述 API 的新代码。确保你总是使用正确的参数调用`addAssembly()`会很好 - 也就是说，你传入的集合确实是`Part`的`Collection`。当然，仿制药是为此量身定制的：

```
package com.mycompany.inventory;

import com.Example.widgets.*;

public class Blade implements Part {
    ...
}

public class Guillotine implements Part {
}

public class Main {
    public static void main(String[] args) {
        Collection<Part> c = new ArrayList<Part>();
        c.add(new Guillotine()) ;
        c.add(new Blade());
        Inventory.addAssembly("thingee", c);
        Collection<Part> k = Inventory.getAssembly("thingee").getParts();
    }
}

```

当我们调用`addAssembly`时，它期望第二个参数是`Collection`类型。实际参数的类型为`Collection&lt;Part&gt;`。这有效，但为什么呢？毕竟，大多数集合不包含`Part`对象，因此通常，编译器无法知道`Collection`类型所指的集合类型。

在适当的通用代码中，`Collection`将始终伴随类型参数。当使用`Collection`这样的泛型类型而没有类型参数时，它被称为 _ 原始类型 _。

大多数人的第一直觉是`Collection`真的意味着`Collection&lt;Object&gt;`。但是，正如我们之前看到的那样，在需要`Collection&lt;Object&gt;`的地方传递`Collection&lt;Part&gt;`是不安全的。更确切地说，`Collection`类型表示某种未知类型的集合，就像`Collection&lt;?&gt;`一样。

但等等，这也不是正确的！考虑调用`getParts()`，它返回`Collection`。然后将其分配给`k`，它是`Collection&lt;Part&gt;`。如果调用的结果是`Collection&lt;?&gt;`，则赋值将是错误。

实际上，分配是合法的，但它会生成 _ 未经检查的警告 _。需要警告，因为事实是编译器无法保证其正确性。我们无法检查`getAssembly()`中的遗留代码，以确保返回的集合确实是`Part`的集合。代码中使用的类型是`Collection`，可以合法地将所有类型的对象插入到这样的集合中。

那么，这不应该是一个错误吗？从理论上讲，是的;但实际上，如果通用代码要调用遗留代码，则必须允许这样做。由程序员来决定，在这种情况下，赋值是安全的，因为`getAssembly()`的契约表示它返回`Part`的集合，即使类型签名没有显示这一点。

因此原始类型非常类似于通配符类型，但它们并没有严格限制。这是一个深思熟虑的设计决策，允许泛型与预先存在的遗留代码进行互操作。

从通用代码调用遗留代码本质上是危险的;一旦将通用代码与非泛型遗留代码混合，泛型类型系统通常提供的所有安全保证都是无效的。但是，你仍然比没有使用泛型更好。至少你知道你的代码是一致的。

目前有非常多的非通用代码，然后有通用代码，并且不可避免地会出现需要混合的情况。

如果您发现必须混合使用旧代码和通用代码，请密切注意未经检查的警告。仔细考虑如何证明产生警告的代码的安全性。

如果你仍然犯了错误会发生什么，导致警告的代码确实不是类型安全的？我们来看看这种情况。在此过程中，我们将深入了解编译器的工作原理。

## 擦除和翻译

```
public String loophole(Integer x) {
    List<String> ys = new LinkedList<String>();
    List xs = ys;
    xs.add(x); // Compile-time unchecked warning
    return ys.iterator().next();
}

```

在这里，我们给别名一个字符串列表和一个普通的旧列表。我们在列表中插入`Integer`，并尝试提取`String`。这显然是错误的。如果我们忽略警告并尝试执行此代码，它将在我们尝试使用错误类型的位置完全失败。在运行时，此代码的行为类似于：

```
public String loophole(Integer x) {
    List ys = new LinkedList;
    List xs = ys;
    xs.add(x); 
    return(String) ys.iterator().next(); // run time error
}

```

当我们从列表中提取元素并尝试通过将其转换为`String`将其视为字符串时，我们将得到`ClassCastException`。与`loophole()`的通用版本完全相同。

原因是，Java 编译器将泛型实现为称为 _ 擦除 _ 的前端转换。您可以（几乎）将其视为源到源的转换，从而将`loophole()`的通用版本转换为非泛型版本。

因此， **Java 虚拟机的类型安全性和完整性永远不会有风险，即使存在未经检查的警告**。

基本上，删除（或 _ 删除 _）所有泛型类型信息。抛出尖括号之间的所有类型信息，因此，例如，像`List&lt;String&gt;`这样的参数化类型被转换为`List`。类型变量的所有剩余使用都由类型变量的上限（通常为`Object`）替换。并且，只要结果代码不是类型正确的，就会插入适当类型的强制类型，如`loophole`的最后一行。

擦除的全部细节超出了本教程的范围，但我们刚刚给出的简单描述与事实相差无几。很高兴知道这一点，特别是如果你想做更复杂的事情，比如将现有的 API 转换为使用泛型（参见[转换遗留代码到使用泛型](convert.html)部分），或者只是想了解为什么他们是这样的。

## 在遗留代码中使用通用代码

现在让我们考虑相反的情况。想象一下，Example.com 选择将他们的 API 转换为使用泛型，但他们的一些客户还没有。所以现在代码看起来像：

```
package com.Example.widgets;

public interface Part { 
    ...
}

public class Inventory {
    /**
     * Adds a new Assembly to the inventory database.
     * The assembly is given the name name, and 
     * consists of a set parts specified by parts. 
     * All elements of the collection parts
     * must support the Part interface.
     **/ 
    public static void addAssembly(String name, Collection<Part> parts) {...}
    public static Assembly getAssembly(String name) {...}
}

public interface Assembly {
    // Returns a collection of Parts
    Collection<Part> getParts();
}

```

客户端代码如下所示：

```
package com.mycompany.inventory;

import com.Example.widgets.*;

public class Blade implements Part {
...
}

public class Guillotine implements Part {
}

public class Main {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add(new Guillotine()) ;
        c.add(new Blade());

        // 1: unchecked warning
        Inventory.addAssembly("thingee", c);

        Collection k = Inventory.getAssembly("thingee").getParts();
    }
}

```

客户端代码是在引入泛型之前编写的，但它使用了包`com.Example.widgets`和集合库，两者都使用泛型类型。客户端代码中泛型类型声明的所有用法都是原始类型。

第 1 行生成未经检查的警告，因为正在传递原始`Collection`，其中`Part`的`Part`是预期的，并且编译器无法确保原始`Collection`确实是`Collection`的`Collection` ] s。

作为替代方法，您可以使用 source 1.4 标志编译客户端代码，确保不生成任何警告。但是，在这种情况下，您将无法使用 JDK 5.0 中引入的任何新语言功能。

* * *