write:2022-3-14
泛型的由来
在JDK5以前的版本中,集合中元素都是Object类型,从集合中获取元素时,常常需要进行强制类型的转换。
以下代码向ArrayList中加入一个String对象,接下来取出这个String对象,并试图把它强制转换为Integer类型:
List list=new ArrayList();
list.add("hello");
String s=(String)list.get(0);
Integer i=(Integer)list.get(0); //抛出ClassCastException(String类型是无法转换为Integer类型的)
错误的类型
按照错误被发现的时间,程序中的错误可分为两种:
1)编译时错误:在编译阶段由Java编译器发现的错误。编程人员遇到这种错误后,必须修改相应的程序代码,保证编译通过。
2)运行时错误:编译时未报错,在运行时抛出运行时异常。编程人员遇到这种错误后,必须调试程序,修改相应的程序代码,避免再出现这样的异常。(这种错误隐藏的可能比较深)
错误发现得越早越好
处理运行时异常的周折:
软件公司把软件交给客户
客户运行软件,遇到运行时异常
客户再通知软件公司修改软件
上面的修改过程就会花费很多的周折;所以,错误发现的越早,就越能提高软件调试的效率,从而提高软件的健壮性,降低软件的开发和维护的成本;因此,我们就应该尽可能的把运行时错误转换为编译时错误;
从JDK5开始,引入泛型,就是为了把运行时异常转变为编译时的类型不兼容错误;
泛型标记
从JDK5开始,所有Java集合都采用了泛型机制。在声明集合变量和创建集合对象时,可以用“< >”标记指定集合中元素的类型:
eg1:
List< String> list=new ArrayList< String>(); //列表中元素必须为String类型
list.add("hello"); //合法
list.add(new Integer(11)); //编译出错,不允许把Integer对象加入到列表中
Integer i=list.get(0); //编译出错,列表中元素为String类型,无法转换为Integer类型
String s=list.get(0); //合法,无需进行强制类型转换
Object o=list.get(0); //合法,允许向上转型,无需进行强制类型转换
eg2:以下代码声明Map类型的键对象为Integer类型,值对象为String类型:
Map<Integer,String> map=new HashMap<Integer,String>();
map.put(1,"Monday");
map.put(2,"Tuesday");
Set< Integer> keys=map.keySet();
for(Integer key: keys){
String value=map.get(key);
System.out.println(key+" "+value);
}
定义泛型类
定义泛型的好处
通过例子来说明定义泛型的好坏:
eg1://没有使用泛型
public class OldBag{
private Object content;
public OldBag(Object content) {
this.content = content;
}
public Object get() {
return this.content;
}
public void set(Object content) {
this.content = content;
}
public static void main(String[] args){
OldBag bag=new OldBag("mybook"); //content被赋值String类型
//运行时抛出ClassCastException
Object content=(Integer)bag.get(); //读取content并转为Integer类型,编译通过,因为get方法返回类型是Object类型兼容Integer,运行时出错,因为content实际上是String类型
}
}
eg2://使用泛型
public class Bag<T>{
private T content;
public Bag(T content) {
this.content = content;
}
public T get() {
return this.content;
}
public void set(T content) { //以上的类型和返回值类型都是泛型T
this.content = content;
}
public static void main(String[] args){
Bag<String> bag=
new Bag<String>("mybook"); //限定Bag类中的content是String类型
Integer content1=bag.get(); //编译出错
//合法,无需进行强制类型转换
String content2=bag.get();
}
}
通过以上两个例子就可以看出定义泛型的好坏;
泛型类可以有多个类型参数
一个泛型类可以有多个类型参数,语法为:类名<T1,T2,T3…TN>{…},例如:
public class Bag<T1,T2>{
private T1 content1;
private T2 content2;
…
}
eg:MyMap类有两个类型参数,<K,V>分别是key的类型和value的类型;
import java.util.*;
public class MyMap<K,V>{
private Map<K,V> map=new HashMap<K,V>();
public void put(K k,V v) {
map.put(k,v);
}
public V get(K k){
return map.get(k);
}
public int size(){
return map.size();
}
public static void main(String[] args){
MyMap<Integer,String> map=
new MyMap<Integer,String>();
map.put(1,"book1");
map.put(2,"book2");
System.out.println(map.get(2));
}
}
用extends关键字限定类型参数
在定义泛型类时,可以用extends关键字来限定类型参数,语法形式为:
<T extends 类名> //T必须是指定类或者其子类
或者:
<T extends 接口名> //T必须是指定接口的实现类
eg:
import java.util.*;
public class LimitBag<T extends Number>{ //T必须是number或者number的子类
private T content;
public LimitBag(T content) {
this.content = content;
}
public T get() {
return this.content;
}
public void set(T content) {
this.content = content;
}
public static void main(String[] args){
LimitBag<String> bag1=
new LimitBag<String>("mybook");//编译出错,String类型不继承number类型
LimitBag<Integer> bag2=
new LimitBag<Integer>(12); //合法
}
}
定义泛型数组
对数组中元素的类型进行限定
eg:
public class ArrayBag<T>{
//private T[] content=new T[10]; //编译出错,不能用泛型来创建数组实例,只能声明泛型数组
private T[] content; //content数组中元素的类型是T类型
public ArrayBag(T[] content) {
this.content = content;
}
public T[] get() {
return this.content;
}
public void set(T[] content) {
this.content = content;
}
public static void main(String[] args){
String[] content={"book1","book2","book3"};
ArrayBag<String> bag=
new ArrayBag<String>(content);
for(String c:bag.get())
System.out.println(c);
}
}
定义泛型方法
对方法的参数或者是返回类型进行限定
eg:
public class MethodTest{
// 泛型方法 printArray()
public static <E> void printArray( E[] array ){ //限定array参数类型,数组中元素的类型E
for( E element : array )
System.out.println( element );
}
//泛型方法 max()
public static <T extends Comparable<T>> T max(T x, T y){ //参数类型和返回值类型都是T类型
return x.compareTo(y)>0 ? x:y;
}
public static void main( String args[] ){
Integer[] intArray = { 1, 2, 3, 4, 5 };
printArray( intArray ); // 传递一个整型数组
System.out.println(max(12,24));
}
}
使用“?”通配符
在泛型机制中,编译器认为HashSet< String>和Set< String>之间存在继承关系,因此以下的赋值是合法的:
Set< String> s1=new HashSet< String>(); //合法,允许向上转型
但编译器认为 HashSet< Object>和HashSet< String>之间不存在继承关系,因此以下赋值是非法的:
HashSet< Object> s2=new HashSet< String>(); //编译出错,不兼容的类型
eg:为了解决以上问题,我们会使用“?”通配符
import java.util.*;
public class WildCastTest {
public static void main(String[] args) throws Exception{
List<Integer> listInteger =new ArrayList<Integer>();
listInteger.add(11);
print (listInteger); //编译出错,不兼容的类型,编译器认为List<Integer>与Collection<Object>是不兼容的类型
printNew (listInteger); //合法
}
public static void print(Collection<Object> collection){
for(Object obj:collection)
System.out.println(obj);
}
public static void printNew(Collection<?> collection){ //使用通配符
for(Object obj:collection)
System.out.println(obj);
}
}
课堂小结
- 泛型允许在定义类或方法时声明类型参数(例如< T>),当程序访问类或方法时,可以提供明确的类型参数(例如< String>)。
- 泛型主要有两大作用:
(1)编译器在编译时就能根据类型参数来检查各种赋值操作是否类型兼容,从而避免ClassCastException运行时异常。
(2)简化程序代码,不必使用强制类型转换。 - 在声明类型参数时,可以通过extends关键字来设定上限,例如< T extends Number>,表示T必须是Number类或者其子类;也可以通过super关键来设定下限,例如< T super ArrayList>,表示T必须是ArrayList类或者是其父类。
- 如果定义了一个Set类型的变量s,它有可能引用TreeSet< String>类型的实例,也可能引用TreeSet< Integer>类型的实例,在这种情况下,可以使用通配符“?”:
Set<?> s=new TreeSet< String>();
s=new TreeSet< Integer>();
练习题
-
泛型有什么作用?
[答案]泛型主要有两大作用:
(1)编译器在编译时就能根据类型参数来检查各种赋值操作是否类型兼容,从而避免ClassCastException运行时异常。
(2)简化程序代码,不必使用强制类型转换。 -
以下哪些赋值语句会编译出错?
a)Set< String> s1=new HashSet< String>();
b)Set< Object> s2=new HashSet< String>();
c)Set<?> s3=new HashSet< String>();
d)HashSet< String> s4=new TreeSet< String>();
[答案] b,d
- 对于LimitBag类,如果定义LimitBag类的代码如下:
public class LimitBag< T extends Set>{…}
以下哪些是合法的赋值语句?
a)
LimitBag<TreeSet< String>> b1=new LimitBag<TreeSet< String>>(new TreeSet< String>());
b)
LimitBag<Set< Integer>> b2=new LimitBag<Set< Integer>>(new TreeSet< Integer>());
c)
LimitBag<List< String>> b3=new LimitBag<List< String>>(new ArrayList< String>());
d)
LimitBag<Set< String>> b4=new LimitBag<Set< String>>(new TreeSet< String>());
[答案] a,b,d