Java进阶-泛型(1)

本预先资料来源于 Oracle 官方文档 Java™ 教程 -Java Tutorials
官方文档 : https://docs.oracle.com/javase/tutorial/java/generics/index.html
中文翻译 : https://pingfangx.github.io/java-tutorials/java/generics/types.html
    泛型( Generic )是 Java 编程语言的强大功能。它们提高了代码的类型安全性,使你在编译时可以检测 到更多错误。
在任何不平凡的软件项目中,错误都是生活中的事实。仔细的计划,编程和测试可以帮助降低其普遍 性,但是无论如何,它们总会在某种程度上找到爬入你的代码的方法。随着新功能的引入以及代码库的大小和复杂性的增加,这一点变得尤为明显。
    幸运的是,某些错误比其它错误更容易发现。例如,可以在早期发现编译时错误;你可以使用编译器的 错误消息找出问题所在,然后就可以在那里进行修复。但是,运行时错误可能会带来更多问题。它们并不总是立即浮出水面,而当它们浮出水面时,可能是在程序中与实际问题原因相去甚远的某个时刻。 泛型通过在编译时检测到更多错误来增加代码的稳定性。你可能需要继续阅读Gilad Bracha的 Generics 教程。
Why Use Generics? (为什么要使用泛型?)
    简而言之,泛型在定义类,接口和方法时使用类型(类和接口)成为参数。与方法中声明使用的更熟悉的形参非常相似,类型参数为你提供了一种不同的输入重复使用相同代码的方法。区别在意形参输入的是值,而类型参数的输入是类型。
    与非泛型代码相比,使用泛型代码的优点:
  1.     在编译时进行更强的类型检查。Java编译器将强类型检查应用于通用代码,如果代码违反类型安全,则会发出错误。修复编译时错误比修复运行时错误容易,后者可能很难找到。
  2. 消除类型转换。以下不带泛型的代码段需要强制转换:
    List list = new ArrayList ();
    list . add ( "hello" );
    String s = ( String ) list . get ( 0 );
  3. 当使用泛型重写时,代码不需要强制转换:
    List < String > list = new ArrayList < String > ();
    list . add ( "hello" );
    String s = list . get ( 0 ); // no cast
  4. 使程序员能够实现通用算法。通过使用泛型,程序员可以实现对不同类型的集合进行工作,可以自 定义并且类型安全且易于阅读的泛型算法。
Generic Types (通用类型)
    通用类型是通过类型进行参数化的通用类或接口。下面的 Box 类将被修改以演示该概念。
     A Simple Box Class (一个简单的 Box 类)
    
   首先检查对任何类型的对象进行操作的非通用 Box 类。它只需要提供两种方法: set (将对象添加到
Box 中)和 get (将其检索到):
public class Box {
    private Object object ;
    public void set ( Object object ) { this . object = object ; }
    public Object get () { return object ; }
}
    由于它的方法接受或返回一个 Object ,因此只要它不是原始类型之一,你就可以随意传递任何想要的 东西。在编译时无法验证类的使用方式。代码的一部分可能会将 Integer 放在 Box 中,并期望从中取出 Integer ,而代码的另一部分可能会错误地传入 String ,从而导致运行时错误。
A Generic Version of the Box Class Box 类的通用版本)
通用类的定义格式如下:
 
class name < T1 , T2 , ..., Tn > { /* ... */ }
    在类名之后,类型参数部分由尖括号( <> )分隔。它指定了类型参数(也称为类型变量) T1 T2 ... 和Tn
    要更新 Box 类以使用泛型,可以通过将代码 public class Box 更改为 public class Box 来创建 泛型类型声明。
    进行此更改后, Box 类变为:
public class Box < T > {
    // T stands for "Type"
    private T t ;
    public void set ( T t ) { this . t = t ; }
    public T get () { return t ; }
}
如你所见,所有出现的 Object 都将替换为 T 。类型变量可以是你指定的任何非基本类型:任何类类型, 任何接口类型,任何数组类型,甚至是另一个类型变量。
可以将相同的技术应用于创建通用接口。
 
Type Parameter Naming Conventions (类型参数命名约定)
Type Parameter Naming Conventions (类型参数命名约定)
按照约定,类型参数名称是单个大写字母。这与你已经知道的变量命名约定形成鲜明对比,并且有充分
的理由:没有该约定,将很难分辨类型变量与普通类或接口名称之间的区别。
最常用的类型参数名称是:
  • E - Element (Java Collections Framework广泛使用)
  • K - Key
  • N - Number
  • T - Type

 

  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

 

你将看到在 Java SE API 以及本课程其余部分中使用的这些名称。
Invoking and Instantiating a Generic Type (调用和实例化泛型类型)
要从代码中引用通用 Box 类,必须执行通用类型调用,该调用将 T 替换为某些具体值,例如 Integer
Box < Integer > integerBox ;
 
    你可以认为泛型类型调用类似于普通方法调用,但是你没有将参数传递给方法,而是将类型参数(在这 种情况下为 Integer )传递给 Box 类本身。
类型参数和类型参数术语:许多开发人员可以互换使用术语 “type parameter” (又名形参)和 “type argument”(又名实参),但是这些术语并不相同。编码时,提供类型实参以创建参数化类 型。因此, Foo 中的 T 是类型参数(形参),而 Foo f 中的 String 是类型参数(实参)。在使用
这些术语时,本课将遵循此定义。
    像任何其它变量声明一样,此代码实际上不会创建新的 Box 对象。它只是声明 integerBox 将保存对 “Box of Integer”的引用,这就是读取 Box 的方式。
泛型类型的调用通常称为参数化类型。
    要实例化此类,请像往常一样使用 new 关键字,但将 `` 放在类名和括号之间:
    Box < Integer > integerBox = new Box < Integer > ();
The Diamond (菱形)
    在 Java SE 7 和更高版本中,只要编译器可以从上下文确定或推断出类型参数,就可以用一组空的类型参 数( <> )替换调用通用类的构造函数所需的类型参数。这对尖括号 <> 被非正式地称为菱形。例如,你可以使用以下语句创建 Box 的实例:
    Box < Integer > integerBox = new Box <> ();
有关菱形符号和类型推断的更多信息,请参见 类型推断
Multiple Type Parameters (多种类型的参数)
如前所述,泛型类可以具有多个类型参数。例如,通用的 OrderedPair 类实现了通用的 Pair 接口:
public interface Pair < K , V > {
    public K getKey ();
    public V getValue ();
}
public class OrderedPair < K , V > implements Pair < K , V > {
    private K key ;
    private V value ;
    public OrderedPair ( K key , V value ) {
        this . key = key ;
        this . value = value ;
    }
    public K getKey () { return key ; }
    public V getValue () { return value ; }
}
以下语句创建 OrderedPair 类的两个实例:
    Pair < String , Integer > p1 = new OrderedPair < String , Integer > ( "Even" , 8 );
    Pair < String , String > p2 = new OrderedPair < String , String > ( "hello" , "world" );
    代码 new OrderedPair K 实例化为 String ,将 V 实例化为 Integer 。因此, OrderedPair 的构造函 数的参数类型分别为 String Integer 。由于自动装箱,将 String int 传递给类是有效的。 如The Diamond (菱形)所述,由于 Java 编译器可以从声明 OrderedPair 推断出 K V 类型,因此可以使用菱形表示法来缩短这些语句:
    OrderedPair < String , Integer > p1 = new OrderedPair <> ( "Even" , 8 );
    OrderedPair < String , String > p2 = new OrderedPair <> ( "hello" , "world" );
要创建通用接口,请遵循与创建通用类相同的约定。
Parameterized Types (参数化类型)
你还可以用参数化类型(即 List )替换类型参数(即 K V )。例如,使用 OrderedPair 示例:
    OrderedPair < String , Box < Integer >> p = new OrderedPair <> ( "primes" , new Box < Integer > (...));
Raw Types (原始类型)
原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定通用 Box 类:
public class Box < T > {
    public void set ( T t ) { /* ... */ }
    // ...
}
要创建 Box 的参数化类型,请为形式类型参数 T 提供一个实际的类型参数:
Box < Integer > intBox = new Box <> ();
 
如果省略实际的类型参数,则创建 Box 的原始类型:
Box rawBox = new Box ();
    因此, Box 是通用类型 Box 的原始类型。但是,非泛型类或接口类型不是原始类型。 原始类型显示在旧版代码中,因为在JDK 5.0 之前,许多 API 类(例如 Collections 类)不是通用的。使 用原始类型时,你实际上会获得泛型行为(Box 为你提供对象)。为了向后兼容,允许将参数化类型分配给其原始类型:
 
Box < String > stringBox = new Box <> ();
Box rawBox = stringBox ; // OK
 
但是,如果将原始类型分配给参数化类型,则会收到警告:
 
 
Box rawBox = new Box (); // rawBox is a raw type of Box
Box < Integer > intBox = rawBox ; // warning: unchecked conversion
 
如果你使用原始类型来调用在相应的泛型类型中定义的泛型方法,也会收到警告:
Box < String > stringBox = new Box <> ();
Box rawBox = stringBox ;
rawBox . set ( 8 ); // warning: unchecked invocation to set(T)
 
该警告表明原始类型会绕过通用类型检查,从而将不安全代码的捕获推迟到运行时。因此,应避免使用
原始类型。
Type Erasure 部分提供了有关 Java 编译器如何使用原始类型的更多信息。
 
Unchecked Error Messages (未检查的错误消息)
如前所述,将旧代码与通用代码混合时,你可能会遇到类似于以下内容的警告消息:
Note : Example . java uses unchecked or unsafe operations .
Note : Recompile with - Xlint : unchecked for details .
当使用旧的 API 操作原始类型时可能会出现这种情况,如下例所示:
public class WarningDemo {
    public static void main ( String [] args ){
        Box < Integer > bi ;
        bi = createBox ();
    }
    static Box createBox (){
        return new Box ();
    }
}
    术语 未检查 表示编译器没有足够的类型信息来执行确保类型安全所需的所有类型检查。尽管编译器会 给出提示,但默认情况下禁用“ 未检查 警告。要查看所有 未检查 的警告,请使用 - Xlint:unchecked 重 新编译。
    使用 - Xlint:unchecked 重新编译前面的示例将显示以下附加信息:
WarningDemo.java:4: warning: [unchecked] unchecked conversion
    found : Box
    required: Box
         bi = createBox();
                                ^
1 warning
 
要完全禁用未检查的警告,请使用 - Xlint:unchecked 标志。 @SuppressWarnings("unchecked")
解禁止未检查的警告。如果你不熟悉 @SuppressWarnings 语法,请参阅 注解
 
Generic Methods (通用方法)
 
    通用方法是指引入自己的类型参数的方法。这类似于声明一个泛型方法,但类型参数的范围仅限于声明 它的方法。允许使用静态和非静态的泛型方法,也允许使用泛型类构造函数。
    通用方法的语法包括类型参数列表,在尖括号内,该列表出现在方法的返回类型之前。对于静态泛型方 法,类型参数部分必须出现在方法的返回类型之前。
    Util 类包含一个通用方法 compare ,该方法比较两个 Pair 对象:
 
Java进阶-泛型(1)_第1张图片
调用此方法的完整语法为:
Pair < Integer , String > p1 = new Pair <> ( 1 , "apple" );
Pair < Integer , String > p2 = new Pair <> ( 2 , "pear" );
boolean same = Util . < Integer , String > compare ( p1 , p2 );
该类型已明确提供,如上所示。通常,可以将其忽略,编译器将推断出所需的类型:

 

Pair p1 = new Pair<>(1, "apple");
Pair p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);
 
此功能称为类型推断,使你可以在不指定尖括号之间的类型的情况下,将通用方法作为普通方法调用。
类型推断 将进一步讨论该主题。
Bounded Type Parameters (限定类型参数)
 
有时你可能想限制可以在参数化类型中用作类型参数的类型。例如,对数字进行操作的方法可能只希望
接受 Number 或其子类的实例。这就是限定类型参数的用途。
要声明一个限定的类型参数,请列出类型参数的名称,然后列出 extends 关键字,然后列出其上限(在
本示例中为 Number )。请注意,在这种情况下, extends 通常用于表示 扩展 (如在类中)或 实现
(如在接口中)。

 

Java进阶-泛型(1)_第2张图片

通过修改通用方法以包含此限定类型参数,由于我们的 inspect 调用仍包含 String ,因此编译现在将
失败:
Box.java: 21 : inspect(U) in Box cannot
be applied to (java.lang.String)
integerBox.inspect(" 10 ");
^
1 error
除了限制可用于实例化泛型类型的类型之外,限定类型参数还允许你调用在范围中定义的方法:
public class NaturalNumber < T extends Integer > {
    private T n ;
    public NaturalNumber ( T n ) { this . n = n ; }
    public boolean isEven () {
        return n . intValue () % 2 == 0 ;
    }
// ...
}
isEven 方法通过 n 调用 Integer 类中定义的 intValue 方法。
Multiple Bounds (多重限定)
前面的示例说明了使用带单个限定的类型参数,但是一个类型参数可以具有多个限定:
< T extends B1 & B2 & B3 >
具有多个限定的类型变量是范围中列出的所有类型的子类型。如果范围之一是类,则必须首先指定它。
例如:
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
class D < T extends A & B & C > { /* ... */ }
如果未首先指定绑定 A ,则会出现编译时错误:
class D < T extends B & A & C > { /* ... */ } // compile-time error
Generic Methods and Bounded Type Parameters (通用方法和
限定类型参数)
限定类型参数是实现通用算法的关键。考虑以下方法,该方法计算数组 T[] 中大于指定元素 elem 的元素数。
public static < T > int countGreaterThan ( T [] anArray , T elem ) {
    int count = 0 ;
    for ( T e : anArray )
        if ( e > elem ) // compiler error
        ++ count ;
      return count ;
}
 
该方法的实现很简单,但是不能编译,因为大于运算符( > )仅适用于基本类型,例如 short 、 int 、 double long float byte char 。你不能使用 > 运算符比较对象。要解决此问题,请使用 Comparable 接口限定的类型参数:
 
public interface Comparable < T > {
    public int compareTo ( T o );
}
结果代码将是:
public static < T extends Comparable < T >> int countGreaterThan ( T [] anArray , T elem ) {
     int count = 0 ;
     for ( T e : anArray )
          if ( e . compareTo ( elem ) > 0 )
               ++ count ;
       return count ;
}
 
Generics, Inheritance, and Subtypes (泛型,继承和 子类型)
    众所周知,只要类型兼容,就可以将一种类型的对象分配给另一种类型的对象。例如,你可以将一个 Integer 分配给一个 Object ,因为 Object Integer 的超类型之一:
Object someObject = new Object ();
Integer someInteger = new Integer ( 10 );
someObject = someInteger ; // OK
在面向对象的术语中,这称为 is a 关系。由于 Integer 是一种 Object ,因此允许分配。但是 Integer 也是 Number 的一种,因此以下代码也有效:

 

public void someMethod ( Number n ) { /* ... */ }
someMethod ( new Integer ( 10 )); // OK
someMethod ( new Double ( 10.1 )); // OK
泛型也是如此。你可以执行通用类型调用,将 Number 作为其类型参数传递,并且如果该参数与 Number 兼容,则可以随后进行 add 的任何后续调用:
 
Box < Number > box = new Box < Number > ();
box . add ( new Integer ( 10 )); // OK
box . add ( new Double ( 10.1 )); // OK
现在考虑以下方法:
public void boxTest ( Box < Number > n ) { /* ... */ }
它接受哪种类型?通过查看其签名,你可以看到它接受一个类型为 Box 的单个参数。但是,这是什么意 思?如你所料,你是否允许传递 Box Box ?答案是 ,因为 Box Box 不是 Box 的子类型。 在使用泛型进行编程时,这是一个常见的误解,但它是一个重要的概念。
 
Java进阶-泛型(1)_第3张图片
 
 
 
注意:给定两种具体的类型 A B (例如 Number Integer ),无论 A B 是否相关, MyClass 与 MyClass 没有关系。 MyClass MyClass 的公共父对象是 Object 。有关在类型参数相关时如 何在两个泛型类之间创建类似子类型的关系的信息,请参见 通配符和子类型
Generic Classes and Subtyping (通用类和子类型)
    你可以通过扩展或实现来泛型通用类或接口。一个类或接口的类型参数与另一类或接口的类型参数之间 的关系由 extends implements 子句确定。
    以 Collections 类为例, ArrayList 实现 List ,而 List 扩展 Collection 。因此, ArrayList 是 List 的子类型,而 List Collection 的子类型。只要你不改变类型参数,子类型关系就保留在类型 之间。
Java进阶-泛型(1)_第4张图片
 
现在假设我们要定义自己的列表接口 PayloadList ,该接口将泛型 P 的可选值与每个元素相关联。它的声明可能看起来像:
 
interface PayloadList < E , P > extends List < E > {
    void setPayload ( int index , P val );
}
PayloadList 的以下参数化是 List 的子类型:
  • PayloadList
  • PayloadList
  • PayloadList
Java进阶-泛型(1)_第5张图片

 

Type Inference (类型推断)
    类型推断是 Java 编译器查看每个方法调用和相应声明以确定使调用适用的类型参数的能力。推断算法确 定参数的类型,以及确定结果是否被分配或返回的类型(如果有)。最后,推断算法尝试找到与所有参 数一起使用的最具体的类型。
    为了说明最后一点,在下面的示例中,推断确定传递给 pick 方法的第二个参数的类型为 Serializable :
static < T > T pick ( T a1 , T a2 ) { return a2 ; }
Serializable s = pick ( "d" , new ArrayList < String > ());
Type Inference and Generic Methods (类型推断和通用方法)
    通用方法为你引入了类型推断,使你可以像调用普通方法一样调用通用方法,而无需在尖括号之间指定 类型。考虑下面的示例BoxDemo ,它需要 Box 类:
 
Java进阶-泛型(1)_第6张图片
以下是此示例的输出:
 

 

Box # 0 contains [ 10 ]
Box # 1 contains [ 20 ]
Box # 2 contains [ 30 ]
通用方法 addBox 定义了一个名为 U 的类型参数。通常, Java 编译器可以推断出通用方法调用的类型参 数。因此,在大多数情况下,你不需要指定这些类型参数。例如,为了调用泛型方法 addBox ,你可以用一个类型见证来指定类型参数,如下所示。
BoxDemo . < Integer > addBox ( Integer . valueOf ( 10 ), listOfIntegerBoxes );
或者,如果你省略了类型见证, Java 编译器会自动推断出(从方法的参数(实参)中)类型参数是Integer 。
BoxDemo . addBox ( Integer . valueOf ( 20 ), listOfIntegerBoxes );
Type Inference and Instantiation of Generic Classes (泛型类的类型推断和 实例化)
你可以用一组空的类型参数( <> )替换调用通用类的构造函数所需的类型参数,只要编译器可以从上下文中推断类型参数即可。这对尖括号被非正式地称为菱形。
例如,考虑以下变量声明:
Map < String , List < String >> myMap = new HashMap < String , List < String >> ();
你可以用一组空的类型参数( <> )代替构造函数的参数化类型:
Map < String , List < String >> myMap = new HashMap <> ();
请注意,要在泛型类实例化过程中利用类型推断的优势,必须使用菱形。在以下示例中,编译器生成未 经检查的转换警告,因为 HashMap() 构造函数引用的是 HashMap 原始类型,而不是 Map> 类型:
Map < String , List < String >> myMap = new HashMap (); // unchecked conversion
warning
Type Inference and Generic Constructors of Generic and Non-Generic Classes (泛型和非泛型类的类型推断和泛型构造函数)
请注意,构造函数在通用和非通用类中都可以是通用的(换句话说,声明自己的形式类型参数)。考虑
以下示例:
class MyClass < X > {
    < T > MyClass ( T t ) {
    // ...
    }
}
考虑类 MyClass 的以下实例化:
new MyClass < Integer > ( "" )
该语句创建参数化类型 MyClass 的实例;该语句为泛型类 MyClass 的形式类型参数 X 明确指定类型 Integer 。请注意,此泛型类的构造函数包含一个形式类型参数 T 。编译器会为该泛型类的构造函数的 形式类型参数T 推断 String 类型(因为该构造函数的实际参数是 String 对象)。
Java SE 7 之前的发行版中的编译器能够推断泛型构造函数的实际类型参数,类似于泛型方法。但是,如 果使用菱形( <> ),则 Java SE 7 和更高版本中的编译器可以推断要实例化的泛型类的实际类型参数。 考虑以下示例:
 
MyClass < Integer > myObject = new MyClass <> ( "" );
在此示例中,编译器为通用类 MyClass 的形式类型参数 X 推断类型 Integer 。它为该泛型类的构造函数的形式类型参数T 推断类型 String
 
注意:值得注意的是,推断算法仅使用调用参数,目标类型,并且可能使用明显的预期返回类 型。推断算法不使用程序后面的结果。
Target Types (目标类型)
 
Java 编译器利用目标类型来推断通用方法调用的类型参数。表达式的目标类型是 Java 编译器期望的数据 类型,具体取决于表达式出现的位置。考虑方法 Collections.emptyList ,该方法声明如下:
static < T > List < T > emptyList ();
考虑以下赋值语句:
List < String > listOne = Collections . emptyList ();
该语句需要 List 的实例;此数据类型是目标类型。因为方法 emptyList 返回类型为 List 的值,所以 编译器推断类型参数T 必须为值 String 。这在 Java SE 7 8 中都可以使用。或者,你可以使用类型见证并按如下所示指定T 的值:
 
List < String > listOne = Collections . < String > emptyList ();
 
但是,在这种情况下,这不是必需的。不过,在其它情况下这是必要的。请考虑以下方法:
 
void processStringList ( List < String > stringList ) {
// process stringList
}
 
假设你要使用空列表调用方法 processStringList 。在 Java SE 7 中,以下语句不会编译:
processStringList ( Collections . emptyList ());
Java SE 7 编译器生成类似于以下内容的错误消息:
List < Object > cannot be converted to List < String >
编译器需要类型参数 T 的值,因此它以值 Object 开头。因此,对 Collections.emptyList 的调用返回 类型为 List 的值,该值与方法 processStringList 不兼容。因此,在 Java SE 7 中,必须指定 type 参数值的值,如下所示:
processStringList ( Collections . < String > emptyList ());
这在 Java SE 8 中已经没有必要了,目标类型的概念已经扩展到包括方法参数,例如方法 processStringList 的参数。在这种情况下, processStringList 需要一个 List 类型的参数。方法 Collections.emptyList 返回一个 List 的值,因此,使用 List 的目标类型,编译器会推断出类型参 数T 的值为 String 。因此,在 Java SE 8 中,编译器会编译出以下语句:
processStringList ( Collections . emptyList ());
有关更多信息,请参见 Lambda 表达式 中的 目标类型
Wildcards (通配符)
 
在通用代码中,称为通配符的问号( ? )表示未知类型。通配符可以在多种情况下使用:作为参数,字 段或局部变量的类型;有时作为返回类型(尽管更具体的做法是更好的编程习惯)。通配符从不用作泛 型方法调用,泛型类实例创建或超类型的类型参数。 以下各节将更详细地讨论通配符,包括上界通配符,下界通配符和通配符捕获。
Upper Bounded Wildcards (上限通配符)
 
你可以使用上限通配符来放宽对变量的限制。例如,假设你要编写一种适用于 List List List 的 方法;可以使用上限通配符来实现。
要声明上限通配符,请使用通配符( ? ),后跟 extends 关键字,然后是其上限。请注意,在这种情况 下, extends 通常用于表示 扩展 (如在类中)或 实现 (如在接口中)。
要编写在 Number 类型的列表和 Number 的子类型(如 Integer Double Float )上工作的方法, 你会指定 List 。术语 List List 更有限制性,因为前者只匹配 Number 类型的列表,而后者匹配 Number 类型的列表或其任何子类。 考虑以下处理方法
public static void process ( List extends Foo > list ) { /* ... */ }
上限通配符, ` ,其中 Foo 是任何类型,匹配 Foo Foo 的任何子类型。 process` 方法可以以类型 Foo 的形式 访问列表元素。
public static void process ( List extends Foo > list ) {
   for ( Foo elem : list ) {
   // ...
   }
}
foreach 子句中, elem 变量对列表中的每个元素进行迭代。在 Foo 类中定义的任何方法现在都可以在
   elem 上使用。
sumOfList 方法返回一个列表中的数字之和:
public static double sumOfList ( List extends Number > list ) {
   double s = 0.0 ;
    for ( Number n : list )
          s += n . doubleValue ();
       return s ;
}
下面的代码,使用一个 Integer 对象的列表,打印出 sum = 6.0
 
List < Integer > li = Arrays . asList ( 1 , 2 , 3 );
System . out . println ( "sum = " + sumOfList ( li ));
一个 Double 值的列表可以使用同样的 sumOfList 方法。下面的代码打印出 sum = 7.0
List < Double > ld = Arrays . asList ( 1.2 , 2.3 , 3.5 );
System . out . println ( "sum = " + sumOfList ( ld ));
Unbounded Wildcards (无限通配符)
无界通配符类型使用通配符( ? )来指定,例如 List 。这就是所谓的未知类型的列表。有两种情况
下,无界通配符是一种有用的方法。
  • 如果你正在编写一个可以使用 Object 类中提供的功能实现的方法。
  • 当代码使用通用类中不依赖于类型参数的方法时。例如, List.size List.clear 。事实上,Class 之所以这么经常使用,是因为 Class 中的大部分方法都不依赖于T

考虑以下方法, printList

public static void printList ( List < Object > list ) {
        for ( Object elem : list )
            System . out . println ( elem + " " );
           System . out . println ();
}  
printList 的目标是打印任何类型的列表,但未能实现该目标(它仅打印 Object 实例的列表);它不 能打印 List List List 等,因为它们不是 List 的子类型。要编写通用的 printList 方法,请使用 List
public static void printList ( List list ) {
          for ( Object elem : list )
             System . out . print ( elem + " " );
             System . out . println ();
}
因为对于任何具体类型 A List List 的子类型,所以可以使用 printList 打印任何类型的列表:
List < Integer > li = Arrays . asList ( 1 , 2 , 3 );
List < String > ls = Arrays . asList ( "one" , "two" , "three" );
printList ( li );
printList ( ls );
 
注意:本课的示例中均使用 Arrays.asList 方法。此静态工厂方法将转换指定的数组并返回固定大小的列表。
重要的是要注意 List List 不同。你可以将 Object Object 的任何子类型插入 List 。但是你只 能将 null 插入 List 通配符使用准则 部分提供了有关如何确定在给定情况下应使用哪种通配符(如果 有)的更多信息。
 
Lower Bounded Wildcards (下界通配符)
上限通配符 部分显示,上限通配符将未知类型限制为特定类型或该类型的子类型,并使用 extends 关 键字表示。以类似的方式,下限通配符将未知类型限制为特定类型或该类型的超类型。
下限通配符使用通配符( ? )表示,后跟 super 关键字,后跟下限 , 如: ``
注意:你可以为通配符指定一个上限,也可以指定一个下限,但不能同时指定两者。
假设你要编写一个将 Integer 对象放入列表的方法。为了最大程度地提高灵活性,你希望该方法可用 于 List List List (可以容纳 Integer 值的任何内容)。
要编写对 Integer 的列表和 Integer 的超类型(如 Integer Number Object )的方法,你可以指 定 List 。术语 List List 的限制性更强,因为前者只匹配 Integer 类型的列表,而后者则匹配作 为 Integer 的超类型的任何类型的列表。
以下代码将数字 1 10 添加到列表的末尾:
 
public static void addNumbers ( List super Integer > list ) {
        for ( int i = 1 ; i <= 10 ; i ++ ) {
            list . add ( i );
          }
}
 
通配符使用准则 部分提供了有关何时使用上限通配符以及何时使用下限通配符的指南。
 
 
 
 
 
 
 

 

 

 

 

你可能感兴趣的:(java,android,java,泛型,android)