Documentation

The Java™ Tutorials
Hide TOC
Interoperating with Legacy Code与遗留代码互操作
Trail: Bonus
Lesson: Generics

Interoperating with Legacy Code与遗留代码互操作

Until now, all our examples have assumed an idealized world, where everyone is using the latest version of the Java programming language, which supports generics.到目前为止,我们所有的例子都假设了一个理想化的世界,每个人都在使用支持泛型的最新版本的Java编程语言。

Alas, in reality this isn't the case. 唉,事实并非如此。Millions of lines of code have been written in earlier versions of the language, and they won't all be converted overnight.数百万行代码已经用该语言的早期版本编写,它们不会在一夜之间全部转换。

Later, in the Converting Legacy Code to Use Generics section, we will tackle the problem of converting your old code to use generics. 稍后,在将遗留代码转换为使用泛型部分,我们将解决将旧代码转换为使用泛型的问题。In this section, we'll focus on a simpler problem: how can legacy code and generic code interoperate? 在本节中,我们将关注一个更简单的问题:遗留代码和泛型代码如何互操作?This question has two parts: using legacy code from within generic code and using generic code within legacy code.这个问题有两个部分:在通用代码中使用遗留代码和在遗留代码中使用通用代码。

Using Legacy Code in Generic Code在泛型代码中使用遗留代码

How can you use old code, while still enjoying the benefits of generics in your own code?如何使用旧代码,同时仍能在自己的代码中享受泛型的好处?

As an example, assume you want to use the package com.Example.widgets. 例如,假设您想使用com.Example.widgets包。The folks at Example.com market a system for inventory control, highlights of which are shown below: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();
}

Now, you'd like to add new code that uses the API above. 现在,您想添加使用上述API的新代码。It would be nice to ensure that you always called addAssembly() with the proper arguments - that is, that the collection you pass in is indeed a Collection of Part. 最好确保始终使用正确的参数调用addAssembly(),也就是说,传入的集合确实是PartCollectionOf course, generics are tailor made for this:当然,仿制药是为这一点量身定制的:

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();
    }
}

When we call addAssembly, it expects the second parameter to be of type Collection. 当我们调用addAssembly时,它希望第二个参数的类型为CollectionThe actual argument is of type Collection<Part>. 实际参数的类型为Collection<Part>This works, but why? 这是可行的,但为什么呢?After all, most collections don't contain Part objects, and so in general, the compiler has no way of knowing what kind of collection the type Collection refers to.毕竟,大多数集合不包含Part对象,因此一般来说,编译器无法知道类型集合所指的集合类型。

In proper generic code, Collection would always be accompanied by a type parameter. 在正确的泛型代码中,Collection总是伴随着一个类型参数。When a generic type like Collection is used without a type parameter, it's called a raw type.如果在没有类型参数的情况下使用像Collection这样的泛型类型,则称之为原始类型

Most people's first instinct is that Collection really means Collection<Object>. 大多数人的第一反应是,Collection实际上意味着Collection<Object>However, as we saw earlier, it isn't safe to pass a Collection<Part> in a place where a Collection<Object> is required. 然而,正如我们前面看到的,在需要Collection<Part>的地方传递Collection<Object>是不安全的。It's more accurate to say that the type Collection denotes a collection of some unknown type, just like Collection<?>.更准确地说,类型Collection表示某种未知类型的集合,就像Collection<?>

But wait, that can't be right either! 但是等等,那也不可能是对的!Consider the call to getParts(), which returns a Collection. 考虑调用getParts(),它返回一个CollectionThis is then assigned to k, which is a Collection<Part>. 然后将其分配给k,这是一个Collection<Part>If the result of the call is a Collection<?>, the assignment would be an error.如果调用的结果是Collection<?>,这项任务将是一个错误。

In reality, the assignment is legal, but it generates an unchecked warning. 事实上,这种分配是合法的,但它会产生未经检查的警告The warning is needed, because the fact is that the compiler can't guarantee its correctness. 警告是必要的,因为编译器不能保证它的正确性。We have no way of checking the legacy code in getAssembly() to ensure that indeed the collection being returned is a collection of Parts. 我们无法检查getAssembly()中的遗留代码,以确保返回的集合确实是Part集合。The type used in the code is Collection, and one could legally insert all kinds of objects into such a collection.代码中使用的类型是Collection,可以合法地将各种对象插入这样的集合中。

So, shouldn't this be an error? 那么,这不是一个错误吗?Theoretically speaking, yes; but practically speaking, if generic code is going to call legacy code, this has to be allowed. 从理论上讲,是的;但实际上,如果泛型代码要调用遗留代码,这是必须允许的。It's up to you, the programmer, to satisfy yourself that in this case, the assignment is safe because the contract of getAssembly() says it returns a collection of Parts, even though the type signature doesn't show this.由程序员自己决定,在这种情况下,赋值是安全的,因为getAssembly()的契约表示它返回一个零件集合,即使类型签名没有显示这一点。

So raw types are very much like wildcard types, but they are not typechecked as stringently. 因此,原始类型非常类似于通配符类型,但它们没有经过严格的类型检查。This is a deliberate design decision, to allow generics to interoperate with pre-existing legacy code.这是一个经过深思熟虑的设计决策,允许泛型与预先存在的遗留代码进行互操作。

Calling legacy code from generic code is inherently dangerous; once you mix generic code with non-generic legacy code, all the safety guarantees that the generic type system usually provides are void. 从泛型代码调用遗留代码本身就是危险的;一旦将泛型代码与非泛型遗留代码混合,泛型类型系统通常提供的所有安全保证都是无效的。However, you are still better off than you were without using generics at all. 然而,与完全不使用泛型相比,您仍然过得更好。At least you know the code on your end is consistent.至少你知道你这边的代码是一致的。

At the moment there's a lot more non-generic code out there then there is generic code, and there will inevitably be situations where they have to mix.目前,非通用代码比通用代码多得多,而且不可避免地会出现它们必须混合的情况。

If you find that you must intermix legacy and generic code, pay close attention to the unchecked warnings. 如果您发现必须混合使用遗留代码和通用代码,请密切注意未检查的警告。Think carefully how you can justify the safety of the code that gives rise to the warning.仔细考虑如何证明引起警告的代码的安全性。

What happens if you still made a mistake, and the code that caused a warning is indeed not type safe? 如果您仍然犯了错误,并且导致警告的代码确实不是类型安全的,会发生什么?Let's take a look at such a situation. 让我们来看看这种情况。In the process, we'll get some insight into the workings of the compiler.在这个过程中,我们将对编译器的工作原理有一些了解。

Erasure and Translation擦除与翻译

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();
}

Here, we've aliased a list of strings and a plain old list. 这里,我们给一个字符串列表和一个普通的旧列表加了别名。We insert an Integer into the list, and attempt to extract a String. 我们在列表中插入一个Integer,并尝试提取一个StringThis is clearly wrong. 这显然是错误的。If we ignore the warning and try to execute this code, it will fail exactly at the point where we try to use the wrong type. 如果我们忽略警告并尝试执行此代码,它将在尝试使用错误类型时完全失败。At run time, this code behaves like:在运行时,此代码的行为如下:

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

When we extract an element from the list, and attempt to treat it as a string by casting it to String, we will get a ClassCastException. 当我们从列表中提取一个元素,并试图通过将其转换为字符串来将其视为String时,我们将得到一个ClassCastExceptionThe exact same thing happens with the generic version of loophole().同样的情况也发生在loophole()的通用版本中。

The reason for this is, that generics are implemented by the Java compiler as a front-end conversion called erasure. 原因是,泛型由Java编译器作为前端转换实现,称为擦除You can (almost) think of it as a source-to-source translation, whereby the generic version of loophole() is converted to the non-generic version.您可以(几乎)将其视为源代码到源代码的转换,从而将loophole()的通用版本转换为非通用版本。

As a result, the type safety and integrity of the Java virtual machine are never at risk, even in the presence of unchecked warnings.因此,即使存在未经检查的警告,Java虚拟机的类型安全性和完整性也不会受到威胁

Basically, erasure gets rid of (or erases) all generic type information. 基本上,擦除会删除(或擦除)所有泛型类型信息。All the type information betweeen angle brackets is thrown out, so, for example, a parameterized type like List<String> is converted into List. 尖括号之间的所有类型信息都被抛出,因此,例如,像List<String>这样的参数化类型被转换为ListAll remaining uses of type variables are replaced by the upper bound of the type variable (usually Object). 类型变量的所有剩余用法都被类型变量的上限(通常是Object)替换。And, whenever the resulting code isn't type-correct, a cast to the appropriate type is inserted, as in the last line of loophole.而且,每当生成的代码的类型不正确时,就会插入一个适当类型的转换,就像在最后一行loophole中一样。

The full details of erasure are beyond the scope of this tutorial, but the simple description we just gave isn't far from the truth. 擦除的全部细节超出了本教程的范围,但我们刚才给出的简单描述与事实并不遥远。It's good to know a bit about this, especially if you want to do more sophisticated things like converting existing APIs to use generics (see the Converting Legacy Code to Use Generics section), or just want to understand why things are the way they are. 了解一些这方面的知识是很好的,尤其是如果您想做更复杂的事情,比如将现有API转换为使用泛型(请参阅将遗留代码转换为使用泛型部分),或者只是想了解为什么事情是这样的。

Using Generic Code in Legacy Code在遗留代码中使用泛型代码

Now let's consider the inverse case. 现在让我们考虑逆情形。Imagine that Example.com chose to convert their API to use generics, but that some of their clients haven't yet. 想象一下Example.com选择将其API转换为使用泛型,但他们的一些客户机尚未这样做。So now the code looks like:现在代码看起来像:

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();
}

and the client code looks like:客户端代码如下所示:

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();
    }
}

The client code was written before generics were introduced, but it uses the package com.Example.widgets and the collection library, both of which are using generic types. 客户机代码是在引入泛型之前编写的,但它使用com.Example.widgets包和集合库,两者都使用泛型类型。All the uses of generic type declarations in the client code are raw types.客户机代码中泛型类型声明的所有用法都是原始类型。

Line 1 generates an unchecked warning, because a raw Collection is being passed in where a Collection of Parts is expected, and the compiler cannot ensure that the raw Collection really is a Collection of Parts.第1行生成了一个未检查的警告,因为在预期的PartCollection中传递了一个原始Collection,而编译器无法确保原始Collection确实是一个PartCollection

As an alternative, you can compile the client code using the source 1.4 flag, ensuring that no warnings are generated. 作为替代方案,您可以使用source 1.4标志编译客户机代码,确保不会生成任何警告。However, in that case you won't be able to use any of the new language features introduced in JDK 5.0.但是,在这种情况下,您将无法使用JDK 5.0中引入的任何新语言功能。



Previous page: Generic Methods
Next page: The Fine Print