论坛首页 Java企业应用论坛

自己翻译的Java.In.A.Nutshell.5th中泛型一章,欢迎拍砖把文章砸的漂亮一些

浏览 20742 次
该帖已经被评为精华帖
作者 正文
   发表时间:2006-09-21  
做些说明吧:转网页的东西,改起来稍觉得的麻烦,就可能滞后。不过pdf文件会不定时的及时更新

4.1. Generic Types
Generic types and methods are the defining new feature of Java 5.0. A generic type is defined using one or more type variables and has one or more methods that use a type variable as a placeholder for an argument or return type. For example, the type java.util.List<E> is a generic type: a list that holds elements of some type represented by the placeholder E. This type has a method named add(), declared to take an argument of type E, and a method named get(), declared to return a value of type E.

4.1. 泛型

泛类型和泛型方法是Java5.0中的新特性。一种泛类型用一个或多个泛型变量定义,可以有一个或多个,泛型变量做参数占位符或做返回值的方法。例如,类型 java.util.List<E> 是一种泛类型:一个 list ,它的元素类型是E 这个占位符表示的类型。这个类型有一个叫 add() 的方法,有一个参数类型为 E ,有一个名叫 get() 的方法,返回一个类型为 E 的值。

In order to use a generic type like this, you specify actual types for the type variable (or variables), producing a parameterized type such as List<String>.[1] The reason to specify this extra type information is that the compiler can provide much stronger compile-time type checking for you, increasing the type safety of your programs. This type checking prevents you from adding a String[], for example, to a List that is intended to hold only String objects. Also, the additional type information enables the compiler to do some casting for you. The compiler knows that the get( ) method of a List<String> (for example) returns a String object: you are no longer required to cast a return value of type Object to a String.

为了可以象往常一样使用泛类型,你需要为泛型变量(或是变量)指定一个实际的类型,产生一个参数化类型,就象 List<String> 。这样做的原因是,为编译器提供一个特定的类型信息,让它可以在编译期为你做类型检查,这样可以大大增加你程序的类型安全。例如,有一个 List 打算容纳 String 类型的对象,这种类型安全检查阻止你增加一个String[] 的元素。而且,这附加的类型信息使编译器帮你做些造型的活儿。例如,编译器知道 List<String> 的 get() 方法返回一个String类型对象,你不再需要把Object类型的返回值造型成一个String类型的对象。

[1] Throughout this chapter, I've tried to consistently use the term " generic type" to mean a type that declares one or more type variables and the term "parameterized type" to mean a generic type that has had actual type arguments substituted for its type varaiables. In common usage, however, the distinction is not a sharp one and the terms are sometimes used interchangeably.

[1] 整个这章,我们将统一使用这些术语。"泛类型":意味着一个类型,可以声明一个或多个泛型变量。"参数化类型",意味着一个(运行期的)泛类型,表示它的泛型变量被实际类型做为参数值替换了。但在通常的应用中,两个术语的差别不是太明显,有时还可以替换。

The collections classes of the java.util package have been made generic in Java 5.0, and you will probably use them frequently in your programs. Typesafe collections are the canonical use case for generic types. Even if you never define generic types of your own and never use generic types other than the collections classes in java.util, the benefits of typesafe collections are so significant that they justify the complexity of this major new language feature.

在Java 5.0中,包java.util中的Collection类都已经被泛化了,你可能会在程序中频繁的用到它们。类型安全的Collection是应用泛类型的典范。可能你还从没有定义过自己的泛类型,也从没用过Collection(在包 java.uitl中)之外的泛类型,但类型安全的Collection的好处是显而易见的,它将证实这个新的,复杂的,主要且重要的语言特性。

We begin by exploring the basic use of generics in typesafe collections, then delve into more complex details about the use of generic types. Next we cover type parameter wildcards and bounded wildcards. After describing how to use generic types, we explain how to write your own generic types and generic methods. Our coverage of generics concludes with a tour of important generic types in the core Java API. It explores these types and their use in depth in order to provide a deeper understanding of how generics work.

我们将从探索类型安全的Collection的基本泛化用法开始,然后深入泛类型用法更为复杂的细节。接下来,我们将覆盖泛型参数通配符和边界通配符。再说明怎么用泛类型,我们将阐述怎样写自己的泛类型和泛型方法。我们涉及的泛型知识包括了 Java API 中关于泛类型的主要部分。深入的探讨了这些类型和他们的用法,为的是更深入的理解泛型是如何工作的。

4.1.1. Typesafe Collections
4.1.1.类型安全的Collection

The java.util package includes the Java Collections Framework for working with sets and lists of objects and mappings from key objects to value objects. Collections are covered in Chapter 5. Here, we discuss the fact that in Java 5.0 the collections classes use type parameters to identify the type of the objects in the collection. This is not the case in Java 1.4 and earlier. Without generics, the use of collections requires the programmer to remember the proper element type for each collection. When you create a collection in Java 1.4, you know what type of objects you intend to store in that collection, but the compiler cannot know this. You must be careful to add elements of the appropriate type. And when querying elements from a collection, you must write explicit casts to convert them from Object to their actual type. Consider the following Java 1.4 code:

在包 java.util 中包含了 Java 的Collection框架(set、list--关于对象、map--关于关键字对象和值对象的对)。 Collection 将在第五章讨论。这里我们只讨论有关在 Java 5.0 的Collection类中,用泛型参数标识Collection对象类型的内容。这在 Java 1.4 或是更早的 Java 版本中,没有这个用法。在没有泛型应用Collection的时候,需要程序员正确记得每个Collection的每个元素的类型。当我们建立一个 Java 1.4 的Collection时,你知道打算把什么样的类型对象放入这个Collection中,但是编译器不知道这些。你增加元素时必须小心正确性。并且当获取一个Collection的元素时,你必须显示的造型对象Object 到它们实际的类型。思考在 Java 1.4 中的如下代码:
public static void main(String[] args) {
    // This list is intended to hold only strings.
      // 这个list仅容纳字符串对象。
    // The compiler doesn't know that so we have to remember ourselves.
    // 编译器不知道这些,我们必须自己记住。
    List wordlist = new ArrayList();  
 
    // Oops! We added a String[] instead of a String.
    // 哎哟! 我们增加了个String[]元素,而不是String类型的。
    // The compiler doesn't know that this is an error.
    // 编译器是不知道那个错误的
    wordlist.add(args);
 
    // Since List can hold arbitrary objects, the get() method returns
// Object.  Since the list is intended to hold strings, we cast the
// return value to String but get a ClassCastException because of
// the error above.
// 因为 List 能容纳任意的对象,方法仅返回Object的对象。因为这个 list 打算
// 存储字符串的,我们把返回值转换成字符串,但却因为上面的错误得到了一个
// ClassCastException的异常。
    
    String word = (String)wordlist.get(0);
}


Generic types solve the type safety problem illustrated by this code. List and the other collection classes in java.util have been rewritten to be generic. As mentioned above, List has been redefined in terms of a type variable named E that represents the type of the elements of the list. The add( ) method is redefined to expect an argument of type E instead of Object and get( ) has been redefined to return E instead of Object.

泛类型就是解决上面示例的安全性问题的。在包java.utiil中的List和其他的Collection类都应用泛型重写过了。在上面说到的,List已被应用泛型变量 E (表示 list 中元素的类型) 重新定义过了, add() 方法被重定义成,要求传递一个类型为E而不是Object的参数;get()方法被重定义成返回一个类型为E而不是 Object 的值。

In Java 5.0, when we declare a List variable or create an instance of an ArrayList, we specify the actual type we want E to represent by placing the actual type in angle brackets following the name of the generic type. A List that holds strings is a List<String>, for example. Note that this is much like passing an argument to a method, except that we use types rather than values and angle brackets instead of parentheses.

在 Java 5.0 ,当我们声明一个List变量或是创建一个ArrayList的实例时,我们指定一个实际少泛型替换类型占位符E,这个实际类型被放到泛类型名后的尖括号内。例如,一个容纳字符串类型的List就这样写List<String>。注意,这个很象一个有合法参数的方法,除了我们用类型替换了值,用尖括号替换了圆括号。

The elements of the java.util collection classes must be objects; they cannot be used with primitive values. The introduction of generics does not change this. Generics do not work with primitives: we can't declare a Set<char>, or a List<int> for example. Note, however, that the autoboxing and autounboxing features of Java 5.0 make working with a Set<Character> or a List<Integer> just as easy as working directly with char and int values. (See Chapter 2 for details on autoboxing and autounboxing).

包java.util中的类的元素必须是对象;它们不能用原生类型。引进泛型并不能改变这一点。泛型应用不是针对原生类型的。例如,我们不能声明Set<char>或是List<int>。但是,注意了,由于Java 5.0自动装箱和自动拆箱的新特性,我们可以应用Set<Character>或是List<Integer>,而直接使用char和int的值(参见第二章关于自动装箱和自动拆箱的详细讲述)。

In Java 5.0, the example above would be rewritten as follows:

在Java 5.0,上面的例子可能被重写成如下形式:
public static void main(String[] args) {
    // This list can only hold String objects
// 这个 list 仅能容纳字符串对象
    List<String> wordlist = new ArrayList<String>();
 
    // args is a String[], not String, so the compiler won't let us do this
      // args 是 String [] 类型的,不是String,因此编译器不让那么做。
    wordlist.add(args);  // Compilation error!  // 编译错误!
 
    // We can do this, though.  
// Notice the use of the new for/in looping statement
// 然后,我们可以这么做
    // 注意这个新的for/in的用法
    for(String arg : args) wordlist.add(arg);
 
    // No cast is required.  List<String>.get() returns a String.
      // 不需要造型转换。 List<String>.get() 返回一个字符串。
    String word = wordlist.get(0);
}


Note that this code isn't much shorter than the nongeneric example it replaces. The cast, which uses the word String in parentheses, is replaced with the type parameter, which places the word String in angle brackets. The difference is that the type parameter has to be declared only once, but the list can be used any number of times without a cast. This would be more apparent in a longer example. But even in cases where the generic syntax is more verbose than the nongeneric syntax, it is still very much worth using generics because the extra type information allows the compiler to perform much stronger error checking on your code. Errors that would only be apparent at runtime can now be detected at compile time. Furthermore, the compilation error appears at the exact line where the type safety violation occurs. Without generics, a ClassCastException can be thrown far from the actual source of the error.

可以看到这段代码并不比非泛型的短多少。圆号形式的造型转换被尖括号的泛型参数替代了。不同的是,泛型参数只声明了一次,而list可以用很多次,且不用造型转换。在比较长的代码中这是显而易见的。但是,泛型语法比非泛型语法要繁琐很多,可是这是非常值得的,因为附加的类型信息允许编译器对你的代码执行强类型错误检查。这样运行期的错误可以在编译时就被找到。特别是,编译期里违反类型安全错误可以精确到行。若无泛型,一个ClassCastException的异常将会在源代码中很远的位置抛出。

Just as methods can have any number of arguments, classes can have more than one type variable. The java.util.Map interface is an example. A Map is a mapping from key objects to value objects. The Map interface declares one type variable to represent the type of the keys and one variable to represent the type of the values. As an example, suppose you want to map from String objects to Integer objects:

正如方法可以很多的参数一样,类可以有不止一种泛型变量。java.util.Map接口就是一个例子。一个Map是一个关键字对象和值对象的对。Map接口声明了一个表示关键字对象类型的泛型变量和一个表示值对象类型的泛型变量。例如,我们假定你有一个关键字类型为String,值类型为Integer的map:
public static void main(String[] args) {
    // A map from strings to their position in the args[] array
      // 一个关键字为字符串类型,表示args[] 数组位置的map
    Map<String,Integer> map = new HashMap<String,Integer>();
 
    // Note that we use autoboxing to wrap i in an Integer object.
      // 注意了,我们用自动装箱把i放进Integer的对象中。
    for(int i=0; i < args.length; i++) map.put(args[i], i);  
 
    // Find the array index of a word.  Note no cast is required!
      // 查找数组中的一个单词。注意,不需要造型!
    Integer position = map.get("hello");
 
    // We can also rely on autounboxing to convert directly to an int,
    // but this throws a NullPointerException if the key does not exist 
    // in the map
      // 但是这个关键字在 map 中不存在,会抛出一个NullPointerException异常
     // 我也可以直接依靠自动拆箱把对象转化成int的值。
 
    int pos = map.get("world");
}


A parameterized type like List<String> is itself a type and can be used as the value of a type parameter for some other type. You might see code like this:

一个象List<String>的 参数化类型是它自己的一个类型,可以作为值用作其他类型的参数化类型。你可以看下面的代码:
// Look at all those nested angle brackets!
// 注意看这些嵌套的尖括号!
Map<String, List<List<int[]>>> map = getWeirdMap();
 
// The compiler knows all the types and we can write expressions
// 编译器我们写的表达式的所有类型
// like this without casting.  We might still get NullPointerException
// 象这种没有造型转换的。当然,我们仍然可能在运行时,得到 NullPointerException 异常
// or ArrayIndexOutOfBounds at runtime, of course.
// 或是 ArrayIndexOutOfBounds 异常
int value = map.get(key).get(0).get(0)[0];
 
// Here's how we break that expression down step by step.
// 在这我们怎么样一步步断句。
List<List<int[]>> listOfLists = map.get(key);
List<int[]> listOfIntArrays = listOfLists.get(0);
int[] array = listOfIntArrays.get(0);
int element = array[0];


In the code above, the get( ) methods of java.util.List<E> and java.util.Map<K,V> return a list or map element of type E and V respectively. Note, however, that generic types can use their variables in more sophisticated ways. Look up List<E> in the reference section of this book, and you'll find that its iterator( ) method is declared to return an Iterator<E>. That is, the method returns an instance of a parameterized type whose actual type parameter is the same as the actual type parameter of the list. To illustrate this concretely, here is a way to obtain the first element of a List<String> without calling get(0).

在上面的代码里, java.util.List<E> 和 java.util.Map<K,V> 的 get() 方法返回一个 list 或是类型分别为 E 和 V 的 map 。但是注意,这些泛类型能用更高级的方式表示他们的变量。参见本书有关 List<E> 的参考说明,你将发现它的 iterator() 方法被声明成返回一个 Iterator<E> 类型。那就是,方法返回一个参数化类型的实例,它的实际泛型参数和 list 的实际泛型参数相同。为了具体证明这一点,这有一个方法可以得到 List<String> 的第一个元素而不用调用 get(0) 方法
List<String> words = // ...initialized elsewhere...  ...其他什么地方初始化...
Iterator<String> iterator = words.iterator();
String firstword = iterator.next();


4.1.2. Understanding Generic Types
4.1.2.理解泛类型
This section delves deeper into the details of generic type usage, explaining the following topics:

·         The consequences of using generic types without type parameters

·         The parameterized type hierarchy

·         A hole in the compile-time type safety of generic types and a patch to ensure runtime type safety

·         Why arrays of parameterized types are not typesafe

这部分将深入 generic type 的详细用法,简述以下主题:

应用没有泛型参数的泛类型的结果
参数化类型的层次
一个编译期的泛类型漏洞和一个保证运行期类型安全的补救办法
为什么数组的参数化类型是不安全的
4.1.2.1 Raw types and unchecked warnings
4.1.2.1原始类型和未检查的警告

Even though the Java collection classes have been modified to take advantage of generics, you are not required to specify type parameters to use them. A generic type used without type parameters is known as a raw type. Existing pre-5.0 code continues to work: you simply write all the casts that you're already used to writing, and you put up with some pestering from the compiler. Consider the following code that stores objects of mixed types into a raw List:

尽管 Java Collection类引入了泛型的优点,但是你不需要在用它们的时候指明泛型参数。没有泛型参数的泛类型将作为原始类型。现有的 5.0 以前的代码可以继续工作:你可以象以前一样简单的写所有的造型转换,然后忍受一些编译器的纠缠。思考下面用原始 List 存储混合类型对象的代码:
List l = new ArrayList();
l.add("hello");  
l.add(new Integer(123));
Object o = l.get(0);


This code works fine in Java 1.4. If we compile it using Java 5.0, however, javac compiles the code but prints this complaint:

在Java 1.4下,这些代码会工作的很好。但如果用 Java 5.0 编译, javac 编译代码但会列出这个抱怨:

Note: Test.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
注意:Test.java 使用了一个未检查或是不安全的操作。
注意:重新用 -Xlint:unchecked 参数编译查看详细信息。


When we recompile with the -Xlint option as suggested, we see these warnings:

当我们用建议的 -Xlint 选项编译后,看到了这样的警告:
Test.java:6: warning: [unchecked]
    unchecked call to add(E) as a member of the raw type java.util.List
        l.add("hello");  
         ^
Test.java:7: warning: [unchecked]
    unchecked call to add(E) as a member of the raw type java.util.List
        l.add(new Integer(123));
         ^


The compiler warns us about the add( ) calls because it cannot ensure that the values being added to the list have the correct types. It is letting us know that because we've used a raw type, it cannot verify that our code is typesafe. Note that the call to get( ) is okay because it is extracting an element that is already safely in the list.

编译器给了我们关于调用 add() 方法的警告,因为它不能确定被增加到 list 中的值有正确的类型。这让我们知道我们用的是一个原始类型,它不能验证我们的代码是安全的。注意, get() 方法的调用是对的,因为它正在析取的元素已经安全的存储在 list 中了。

If you get unchecked warnings on files that do not use any of the new Java 5.0 features, you can simply compile them with the -source 1.4 flag, and the compiler won't complain. If you can't do that, you can ignore the warnings, suppress them with an @SuppressWarnings("unchecked") annotation (see Section 4.3 later in this chapter) or upgrade your code to specify a type parameter.[2] The following code, for example, compiles with no warnings and still allows you to add objects of mixed types to the list:

如果你的文件因为没有用到 Java 5.0 的新特性,而得到 unchecked 警告;你可以简单用的加 -source 1.4 参数编译,这样编译器将不再抱怨。如果你不那样做,你可以忽略这些警告,也可用 annotation (参见本章稍后的4.3部分)中的 @SuppressWarnings("uncheckd") 压制这些警告,或者,用泛型参数更新你的代码。例如下面的代码,当有你增加混合对象到 list 时,编译器将不再发出警告:

[2] At the time of this writing, javac does not yet honor the @SuppressWarnings annotation. It is expected to do so in Java 5.1.

[2] 就在本书写作时 ,javac 还不能兑现 annotation 的@SuppressWarnings操作。它将可以在Java 5.1版本中实现。
List<Object> l = new ArrayList<Object>();
l.add("hello");  
l.add(123);              // autoboxing 自动装箱
Object o = l.get(0);


4.1.2.2 The generic type hierarchy
parameterized types form a type hierarchy, just as normal types do. The hierarchy is based on the base type, however, and not on the type of the parameters. Here are some experiments you can try:

4.1.2.2 参数化类型的层次结构

参数化类型形成了一个典型的层次结构,就象通常类型一样的。但是这种分层是基于泛型参数的类型的,而不是基于参数化类型变量的类型的。这里有些试验你可以试一下
ArrayList<Integer> l = new ArrayList<Integer>();
List<Integer> m = l;                            // okay
Collection<Integer> n = l;                      // okay
ArrayList<Number> o = l;                        // error
Collection<Object> p = (Collection<Object>)l;   // error, even with cast 
                                                // 错误,发生在造型那里


A List<Integer> is a Collection<Integer>, but it is not a List<Object>. This is nonintuitive, and it is important to understand why generics work this way. Consider this code:

一个 List<integer> 是一个 Collection<Ineger> ,但不是 List<Object> 。这个不太直观,但对于理解泛型是如何工作又是非常重要的。思考下面的代码
List<Integer> li = new ArrayList<Integer>();
li.add(123);
 
// The line below will not compile.  But for the purposes of this
// thought-experiment, assume that it does compile and see how much
// trouble we get ourselves into.
// 下面的这行是不能编译的。但是依照我们通常的经验和观点,假定它能编译,看看
// 会有什么样的麻烦
List<Object> lo = li;  
 
// Now we can retrieve elements of the list as Object instead of Integer
// 现在我用 Object 检索 list 中的元素,而不是用Integer
Object number = lo.get(0);
 
// But what about this?
// 但这是什么?
lo.add("hello world");
 
// If the line above is allowed then the line below throws ClassCastException
// 如果上面这行是对的,那么下面一行会抛出 ClassCastException 异常
Integer i = li.get(1);  // Can't cast a String to Integer!



This then is the reason that a List<Integer> is not a List<Object>, even though all elements of a List<Integer> are in fact instances of Object. If the conversion to List<Object> were allowed, non-Integer objects could be added to the list.

这就是为什么 List<Integer> 不是 List<Object> ,虽说所有的 List<Integer> 元素是 Object 的实例。如果这种转化成 List<Object> 的操作是允许的,那么 non-Integer 的对象就会被允许加入到 list 中。

4.1.2.3 Runtime type safety
As we've seen, a List<X> cannot be converted to a List<Y>, even when X can be converted to Y. A List<X> can be converted to a List, however, so that you can pass it to a legacy method that expects an argument of that type and has not been updated for generics.

4.1.2.3 运行期安全

正如我们看到的, List<X> 不能被转化成 List<Y> ,尽管 X 能被转化成 Y 。但是 List<X> 能被转化成 List ,因此你可以通过这个方式处理遗留的代码(有那种泛型参数不能升级为泛型的代码)

This ability to convert parameterized types to nonparameterized types is essential for backward compatibility, but it does open up a hole in the type safety system that generics offer:

这种转化参数化类型为非参数化类型的能力是最基本的向后兼容,但这也给具有泛型的系统带来了一个类型安全漏洞。
// Here's a basic parameterized list.
// 这是一个基本的参数化类型类型的 list 。
List<Integer> li = new ArrayList<Integer>();
 
// It is legal to assign a parameterized type to a nonparameterized variable
// 这样把一个参数化类型赋给一个非参数化类型变量是合法的。
List l = li;   
 
// This line is a bug, but it compiles and runs.
// The Java 5.0 compiler will issue an unchecked warning about it.
// If it appeared as part of a legacy class compiled with Java 1.4, however,
// then we'd never even get the warning.  
// 这行是个 bug ,但是可以编译运行。
// Java 5.0 会发布一个 unchecked 警告信息
// 但是,如果用 Java 1.4 编译遗留的代码,不会有任何的警告。
l.add("hello");
 
// This line compiles without warning but throws ClassCastException at runtime.
// Note that the failure can occur far away from the actual bug.
// 这行编译没问题,但是会在运行时抛出 ClassCastException 异常
// 注意这个错误发现的地方,可能会远离它发生的实际位置 
Integer i = li.get(0);


Generics provide compile-time type safety only. If you compile all your code with the Java 5.0 compiler and do not get any unchecked warnings, these compile-time checks are enough to ensure that your code is also typesafe at runtime. But if you have unchecked warnings or are working with legacy code that manipulates your collections as raw types, you may want to take additional steps to ensure type safety at runtime. You can do this with methods like checkedList() and checkedMap( ) of java.util.Collections. These methods enclose your collection in a wrapper collection that performs runtime type checks to ensure that only values of the correct type are added to the collection. For example, we could prevent the type safety hole shown above like this:

泛型仅提供了编译期的类型安全检查。如果你用 Java 5.0 编译代码并且没有任何的 unchecked 警告,那么这些编译的检查能够保证你的代码也在运行期是安全的。但是如果你收到 unchecked 警告,或是用了遗留下来的代码(用原始的类型操作Collection),你可能想用些附加的步骤来确保运行期的类型安全。你可以用 java.util.Collections 的这些方法 checkedList() 和 checkedMap() 来做。这些方法封装你的Collection到一个包装的Collection中,它执行运行期的检查确保正确类型的值被加入到你的Collection中。例如,我们可这样阻止类型安全的漏洞:
// Here's a basic parameterized list.
// 这是一个基本的参数化类型的 list 。
List<Integer> li = new ArrayList<Integer>();
 
// Wrap it for runtime type safety
// 为了运行期的安全,我们包装了它
List<Integer> cli = Collections.checkedList(li, Integer.class);
 
// Now widen the checked list to the raw type
// 现在放到这个 checked 的 list 到原始类型
List l = cli;   
 
// This line compiles but fails at runtime with a ClassCastException.
// 这行可编译,但是运行期会抛出 ClassCastException 异常。
// The exception occurs exactly where the bug is, rather than far away
// 这个异常能够定位 bug 的位置,而不是远离它实际的位置
l.add("hello");
 

4.1.2.4 Arrays of generic type
4.1.2.4 关于数组的参数化类型

Arrays require special consideration when working with generic types. Recall that an array of type S[ ] is also of type T[], if T is a superclass (or interface) of S. Because of this, the Java interpreter must perform a runtime check every time you store an object in an array to ensure that the runtime type of the object and of the array are compatible. For example, the following code fails this runtime check and throws an ArrayStoreException:

当数组遇到泛类型时,我们需要特别的考虑。回想一个类型 S[] 的数组也是类型为 T[]  的数组,如果 T 是 S 的超类(或是接口)。因此,必须每当你存储一个对象到一个数组时,Java 的解析器就会执行一个运行期检查,以确保运行时放入的对象的类型和数组的类型相容。例如,下面的代码在运行期检查是失败的,并且抛出一个 ArrayStoreException 异常:
String[] words = new String[10];
Object[] objs = words;
objs[0] = 1;  // 1 autoboxed to an Integer, throws ArrayStoreException 
           // 1 被自动装箱到 Integer,抛出ArrayStoreException 异常
 


Although the compile-time type of objs is Object[], its runtime type is String[ ], and it is not legal to store an Integer in it.

虽然编译时 objs 的类型是 Object[],但在运行时的类型是 String[] ,并非法的把 Integer 值放入其中。

When we work with generic types, the runtime check for array store exceptions is no longer sufficient because a check performed at runtime does not have access to the compile-time type parameter information. Consider this (hypothetical) code:

当我们应用泛类型时,运行期关于数组存储异常的检测已经不能满足需要,因为一个运行期的检测不能读取编译期的泛型参数信息。考虑下面的代码(假设可以执行):
List<String>[] wordlists = new ArrayList<String>[10];
ArrayList<Integer> ali = new ArrayList<Integer>();
ali.add(123);
Object[] objs = wordlists;
objs[0] = ali;                       // No ArrayStoreException //没有 ArrayStoreException 异常
String s = wordlists[0].get(0);         // ClassCastException!  //ClassCastException 异常
 


If the code above were allowed, the runtime array store check would succeed: without compile-time type parameters, the code simply stores an ArrayList into an ArrayList[] array, which is perfectly legal. Since the compiler can't prevent you from defeating type safety in this way, it instead prevents you from creating any array of generic type. The scenario above can never occur because the compiler will refuse to compile the first line.

如果上面的代码可以执行,运行时数组存储检查将是成功的;没有编译时的泛型参数,这段代码简单的把一个 ArrayList 存到 ArrayList[] 数组中,那都是非常正确的。因为编译器不能从这个方式阻止你破坏类型安全,它只能阻止你创建任何的参数化类型类型数组。其实上面的一幕是不会发生的,因为编译器将会拒绝第一个行的编译。

Note that this is not a blanket restriction on using arrays with generics; it is just a restriction on creating arrays of parameterized type. We'll return to this issue when we look at how to write generic methods.

注意这不是完全禁止数组应用泛型;这仅是禁止用参数化类型创建数组。当看过怎样写泛型方法后,会回到这个话题。

4.1.3. Type Parameter Wildcards
4.1.3. 泛型参数通配符

Suppose we want to write a method to display the elements of a List.[3] Before List was a generic type, we'd just write code like this:

假设我们想写一个显示 List 元素的方法。[3]在 List 是应用泛类型前,我们仅需要这样写代码:

[3] The three printList() methods shown in this section ignore the fact that the List implementations classes in java.util all provide working toString() methods. Notice also that the methods assume that the List implements RandomAccess and provides very poor performance on LinkedList instances.

[3] 这段的三个 priintList() 方法忽略了一个前提,就是 List 在 java.util 包中的实现类都实现了 toString() 方法。注意这些方法也假定 List 实现了 RandomAccess 功能和提供了比较弱的 LinkedList 实例的性能。
public static void printList(PrintWriter out, List list) {
    for(int i=0, n=list.size(); i < n; i++) {
        if (i > 0) out.print(", ");
        out.print(list.get(i).toString());
    }
}
 

In Java 5.0, List is a generic type, and, if we try to compile this method, we'll get unchecked warnings. In order to get rid of those warnings, you might be tempted to modify the method as follows:

在 Java 5.0 中, List 是一个泛类型,并且,如果你试着编译这个方法,将会得到 unchecked 警告。为了除去这些警告,你可能很想修改方法成下面的样子:
public static void printList(PrintWriter out, List<Object> list) {
    for(int i=0, n=list.size(); i < n; i++) {
        if (i > 0) out.print(", ");
        out.print(list.get(i).toString());
    }
}
 


This code compiles without warnings but isn't very useful because the only lists that can be passed to it are lists explicitly declared of type List<Object>. Remember that List<String> and List<Integer> (for example) cannot be widened or cast to List<Object>. What we really want is a typesafe printList() method to which we can pass any List, regardless of how it has been parameterized. The solution is to use a wildcard as the type parameter. The method would then be written like this:

这代码这么编译不会有警告,但没太大用处。因为仅当传递的是 List<Object> 类型的值给 list 时可用。还记得那个 List<String> 和 List<integer> 的例子吧,不可以放大造型成 List<Object>。怎么样才可以构建一个可以传任何类型的 List 的printList() 方法呢,而不关心这个 List 是否被泛化。解决的方法就是用泛型参数通配符。这个方法可以这么写:
public static void printList(PrintWriter out, List<?> list) {
    for(int i=0, n=list.size(); i < n; i++) {
        if (i > 0) out.print(", ");
        Object o = list.get(i);
        out.print(o.toString());
    }
}
 


This version of the method compiles without warnings and can be used the way we want it to be used. The ? wildcard represents an unknown type, and the type List<?> is read as "List of unknown."

这个版本的方法可以顺利编译没有警告,并且做你想做的用途。这个"?"通配符表示不知道具体类型,这个类型 List<?> 读作"List of unknown"。

As a general rule, if a type is generic and you don't know or don't care about the value of the type variable, you should always use a ? wildcard instead of using a raw type. Raw types are allowed only for backward compatibility and should be used only in legacy code. Note, however, that you cannot use a wildcard when invoking a constructor. The following code is not legal:

作为泛化规则,如果一个类型是泛化的并且你不知道或是不关心泛型变量的值,你总是应该用 ? 通配符替换一个原始类型。原始类型仅应用在向后兼容和遗留的代码中。注意,但是你不可以在构造方法中使用。下面的代码是非法的:
List<?> l = new ArrayList<?>();
 


There is no sense in creating a List of unknown type. If you are creating it, you should know what kind of elements it will hold. You may later want to pass such a list to a method that does not care about its element type, but you need to specify an element type when you create it. If what you really want is a List that can hold any type of object, do this:

不可以用未知的类型创建一个 List 实例,如果你创建一个实例,你应该知道可以容纳的元素类型。接下来,你可能想传一个 list 到方法中,并不关心 list 中元素的类型,可惜你必需在创建它的时候指定一个元素的类型。如果你真得想创建一个可容纳任何类型对象的 List ,你可这么做:
List<Object> l = new ArrayList<Object>();


It should be clear from the printList( ) variants above that a List<?> is not the same thing as a List<Object> and that neither is the same thing as a raw List. A List<?> has two important properties that result from the use of a wildcard. First, consider methods like get() that are declared to return a value of the same type as the type parameter. In this case, that type is unknown, so these methods return an Object. Since all we need to do with the object is invoke its toString() method, this is fine for our needs.

这是非常清楚的,变种方法printList() 中的 List<?> 和 List<object> 是不同的。而且它们和原始类型的 List 一点也不一样。List<?>有两个重要的属性。首先,是用了通配符的结果。思考 get() 这样的方法,它被声明成返回值的类型和泛型参数指定的类型相同。在这个例子中,类型未知因此方法返回 Object 。既然我们所有要做的是调用 object 的 toString() 方法,那它一定会如我们所愿。

Second, consider List methods such as add() that are declared to accept an argument whose type is specified by the type parameter. This is the more surprising case: when the type parameter is unknown, the compiler does not let you invoke any methods that have a parameter of the unknown type because it cannot check that you are passing an appropriate value. A List<?> is effectively read-only since the compiler does not allow us to invoke methods like add( ), set(), and addAll( ).

第二,思考 List 的 add() 方法,它接受一个由泛型参数指定类型的参数。这是个很让人吃惊的情况:当泛型参数不未知时,编译器不会让我们调用任何方法,因为参数的类型未知,它不能检测你传递参数值的类型。List<?> 对仅仅是读是有效的,因为编译器不允许我们调用象 add(),set() 和 addAll() 此类的方法。

4.1.3.1 Bounded wildcards
4.1.3.1 边界通配符

Let's continue now with a slightly more complex variant of our original example. Suppose that we want to write a sumList() method to compute the sum of a list of Number objects. As before, we could use a raw List, but we would give up type safety and have to deal with unchecked warnings from the compiler. Or we could use a List<Number>, but then we wouldn't be able to call the method for a List<Integer> or List<Double>, types we are more likely to use in practice. But if we use a wildcard, we don't actually get the type safety that we want because we have to trust that our method will be called with a List whose type parameter is actually Number or a subclass and not, say, a String. Here's what such a method might look like:

接着让我们对最初的例子做个稍微复杂的变化。假设我们需要写一个 sumList() 方法,来计算一个 Number 类型 list 中元素的的和。在往常,我们用原始类型的 List ,但那会放弃类型安全检测,必须处理编译器的 unchecked 警告信息。或者我们可以用一个 List<Number>类型,但是那样做我们不能让方法用 List<Integer> 类型或是 List<Double> 类型,或是我们实际喜欢的类型的参数值。但是你如果用了通配符,却不能得到你想要的类型安全。因为我们必须委托调用参数为 List 类型的方法,它的实际的泛型参数值可能是 Number 或是它的子类,或者和它根本就不相关,比如说是一个 String 。这有一个这样的例子,我们可以看一下:
public static double sumList(List<?> list) {
    double total = 0.0;
    for(Object o : list) {
        Number n = (Number) o;  // A cast is required and may fail //需要造型,但可能会失败
        total += n.doubleValue();
    }
    return total;
}


To fix this method and make it truly typesafe, we need to use a bounded wildcard that states that the type parameter of the List is an unknown type that is either Number or a subclass of Number. The following code does just what we want:

为了修订这个问题,真正做到类型安全,人们需要边界通配符。情况是这样的, List 的泛型参数是 Number 或是它的子类。下面的代码才是我们想要的:
public static double sumList(List<? extends Number> list) {
    double total = 0.0;
    for(Number n : list) total += n.doubleValue();
    return total;
}
 


The type List<? extends Number> could be read as "List of unknown descendant of Number." It is important to understand that, in this context, Number is considered a descendant of itself.

类型 List<? extends Number> 可以读作"List of unknown descendant of Number.( Number 未知后裔的 List 类型)"。理解这一点是非常重要的。在这个语境中,Number 是自己的后裔。

Note that the cast is no longer required. We don't know the type of the elements of the list, but we know that they have an "upper bound" of Number so we can extract them from the list as Number objects. The use of a for/in loop obscures the process of extracting elements from a list somewhat. The general rule is that when you use a bounded wildcard with an upper bound, methods (like the get() method of List) that return a value of the type parameter use the upper bound. So if we called list.get( ) instead of using a for/in loop, we'd also get a Number. The prohibition on calling methods like list.add( ) that have arguments of the type parameter type still stands: if the compiler allowed us to call those methods we could add an Integer to a list that was declared to hold only Short values, for example.

注意,这里不再需要造型。我们不知道 list 元素的类型,但是我们知道他们都有一个"上界"类型是 Number ,因此我们可以把它们当作 Number 的对象拿出来。这里我们用隐式的 for/in 结构处理从 list 中拿到的每个元素。通常的规则是我们用上界通配符,让方法(象 list当中的 get() 方法)返回一个是上界泛型参数指定类型的值。例如,如果我们调用了 list.get() 方法,而不是用 for/in 结构,我会得到一个 Number 对象;但是仍然禁止调用有泛型参数的 list.add() 方法,如果编译器允许我们这样调用方法,我们就可以增加一个 Integer 的对象到容纳 Short 类型的 list 当中。

It is also possible to specify a lower-bounded wildcard using the keyword super instead of extends. This technique has a different impact on what methods can be called. Lower-bounded wildcards are much less commonly used than upper-bounded wildcards, and we discuss them later in the chapter.

当然你也可以有关键字 super 替换 extends 指定一个下界通配符。调用这样的方法的技术会带给我们不同的影响。下界通配符不如上界通配符用的多,我们会在章节的后面讨论。

4.1.4. Writing Generic Types and Methods
4.1.4.写泛类型和泛型方法

Creating a simple generic type is straightforward. First, declare your type variables by enclosing a comma-separated list of their names within angle brackets after the name of the class or interface. You can use those type variables anywhere a type is required in any instance fields or methods of the class. Remember, though, that type variables exist only at compile time, so you can't use a type variable with the runtime operators instanceof and new.

创建一个简单的泛类型是容易的。首先,在类名或是接口名后,用封闭的尖括号用逗号隔开声明一系列泛型变量。你可以在这个类的任何需要类型的地方,把泛型变量作为类型,应用到实例变量或是方法中。但是记住,泛型变量仅存在于编译期,因此你不能让泛型变量和运行的 instanceof 操作或是 new 操作连用

We begin this section with a simple generic type, which we will subsequently refine. This code defines a tree data structure that uses the type variable V to represent the type of the value held in each node of the tree:

这段开始我们定义了一个简单的泛类型,随后我们将重新定义它。代码定义了一个 tree 结构,用泛型变量 V 表示存储在 tree 中每个节点值的类型:
import java.util.*;
 
/**
 * A tree is a data structure that holds values of type V.
 * Each tree has a single value of type V and can have any number of
 * branches, each of which is itself a Tree.
 */
/**
 * 一个tree的数据结构,容纳类型为 V 的值。
 * 每一个树有一个独立的 V 类型的值,可以有一定数据的分枝,它们又可以有自己的树
 */
public class Tree<V> {
    // The value of the tree is of type V.
      // 这个树的 vaule 类型是V
    V value;
 
    // A Tree<V> can have branches, each of which is also a Tree<V>
      // 一个 Tree<V> 可以其他分枝,每个分析又都是一个 Tree<V> 
    List<Tree<V>> branches = new ArrayList<Tree<V>>();
 
    // Here's the constructor.  Note the use of the type variable V.
      // 这是构造方法。注意这里用的是泛型变量 V 
    public Tree(V value) { this.value = value; }
 
    // These are instance methods for manipulating the node value and branches.
    // Note the use of the type variable V in the arguments or return types.
    // 这些实例方法操作节点值和分枝。
    // 注意这里在参数类型和返回值类型中用了泛型变量 V 
    V getValue() { return value; }
    void setValue(V value) { this.value = value; }
    int getNumBranches() { return branches.size(); }
    Tree<V> getBranch(int n) { return branches.get(n); }
    void addBranch(Tree<V> branch) { branches.add(branch); }
}
 

As you've probably noticed, the naming convention for type variables is to use a single capital letter. The use of a single letter distinguishes these variables from the names of actual types since real-world types always have longer, more descriptive names. The use of a capital letter is consistent with type naming conventions and distinguishes type variables from local variables, method parameters, and fields, which are sometimes written with a single lowercase letter. Collection classes like those in java.util often use the type variable E for "Element type." When a type variable can represent absolutely anything, T (for Type) and S are used as the most generic type variable names possible (like using i and j as loop variables).

可能你已经注意到了,泛型变量的命名约定是用一个大写字母表示。就是用一个单独的字母表示的变量去区别实际类型的变量名,因为真实的类型名总是很长的。一个大写字母的用法是和类型命名的约定相一致的,并这些泛型变量区别于局部判变量,方法参数和实例变量(这些变量有时用一个字母,不过是小写的)。象在包 java.util 中的Collection类用泛型变量 E 表示" Element type 元素类型"。在所有的泛型变量里,T(表示类型)和 S 可能是用得最多的变量名(就象循环变量 i 和 j 一样)。

Notice that the type variables declared by a generic type can be used only by the instance fields and methods (and nested types) of the type and not by static fields and methods. The reason, of course, is that it is instances of generic types that are parameterized. Static members are shared by all instances and parameterizations of the class, so static members do not have type parameters associated with them. Methods, including static methods, can declare and use their own type parameters, however, and each invocation of such a method can be parameterized differently. We'll cover this later in the chapter.

注意用泛类型声明的泛型变量仅被用作实例变量和方法类型(嵌套类型),不可用于静态域和静态方法。当然,理由是泛类型是实例初始化的。静态方法被类的所有实例共享,是类初始化的。因此静态方法不能有泛型参数相关联。方法(包括静态方法)的声明和使用可以是自己的泛型参数,不过每次调用的是一个不同参数化的方法。本章稍后我们会涉及到这个问题。

4.1.4.1 Type variable bounds
4.1.4.1 泛型变量边界

The type variable V in the declaration above of the TRee<V> class is unconstrained: TRee can be parameterized with absolutely any type. Often we want to place some constraints on the type that can be used: we might want to enforce that a type parameter implements one or more interfaces, or that it is a subclass of a specified class. This can be done by specifying a bound for the type variable. We've already seen upper bounds for wildcards, and upper bounds can also be specified for type variables using a similar syntax. The following code is the tree example rewritten to make tree objects Serializable and Comparable. In order to do this, the example uses a type variable bound to ensure that its value type is also Serializable and Comparable. Note how the addition of the
Comparable bound on V enables us to write the compareTo() method tree by guaranteeing the existence of a compareTo() method on V.[4]

在上面 TRee<V> 类中声明的泛型变量 V 是没有被约束的: Tree 可以被参数化成任何其他类型。我们经常会给要用的类型一定的约束:我们可能想这么做,让一个类型实现一个或多个接口,或者是一个指定类的。这个任务可以通过指定泛型变量的边界完成。我们已经见过上界通配符了,它可以用相似的语法被应用到泛型变量上。下面的 tree 代码应用了 Serializable 和 Comparable 进行了重写。为了完成任务,这个例子用了一个泛型变量通配符,以保证它的值类型也是 Serializable 和 Comparable的。注意,为了(通过)保证 V 中存在 compareTo() 方法,这种为 Comparable 增加 V 边界的变化是怎样让我们写一个有 compareTo()方法的 tree 的。

[4] The bound shown here requires that the value type V is comparable to itself, in other words, that it implements the Comparable interface directly. This rules out the use of types that inherit the Comparable interface from a superclass. We'll consider the Comparable interface in much more detail at the end of this section and present an alternative there.

这儿的边界要求值的类型 V 可以和自己比较,换句话说,它直接实现了 Comparable 接口。另外的一个用法是,让它从超类那里继承这个 Comparable 接口。我们将在这章结束时更详细的思考 Comparable 接口,现在这里两个中选择哪一种都行。
import java.io.Serializable;
import java.util.*;
 
public class Tree<V extends Serializable & Comparable<V>>
    implements Serializable, Comparable<Tree<V>>
{
    V value;
    List<Tree<V>> branches = new ArrayList<Tree<V>>();
 
    public Tree(V value) { this.value = value; }
 
    // Instance methods
    // 实例方法
    V getValue() { return value; }
    void setValue(V value) { this.value = value; }
    int getNumBranches() { return branches.size(); }
    Tree<V> getBranch(int n) { return branches.get(n); }
    void addBranch(Tree<V> branch) { branches.add(branch); }
 
    // This method is a nonrecursive implementation of Comparable<Tree<V>>
    // It only compares the value of this node and ignores branches.
    // 这里没有循环实现 Comparable<Tree<V>> 接口,只是比较这个节点的值,而忽略了分枝
    public int compareTo(Tree<V> that) {
        if (this.value == null && that.value == null) return 0;
        if (this.value == null) return -1;
        if (that.value == null) return 1;
        return this.value.compareTo(that.value);
    }
 
    // javac -Xlint warns us if we omit this field in a Serializable class
    // 如果在一个 Serializable 的类中省略这个域,如此编译 :javac -Xlint ,会得到警告
    private static final long serialVersionUID = 833546143621133467L;
}
 


The bounds of a type variable are expressed by following the name of the variable with the word extends and a list of types (which may themselves be parameterized, as Comparable is). Note that with more than one bound, as in this case, the bound types are separated with an ampersand rather than a comma. Commas are used to separate type variables and would be ambiguous if used to separate type variable bounds as well. A type variable can have any number of bounds, including any number of interfaces and at most one class.

泛型变量边界通过其后的变量名、 extends 和类型列表(它们自己是可参数化的,比如 Comparable 就是)表达。注意,这个例子里是多个边界而不是一个,边界类型用之间用"&"分隔而不是","。","被用作泛型变量的分隔,再用作泛型变量边界分隔会产生歧义。一个泛型变量可以有相当数量的边界,包括一定量的接口和至多一个类。
   发表时间:2006-09-21  
4.1.4.2 Wildcards in generic types
4.1.4.2 泛类型中的通配符

Earlier in the chapter we saw examples using wildcards and bounded wildcards in methods that manipulated parameterized types. They are also useful in generic types. Our current design of the tree class requires the value object of every node to have exactly the same type, V. Perhaps this is too strict, and we should allow branches of a tree to have values that are a subtype of V instead of requiring V itself. This version of the tree class (minus the Comparable and Serializable implementation) is more flexible:

本章早些时候,我们有例子用了通配符和在方法中操控参数化类型的边界通配符。它们都是非常有用的 generic type 。当前我们设计 tree 类,需要每一个节点值的对象类型精确的是 V 。可能这太过苛刻了,我们应该允许 tree 的分枝可以容纳类型 V 的子类型,不一定是 V 自己。下面这个 tree 的版本就相当灵活的(除去了 Comparable 和 Serializable)。
public class Tree<V> {
    // These fields hold the value and the branches
    // 这个 value 域包含分枝
    V value;
    List<Tree<? extends V>> branches = new ArrayList<Tree<? extends V>>();
 
    // Here's a constructor
    // 这是构造方法
    public Tree(V value) { this.value = value; }
 
    // These are instance methods for manipulating value and branches
    // 这是操控 value 和分枝的实例方法
    V getValue() { return value; }
    void setValue(V value) { this.value = value; }
    int getNumBranches() { return branches.size(); }
    Tree<? extends V> getBranch(int n) { return branches.get(n); }
    void addBranch(Tree<? extends V> branch) { branches.add(branch); }
}


The use of bounded wildcards for the branch type allow us to add a tree<Integer>, for example, as a branch of a tree<Number>:
这分枝中边界通配符的用法允许你增加 tree<Integer> 对象,例如,一个分枝是 tree<Number> 类型的:
Tree<Number> t = new Tree<Number>(0);  // Note autoboxing //这是自动装箱
t.addBranch(new Tree<Integer>(1));     // int 1 autoboxed to Integer //int型的 1 自动转换成 Integer
 


If we query the branch with the getBranch( ) method, the value type of the returned branch is unknown, and we must use a wildcard to express this. The next two lines are legal, but the third is not:

如果我们用方法 getBranch() 检索分枝,分枝返回值的类型是未知,我们必须用通配符表示。下面前两句是合法的,而第三句不是:
Tree<? extends Number> b = t.getBranch(0);
Tree<?> b2 = t.getBranch(0);
Tree<Number> b3 = t.getBranch(0);  // compilation error // 编译错误
 


When we query a branch like this, we don't know the precise type of the value, but we do still have an upper bound on the value type, so we can do this:

当我们这样检索分枝时,是不知道确切值的类型的,但我们仍然有一个向上的边界类型,因此我们可以这么做:
Tree<? extends Number> b = t.getBranch(0);
Number value = b.getValue();


What we cannot do, however, is set the value of the branch, or add a new branch to that branch. As explained earlier in the chapter, the existence of the upper bound does not change the fact that the value type is unknown. The compiler does not have enough information to allow us to safely pass a value to setValue() or a new branch (which includes a value type) to addBranch(). Both of these lines of code are illegal:

但什么是我们不能做的呢,就是为分枝赋值或是增加一个新元素到分枝时,是不可以的。就如我们较早解释的那样,向上边界不可以改变的一事实是:值的类型是未知的。编译器没有足够的信息允许你传一个值,到 setValue() 方法或是增加新分枝(其中包括了值的类型)的 addBranch() 方法中。下面两行是不合法的:
b.setValue(3.0); // Illegal, value type is unknown // 非法,值类型未知
b.addBranch(new Tree<Double>(Math.PI));
 

This example has illustrated a typical trade-off in the design of a generic type: using a bounded wildcard made the data structure more flexible but reduced our ability to safely use some of its methods. Whether or not this was a good design is probably a matter of context. In general, generic types are more difficult to design well. Fortunately, most of us will use the preexisting generic types in the java.util package much more frequently than we will have to create our own.

这个例子已经简述了一个典型的折衷泛类型设计方法:用一个边界通配符构造一个数据结构是非常灵活的,但是降低了一些它自己方法的安全性。幸好,我们多数时候使用 java.util 包中的泛类型而不是我们自己创建的。

4.1.4.3 Generic methods
4.1.4.3 泛型方法

As noted earlier, the type variables of a generic type can be used only in the instance members of the type, not in the static members. Like instance methods, however, static methods can use wildcards. And although static methods cannot use the type variables of their containing class, they can declare their own type variables. When a method declares its own type variable, it is called a generic method.

正如前面所释的,一个泛类型的泛型变量仅可以用作类的实例成员,不能是静态成员。但是象实例方法,静态方法可以用通配符。尽管静态方法不能使用它们所在类的泛型变量,但是它们能声明自己的泛型变量。当一个方法声明自己的泛型变量时,这个方法叫作:generic method。

Here is a static method that could be added to the Tree class. It is not a generic method but uses a bounded wildcard much like the sumList() method we saw earlier in the chapter:

这个方法可以增加到 Tree 类中。它不是 generic method ,但是用了边界通配符,就象本章前些时候看到的 sumList() 方法一样:
/** Recursively compute the sum of the values of all nodes on the tree */
/** 递归计算 tree 中所有值的和 
public static double sum(Tree<? extends Number> t) {
    double total = t.value.doubleValue();
    for(Tree<? extends Number> b : t.branches) total += sum(b);
    return total;
}
 

This method could also be rewritten as a generic method by declaring a type variable to express the upper bound imposed by the wildcard:

这个方法用泛型变量,重写成了泛型方法,来实现通配符版的功能。
public static <N extends Number> double sum(Tree<N> t) {
    N value = t.value;
    double total = value.doubleValue();
    for(Tree<? extends N> b : t.branches) total += sum(b);
    return total;
}
 


The generic version of sum() is no simpler than the wildcard version and the declaration of the type variable does not gain us anything. In a case like this, the wildcard solution is typically preferred over the generic solution. Generic methods are required where a single type variable is used to express a relationship between two parameters or between a parameter and a return value. The following method is an example:

这个泛型版的 sum() 不比通配符版的简单,并没有通过声明泛型变量得到什么好处。就象这个例子中,通配符版的解决方案要好于泛型版的。泛型方法通常 适用于只有一个泛型变量,表示两个参数之间的联系,或是表示一个参数和一个值之间的关系。就象下面的代码:
// This method returns the largest of two trees, where tree size
// is computed by the sum() method.  The type variable ensures that 
// both trees have the same value type and that both can be passed to sum().
// 当调用了 sum()方法后,这个方法返回两个树中最大的那个。这个泛型变量使得两个 tree 
// 有相同类型的值,可以使用 sum() 方法。
public static <N extends Number> Tree<N> max(Tree<N> t, Tree<N> u) {
    double ts = sum(t);
    double us = sum(u);
    if (ts > us) return t;
    else return u;
}
 


This method uses the type variable N to express the constraint that both arguments and the return value have the same type parameter and that that type parameter is Number or a subclass.

这个方法用泛型变量 N 表示两个参数之间的约束,并且返回值有相同的泛型参数表示的类型,而且泛型参数要么是 Number 或是它的子类。

It could be argued that constraining both arguments to have the same value type is too restrictive and that we should be allowed to call the max( ) method on a tree<Integer> and a tree<Double>. One way to express this is to use two unrelated type variables to represent the two unrelated value types. Note, however, that we cannot use either variable in the return type of the method and must use a wildcard there:

对于两个参数必须有两相同的类型是不是太苛刻了,就这一点是有争议的。我们应该允许用 tree<Integer> 和 tree<Double> 的参数调用 max()方法。一个办法是用两个无关的泛型变量表示两个无关的值类型。但是,注意这里方法不能再用变量返回,而用一个通配符:

 
public static <N extends Number, M extends Number>
    Tree<? extends Number> max(Tree<N> t, Tree<M> u) {...}
 


Since the two type variables N and M have no relation to each other, and since each is used in only a single place in the signature, they offer no advantage over bounded wildcards. The method is better written this way:

因为两个泛型变量 N 和 M 彼此不相关,每次只能返回一个类型标识。这不比边界通配符有优越性。这个方法这么写更好:
public static Tree<? extends Number> max(Tree<? extends Number> t,
                                         Tree<? extends Number> u) {...}
 

All the examples of generic methods shown here have been static methods. This is not a requirement: instance methods can declare their own type variables as well.

这例子中所述的所有泛型方法都是静态方法。这不是强求的:实例方法也能声明自己的泛型变量。

4.1.4.4 Invoking generic methods
4.1.4.4 调用 generic method

When you use a generic type, you must specify the actual type parameters to be substituted for its type variables. The same is not generally true for generic methods: the compiler can almost always figure out the correct parameterization of a generic method based on the arguments you pass to the method. Consider the max() method defined above, for instance:

如果你用了一个泛类型 ,你必须用它自己实际的泛型参数替换。但这对泛型方法却不一定:编译器几乎总是能算出传给泛型方法的参数的真实类型。考虑上面定义的 max() 方法,例如:
public static <N extends Number> Tree<N> max(Tree<N> t, Tree<N> u) {...}
 


You need not specify N when you invoke this method because N is implicitly specified in the values of the method arguments t and u. In the following code, for example, the compiler determines that N is Integer:

当你调用这个方法的时候不需要确定 N 的值。因为 N 被方法参数 t 和 u 隐含指定了。例如下面的代码,编译器断定 N 是 Integer 类型的:
Tree<Integer> x = new Tree<Integer>(1);
Tree<Integer> y = new Tree<Integer>(2);
Tree<Integer> z = Tree.max(x, y);
 

The process the compiler uses to determine the type parameters for a generic method is called type inference. Type inference is relatively intuitive to understand, but the actual algorithm the compiler must use is surprisingly complex and is well beyond the scope of this book. Complete details are in Chapter 15 of The Java Language Specification, Third Edition.

编译器这种断定被调泛型方法泛型参数的过程叫做 type inerence (类型推定)。类型推定是相对直观和容易理解的,但编译器实现时的算法却是令人吃惊的复杂,这个讨论已经走出了本书的讨论范围。详细内容你可以参照 The Java Language Specification, Third Edition 的15章。

Let's look at a slightly more complex version of type inference. Consider this method:

让我看一个稍微复杂版本的类型推定。考虑下面的方法:
public class Util {
    /** Set all elements of a to the value v; return a. */
    /** 用 V 类型赋所有值;返回 a */
    public static <T> T[] fill(T[] a, T v) {
        for(int i = 0; i < a.length; i++) a[i] = v;
        return a;
    }
}
 

Here are two invocations of the method:

这有对这个方法的两种不同调用:
Boolean[] booleans = Util.fill(new Boolean[100], Boolean.TRUE);
Object o = Util.fill(new Number[5], new Integer(42));


In the first invocation, the compiler can easily determine that T is Boolean. In the second invocation, the compiler determines that T is Number.

前面的调用,编译器很容易判断 T 是 Boolean 类型的。第二种调用,编译器断定 T 是 Number 类型的。

In very rare circumstances you may need to explicitly specify the type parameters for a generic method. This is sometimes necessary, for example, when a generic method expects no arguments. Consider the java.util.Collections.emptySet( ) method: it returns a set with no elements, but unlike the Collections.singleton( ) method (you can look these up in the reference section), it takes no arguments that would specify the type parameter for the returned set. You can specify the type parameter explicitly by placing it in angle brackets before the method name:

在非常少的情况下,你需要为泛型方法显示指定泛型参数。这有时是需要的,例如,当一个 generic method 不希望有参数时。思考 java.util.Collections.emptySet() 方法:它返回一个没有元素的 set ,不象 Collections.singleton() 方法(你可以在上面的章节中找到),它没有参数可是指定 set 的返回泛型参数。你需要在方法名称前的尖括号中显示的指定泛型参数。
Set<String> empty = Collections.<String>emptySet();

Type parameters cannot be used with an unqualified method name: they must follow a dot or come after the keyword new or before the keyword this or super used in a constructor.

泛型参数不能和不合法的方法名连用:它们必须在一个"."后,或是紧跟在关键字 new 后,或者构造方法中在 this 或 super前。

It turns out that if you assign the return value of Collections.emptySet() to a variable, as we did above the type inference mechanism is able to infer the type parameter based on the variable type. Although the explicit type parameter specification in the code above can be a helpful clarification, it is not necessary and the line could be rewritten as:

其结果是如果你把 Collections.emptySet() 的返回值赋给变量,接着就会起动上面的类型推定机制断定泛型参数所基于的变量类型。虽然上面的代码显式的指出泛型参数,对识别很有帮助。但那是不一定需要的。这行代码可以这样改写:
Set<String> empty = Collections.emptySet();

An explicit type parameter is necessary when you use the return value of the emptySet( ) method within a method invocation expression. For example, suppose you want to call a method named printWords( ) that expects a single argument of type Set<String>. If you want to pass an empty set to this method, you could use this code:

显式的泛型参数还是需要的,比如说你在一个方法的调用中,用了 emptySet()方法的返回值。例如,假定你想你调用的 printWords() 中有一个单独的参数,类型是 Set<String> 。如果你想传递一个空 set 给方法,你可以这样写代码:
printWords(Collections.<String>emptySet());
 


In this case, the explicit specification of the type parameter String is required.

在这个例子中,显示的说明泛型参数 String 是需要的。

4.1.4.5 Generic methods and arrays
4.1.4.5 generic method 和数组

Earlier in the chapter we saw that the compiler does not allow you to create an array whose type is parameterized. This is not, however, a restriction on all uses of arrays with generics. Consider the Util.fill() method defined above, for example. Its first argument and its return value are both of type T[]. The body of the method does not have to create an array whose element type is T, so the method is perfectly legal.

本意早些时候,我们看到编译器不允许创建一个参数化类型的数组。但是,这不是约束了数组的有关的用法。例如,考虑上面定义的 Util.fill() 的方法。它的第一个参数和返回值都是 T[] 类型的,方法体不一定创建一个有类型 T 元素的数组,因此这个方法是完全合法的。

If you write a method that uses varargs (see Section 2.6.4 in Chapter 2) and a type variable, remember that invoking a varargs method performs an implicit array creation. Consider this method:

如果你写了一个用 varargs 的方法和一个泛型变量,记住调用一个 varargs 方法会执行一个数组的隐式创建。考虑这个方法:
/** Return the largest of the specified values or null if there are none */
/** 返回指定值中最大的或是null(如果其中没有最大的) */
public static <T extends Comparable<T>> T max(T... values) { ...  }
 


You can invoke this method with parameters of type Integer because the compiler can insert the necessary array creation code for you when you call it. But you cannot call the method if you've cast the same arguments to be type Comparable<Integer> because it is not legal to create an array of type Comparable<Integer>[ ].

你可以用类型为 Integer 调用这个方法,因为为了可以让你调用编译器可以根据需要动态创建一个数组。但你不能造型参数为 Comparable<integer>类型,因为创建类型为 Comparable<Ineteger> 的数组是非法的。

4.1.4.6 Parameterized exceptions
4.1.4.6 参数化异常

Exceptions are thrown and caught at runtime, and there is no way for the compiler to perform type checking to ensure that an exception of unknown origin matches type parameters specified in a catch clause. For this reason, catch clauses may not include type variables or wildcards. Since it is not possible to catch an exception at runtime with compile-time type parameters intact, you are not allowed to make any subclass of THRowable generic. Parameterized exceptions are simply not allowed.

异常是在运行期抛出和捕获的,并且没有办法让编译器执行类型检查确保一个未知类型异常是否符合 catch 子句指定的泛型参数。为了这个理由, catch 子句不可以包含泛型变量或通配符。因为它不能在运行时捕获一个编译期原样的泛型参数指定的异常,不允许使用 THRowable的泛型。参数化异常显然不允许。

You can, however, use a type variable in the throws clause of a method signature. Consider this code, for example:

但你可以在 thows 子句里用泛型变量标记。例如,思考下面的代码:
public interface Command<X extends Exception> {
    public void doit(String arg) throws X;
}
 


This interface represents a "command": a block of code with a single string argument and no return value. The code may throw an exception represented by the type parameter X. Here is an example that uses a parameterization of this interface:

这个接口表示了一个"命令":这个代码块有一单个的 String 泛型参数和无返回值。这段代码可以抛出泛型参数 X 表示的类型的异常。这有一个应用参数接口的例子:
Command<IOException> save = new Command<IOException>() {
    public void doit(String filename) throws IOException {
        PrintWriter out = new PrintWriter(new FileWriter(filename));
        out.println("hello world");
        out.close();
    }
};
 
try { save.doit("/tmp/foo");  }
catch(IOException e) { System.out.println(e); }
 

4.1.5. Generics Case Study: Comparable and Enum
4.1.5. 泛型实例学习:Comarable 和 Enum

The new generics features in Java 5.0 are used in the Java 5.0 APIs, most notably in java.util but also in java.lang, java.lang.reflect, and java.util.concurrent. These APIs were carefully created or reviewed by the inventors of generic types, and we can learn a lot about the good design of generic types and methods through the study of these APIs.

Java 5.0 的这个泛型新特征被应用到了 Java 5.0 的 API 中,尤其是在 java.util 包中,但在包 java.lang,java.lang.reflect 和包java.util.cocurrent中也有。这些 API 被 Java 泛型的创造者们精心的用泛型重写过了,我们可以通过学习这些 API ,从中学到很多有关 generic type  generic method的很好的设计思想。

The generic types of java.util are relatively easy: for the most part they are collections classes, and type variables are used to represent the element type of the collection. Several important generic types in java.lang are more difficult. They are not collections, and it is not immediately apparent why they have been made generic. Studying these difficult generic types gives us a deeper understanding of how generics work and introduces some concepts that we have not yet covered in this chapter. Specifically, we'll examine the Comparable interface and the Enum class (the supertype of enumerated types, described later in this chapter) and will learn about an important but infrequently used feature of generics known as lower-bounded wildcards.

包 java.util 的泛类型相对还是比较容易的:大多数是Collection的类,通常泛型变量用来表示Collection元素的类型。几个重要的泛类型是在 java.lang 包中的,而且也比较难。他们不是Collection,也不是显而易见的被泛化了。要研究这些比较难的泛类型,就要求我们深入理解泛型是怎么工作的,深入理解本章涉及到的概念。特别是,我们在验证接口 Comparable 和类 Enum (它是 enumerated 类型的超型,也就是我们下一章要学习的) 将学到很重要但用的很少的著名的下界通配符。

In Java 5.0, the Comparable interface has been made generic, with a type variable that specifies what a class is comparable to. Most classes that implement Comparable implement it on themselves. Consider Integer:

在 Java 5.0 中,接口 Comparable 接口已经被泛化,用泛型变量指定一个可比较的类。大多数类自己实现 Comparable 。思考类型 Integer :
public final class Integer extends Number implements Comparable<Integer>
 


The raw Comparable interface is problematic from a type-safety standpoint. It is possible to have two Comparable objects that cannot be meaningfully compared to each other. Prior to Java 5.0, the nongeneric Comparable interface was useful but not fully satisfactory. The generic version of this interface, however, captures exactly the information we want: it tells us that a type is comparable and tells us what we can compare it to.

以类型安全的观点看,原始的 Comparable 接口是有总是的。它可能有两要比较的对象,但它们之间可能没有有意义的比较。在 Java 5.0 之前,非泛型的 Comparable 接口是非常有用的,但不够安全。现在泛型版的这个接口,可以得到我们想要的精确信息:它告诉我们类型是 Comparable 的,可以进行比较。

Now consider subclasses of comparable classes. Integer is final and cannot be subclassed, so let's look at java.math.BigInteger instead:

现在思考 Comarable 的子类。 Integer 类型是 final 和不可子类化的,因此,我们用 java.math.BigInteger 替代一下:
public class BigInteger extends Number implements Comparable<BigInteger>
 


If we implement a BiggerInteger subclass of BigInteger, it inherits the Comparable interface from its superclass. But note that it inherits Comparable<BigInteger> and not Comparable<BiggerInteger>. This means that BigInteger and BiggerInteger objects are mutually comparable, which is usually a good thing. BiggerInteger can override the compareTo( ) method of its superclass, but it is not allowed to implement a different parameterization of Comparable. That is, BiggerInteger cannot both extend BigInteger and implement Comparable<BiggerInteger>. (In general, a class is not allowed to implement two different parameterizations of the same interface: we cannot define a type that implements both Comparable<Integer> and Comparable<String>, for example.)

如果我们实现了一个 BigInteger 的子类 BiggerInteger ,它从超类那里继承了 Comparable 接口。但是注意了,它继承的是 Comparable<BigInteger> 而不是 Comparable<BiggerInteger> 。这意味着 BigInteger 和 BiggerInteger 的对象是相互兼容的,这通常是个好事。BiggerInteger 能覆盖超类的 compareTo() 方法,但是不能被参数化成 Comparable 以外的类型实例。那就是说 BiggerInteger 不能扩展自 BigInteger 实现 Comparable<BiggerInteger> 。(通常,一个类不可以对同一接口进行不同的参数化:例如,我们不能定义一个类型实现接口 Comarable<integer> ,又实现接口 Comarable<String> 。)

When you're working with comparable objects (as you do when writing sorting algorithms, for example), remember two things. First, it is not sufficient to use Comparable as a raw type: for type safety, you must also specify what it is comparable to. Second, types are not always comparable to themselves: sometimes they're comparable to one of their ancestors. To make this concrete, consider the java.util.Collections.max() method:

当你应用 Comparable 的对象时(比如说你做排序算法时),记住两件事。第一,用原始的 Comparable 类型是不够的:为了类型安全你必须指定它比较的是什么。第二,类型总是不可以和自己比较:有时可能会和自己的祖先比较。为了证实这一点,思考 java.util.Collections.max() 方法:
public static <T extends Comparable<? super T>> T max(Collection<? extends T> c)
 


This is a long, complex generic method signature. Let's walk through it:

·         The method has a type variable T with complicated bounds that we'll return to later.

·         The method returns a value of type T.

·         The name of the method is max( ).

·         The method's argument is a Collection. The element type of the collection is specified with a bounded wildcard. We don't know the exact type of the collection's elements, but we know that they have an upper bound of T. That is, we know that the elements of the collection are type T or a subclass of T. Any element of the collection could therefore be used as the return value of the method.

这是一个比较长又复杂的泛型方法说明,让我们一步一步来解读它:

这个方法有一个有复杂边界的泛型变量 T (我们稍后再说)。
这个方法返回值的类型是 T 。
这个方法的名称是 max() 。
这个方法的参数是类型 Collection 的。Collection 的元素类型也使用定了边界通配符。我们不知道 Collection 元素的确切类型,但是我们知道它们都有共同的上界类型 T 。就是说,我们知道元素类型是 T 或是其子类。因此可以返回任何是 Collection 类型的元素值。
That much is relatively straightforward. We've seen upper-bounded wildcards elsewhere in this section. Now let's look again at the type variable declaration used by the max( ) method:

这还是相对容易看明白的。这个章节我们已经看过其他的上界通配符。现在让我们再这个泛型变量在 max() 方法中的声明:
<T extends Comparable<? super T>>
 


This says first that the type T must implement Comparable. (Generics syntax uses the keyword extends for all type variable bounds, whether classes or interfaces.) This is expected since the purpose of the method is to find the "maximum" object in a collection. But look at the parameterization of the Comparable interface. This is a wildcard, but it is bounded with the keyword super instead of the keyword extends. This is a lower-bounded wildcard. ? extends T is the familiar upper bound: it means T or a subclass. ? super T is less commonly used: it means T or a superclass.

这就是说首先,类型 T 必须实现 Coomparable 接口。(泛型语法在类型边界中用关键字 extends ,它也用在类和接口中。)我们现在的意图是方法能找出Collection中 "maximum" 的对象。但是我们看一下这个参数化的 Comarable 接口。这个通配符用的边界关键字是 super 而不是 extends 。这是向下通配。?  extends  T 我们是非常熟悉的:这意味着 T 或是其他子类。? super T 用的较少,这个意思是 T 或是它的超类。

To summarize, then, the type variable declaration states "T is a type that is comparable to itself or to some superclass of itself." The Collections.min() and Collections.binarySearch( ) methods have similar signatures.

总结一下,那就是泛型变量声明的是" T 是一个可以自己比较或是可以和自己超类比较的类型"。Collections.min() 和 Collections.binarySearch() 方法有着类似的说明。

For other examples of lower-bounded wildcards (that have nothing to do with Comparable), consider the addAll(), copy( ), and fill() methods of Collections. Here is the signature for addAll():

其他下界通配符的例子(Comparable没做什么),考虑 Collections 的 addAll(),copy() 和 fill() 方法。这里是 addAll() 声明:
public static <T> boolean addAll(Collection<? super T> c, T... a)
 


This is a varargs method that accepts any number of arguments of type T and passes them as a T[ ] named a. It adds all the elements of a to the collection c. The element type of the collection is unknown but has a lower bound: the elements are all of type T or a superclass of T. Whatever the type is, we are assured that the elements of the array are instances of that type, and so it is always legal to add those array elements to the collection.

这是一个 varargs 方法,能接受数目变化类型为 T 的参数,然后解析它成一个类型为 T[] 的数组 a 。它把 a  中的所有元素增加到Collection c 中。Collection中元素的类型是不知道的,但是有一个最低边界:所有元素的类型是 T 或是 T 的超类。不管类型是什么,我们都可以放心的是:数组元素都是这个类型的;因此它总是可以合法的被增加到 c 中。

Recall from our earlier discussion of upper-bounded wildcards that if you have a collection whose element type is an upper-bounded wildcard, it is effectively read-only. Consider List<? extends Serializable>. We know that all elements are Serializable, so methods like get() return a value of type Serializable. The compiler won't let us call methods like add() because the actual element type of the list is unknown. You can't add arbitrary serializable objects to the list because their implementing class may not be of the correct type.

回顾我们上面讨论的上界通配符,如果我们有一个Collection,它的元素类型是上界通配符,这样的元素是只读的。思考 List<? extends Serializable>。我们知道所有元素是 Serializable 的,因此象方法 get() 返回值的类型是 Serializable 的。编译器不让我们调用 add() 方法,因为实际的 list 元素的类型是未知的。你不能为 list 增加任意的序列化对象,因为他们的类型可能是不正确的。

Since upper-bounded wildcards result in read-only collections, you might expect lower-bounded wildcards to result in write-only collections. This isn't actually the case, however. Suppose we have a List<? super Integer>. The actual element type is unknown, but the only possibilities are Integer or its ancestors Number and Object. Whatever the actual type is, it is safe to add Integer objects (but not Number or Object objects) to the list. And, whatever the actual element type is, all elements of the list are instances of Object, so List methods like get( ) return Object in this case.

因为上界通配符,Collection中的结果是只读的,你可能希望下界通配符的结果是只可写的。但实际上却不是这样的。假设我们有一个类型 List<? super Integer> 。实际元素的类型是未知的,但这仅可能是类型 Integer 或是其祖先类型和 Object 。无论实际类型是什么,list 增加一个类型为 Integer 的元素是安全的( 但不是 Number 或是 Object )。并且,无论实际的类型是什么,list 的所有元素都是 Object 的实例,因此 List 的方法象 get() 在这时返回一个 Object。

Finally, let's turn our attention to the java.lang.Enum class. Enum serves as the supertype of all enumerated types (described later). It implements the Comparable interface but has a confusing generic signature:

最后,让我们再看一下 java.lang.Enum 类。Enum 是服务于所有 enumerated type(稍后讨论) 类型的超型。它实现了 Comparable 接口,但是有着一个晦涩的声明:

public class Enum<E extends Enum<E>> implements Comparable<E>, Serializable


At first glance, the declaration of the type variable E appears circular. Take a closer look though: what this signature really says is that Enum must be parameterized by a type that is itself an Enum. The reason for this seemingly circular type variable declaration becomes apparent if we look at the implements clause of the signature. As we've seen, Comparable classes are usually defined to be comparable to themselves. And subclasses of those classes are comparable to their superclass instead. Enum, on the other hand, implements the Comparable interface not for itself but for a subclass E of itself!

第一眼看过去,这个泛型变量 E 的声明显然是循环。再仔细看:这个声明说了明了什么呢? Enum 必须是被参数化成自己的类型。如果你看了 implements 部分,这显示是看起来循环定义泛型变量的原因。我们知道, Comparable 的类通常被定义成可以和自己比较。他们的子类可以和他们的超类比较。从另一方面说,Enum 实现接口 Comparable 不是为自己,是为了它自己的子类 E 。
0 请登录后投票
   发表时间:2006-09-21  
另外给出PDF版
修订一次:改了一些校对疏忽的地方。
0 请登录后投票
   发表时间:2006-09-21  
先对楼主敬仰一下,辛苦辛苦 

回头仔细看看再拍砖。
0 请登录后投票
   发表时间:2006-09-21  
厉害!
0 请登录后投票
   发表时间:2006-09-21  
好样的,有时间一定好好看看你的成果。
0 请登录后投票
   发表时间:2006-09-22  
辛苦楼主了,看了中文翻译,明朗很多
0 请登录后投票
   发表时间:2006-09-22  
辛苦了!
0 请登录后投票
   发表时间:2006-09-22  
好啊
有空了一定看看
0 请登录后投票
   发表时间:2006-09-22  
因您的資料.我更容易理解!THANKS!

0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics