任务2 查询标题功能升级

关键步骤如下。

  • 修改任务1,将集合改为泛型形式。
  • 修改遍历集合的代码。

1.2.1 认识泛型

泛型是JDK 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,使代码可以应用于多种类型。简单说来,Java语言引入泛型的好处是安全简单,且所有强制转换都是自动和隐式进行的,提高了代码的重用率。

1. 泛型的定义

0

泛型集合

将对象的类型作为参数,指定到其他类或者方法上,从而保证类型转换的安全性和稳定性,这就是泛型。泛型的本质就是参数化类型。

泛型的定义语法格式如下。

类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已经全部完成。