[翻译]Java 范型与集合类 :演化 ,而不是革命 (第一部分)
原文地址:http://www.onjava.com/pub/a/onjava/excerpt/javagenerics_chap05/index.html
Java 范型与集合类 :演化 ,而不是革命(第一部分)
作者:Maurice Naftalin 和Philip Wadler
Editor's Note: In their new book Java Generics and Collections, authors Maurice Naftalin and Philip Wadler offer the thorough introduction to the syntax and semantics of Java 5.0 generics that you'd expect from an in-depth book on the topic. But they go a step further by considering the real-world, practical concerns of using generics in your work. Unless you're starting a project from scratch in Java 5.0, odds are you have a legacy code-base that does not currently use generics. Is bulk-converting it to use generics in one release a realistic option? Assuming it's not, you need to consider your options for a gradual introduction. Fortunately, the implementation of generics makes this eminently practical, as they describe in Chapter 5, "Evolution, Not Revolution," which we are excerpting over the course of the next two weeks on ONJava.
编者的话:作者Maurice Naftalin和Philip Wadler在他们的新书《Java Generics and Collections》中完整的介绍了Java 5.0 中范型的语法和语义,这些内容正是读者们所期待的关于这个话题的深入的资料。但是此外他们还考虑了读者们在日常工作中使用范型的实际的顾虑。除非你是用Java 5.0 从头开发一个项目,否则的话你将会有很大的可能遇到一堆没有使用范型的旧的代码库。难道在一个release版本中将这样多的代码全部改进为使用范型的是一个现实的选择吗?如果它不是,你就需要考虑逐渐的改进你的代码。幸运的是,Java中范型的实现使得这样的选择是相当可行的,正如两位作者在该书第五章中提到的“演化,而不是革命”,这就是OnJava网站在接下来两周的课程中将不断重复提到的。
One motto underpinning the design of generics for Java is evolution, not revolution. It must be possible to migrate a large, existing body of code to use generics gradually (evolution) without requiring a radical, all-at-once change (revolution). The generics design ensures that old code compiles against the new Java libraries, avoiding the unfortunate situation in which half of your code needs old libraries and half of your code needs new libraries.
在Java中范型的设计过程中,不断提到的设计思想就是“演化,而不是革命”。必须使得人们可以将大量的现存的代码体逐步转移到使用范型上(演化),而不需要突然的变动(革命)。范型的设计原则保证了那些旧的代码可以在新的Java类库下正常编译,避免那种一半代码需要旧的类库而一半代码需要新的类库的情况。
The requirements for evolution are much stronger than the usual backward compatibility. With simple backward compatibility, one would supply both legacy and generic versions for each application; this is exactly what happens in C#, for example. If you are building on top of code supplied by multiple suppliers, some of whom use legacy collections and some of whom use generic collections, this might rapidly lead to a versioning nightmare.
演化的要求比通常的“向下兼容”的要求高了许多。在简单的“向下兼容”情况下,只需要为每一个应用提供遗留的版本和范型的版本,例如在C#语言中就是这种情况。如果需要在多个提供商提供的代码的基础上来构建一个项目,而一些提供商使用旧有的集合类而其他一些使用范型的集合类,这样迅速导致一个关于版本的噩梦。
What we require is that the same client code works with both the legacy and generic versions of a library. This means that the supplier and clients of a library can make completely independent choices about when to move from legacy to generic code. This is a much stronger requirement than backward compatibility; it is called migration compatibility or platform compatibility.
我们所要求的就是相同的客户代码可以同时在旧的和新的范型的库的基础上裕兴。这就意味这提供商和客户可以完全自主的选择何时从旧的类库转移到新的范型类库的使用上;这被叫做“迁移兼容性”或者“平台兼容性”
Java implements generics via erasure, which ensures that legacy and generic versions usually generate identical class files, save for some auxiliary information about types. It is possible to replace a legacy class file by a generic class file without changing, or even recompiling, any client code; this is called binary compatibility.
Java通过“擦除”技术来实现了范型,“擦除”意味这旧的类库和范型的版本一般都生成同样的class文件(除了一些关于类型的辅助信息的不同)。这样就可以用一个范型的class文件来替换就的class文件而不需要做任何变化,甚至是重新编译任何代码;这叫做“二进制文件兼容”
We summarize this with the motto binary compatibility ensures migration compatibility—or, more concisely, erasure eases evolution.
我们用名词“二进制兼容保证了迁移兼容性-或者更确切的说是,擦除简化了演化”来概括这个特性。
This section shows how to add generics to existing code; it considers a small example, a library for stacks that extends the Collections Framework, together with an associated client. We begin with the legacy stack library and client (written for Java before generics), and then present the corresponding generic library and client (written for Java with generics). Our example code is small, so it is easy to update to generics all in one go, but in practice the library and client will be much larger, and we may want to evolve them separately. This is aided by raw types, which are the legacy counterpart of parameterized types.
这个部分将介绍如何向已有的代码中引入范型;我们来看一个小例子,一个继承了集合框架的堆栈库,以及一个相应的客户端代码。我们从这个旧的堆栈的库和代码(用引入范型以前的Java写的)开始,然后展现对应的使用了范型的库和代码(用引入了范型的Java写的)。我们的样例代码很小,所以很容易就可以一次性的更新到范型上,但是在实际中类库和客户端代码都会很大,所以我们可能向要分开的改进他们。这是在raw类型的帮助下完成的,raw类型就是参数类型的旧的对应。
The parts of the program may evolve in either order. You may have a generic library with a legacy client; this is the common case for anyone that uses the Collections Framework in Java 5 with legacy code. Or you may have a legacy library with a generic client; this is the case where you want to provide generic signatures for the library without the need to rewrite the entire library. We consider three ways to do this: minimal changes to the source, stub files, and wrappers. The first is useful when you have access to the source and the second when you do not; we recommend against the third.
程序的各个部分可能以不同的顺序演化。我们可能是拥有一个范型的库和一个旧的客户端,这种情况对于那些使用java5用的范型框架以及旧的代码的人来说是很常见的。或者你可能有一个旧的库和范型的客户端,这种情况是你打算为库提供范型的签名而不用重写整个库。我们考虑以下三种情况:对源代码做最小的变动,存根文件和包装器。第一种情况当你能够获取源代码的时候是很有用的,第二种是你不能获取源代码时候。我们推荐不使用第三种。
In practice, the library and client may involve many interfaces and classes, and there may not even be a clear distinction between library and client. But the same principles discussed here still apply, and may be used to evolve any part of a program independently of any other part.
在实际中,库和客户端代码可能拥有许多的接口和类,并且在库和客户端直面没有明显的区分。但是在这种情形下,我们讨论的原则仍然是适用的,并且可以用于演化程序的一部分,而不影响其他的部分。
Legacy Library with Legacy Client
旧的库和旧的客户端
We begin with a simple library of stacks and an associated client, as presented in Example 5.1. This is legacy code, written for Java 1.4 and its version of the Collections Framework. Like the Collections Framework, we structure the library as an interface Stack (analogous to List), an implementation class ArrayStack (analogous to ArrayList), and a utility class Stacks (analogous to Collections). The interface Stack provides just three methods: empty, push, and pop. The implementation class ArrayStack provides a single constructor with no arguments, and implements the methods empty, push, and pop using methods size, add, and remove on lists. The body of pop could be shorter—instead of assigning the value to the variable, it could be returned directly—but it will be interesting to see how the type of the variable changes as the code evolves. The utility class provides just one method, reverse, which repeatedly pops from one stack and pushes onto another.
我们从一个简单的堆栈的库和相应的客户端来开始,如例5-1所示。这是用java1.4以及其包含的集合类来写的旧的代码。和集合框架一样,我们把类库分为一个Stack接口(和List很相似),以及一个实用类Stacks(和Collections相似)。Stack接口只提供了三个方法:emtpy,push和pop.实现类ArrayStack提供了一个不带参数的构造函数,并实用lists里面的size,add,remove函数实现了empty,pop和push函数。Pop函数体可以更短些的(可以直接返回值的,而不是把值赋值给一个变量),但是现在的这种写法更有趣,我们可以看到随着代码的演化,变量的类型是如何的变化的。工具类只提供了一个方法reverse,这个在一个堆栈上重复pop操作并把值push到另外一个里面去。
Example 5-1. Legacy library with legacy client
l/Stack.java:
interface Stack {
public boolean empty();
public void push(Object elt);
public Object pop();
}
l/ArrayStack.java:
import java.util.*;
class ArrayStack implements Stack {
private List list;
public ArrayStack() { list = new ArrayList(); }
public boolean empty() { return list.size() == 0; }
public void push(Object elt) { list.add(elt); }
public Object pop() {
Object elt = list.remove(list.size()-1);
return elt;
}
public String toString() { return "stack"+list.toString(); }
}
l/Stacks.java:
class Stacks {
public static Stack reverse(Stack in) {
Stack out = new ArrayStack();
while (!in.empty()) {
Object elt = in.pop();
out.push(elt);
}
return out;
}
}
l/Client.java:
class Client {
public static void main(String[] args) {
Stack stack = new ArrayStack();
for (int i = 0; i<4; i++) stack.push(new Integer(i));
assert stack.toString().equals("stack[0, 1, 2, 3]");
int top = ((Integer)stack.pop()).intValue();
assert top == 3 && stack.toString().equals("stack[0, 1, 2]");
Stack reverse = Stacks.reverse(stack);
assert stack.empty();
assert reverse.toString().equals("stack[2, 1, 0]");
}
}
The client allocates a stack, pushes a few integers onto it, pops an integer off, and then reverses the remainder into a fresh stack. Since this is Java 1.4, integers must be explicitly boxed when passed to push, and explicitly unboxed when returned by pop.
客户端代码定义了一个堆栈,将一些int数值放进去,然后将pop一些出来,最后将剩下的数reverse到一个新的Stack中。因为是在java1.4下写的代码,interger变量在push之前必须显式的用Integer包装下,然后在pop之前显式的解包装。
Generic Library with Generic Client
范型的库和范型的客户端
Next, we update the library and client to use generics, as presented in Example 5.2. This is generic code, written for Java 5 and its version of the Collections Framework. The interface now takes a type parameter, becoming Stack<E> (analogous to List<E>), and so does the implementing class, becoming ArrayStack<E> (analogous to ArrayList<E>), but no type parameter is added to the utility class Stacks (analogous to Collections). The type Object in the signatures and bodies of push and pop is replaced by the type parameter E. Note that the constructor in ArrayStack does not require a type parameter. In the utility class, the reverse method becomes a generic method with argument and result of type Stack<T>. Appropriate type parameters are added to the client, and boxing and unboxing are now implicit.
接下来,我们使用范型来更新我们的库和客户端,如例5-2所示。这是用Java 5及其所包含的集合框架来写的代码。现在的Stack接口带了一个类型参数,变成了Stack<E>(和List<E>相似),因此其实现类变成了ArrayStack<E>(和ArrayList<E>相似),但是工具类Stacks没有增加范型参数的(和Collections类相似)。在pop和push函数的申明和体中的Object对象也用类型参数E代替了。注意,ArrayStack的构造函数不需要一个类型参数。在实用类中,reverse方法也成为了一个带类型参数的范型的方法,返回值变成了Stack<T>。在客户端代码中也加入了合适的范型参数,并且int数值的包装和解包装不用显式的写出来了。
Example 5-2. Generic library with generic client
g/Stack.java:
interface Stack<E> {
public boolean empty();
public void push(E elt);
public E pop();
}
g/ArrayStack.java:
import java.util.*;
class ArrayStack<E> implements Stack<E> {
private List<E> list;
public ArrayStack() { list = new ArrayList<E>(); }
public boolean empty() { return list.size() == 0; }
public void push(E elt) { list.add(elt); }
public E pop() {
E elt = list.remove(list.size()-1);
return elt;
}
public String toString() { return "stack"+list.toString(); }
}
g/Stacks.java:
class Stacks {
public static <T> Stack<T> reverse(Stack<T> in) {
Stack<T> out = new ArrayStack<T>();
while (!in.empty()) {
T elt = in.pop();
out.push(elt);
}
return out;
}
}
g/Client.java:
class Client {
public static void main(String[] args) {
Stack<Integer> stack = new ArrayStack<Integer>();
for (int i = 0; i<4; i++) stack.push(i);
assert stack.toString().equals("stack[0, 1, 2, 3]");
int top = stack.pop();
assert top == 3 && stack.toString().equals("stack[0, 1, 2]");
Stack<Integer> reverse = Stacks.reverse(stack);
assert stack.empty();
assert reverse.toString().equals("stack[2, 1, 0]");
}
}
In short, the conversion process is straightforward: just add a few type parameters and replace occurrences of Object by the appropriate type variable. All differences between the legacy and generic versions can be spotted by comparing the highlighted portions of the two examples. The implementation of generics is designed so that the two versions generate essentially equivalent class files. Some auxiliary information about the types may differ, but the actual bytecodes to be executed will be identical. Hence, executing the legacy and generic versions yields the same results. The fact that legacy and generic sources yield identical class files eases the process of evolution, as we discuss next.
简而言之,这个转换的过程是直接的:仅仅增加了一些类型参数,并把原先的Object用合适的类型变量代替了。
Generic Library with Legacy Client
范型的类库和旧的客户端
Now let's consider the case where the library is updated to generics while the client remains in its legacy version. This may occur because there is not enough time to convert everything all at once, or because the library and client are controlled by different organizations. This corresponds to the most important case of backward compatibility, where the generic Collections Framework of Java 5 must still work with legacy clients written against the Collections Framework in Java 1.4.
现在让我们考虑这样的一种情况:类库被更新到范型了,但是客户端仍然是旧的版本。这种情况的发生可能是因为没有足够的时间同时转换所有的代码,或是因为类库和客户端是由不同的公司控释的。这种情况就是向后兼容中最重要的一种情形了,即Java 5的范型集合框架必须仍然能够与用Java 1.4中的集合框架写的旧的客户端代码一起工作。
In order to support evolution, whenever a parameterized type is defined, Java also recognizes the corresponding unparameterized version of the type, called a raw type. For instance, the parameterized type Stack<E> corresponds to the raw type Stack, and the parameterized type ArrayStack<E> corresponds to the raw type ArrayStack.
为了能够支持演化,无论何时定义一个参数化的类型,Java必须能够识别出对应的未参数化版本的类型,叫做raw类型。例如,参数化的类型Stack<E>对应的raw类型Stack,以及ArrayStack<E>对应的ArrayStack。
Every parameterized type is a subtype of the corresponding raw type, so a value of the parameterized type can be passed where a raw type is expected. Usually, it is an error to pass a value of a supertype where a value of its subtype is expected, but Java does permit a value of a raw type to be passed where a parameterized type is expected—however, it flags this circumstance by generating an unchecked conversion warning. For instance, you can assign a value of type Stack<E> to a variable of type Stack, since the former is a subtype of the latter. You can also assign a value of type Stack to a variable of type Stack<E>, but this will generate an unchecked conversion warning.
每一个参数化的类型都有一个对应的raw类型的子类型(subtype),一次老公一个参数化类型的值可以传递到任何期待raw类型的地方。通常,向一个期待subtype的地方传递一个supertype的值的时候会发生错误,但是Java的确允许向一个其他参数化类型的地方传递一个raw类型的值。然而,在这种情况下需要产生一个“unchecked conversion”的警告。例如,可以将一个Stack<E>的值赋给Stack类型,因为前者是后者的一个子类型。同样可以将一个Stack类型的值赋给类型为Stack<E>的变量,但是这将产生一个“unchecked conversion”的警告。
To be specific, consider compiling the generic source for Stack<E>, ArrayStack<E>, and Stacks from Example 5.2 (say, in directory g) with the legacy source for Client from Example 5.1 (say, in directory l). Sun's Java 5 compiler yields the following message:
具体来说,我们来看一下在编译例5-2(在目录g下)中Stack<E>,ArrayStack<E>和Stacks类的范型代码以及例5-1(在目录l中)中的旧的客户端代码这样一种情形。Sun的Java 5编译器将会产生如下的信息:
% javac g/Stack.java g/ArrayStack.java g/Stacks.java l/Client.java
Note: Client.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
The unchecked warning indicates that the compiler cannot offer the same safety guarantees that are possible when generics are used uniformly throughout. However, when the generic code is generated by updating legacy code, we know that equivalent class files are produced from both, and hence (despite the unchecked warning) running a legacy client with the generic library will yield the same result as running the legacy client with the legacy library. Here we assume that the only change in updating the library was to introduce generics, and that no change to the behavior was introduced, either on purpose or by mistake.
Unchecked警告指出编译器在范型没有一致的使用时不能提供相同的安全保证。然而,当范型的代码是通过更新旧的代码来产生的时候,我们知道相应的class文件是新的和旧的代码同时产生的,因此(尽管有unchecked警告),在范型的库上运行旧的客户端代码将会和在旧的库上运行旧的代码产生相同的结果。在这里,我们假设在更新类库的过程中,我们只是引入了范型,而没有对函数做任何功能上的改变,不管是故意的还是犯了错误。
If we follow the suggestion above and rerun the compiler with the appropriate switch enabled, we get more details:
% javac -Xlint:unchecked g/Stack.java g/ArrayStack.java \
% g/Stacks.java l/Client.java
l/Client.java:4: warning: [unchecked] unchecked call
to push(E) as a member of the raw type
Stack
for (int i = 0; i<4; i++) stack.push(new Integer(i));
^
l/Client.java:8: warning: [unchecked] unchecked conversion
found : Stack
required: Stack<E>
Stack reverse = Stacks.reverse(stack);
^
l/Client.java:8: warning: [unchecked] unchecked method invocation:
<E>reverse(Stack<E>) in Stacks is applied to (Stack)
Stack reverse = Stacks.reverse(stack);
^
3 warnings
Not every use of a raw type gives rise to a warning. Because every parameterized type is a subtype of the corresponding raw type, but not conversely, passing a parameterized type where a raw type is expected is safe (hence, no warning for getting the result from reverse), but passing a raw type where a parameterized type is expected issues a warning (hence, the warning when passing an argument to reverse); this is an instance of the Substitution Principle. When we invoke a method on a receiver of a raw type, the method is treated as if the type parameter is a wildcard, so getting a value from a raw type is safe (hence, no warning for the invocation of pop), but putting a value into a raw type issues a warning (hence, the warning for the invocation of push); this is an instance of the Get and Put Principle.
不是所有使用raw类型的地方都回产生一个警告。因为每一个参数化的类型都是对应的raw类型的一个子类型,但是相反的情况不不是一样的。向期待一个raw类型的地方传递一个参数化的类型是安全的(因此reverse函数的结果不会产生警告),但是向期待一个参数化类型的地方那个传递一个raw类型将会产生一个警告(因此,向reverse函数传递一个参数将会产生警告);这就是置换原则的一个例子。当我们调用一个需要raw类型的函数,这个方法将被这样处理:类型参数是一个wildcard,因此从一个raw类型获取一个值是安全的(因此调用pop函数没有产生任何警告),但是将一个值放到raw类型中将会产生一个警告(因此调用push函数将会产生一个警告)。这就是“Get/Put”原则的一个例子。
Even if you have not written any generic code, you may still have an evolution problem because others have generified their code. This will affect everyone with legacy code that uses the Collections Framework, which has been generified by Sun. So the most important case of using generic libraries with legacy clients is that of using the Java 5 Collections Framework with legacy code written for the Java 1.4 Collections Framework.
即使你没有写任何范型的代码,你仍然可能碰到演化的问题,因为其他人可能范型了他们的代码。这样影响任何使用Sun公司范型化了的集合框架书写的代码。因此使用范型的类库和旧的客户端代码的情形中最重要的就是使用Java 5的集合框架和用Java 1.4集合框架书写的旧的代码。
In particular, applying the Java 5 compiler to the legacy code in Example 5.1 also issues unchecked warnings, because of the uses of the generified class ArrayList from the legacy class ArrayStack. Here is what happens when we compile legacy versions of all the files with the Java 5 compiler and libraries:
特别的,用java 5的编译器来编译例5.1中的旧的代码也会产生unchecked警告,因为在旧的ArrayStack类中使用了范型的ArrayList。下面就是我们用Java 5编译器来编译旧版本的所有文件时候产生的信息:
% javac -Xlint:unchecked l/Stack.java l/ArrayStack.java \
% l/Stacks.java l/Client.java
l/ArrayStack.java:6: warning: [unchecked] unchecked call to add(E)
as a member of the raw type java.util.List
public void push(Object elt) list.add(elt);
^
1 warning
Here the warning for the use of the generic method add in the legacy method push is issued for reasons similar to those for issuing the previous warning for use of the generic method push from the legacy client.
在这儿,使用旧的push方法中增加的范型的方法产生的警告和从旧的客户端代码中使用范型的push方法产生的警告是相似的。
It is poor practice to configure the compiler to repeatedly issue warnings that you intend to ignore. It is distracting and, worse, it may lead you to ignore warnings that require attention—just as in the fable of the little boy who cried wolf. In the case of pure legacy code, such warnings can be turned off by using the -source 1.4 switch:
配置编译器来反复的产生希望忽略的警告是一个不好的实践。这是一种发疯的行为,更糟的是,这可能导致你忽略那些需要注意的警告,就像那个“狼来了”的寓言。在纯旧的代码的情况下,可以通过使用“-source 1.4”来关闭这样的警告。
% javac -source 1.4 l/Stack.java l/ArrayStack.java \
% l/Stacks.java l/Client.java
This compiles the legacy code and issues no warnings or errors. This method of turning off warnings is only applicable to true legacy code, with none of the features introduced in Java 5, generic or otherwise. One can also turn off unchecked warnings by using annotations, as described in the next section, and this works even with features introduced in Java 5.
这样我们就可以编译旧的代码,并且不产生任何警告或者错误了。这种关闭警告的方法只使用与纯旧的代码,没有使用Java 5中新引入的范型或者其他任何新特性的时候。我们还可以通过使用annotations来关闭unchecked警告,这种方法将在下一个章节中介绍,并且这个方法可以在使用了Java 5特性的时候使用。
Maurice Naftalin is Director of Software Development at Morningside Light Ltd., a software consultancy in the United Kingdom.
Maurice Naftalin是英国的软件咨询公司Morningside Light有限公司的软件开发主管
Philip Wadler is a professor of theoretical computer science at the University of Edinburgh, Scotland, where his research focuses on functional and logic programming.
Philip Wadler是苏格兰爱丁堡大学的理论计算机科学的一个教授,他主要关注functional和logic编程。
原文地址:http://www.onjava.com/pub/a/onjava/excerpt/javagenerics_chap05/index.html
Java 范型与集合类 :演化 ,而不是革命(第一部分)
作者:Maurice Naftalin 和Philip Wadler
Editor's Note: In their new book Java Generics and Collections, authors Maurice Naftalin and Philip Wadler offer the thorough introduction to the syntax and semantics of Java 5.0 generics that you'd expect from an in-depth book on the topic. But they go a step further by considering the real-world, practical concerns of using generics in your work. Unless you're starting a project from scratch in Java 5.0, odds are you have a legacy code-base that does not currently use generics. Is bulk-converting it to use generics in one release a realistic option? Assuming it's not, you need to consider your options for a gradual introduction. Fortunately, the implementation of generics makes this eminently practical, as they describe in Chapter 5, "Evolution, Not Revolution," which we are excerpting over the course of the next two weeks on ONJava.
编者的话:作者Maurice Naftalin和Philip Wadler在他们的新书《Java Generics and Collections》中完整的介绍了Java 5.0 中范型的语法和语义,这些内容正是读者们所期待的关于这个话题的深入的资料。但是此外他们还考虑了读者们在日常工作中使用范型的实际的顾虑。除非你是用Java 5.0 从头开发一个项目,否则的话你将会有很大的可能遇到一堆没有使用范型的旧的代码库。难道在一个release版本中将这样多的代码全部改进为使用范型的是一个现实的选择吗?如果它不是,你就需要考虑逐渐的改进你的代码。幸运的是,Java中范型的实现使得这样的选择是相当可行的,正如两位作者在该书第五章中提到的“演化,而不是革命”,这就是OnJava网站在接下来两周的课程中将不断重复提到的。
One motto underpinning the design of generics for Java is evolution, not revolution. It must be possible to migrate a large, existing body of code to use generics gradually (evolution) without requiring a radical, all-at-once change (revolution). The generics design ensures that old code compiles against the new Java libraries, avoiding the unfortunate situation in which half of your code needs old libraries and half of your code needs new libraries.
在Java中范型的设计过程中,不断提到的设计思想就是“演化,而不是革命”。必须使得人们可以将大量的现存的代码体逐步转移到使用范型上(演化),而不需要突然的变动(革命)。范型的设计原则保证了那些旧的代码可以在新的Java类库下正常编译,避免那种一半代码需要旧的类库而一半代码需要新的类库的情况。
The requirements for evolution are much stronger than the usual backward compatibility. With simple backward compatibility, one would supply both legacy and generic versions for each application; this is exactly what happens in C#, for example. If you are building on top of code supplied by multiple suppliers, some of whom use legacy collections and some of whom use generic collections, this might rapidly lead to a versioning nightmare.
演化的要求比通常的“向下兼容”的要求高了许多。在简单的“向下兼容”情况下,只需要为每一个应用提供遗留的版本和范型的版本,例如在C#语言中就是这种情况。如果需要在多个提供商提供的代码的基础上来构建一个项目,而一些提供商使用旧有的集合类而其他一些使用范型的集合类,这样迅速导致一个关于版本的噩梦。
What we require is that the same client code works with both the legacy and generic versions of a library. This means that the supplier and clients of a library can make completely independent choices about when to move from legacy to generic code. This is a much stronger requirement than backward compatibility; it is called migration compatibility or platform compatibility.
我们所要求的就是相同的客户代码可以同时在旧的和新的范型的库的基础上裕兴。这就意味这提供商和客户可以完全自主的选择何时从旧的类库转移到新的范型类库的使用上;这被叫做“迁移兼容性”或者“平台兼容性”
Java implements generics via erasure, which ensures that legacy and generic versions usually generate identical class files, save for some auxiliary information about types. It is possible to replace a legacy class file by a generic class file without changing, or even recompiling, any client code; this is called binary compatibility.
Java通过“擦除”技术来实现了范型,“擦除”意味这旧的类库和范型的版本一般都生成同样的class文件(除了一些关于类型的辅助信息的不同)。这样就可以用一个范型的class文件来替换就的class文件而不需要做任何变化,甚至是重新编译任何代码;这叫做“二进制文件兼容”
We summarize this with the motto binary compatibility ensures migration compatibility—or, more concisely, erasure eases evolution.
我们用名词“二进制兼容保证了迁移兼容性-或者更确切的说是,擦除简化了演化”来概括这个特性。
This section shows how to add generics to existing code; it considers a small example, a library for stacks that extends the Collections Framework, together with an associated client. We begin with the legacy stack library and client (written for Java before generics), and then present the corresponding generic library and client (written for Java with generics). Our example code is small, so it is easy to update to generics all in one go, but in practice the library and client will be much larger, and we may want to evolve them separately. This is aided by raw types, which are the legacy counterpart of parameterized types.
这个部分将介绍如何向已有的代码中引入范型;我们来看一个小例子,一个继承了集合框架的堆栈库,以及一个相应的客户端代码。我们从这个旧的堆栈的库和代码(用引入范型以前的Java写的)开始,然后展现对应的使用了范型的库和代码(用引入了范型的Java写的)。我们的样例代码很小,所以很容易就可以一次性的更新到范型上,但是在实际中类库和客户端代码都会很大,所以我们可能向要分开的改进他们。这是在raw类型的帮助下完成的,raw类型就是参数类型的旧的对应。
The parts of the program may evolve in either order. You may have a generic library with a legacy client; this is the common case for anyone that uses the Collections Framework in Java 5 with legacy code. Or you may have a legacy library with a generic client; this is the case where you want to provide generic signatures for the library without the need to rewrite the entire library. We consider three ways to do this: minimal changes to the source, stub files, and wrappers. The first is useful when you have access to the source and the second when you do not; we recommend against the third.
程序的各个部分可能以不同的顺序演化。我们可能是拥有一个范型的库和一个旧的客户端,这种情况对于那些使用java5用的范型框架以及旧的代码的人来说是很常见的。或者你可能有一个旧的库和范型的客户端,这种情况是你打算为库提供范型的签名而不用重写整个库。我们考虑以下三种情况:对源代码做最小的变动,存根文件和包装器。第一种情况当你能够获取源代码的时候是很有用的,第二种是你不能获取源代码时候。我们推荐不使用第三种。
In practice, the library and client may involve many interfaces and classes, and there may not even be a clear distinction between library and client. But the same principles discussed here still apply, and may be used to evolve any part of a program independently of any other part.
在实际中,库和客户端代码可能拥有许多的接口和类,并且在库和客户端直面没有明显的区分。但是在这种情形下,我们讨论的原则仍然是适用的,并且可以用于演化程序的一部分,而不影响其他的部分。
Legacy Library with Legacy Client
旧的库和旧的客户端
We begin with a simple library of stacks and an associated client, as presented in Example 5.1. This is legacy code, written for Java 1.4 and its version of the Collections Framework. Like the Collections Framework, we structure the library as an interface Stack (analogous to List), an implementation class ArrayStack (analogous to ArrayList), and a utility class Stacks (analogous to Collections). The interface Stack provides just three methods: empty, push, and pop. The implementation class ArrayStack provides a single constructor with no arguments, and implements the methods empty, push, and pop using methods size, add, and remove on lists. The body of pop could be shorter—instead of assigning the value to the variable, it could be returned directly—but it will be interesting to see how the type of the variable changes as the code evolves. The utility class provides just one method, reverse, which repeatedly pops from one stack and pushes onto another.
我们从一个简单的堆栈的库和相应的客户端来开始,如例5-1所示。这是用java1.4以及其包含的集合类来写的旧的代码。和集合框架一样,我们把类库分为一个Stack接口(和List很相似),以及一个实用类Stacks(和Collections相似)。Stack接口只提供了三个方法:emtpy,push和pop.实现类ArrayStack提供了一个不带参数的构造函数,并实用lists里面的size,add,remove函数实现了empty,pop和push函数。Pop函数体可以更短些的(可以直接返回值的,而不是把值赋值给一个变量),但是现在的这种写法更有趣,我们可以看到随着代码的演化,变量的类型是如何的变化的。工具类只提供了一个方法reverse,这个在一个堆栈上重复pop操作并把值push到另外一个里面去。
Example 5-1. Legacy library with legacy client
l/Stack.java:
interface Stack {
public boolean empty();
public void push(Object elt);
public Object pop();
}
l/ArrayStack.java:
import java.util.*;
class ArrayStack implements Stack {
private List list;
public ArrayStack() { list = new ArrayList(); }
public boolean empty() { return list.size() == 0; }
public void push(Object elt) { list.add(elt); }
public Object pop() {
Object elt = list.remove(list.size()-1);
return elt;
}
public String toString() { return "stack"+list.toString(); }
}
l/Stacks.java:
class Stacks {
public static Stack reverse(Stack in) {
Stack out = new ArrayStack();
while (!in.empty()) {
Object elt = in.pop();
out.push(elt);
}
return out;
}
}
l/Client.java:
class Client {
public static void main(String[] args) {
Stack stack = new ArrayStack();
for (int i = 0; i<4; i++) stack.push(new Integer(i));
assert stack.toString().equals("stack[0, 1, 2, 3]");
int top = ((Integer)stack.pop()).intValue();
assert top == 3 && stack.toString().equals("stack[0, 1, 2]");
Stack reverse = Stacks.reverse(stack);
assert stack.empty();
assert reverse.toString().equals("stack[2, 1, 0]");
}
}
The client allocates a stack, pushes a few integers onto it, pops an integer off, and then reverses the remainder into a fresh stack. Since this is Java 1.4, integers must be explicitly boxed when passed to push, and explicitly unboxed when returned by pop.
客户端代码定义了一个堆栈,将一些int数值放进去,然后将pop一些出来,最后将剩下的数reverse到一个新的Stack中。因为是在java1.4下写的代码,interger变量在push之前必须显式的用Integer包装下,然后在pop之前显式的解包装。
Generic Library with Generic Client
范型的库和范型的客户端
Next, we update the library and client to use generics, as presented in Example 5.2. This is generic code, written for Java 5 and its version of the Collections Framework. The interface now takes a type parameter, becoming Stack<E> (analogous to List<E>), and so does the implementing class, becoming ArrayStack<E> (analogous to ArrayList<E>), but no type parameter is added to the utility class Stacks (analogous to Collections). The type Object in the signatures and bodies of push and pop is replaced by the type parameter E. Note that the constructor in ArrayStack does not require a type parameter. In the utility class, the reverse method becomes a generic method with argument and result of type Stack<T>. Appropriate type parameters are added to the client, and boxing and unboxing are now implicit.
接下来,我们使用范型来更新我们的库和客户端,如例5-2所示。这是用Java 5及其所包含的集合框架来写的代码。现在的Stack接口带了一个类型参数,变成了Stack<E>(和List<E>相似),因此其实现类变成了ArrayStack<E>(和ArrayList<E>相似),但是工具类Stacks没有增加范型参数的(和Collections类相似)。在pop和push函数的申明和体中的Object对象也用类型参数E代替了。注意,ArrayStack的构造函数不需要一个类型参数。在实用类中,reverse方法也成为了一个带类型参数的范型的方法,返回值变成了Stack<T>。在客户端代码中也加入了合适的范型参数,并且int数值的包装和解包装不用显式的写出来了。
Example 5-2. Generic library with generic client
g/Stack.java:
interface Stack<E> {
public boolean empty();
public void push(E elt);
public E pop();
}
g/ArrayStack.java:
import java.util.*;
class ArrayStack<E> implements Stack<E> {
private List<E> list;
public ArrayStack() { list = new ArrayList<E>(); }
public boolean empty() { return list.size() == 0; }
public void push(E elt) { list.add(elt); }
public E pop() {
E elt = list.remove(list.size()-1);
return elt;
}
public String toString() { return "stack"+list.toString(); }
}
g/Stacks.java:
class Stacks {
public static <T> Stack<T> reverse(Stack<T> in) {
Stack<T> out = new ArrayStack<T>();
while (!in.empty()) {
T elt = in.pop();
out.push(elt);
}
return out;
}
}
g/Client.java:
class Client {
public static void main(String[] args) {
Stack<Integer> stack = new ArrayStack<Integer>();
for (int i = 0; i<4; i++) stack.push(i);
assert stack.toString().equals("stack[0, 1, 2, 3]");
int top = stack.pop();
assert top == 3 && stack.toString().equals("stack[0, 1, 2]");
Stack<Integer> reverse = Stacks.reverse(stack);
assert stack.empty();
assert reverse.toString().equals("stack[2, 1, 0]");
}
}
In short, the conversion process is straightforward: just add a few type parameters and replace occurrences of Object by the appropriate type variable. All differences between the legacy and generic versions can be spotted by comparing the highlighted portions of the two examples. The implementation of generics is designed so that the two versions generate essentially equivalent class files. Some auxiliary information about the types may differ, but the actual bytecodes to be executed will be identical. Hence, executing the legacy and generic versions yields the same results. The fact that legacy and generic sources yield identical class files eases the process of evolution, as we discuss next.
简而言之,这个转换的过程是直接的:仅仅增加了一些类型参数,并把原先的Object用合适的类型变量代替了。
Generic Library with Legacy Client
范型的类库和旧的客户端
Now let's consider the case where the library is updated to generics while the client remains in its legacy version. This may occur because there is not enough time to convert everything all at once, or because the library and client are controlled by different organizations. This corresponds to the most important case of backward compatibility, where the generic Collections Framework of Java 5 must still work with legacy clients written against the Collections Framework in Java 1.4.
现在让我们考虑这样的一种情况:类库被更新到范型了,但是客户端仍然是旧的版本。这种情况的发生可能是因为没有足够的时间同时转换所有的代码,或是因为类库和客户端是由不同的公司控释的。这种情况就是向后兼容中最重要的一种情形了,即Java 5的范型集合框架必须仍然能够与用Java 1.4中的集合框架写的旧的客户端代码一起工作。
In order to support evolution, whenever a parameterized type is defined, Java also recognizes the corresponding unparameterized version of the type, called a raw type. For instance, the parameterized type Stack<E> corresponds to the raw type Stack, and the parameterized type ArrayStack<E> corresponds to the raw type ArrayStack.
为了能够支持演化,无论何时定义一个参数化的类型,Java必须能够识别出对应的未参数化版本的类型,叫做raw类型。例如,参数化的类型Stack<E>对应的raw类型Stack,以及ArrayStack<E>对应的ArrayStack。
Every parameterized type is a subtype of the corresponding raw type, so a value of the parameterized type can be passed where a raw type is expected. Usually, it is an error to pass a value of a supertype where a value of its subtype is expected, but Java does permit a value of a raw type to be passed where a parameterized type is expected—however, it flags this circumstance by generating an unchecked conversion warning. For instance, you can assign a value of type Stack<E> to a variable of type Stack, since the former is a subtype of the latter. You can also assign a value of type Stack to a variable of type Stack<E>, but this will generate an unchecked conversion warning.
每一个参数化的类型都有一个对应的raw类型的子类型(subtype),一次老公一个参数化类型的值可以传递到任何期待raw类型的地方。通常,向一个期待subtype的地方传递一个supertype的值的时候会发生错误,但是Java的确允许向一个其他参数化类型的地方传递一个raw类型的值。然而,在这种情况下需要产生一个“unchecked conversion”的警告。例如,可以将一个Stack<E>的值赋给Stack类型,因为前者是后者的一个子类型。同样可以将一个Stack类型的值赋给类型为Stack<E>的变量,但是这将产生一个“unchecked conversion”的警告。
To be specific, consider compiling the generic source for Stack<E>, ArrayStack<E>, and Stacks from Example 5.2 (say, in directory g) with the legacy source for Client from Example 5.1 (say, in directory l). Sun's Java 5 compiler yields the following message:
具体来说,我们来看一下在编译例5-2(在目录g下)中Stack<E>,ArrayStack<E>和Stacks类的范型代码以及例5-1(在目录l中)中的旧的客户端代码这样一种情形。Sun的Java 5编译器将会产生如下的信息:
% javac g/Stack.java g/ArrayStack.java g/Stacks.java l/Client.java
Note: Client.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
The unchecked warning indicates that the compiler cannot offer the same safety guarantees that are possible when generics are used uniformly throughout. However, when the generic code is generated by updating legacy code, we know that equivalent class files are produced from both, and hence (despite the unchecked warning) running a legacy client with the generic library will yield the same result as running the legacy client with the legacy library. Here we assume that the only change in updating the library was to introduce generics, and that no change to the behavior was introduced, either on purpose or by mistake.
Unchecked警告指出编译器在范型没有一致的使用时不能提供相同的安全保证。然而,当范型的代码是通过更新旧的代码来产生的时候,我们知道相应的class文件是新的和旧的代码同时产生的,因此(尽管有unchecked警告),在范型的库上运行旧的客户端代码将会和在旧的库上运行旧的代码产生相同的结果。在这里,我们假设在更新类库的过程中,我们只是引入了范型,而没有对函数做任何功能上的改变,不管是故意的还是犯了错误。
If we follow the suggestion above and rerun the compiler with the appropriate switch enabled, we get more details:
% javac -Xlint:unchecked g/Stack.java g/ArrayStack.java \
% g/Stacks.java l/Client.java
l/Client.java:4: warning: [unchecked] unchecked call
to push(E) as a member of the raw type
Stack
for (int i = 0; i<4; i++) stack.push(new Integer(i));
^
l/Client.java:8: warning: [unchecked] unchecked conversion
found : Stack
required: Stack<E>
Stack reverse = Stacks.reverse(stack);
^
l/Client.java:8: warning: [unchecked] unchecked method invocation:
<E>reverse(Stack<E>) in Stacks is applied to (Stack)
Stack reverse = Stacks.reverse(stack);
^
3 warnings
Not every use of a raw type gives rise to a warning. Because every parameterized type is a subtype of the corresponding raw type, but not conversely, passing a parameterized type where a raw type is expected is safe (hence, no warning for getting the result from reverse), but passing a raw type where a parameterized type is expected issues a warning (hence, the warning when passing an argument to reverse); this is an instance of the Substitution Principle. When we invoke a method on a receiver of a raw type, the method is treated as if the type parameter is a wildcard, so getting a value from a raw type is safe (hence, no warning for the invocation of pop), but putting a value into a raw type issues a warning (hence, the warning for the invocation of push); this is an instance of the Get and Put Principle.
不是所有使用raw类型的地方都回产生一个警告。因为每一个参数化的类型都是对应的raw类型的一个子类型,但是相反的情况不不是一样的。向期待一个raw类型的地方传递一个参数化的类型是安全的(因此reverse函数的结果不会产生警告),但是向期待一个参数化类型的地方那个传递一个raw类型将会产生一个警告(因此,向reverse函数传递一个参数将会产生警告);这就是置换原则的一个例子。当我们调用一个需要raw类型的函数,这个方法将被这样处理:类型参数是一个wildcard,因此从一个raw类型获取一个值是安全的(因此调用pop函数没有产生任何警告),但是将一个值放到raw类型中将会产生一个警告(因此调用push函数将会产生一个警告)。这就是“Get/Put”原则的一个例子。
Even if you have not written any generic code, you may still have an evolution problem because others have generified their code. This will affect everyone with legacy code that uses the Collections Framework, which has been generified by Sun. So the most important case of using generic libraries with legacy clients is that of using the Java 5 Collections Framework with legacy code written for the Java 1.4 Collections Framework.
即使你没有写任何范型的代码,你仍然可能碰到演化的问题,因为其他人可能范型了他们的代码。这样影响任何使用Sun公司范型化了的集合框架书写的代码。因此使用范型的类库和旧的客户端代码的情形中最重要的就是使用Java 5的集合框架和用Java 1.4集合框架书写的旧的代码。
In particular, applying the Java 5 compiler to the legacy code in Example 5.1 also issues unchecked warnings, because of the uses of the generified class ArrayList from the legacy class ArrayStack. Here is what happens when we compile legacy versions of all the files with the Java 5 compiler and libraries:
特别的,用java 5的编译器来编译例5.1中的旧的代码也会产生unchecked警告,因为在旧的ArrayStack类中使用了范型的ArrayList。下面就是我们用Java 5编译器来编译旧版本的所有文件时候产生的信息:
% javac -Xlint:unchecked l/Stack.java l/ArrayStack.java \
% l/Stacks.java l/Client.java
l/ArrayStack.java:6: warning: [unchecked] unchecked call to add(E)
as a member of the raw type java.util.List
public void push(Object elt) list.add(elt);
^
1 warning
Here the warning for the use of the generic method add in the legacy method push is issued for reasons similar to those for issuing the previous warning for use of the generic method push from the legacy client.
在这儿,使用旧的push方法中增加的范型的方法产生的警告和从旧的客户端代码中使用范型的push方法产生的警告是相似的。
It is poor practice to configure the compiler to repeatedly issue warnings that you intend to ignore. It is distracting and, worse, it may lead you to ignore warnings that require attention—just as in the fable of the little boy who cried wolf. In the case of pure legacy code, such warnings can be turned off by using the -source 1.4 switch:
配置编译器来反复的产生希望忽略的警告是一个不好的实践。这是一种发疯的行为,更糟的是,这可能导致你忽略那些需要注意的警告,就像那个“狼来了”的寓言。在纯旧的代码的情况下,可以通过使用“-source 1.4”来关闭这样的警告。
% javac -source 1.4 l/Stack.java l/ArrayStack.java \
% l/Stacks.java l/Client.java
This compiles the legacy code and issues no warnings or errors. This method of turning off warnings is only applicable to true legacy code, with none of the features introduced in Java 5, generic or otherwise. One can also turn off unchecked warnings by using annotations, as described in the next section, and this works even with features introduced in Java 5.
这样我们就可以编译旧的代码,并且不产生任何警告或者错误了。这种关闭警告的方法只使用与纯旧的代码,没有使用Java 5中新引入的范型或者其他任何新特性的时候。我们还可以通过使用annotations来关闭unchecked警告,这种方法将在下一个章节中介绍,并且这个方法可以在使用了Java 5特性的时候使用。
Maurice Naftalin is Director of Software Development at Morningside Light Ltd., a software consultancy in the United Kingdom.
Maurice Naftalin是英国的软件咨询公司Morningside Light有限公司的软件开发主管
Philip Wadler is a professor of theoretical computer science at the University of Edinburgh, Scotland, where his research focuses on functional and logic programming.
Philip Wadler是苏格兰爱丁堡大学的理论计算机科学的一个教授,他主要关注functional和logic编程。
发表评论
-
什么样的架构才是清晰的架构?这个有什么需要注意的?(zz from 水木)
2011-10-13 13:00 850发信人: zms (小美), ... -
java面试
2011-10-12 23:52 01、try{}里有一个return语句,那么紧跟在这个try后 ... -
JAVA与C++::关于JNI中文字符串操作问题总结
2011-10-12 23:26 929JAVA与C++::关于JNI中文字符串操作问题总结 Linu ... -
版本控制与CVS
2011-10-12 23:09 8662007-12-29 来源:chinaunix.net ... -
学习笔记 - java.util.concurrent 多线程框架 [转]
2011-10-12 22:58 688java concurrent 多线程 pac ... -
java反射机制
2011-10-12 22:39 746java反射机制 2007年02月2 ... -
转载: 五种常见开源协议的比较
2011-09-28 11:29 880BSD开源协议 BSD开源协议是一个给于使用者很 ...
相关推荐
Java 范型Java 范型
#### 一、引言:Java范型的引入 在Java的发展历程中,范型(Generics)的引入标志着语言设计上的一个重要里程碑。自Java 1.5发布以来,范型成为了Java语言的重要特性之一,极大地提升了代码的类型安全性与复用性。...
本书第1章分析多范型设计的必要性,第2章和第3章分别介绍共同性和差异性分析。第4章解释如何使用领域分析来找到应用领域中的抽象。第5章说明怎样将领域工程的原则用作对象范型的抽象技术的基础。第6章应用“分析”...
- **容器类的使用**:Java集合框架中的类如`ArrayList`、`LinkedList`等都支持泛型,确保元素的类型安全。 总之,Java泛型通过引入类型参数和类型安全机制,极大地提高了代码的质量和可维护性,降低了类型转换的...
第一部分详细讨论了分布式系统的原理、概念和技术,其中包括通信、进程、命名、同步、一致性和复制、容错以及安全。第二部分给出了一些实际的分布式系统:基于对象的分布式系统、分布式文件系统、基于文档的分布式...
第一部分详细讨论了分布式系统的原理、概念和技术,其中包括通信、进程、命名、同步、一致性和复制、容错以及安全。第二部分给出了一些实际的分布式系统:基于对象的分布式系统、分布式文件系统、基于文档的分布式...
总的来说,范型和容器类是Java集合框架的关键组成部分,它们为处理各种数据结构提供了强大的支持。理解并熟练运用这些概念,能够帮助开发者编写更加高效、安全和易于维护的代码。在实际开发中,根据需求选择合适的...
第2~9章讨论的是分布式系统的的原理、概念和技术,包括通信、进程、命名、同步化、一致性和复制、容错性以及安全性等,而分布式应用的开发方法(即范型)在第10~13章中进行了讨论。但是,与前一版不同的是,我们...
《分布式系统原理与范型(第二版)》这本书深入浅出地介绍了这一主题,旨在帮助读者理解分布式系统的概念、设计模式以及实际应用。现在我们来详细探讨一下该书可能涵盖的关键知识点。 1. **分布式系统定义**:...
在Java编程语言中,泛型(Generics)是一种强大的特性,它允许我们在编写代码时指定容器(如集合)可以存储的数据类型。这提高了代码的安全性和效率,因为编译器可以在编译时检查类型,避免了运行时...
在具体章节构成上,《分布式系统原理与范型》主要由两大部分内容组成:第一部分讨论了分布式系统的基本原理和关键技术,由第1章至第7章组成;第二部分则着重于分布式系统的实际应用范例,从第9章至第12章展开。这样...
分布式系统原理与范型是IT领域中至关重要的一个主题,特别是在云计算、大数据处理以及现代互联网服务的背景下,理解和掌握分布式系统的概念与实践至关重要。本书作为第二版,深度探讨了这一领域的核心理论和常见模式...
第一,用户和应用程序与系统交互时,各种计算机和通信方式的差异是被隐藏的。第二,用户和应用程序能够在任何时间和任何地点以一致和统一的方式与分布式系统交互。这种一致性是分布式系统设计中非常重要的目标。 3....
Java泛型是Java 5版本引入的一个重要特性,极大地增强了代码的类型安全性和可读性。泛型允许我们在编写代码时指定容器(如List、Set、Map等集合类)能够存储的数据类型,从而避免了不必要的类型转换,并在编译时期就...
Java泛型是Java 5版本引入的一个重要特性,极大地增强了代码的类型安全性和效率。泛型允许我们在编写类、接口和方法时指定一种或多种类型参数,使得代码能够处理多种不同类型的对象,同时在编译时进行严格的类型检查...
《范型程序设计与 STL》是一本深入探讨C++编程中的关键概念和技术的书籍,主要聚焦于范型(Generic Programming)和标准模板库(Standard Template Library,简称STL)。范型编程是一种强大的软件开发方法,它允许...
在Java编程语言中,集合与范型是两个非常重要的概念,它们构成了Java处理对象数组的基础。本实验将深入探讨这两个主题,旨在帮助你理解和熟练应用它们。以下是对这两个概念的详细解释。 首先,集合(Collections)...
分布式系统原理与范型是计算机科学中的一个重要领域,它涉及到多台计算机协同工作,共同处理一个任务或数据,以提供高可用性、可扩展性和性能优化。这些课件旨在为学习者提供一个全面且系统的分布式系统知识框架。...
分布式系统是一种由多台计算机组成的网络系统,这些计算机在用户看来就像是一个单一的系统。它们通过网络互相通信和协调工作,以共同完成任务。分布式系统的设计和实现涉及许多核心概念和原则,例如透明性、开放性、...