java泛型详解

java泛型详解

(泛型类、泛型方法、类型变量限定、类型擦除、桥方法、继承规则(通配符)、约束与局限性)


大部分的泛型文章只涉及到泛型类与泛型方法等一些部分,在这一篇文章中,尽量对泛型有一个详细全面的基础描述,介绍实现自己的泛型需要了解的各种知识。

为什么使用泛型?

泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行类型强制转换的代码具有更好的安全性可读性。泛型程序设计意味着代码可以被很多不同类型的对象所重用

一、泛型类

一个泛型类就是一个具有一个或多个类型变量的类。

例如,定义一个名为Pair的泛型类

public class Pair<T>

{

private T first;

private T second;

}

Pair类引入一个类型变量T,用<>括起来,并放在类型后面。

 

例如,当你要声明一个Strng类型变量的Pair类时可以:

Pair<String> p;

代表:

public class Pair<String>

{

private Sting first;

private Sting second;

}

 

同样,当first与second的类型不一样时,也可以设计如下一个泛型类。

public class Pair<T,U>

{

private T first;

private U second;

}

声明时,Pair<Sting,Date>p;

 

注:在java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值,T(有时使用U和S)表示“任意类型”

 

二、泛型方法

除了定义一个泛型类之外,我们也可以定义一个泛型方法。泛型方法可以定义在普通类与泛型类当中。

在Pair类原有的基础上,我们再增加getter与setter方法。

public class Pair<T>

{

private T first;

private T second;

public <T> T getFirst(){

 return first;

}

public <T> setFirst(T first){

this.first = first;

}

}

在泛型方法中,类型参数<T>放在修饰符(此处是public)后面,返回类型前面(此处是T)。

 

三、类型变量限定

有时候,我们需要对类型T做一些限定,比如传入的类型需要是能够进行比较排序的类型。

例如,我们定义一个找出最小值的泛型方法min,类型T是继承了Comparable接口的类型。

 

class sort{

 public static <T extendsComparable> T min(T[] a)

{

  T smallest = a[0];

  for(int i = 0;i < a.length;i++)

{

//如果T不限定成是继承了comparable接口的类,则不能够保证传入的类型变量拥有compareTo方法,在程序运行时容易出错。

 If(smallest.compareTo(T[i]) > 0)

  Smallest = T[i];

}

}

}

 

在java继承中,可以根据需要限定多个接口超类型,但最多限定一个类,限定类型用“&”分隔。

 

例如限定一个继承Comparable接口、Serializable接口与Pair类接口的子类类型,则min方法应该这么定义:

public static <T extends Pair & Comparable & Serializable>T min(T[] a){…}

 

注意:限定类时,类应该放在限定列表的第一个

 

四、类型擦除

首先,记住以下四点:

1、虚拟机(JVM)中,没有泛型,只有普通的类与方法;

2、所有的类型参数都用它们的限定类型替换;

3、为保持类型安全性,必要时插入强制类型转换(虚拟机自动完成此项工作);

4、桥方法被合成来保持多态(虚拟机自动完成此项工作);

 

下面将分别阐述以上四点

 

4.1与4.2:虚拟机中没有泛型

在虚拟机中,所有的类与方法都是普通类。无论何时定义一个泛型,在虚拟机中,都自动提供一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数(<T>)后的泛型类型名。

 

例如前面的Pair<T>的原始类型名就是Pair。而类型变量T 就会被擦除,替换成Object。

public class Pair<T>

{

private T first;

private T second;

}

类型擦除后:

public class Pair

{

private Object first;

private Object second;

}

当T是有单个或者多个限定类型时,T被替换成限定列表的第一个:

 

加粗部分即有变化部分

class sort{

 public static <T extendsComparable> T min(T[] a)

{

  T smallest = a[0];

  for(int i = 0;i < a.length;i++)

{

If(smallest.compareTo(T[i]) > 0)

  Smallest = T[i];

}

}

}

类型擦除后:

class sort{

 public static Comparable min(Comparable [] a)

{

  Comparable smallest = a[0];

  for(int i = 0;i < a.length;i++)

{

If(smallest.compareTo(Comparable [i]) > 0)

  Smallest = [i];

}

}

}

 

4.3:翻译泛型表达式

给出如下的一个泛型类

class Pair<T>{

private Tfirst;

private T second;

public Pair(T first,T second)

{

 this.first = first;

 this.second = second;

}

public T getFIrst()

{

return first;

}

}

   当类型擦除后变成了

class Pair{

private Objectfirst;

private Objectsecond;

public Pair(Object first,Object second)

{

  this.first = first;

  this.second = second;

}

 

public Object getFIrst()

{

return first;

}

}

考虑如下代码:

Pair<String> p = new Pair(“hello”,”world”);

String p1 = p.getFirst();

记住,由于类型擦除,Pair类里面的getFirst

 public Object getFIrst()

{

return first;

}

返回的是Object对象,编译器自动插入String的强制类型转换,类似于

String p1 = (Strng)p.getFirst();

 

4.4、桥方法

桥方法的存在是为了解决泛型方法类型擦除后产生的问题。

假设有一个类继承自Pair<String>

 

class PairChild extends Pair<String>{

  public void setFirst(String first)

{

//如果first是字符串“hello”,则调用父类的setFirst()方法

If(first == “hello”)

  super.setFirst(first);

}

}

这段代码在类型擦除后变成了:

class PairChild extends Pair{

//注意此处的setFirst(String first)方法

  public void setFirst(String first)

{

//如果first是字符串“hello”,则调用父类的setFirst()方法

If(first == “hello”)

  super.setFirst(first);

}

}

此时注意父类Pair

public class Pair<T>

{

private T first;

private T second;

public <T> setFirst(T first){

this.first = first;

}

}

类型擦除后变成:

public class Pair

{

private Objectfirst;

private Object second;

public void setFirst(Objectsecond){…}

}

这里有一个public voidsetFirst(Object second){…}方法。

 

子类PairChild继承了父类的public void setFirst(Object second)方法,同时自身又有一个public void setFirst(String first),此时,setFirst方法有了多态。

   

那么当有以下代码时:

PairChlid pc = new PairChild();

pc.setFirst(“world”);

由于拥有父类参数的方法能传入子类,此时的pc.setFirst(“world”)应该调用public void setFirst(Object second)还是publicvoid setFirst(String first)方法呢?

 

为了避免这种疑惑,虚拟机在PairChlid类中生成一个桥方法

 

class PairChild extends Pair<String>{

//覆盖了父类的setFirst(Object first)方法的同时,调用了setFirst(String first)方法

public void setFirst(Object first){

 setFirst((String)first);

}

 

  public void setFirst(String first)

{

//如果first是字符串“hello”,则调用父类的setFirst()方法

If(first == “hello”)

  super.setFirst(first);

}

}

 

五、继承规则(通配符的上下限)

可以说,通配符的存在是为了解决泛型中的继承问题。

假设有一个父类Father和一个子类child

有一个方法public static void min(father f){..}

此时这样调用方法

 Child c = new Child();

min(c);//编译通过

但是如果是泛型方法:public static voidmin(Pair<father> f){..}

这样调用

Pair<child> c = new Pair<child>();

min(c);//编译错误

 

 

Child是Father的子类,但是Pair<Child>不是Pair<Father>的子类。

要解决这一点很简单,引入一个通配符“?”。

 

泛型方法min可以这样设计:

 

public static void min(Pair<? extends Father> f){..}

表示传入的参数类型“?”是Father的某个子类类型或者Father本身

此时

Pair<child> c = new Pair<child>();

min(c);//编译通过

  另外一种情况,当传入的参数类型不要求是father的子类而要求是father类的父类时,泛型方法可以这样定义:

public static void min(Pair<? extends Father> f){..}

表示传入的类型参数“?”应该是father的父类。

如果传入的类型参数无类型要求时,min可以这样定义:

public static void min(Pair<? > f){..}

表示任何的Pair参数对象都可以传递进min方法。

 

注:可能会有人疑惑参数类型T与通配符?有何不同?T一般是在定义泛型类与泛型方法时使用,并且没有参数类型是继承自…或者是…的父类的意思,与?并不相同,?也不是类型变量。

当然 public static <T> void min(pair<T> f){…} 与public static void min(Pair<? > f){..}作用相同。

 

捕获通配符。

考虑有一个swap方法用于交换Pair类型的first与second属性,怎么交换?。

public static void swap(Pair<? > f){

..   ? temp = f.getFirst();//编译错误,?并不是类型变量

}

此时应该用T先写一个方法swapTemp来捕获通配符。

public static <T> void swapTemp(Pair<T> f){

..   T temp = f.getFirst();

   f.setFirst(f.getSecond());

   f.setSecond(temp);

}

再将swap改写成:

public static void swap(Pair<? > f){

..   swapTemp (f);

}

六、约束与局限性

终于来到最后一个部分,这里简单阐述一下泛型的一些局限。

1、不能用基本类型实例化类型参数,没有Pair<int>。

2、运行时的类型检查只适应于原始类型查询。(原因参见类型擦除的4.1)

  例如:Pair<String>s = new Pair<String>();

  s instanceof Pair<String> 是错误的.

无论何种Pair,只能使用s instanceof Pair。GetClass方法返回的也是Pair。

3、不能创建参数化类型的数组。例如:Pair<String>[] ss = new Pair<String>[10]是错误的。当然,声明Pair<String>[] ss是可以的,只不过不能用new初始化。

4、不能实例化类型变量。如new T(…)是错误的,一般用getter与setter设置泛型属性。

5、禁止使用带有类型变量的静态域与方法。

如public static T first 与publicstatic T getFirst()是禁止的。

6、注意类型擦除后的冲突


 参考:《java核心技术卷I》

欢迎转载~转载请注明出处

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值