Core Java (十九) 泛型程序设计(Generic parogramming)

释放双眼,带上耳机,听听看~!

泛型程序设计(Generic parogramming)意味着编写的代码可以被很多不同类型的对象所重用。

泛型,提供了类型参数(type parameters),使得程序具有更好的可读性和安全性。

例如:


1
2
1    ArrayList<String> files = new ArrayList<String>();
2

1
2
1  表明了ArrayList类有一个类型参数String来指示元素的类型。
2

泛型类

泛型类的简单实例:


1
2
1    Pair class Pair<T, U>{......}
2

泛型类的定义:一个泛型类就是具有一个或多个类型变量的类。如上述泛型类Pair具有两个类型变量(T和U)。

类型变量:用大写的T(或U,S)表示普通类型变量,E表示集合的元素类型,K和V分别表示表的关键字与值的类型。

实例化泛型类型:用具体的类型替换类型变量就可以实例化泛型类型。

泛型方法

泛型方法可以定义在普通类中,也可以定义在泛型类中。

例如:


1
2
3
4
1    public static <T> T getMiddle(T[] a){
2        return a[a.length / 2];
3    }
4

1
2
1类型变量<T>,要放在修饰符的后面,返回类型的前面。
2

调用泛型方法,在方法名前的尖括号中放入具体的类型。


1
2
3
4
1    String[] names = {"john", "Bob", "Lily"};
2    String middle = ArrayAlg.<String>getMiddle(names);
3
4

1
2
1  但是,大多数情况在,我们可以省略类型参数,直接调用方法。
2

1
2
3
1    String[] names = {"john", "Bob", "Lily"};
2    String middle = ArrayAlg.getMiddle(names);
3

类型变量的限定

有时候,我们必须要求某个类型变量必须实现某个接口再能让程序正确执行,比如 public static <T> T min(T[] a){ … }方法,很容易知道T必须是实现了Comparale接口才能得到最小值。我们只需要:


1
2
1    public static &lt;T extends Comparable&gt; T min(T[] a){ ... }
2

1
2
1这表示,min方法只能被实现了Comparable接口的类的数组调用。
2

<T extends BoundingType>,T是绑定类型(BoundingType)的子类型,BoundingType可以是类或者接口。当BoundingType有多个时,用&分隔,且限定中至多有一个类,如果有类,则类必须是限定列表中的第一个。

泛型代码和虚拟机

虚拟机没有泛型类型对象,所有对象都属于普通类。无论何时定义一个泛型类型,都自动提供一个相应的原始类型(raw type)。

原始类型用第一个限定的类型变量来替换,如果没有限定就用Object来替换。

翻译泛型表达式:

当调用泛型方法是,如果擦除返回类型,
编译器会插入强制类型转换。

例如:


1
2
3
1    Pair&lt;Employee&gt; buddies = ......
2    Employee buddy = buddies.getFirst();
3

1
2
1getFirst方法在泛型中返回一个Employee类型的对象,但是类型擦除后,返回一个Object类型的对象,这时,编译器会自动插入(Employee)buddies.getFirst(),强制类型转换。
2

翻译泛型方法:

public static <T extends Comparable> T min(T[] a)   —> public static Comparable min(Comparable[] a)

合成的桥方法:

这是为了解决类型擦除导致的多态性丧失而创造的方法。

小例子:

擦除类型前:

泛型类Pair:


1
2
3
4
5
1 public class Pair&lt;T&gt;{
2        ........
3        public void setSecond(T second){......}
4    }
5

一个普通的类:


1
2
3
4
1    class DateInterval extends Pair&lt;Date&gt;{
2        public void setSecond(Date second){...}
3    }
4

擦除类型后

Pair类变成了:


1
2
3
4
5
1 public class Pair{
2        ........
3        public void setSecond(Object second){......}
4    }
5

这个DateInterval类变成了:


1
2
3
4
1class DateInterval extends Pair{
2        public void setSecond(Date second){...};
3}
4

可以看出,两个setSecond方法的参数完全不同,他们是两个完全不同的方法,如果运行如下语句:


1
2
3
4
1    DateInterval interval = new DateInterval(...);
2    Pair&lt;Date&gt; pair = interval;
3    pair.setSecond(aDate);
4

1
2
1那么,最后一句,pair.setSecond(aDate)是调用的那个方法呢?下面来分析一下:
2

setSecond(Date second)方法仅在DateInterval中有效,setSecond(Objiec second)在Pair及其子类中都有效。这里已经擦除了类型,所以pair.setSecond(aDate)中的pair为Pair类的对象,必将调用Pair类中的那个setSecond(Object second)方法。这样,setSecond本应具有的多态性消失了。
注意:DateInterval类并没有重写setSecond方法。

所以,要利用合成的桥方法重新找回多态性。

合成的桥方法只需要在DateInterval类中加一个setSecond(Object second)方法:


1
2
3
4
5
6
7
1class DateInterval extends Pair{
2           public void setSecond(Date second){...};
3           public void setSecond(Object second){
4               setSecond((Date)second);
5           }          
6       }
7

就这样,setSecond(Object second)重写了超类中的同签名方法,实现了多态性。

约束与局限性

  1. 约束与局限性大多是由类型擦除引起的。
  2. 不能用基本类型实例化类型参数。由于primitive type不是Object的子类。
  3. 运行时类型查询只适用于原始类型(raw type)。a instanceof Pair<T>与a instanceof Pair<String>有相同结果,T被忽略了。
  4. 不能抛出也不能捕获泛型类实例。
  5. 参数化类型的数组不合法。Pair<String>[]  myTest = new Pair<String>[10]; //错误
  6. 不能实例化类型变量。new T(…);//错误
  7. 泛型类的静态上下文中类型变量无效。
  8. 注意擦除后的冲突。

通配符(wildcard type)

Pair<? extends Employee>
子类型限定的通配符

表示任何泛型Pair类型,它的类型参数是Employee的子类。

Pair<? extends Employee>是Pair<Employee>和Pair<Manager>的超类,而Pair<Employee>和Pair<Manager>没有任何关系。

  • ? extends Employee getFirst();
  • void setFirst(? extends Employee);

Pair<? extends Employee>中某个访问器getFirst()方法的返回类型是
? extends Employee,它赋给一个Employee类型的对象是完全合法的。而setFirst方法,编译器只知道参数类型是Employee类或其子类,具体不知道,所以编译器拒绝传递任何特定的类型。

Pair<? super Employee>
超类型限定的通配符

表示任何泛型Pair类型,它的类型参数是Employee的超类。

Pair<? super Employee>是Pair<Employee>和Pair<Object>的超类。

  • ? super Employee getFirst();
  • void setFirst(? super Employee);

Pair<? super Employee>中某个访问器getFirst()方法的返回类型是
? super Employee,即Employee类型或其超类,只能把它赋给一个Object。

而更改器方法void setFirst(? super Employee),编译器虽然不知道其参数确切的类型,但是可以通过Employee类及其子类对象调用它(其实就是调用超类的方法)。

综上,带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。

PECS法则:

producer-extends,consumer-super。如果参数化类型表示一个生产者,就使用<? extends T>,如果他表示消费者,就使用<? super T>。

Pair<?>
无限定通配符

  • ?  getFirst();
  • void setFirst(? );

getFirst方法仅能赋给Object类型的对象。setFirst方法不能被调用。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

详解Node.js API系列 Http模块(1) 构造一个简单的静态页服务器

2021-12-21 16:36:11

安全技术

从零搭建自己的SpringBoot后台框架(二十三)

2022-1-12 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索