java 深入泛型_Java 学习之路 之 深入泛型(四十)

所谓泛型,就是运行在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。Java 5 改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参,这就是在前面程序中看到的 List 和 ArrayList 两种类型。

1,定义泛型接口、类

下面是 Java 5 改写后 List 接口、Iterator 接口、Map 的代码片段。

//定义接口时制定了一个类型形参,该形参名为 E

public interface List

{

//在该接口里, E 可作为类型使用

//下面方法可以使用 E 作为参数类型

void add(E x);

Iterator iterator();//1

...

}

//定义接口时指定了一个类型形参,该形参名为 E

public interface Iterator{

// 在该接口里 E 完全可以作为类型使用

E next();

boolean hashNext();

...

}

//定义接口时指定了两个类型形参,其形参名为K、V

public interface Map{

//在该接口里K、V完全可以作为类型使用

Set keySet();//2

V put(K key, V value)

...

}上面三个接口声明是比较简单的, 除了尖括号中的内容----就就是泛型的实质:允许在定义接口、类时申明类型形参,类型形参在整个接口、类体内可当成类型使用,几乎所有可后使用普通类型的地方都可以使用这种类型形参。

除此之外,我们发现 1、2出方法申明返回值类型是 Iterator、Set,这表明 Set 形式是一种特殊的数据类型,是一种与 Set 不同的数据类型 ---- 可以认为是 Set 类型的子类。

例如,我们使用 List 类型时,为 E 形参传入 String 类型实参,则产生了一个新的类型: List 类型,我们可以把 List 想象成 E 被全部换成 String 的特殊 List 子接口。

// List 等同于如下接口

public interface ListString extends List{

//原来的 E 形参全部变成 String 类型实参

void add(String x);

Iterator iterator();

...

}虽然程序只定义了一个 List 接口,但实际使用时可以产生无数多个 List 接口,只要为 E 传入不同的类型实参,系统就会多出一个新的 List 子接口。必须指出:List 绝不会被替换成 ListString,系统没有进行源代码复制,二进制代码中没有,磁盘中没有,内存中也没有。

包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上并不存在。

通过上面介绍可以发现,我们可以为任何类、接口增加泛型声明(并不是只有集合类可以使用泛型声明,虽然集合类是泛型的重要使用场所)。下面自定义一个 Apple 类,这个 Apple 类就可以包含一个泛型声明。

package com.sym.demo3;

//定义 Apple 类时使用了泛型声明

public class Apple {

//使用 T 类型形参定义实例变量

private T info;

public Apple(){}

// 下面方法中使用 T 类型形参来定义构造器

public Apple(T info){

this.info = info;

}

public void setInfo(T info){

this.info = info;

}

public T getInfo(){

return this.info;

}

public static void main(String[] args) {

// 因为传给 T 形参的是 String 实际类型

// 所以构造器的参数智能是 String

Apple a1 = new Apple<>("苹果");

System.out.println(a1.getInfo());

// 因为传给 T 形参的是 Double 实际类型

// 所以构造器的参数只能是 Double 或者 double

Apple a2 = new Apple<>(5.67);

System.out.println(a2.getInfo());

}

}上面程序定义了一个带泛型声明的 Apple 类(不要理会这个类型形参是否具有实际意义),使用 Apple 类时就可为 T 类型形参传入实际类型,这样就可以生成如 Apple、Apple...形式的多个逻辑子类(物理上并不存在)。

当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明、例如,为 Apple 类定义构造器,其构造器名依然是 Apple,而不是Apple! 低调用该构造器时却可以使用 Apple 的形式,当然应该为 T 形参传入实际的类型参数。Java 7 提供了菱形语法,允许省略<>中的类型实参。

2,从泛型类派生子类

当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,但需要指出的是,当使用这些接口、父类时不能再包含类型形参。例如,下面代码就是错误的。

// 定义类 A 继承 Apple 类,Apple 类不能跟类型形参

public class A extends Apple {}方法中的形参代表变量、常量、表达式等数据,本书把它们直接称为形参,或者称为数据形参,定义方法时可以声明数据形参,调用方法(使用方法)时必须为这些数据形参传入实际的数据;与此类似的是,定义类、接口、方法时可以声明类型形参,使用类、接口、方法时应为类型形参传入实际的类型。

如果想从 Apple 类派生一个子类,则可以改为如下代码:

//使用 App 类时为 T 形参传入 String 类型

public class A extends Apple调用方法时必须为所有的数据形参传入参数值,与调用方法不同的是,使用类、接口时可以不为类型形参传入实际的类型参数,即下面代码也是正确的。

// 使用 Apple 类时,没有为 T 形参传入实际的类型参数

public class A extends Apple如果从 Apple 类派生子类,则在 Apple 类中没有使用 T 类型形参的地方都被替换成 String 类型,即它的子类将会继承到 String getInfo() 和 void setInfo(String info) 两个方法,如果子类需要重写父类的方法,就必须注意这一点。下面程序示范了这一点。

package com.sym.demo3;

public class A1 extends Apple {

//正确重写了父类的方法,返回值

//与父类 Apple 的返回值完全相同

public String getInfo(){

return "子类" + super.getInfo();

}

/*

// 下面方法是错误的,重写父类方法时返回值类型不一致

public Object getInfo(){

return "子类";

}

*/

}如果使用 Apple 类时没有传入实际的类型参数,Java 编译器可能发出警告:使用了未经检查或不安全的操作----这就是泛型检查的警告,读者在前一章中应该多次看到这样的警告。如果希望看到该警告提示的更详细信息,则可以通过为 javac 命令增加 -Xlint:unchecked 选项来实现。此时,系统会把 Apple 类里的 T 形参当成 Object 类型处理。

package com.sym.demo3;

public class A2 extends Apple{

//重写父类的方法

public String getInfo(){

// super.getInfo()方法返回值是 Object 类型

// 所以加 toString() 才返回 String 类型

return super.getInfo().toString();

}

}上面程序都是从带泛型声明的福利来派生子类,创建带泛型声明的接口的实现类与此几乎完全一样。

3,并不存在的泛型类

前面提到可以把 ArrayList 类当成 ArrayList 的子类,事实上,ArrayList 类也确实像一种特殊的 ArrayList 类,这个 ArrayList 对象只能添加String 对象作为集合元素。但实际上,系统并没有为 ArrayList 生成新的 class 文件,而且也不会把 ArrayList 当成新类来处理。

看到下面代码的打印结果是什么?

// 分别创建 List 对象和 List 对象

List l1 = new ArrayList<>();

List l2 = new ArrayList<>();

// 调用 getClass() 方法来比较 l1 和 l2 的类是否相等

System.out.println(l1.getClass() == l2.getClass());运行上面的代码片段,可能有读者认为应该输出 false,但实际输出 true。因为不管泛型的实际类型参数时什么,它们在运行时总有相同的类(class)。

不管为泛型的类型形参传入哪一种类型实参,对于 Java 来说,它们依然被当成同一个类处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。下面程序演示了这种错误。

public class R{

//下面代码错误,不能在静态 Field 声明中使用类型形参

static T info;

T age;

public void foo(T msg){}

// 下面代码错误,不能在静态方法声明中使用类型形参

public static void bar(T msg){}

}由于系统中并不会真正生成泛型类,所以 instanceof 运算符后不能使用泛型类。例如,下面代码时错误的。

Collection cs = new ArrayList();

// 下面代码编译时引起错误,instanceof 运算符不能使用泛型类

if (cs instanceof List) {...}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值