
任务2 查询标题功能升级
关键步骤如下。
- 修改任务1,将集合改为泛型形式。
- 修改遍历集合的代码。
1.2.1 认识泛型
泛型是JDK 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,使代码可以应用于多种类型。简单说来,Java语言引入泛型的好处是安全简单,且所有强制转换都是自动和隐式进行的,提高了代码的重用率。
1. 泛型的定义

泛型集合
将对象的类型作为参数,指定到其他类或者方法上,从而保证类型转换的安全性和稳定性,这就是泛型。泛型的本质就是参数化类型。
泛型的定义语法格式如下。
类1或者接口<类型实参> 对象=new类2<类型实参>();
注意
首先,“类2”可以是“类1”本身,可以是“类1”的子类,还可以是接口的实现类;其次,“类2”的类型实参必须与“类1”中的类型实参相同。
例如:ArrayList<String> list=new ArrayList<String>();
上述代码表示创建一个ArrayList集合,但规定该集合中存储的元素类型必须为String类型。
2. 泛型在集合中的应用
前面学习List接口时已经提到,其add()方法的参数是Object类型,不管把什么对象放入List接口及其子接口或实现类中,都会被转换为Object类型。在通过get()方法取出集合中元素时必须进行强制类型转换,不仅烦琐而且容易出现ClassCastException异常。Map接口中使用put()方法和get()方法存取对象时,以及使用Iterator的next()方法获取元素时存在同样问题。JDK 1.5中通过引入泛型有效地解决了这个问题。JDK 1.5中已经改写了集合框架中的所有接口和类,增加了对泛型的支持,也就是泛型集合。
使用泛型集合在创建集合对象时指定集合中元素的类型,从集合中取出元素时无需进行强制类型转换,并且如果把非指定类型对象放入集合,会出现编译错误。
List和ArrayList的泛型形式是List<E>和ArrayList<E>,ArrayList<E>与ArrayList类的常用方法基本一样,示例11演示了List<E>和ArrayList<E>的用法。
示例11
使用ArrayList的泛型形式改进示例2。
实现步骤如下。
(1)实现步骤同示例2。
(2)创建集合对象时,使用的是ArrayList<NewTitle>。
(3)遍历集合时不需要进行类型转换。
关键代码:
//省略与示例2相同部分的代码 List<NewTitle> newsTitleList=new ArrayList<NewTitle>(); //按照顺序依次添加新闻标题 newsTitleList.add(car); newsTitleList.add(test); //根据位置获取相应新闻标题,逐条输出每条新闻标题的名称 System.out.println("新闻标题的名称为:"); for (NewTitle title:newsTitleList) { System.out.println(title.getTitleName()); }
输出结果与示例2相同,如图1.3所示。
示例11中通过<NewTitle>指定了ArrayList中元素的类型,代码中指定了ArrayList中只能添加NewTitle类型的数据,如果添加其他类型数据,将会出现编译错误,这在一定程度上保证了代码安全性。并且数据添加到集合中后不再转换为Object类型,保存的是指定的数据类型,所以在集合中获取数据时也不再需要进行强制类型转换。
同样的,Map与HashMap也有它们的泛型形式,即Map<K,V>和HashMap<K,V>。因为它们的每一个元素都包含两个部分,即key和value,所以,在应用泛型时,要同时指定key的类型和value的类型,K表示key的类型,V表示value的类型。
HashMap<K,V>操作数据的方法与HashMap基本一样,示例12演示了Map<K,V>和HashMap<K,V>的用法。
示例12
使用HashMap的泛型形式改进示例7。
实现步骤如下。
(1)实现步骤同示例7。
(2)创建集合对象时,使用的是HashMap<String,Student>。
(3)遍历集合时不需要进行类型转换。
关键代码:
//省略与示例7相同部分的代码 Map<String,Student> students=new HashMap<String,Student>(); //把英文名称与学员对象按照“键——值对”的方式存储在HashMap中 students.put("Jack", student1); students.put("Rose", student2); //输出英文名 System.out.println("学生英文名:"); for(String key:students.keySet()){ System.out.println(key); } //输出学生详细信息 System.out.println("学生详细信息:"); for(Student value:students.values()){ System.out.println("姓名:"+value.getName()+",性别:"+value.getSex()); }
输出结果与示例7相同,如图1.8所示。
在示例12中,通过<String,Student>指定了Map集合的数据类型,在使用put()方法存储数据时,Map集合的key必须为String类型,value必须为Student类型的数据,而在遍历键集的for循环中,变量key的类型不再是Object,而是String;在遍历值集的for循环中,变量value的类型不再是Object,而是Student,同样,Map.get(key)得到的值也是Student类型数据,不再需要进行强制类型转换。
当然,其他的集合类,如前面讲到的LinkedList、HashSet等也都有自己的泛型形式,用法和ArrayList、HashMap的泛型形式类似,这里不再赘述。
泛型使集合的使用更方便,也提升了安全:
- 存储数据时进行严格类型检查,确保只有合适类型的对象才能存储在集合中。
- 从集合中检索对象时,减少了强制类型转换。
1.2.2 深入泛型
在集合中使用泛型只是泛型多种应用的一种,在接口、类、方法等方面也有着泛型的广泛应用。泛型的本质就是参数化类型,参数化类型的重要性在于允许创建一些类、接口和方法,其所操作的数据类型被定义为参数,可以在真正使用时指定具体的类型。
在学习如何使用泛型之前,还需要了解以下两个重要的概念。
- 参数化类型:参数化类型包含一个类或者接口,以及实际的类型参数列表。
- 类型变量:是一种非限定性标识符,用来指定类、接口或者方法的类型。
1. 定义泛型类、泛型接口和泛型方法
对于一些常常处理不同类型数据转换的接口或者类,可以使用泛型定义,如Java中的List接口。定义泛型接口或类的过程,与定义一个接口或者类相似。
(1)泛型类
泛型类简单地说就是具有一个或者多个类型参数的类。
定义泛型类的语法格式如下。
访问修饰符 class className<TypeList>
TypeList表示类型参数列表,每个类型变量之间以逗号分隔。
例如:
public class GenericClass<T>{……}
创建泛型类实例的语法格式如下:
new className<TypeList>(argList);
- TypeList表示定义的类型参数列表,每个类型变量之间以逗号分隔。
- argList表示实际传递的类型参数列表,每个类型变量之间同样以逗号分隔。
例如:
new GenericClass<String>("this is String object")
(2)泛型接口
泛型接口就是拥有一个或多个类型参数的接口。泛型接口的定义方式与定义泛型类类似。
定义泛型接口的语法格式如下。
访问修饰符interface interfaceName<TypeList>
TypeList表示由逗号分隔的一个或多个类型参数列表。
例如:
public interface TestInterface<T>{ public T print(T t); }
泛型类实现泛型接口的语法格式如下。
访问修饰符 class className<TypeList> implements interfaceName<TypeList>
示例13
定义泛型接口、泛型类,泛型类实现泛型接口,在泛型类中添加相应的泛型方法。
实现步骤如下。
1)定义泛型接口TestInterface<T>,添加方法getName(),并设置返回类型为T。
2)定义泛型类Student<T>,并实现接口TestInterface<T>,声明类型为T的字段name,添加构造方法。
3)使用Student<T>实例化TestInterface<T>。
关键代码:
//定义泛型接口 interface TestInterface<T>{ public T getName(); //设置的类型由外部决定 } //定义泛型类 class Student<T> implements TestInterface<T>{ //实现接口TestInterface<T> private T name; //设置的类型由外部决定 public Student(T name){ this.setName(name); } public void setName(T name){ this.name=name; } public T getName(){ //返回类型由外部决定 return this.name; } } public class GenericesClass{ public static void main(String[] args){ TestInterface<String> student=new Student<String>("张三"); //① System.out.println(student.getName()); } }
输出结果:
张三
在示例13中,①的代码用来创建Student对象,Student泛型类的泛型参数定义为String类型,执行此代码后,TestInterface接口和Student类中的泛型参数类型都为String。并会通过Student类中只有一个String参数的构造方法来创建对象,则name属性的值为“张三”。
(3)泛型方法
一些方法常常需要对某一类型数据进行处理,若处理的数据类型不确定,则可以通过泛型方法的方式来定义,达到简化代码、提高代码重用性的目的。
泛型方法实际上就是带有类型参数的方法。需要特别注意的是,定义泛型方法与方法所在的类、或者接口是否是泛型类或者泛型接口没有直接的联系,也就是说无论是泛型类还是非泛型类,如果需要就可以定义泛型方法。
定义泛型方法的语法格式如下。
访问修饰符 <类型参数> 返回值 方法名(类型参数列表)
例如:
public <String> void showName(String s){ }
注意在泛型方法中,类型变量放置在访问修饰符与返回值之间。
示例14
定义泛型方法并调用。
实现步骤如下。
1)定义泛型方法。
2)调用泛型方法。
关键代码:
public class GenericMethod { //定义泛型方法 public <Integer> void showSize(Integer o){ System.out.println(o.getClass().getName()); } public static void main(String[] args) { GenericMethod gm=new GenericMethod(); gm.showSize(10); } }
输出结果:
java.lang.Integer
2. 多个参数的泛型类
前面的示例中,泛型类的类型参数都只有一个,实际上类型参数可以有多个,如HashMap<K,V>就有两个类型参数,一个指定key的类型,一个指定value的类型。下面介绍如何自定义一个包含多个类型参数的泛型类。
示例15
定义泛型类,并设置两个类型参数。
实现步骤如下。
(1)定义泛型类。
(2)实例化泛型类。
关键代码:
//创建泛型类 class GenericDemo<T,V>{ private T a; private V b; public GenericDemo(T a,V b){ this.a=a; this.b=b; } public void showType(){ System.out.println("a的类型是"+a.getClass().getName()); System.out.println("b的类型是"+b.getClass().getName()); } } //实例化泛型类 public class Demo{ public static void main(String[] args) { GenericDemo<String,Integer> ge1=new GenericDemo<String,Integer>("Jack",23); ge1.showType(); } }
输出结果:
a的类型是java.lang.String b的类型是java.lang.Integer
在示例15中,GenericDemo<T,V>类定义了两个类型参数,分别是T和V。定义时这两个类型变量的具体类型并不知道。注意,当在一个泛型中,需要声明多个类型参数时,只需要在每个类型参数之间使用逗号将其隔开即可。在实例化泛型类时,就需要传递两个类型参数,这里分别使用了String和Integer代替了T和V。
3. 从泛型类派生子类
面向对象的特性同样适用于泛型类,所以泛型类也可以继承。不过,继承了泛型类的子类,必须也是泛型类。
继承泛型类的语法格式如下。
class 子类<T> extends 父类<T>{ }
示例16
定义泛型父类,同时定义一个泛型子类继承泛型父类。
实现步骤如下。
(1)定义父类Farm<T>,并添加整型字段plantNum、方法plantCrop(T crop)。
(2)定义子类FruitFarm <T>,重写方法plantCrop (List<T> list)。
关键代码:
//父类Farm<T>(农场类) public class Farm<T>{ protected int plantNum=0; //农作物种植数量 public void plantCrop(T crop){ //种植农作物的方法 plantNum ++; } } //子类果园类继承泛型类Farm<T> public class FruitFarm<T> extends Farm<T>{ public void plantCrop(List<T> list){ //重写种植农作物的方法 plantNum+=list.size(); } }
至此,任务2已经全部完成。