当前位置:文档之家› J2EE课程设计《技术应用指导》——第1章 Java集合和泛型技术及应用(第3部分)

J2EE课程设计《技术应用指导》——第1章 Java集合和泛型技术及应用(第3部分)

J2EE课程设计《技术应用指导》——第1章 Java集合和泛型技术及应用(第3部分)
J2EE课程设计《技术应用指导》——第1章 Java集合和泛型技术及应用(第3部分)

第1章Java集合和泛型技术及应用(第3/4部分)

1.1Java泛型技术及应用

应用泛型编程技术能够产生出高度灵活性和通用性的功能实现代码,并使得开发人员能够专注于数据类型的抽象化方面而产生出应用广泛的功能实现算法。读者在项目开发编程实现中如果能够充分地应用面向对象中的多态、Java语言中的反射、以及Java语言中的泛型编程技术(也就是模板技术),便能够获得极高层次上的代码可重用性和灵活性的功能实现程序——泛型编程使得开发人员能够专注于类型抽象化和通用的功能实现。

1.1.1为什么要应用泛型技术

1、什么是泛型——泛型的本质是参数化类型

(1)泛型的本质是参数化类型

泛型是从Java SE 1.5版开始增加的一个新的特性,泛型的本质其实是参数化类型——也就是说所操作的数据类型可以被指定为一个参数。

(2)Java语言中泛型的种类

Java语言中的泛型可以用在对类、接口和方法的定义中,而分别称为泛型类、泛型接口、泛型方法。

(3)Java语言中泛型类、泛型接口和泛型方法的代码示例

下面的【例1-18】中的代码示例为一个泛型类的代码示例,其中的参数代表某种数据类型的替代符,因此SomeGenericsDemo为一个泛型类定义、而public void doSomeThing(G oneElement)为一个泛型方法定义、Collection oneCollection;为一个利用泛型类的对象声明定义。

【例1-18】Java语言中泛型类、泛型接口和泛型方法的代码示例

public class SomeGenericsDemo {

Collection oneCollection;

public void doSomeThing(G oneElement) {

oneCollection.add(oneElement);

// ... 其它的功能实现代码,在此省略

}

}

2、为什么要应用泛型技术

泛型就好比Office Word文字处理软件中为用户提供的模板技术,在Word的某个模板文件中,提供了对某中特定应用的文档基本的文档编辑内容,在定义Word模板文件时,对具体编辑哪种类型的应用文档是未知的、但达到一种通用的格式定义的效果。在Java语言中的泛型技术则为开发人员提供了对类、接口和方法、变量等定义的模板,而在泛型定义中的等符号(当然,也可以为其他符号)也可以看作是占位符,与定义Word模板格式文件时类似,定义泛型时的具体类型在程序设计时是未知的。

(1)Java语言引入泛型的优点是安全和简单

在Java SE 1.5版之前的JDK系统类库中,由于没有提供对泛型的技术支持,一般是通过对Object类型的对象引用来实现参数的“任意化”——参数的“任意化”所带来的缺点是需要在程序代码中要做显式的数据类型的强制类型转换,而这种类型转换是要求开发者对实际参数类型是可以预知的情况下进行的、否则将会出现运行时的异常错误。

下面的【例1-19】为采用Object类型的对象引用来实现参数的“任意化”的代码实现示例,在方法doSomeThing体内必须对可能的各种数据类型进行判断、并根据参数的不同类型分别完成不同的功能处理——请注意其中黑体所标识的代码。

【例1-19】采用Object类型的对象引用实现参数的“任意化”的代码实现示例public class SomeDigitClass{

public void doSomeThing(Object oneParameterObject) {

if(oneParameterObject instanceof Integer){

Integer targetParameterObject=(Integer)oneParameterObject;

}

else if(oneParameterObject instanceof Float){

Float targetParameterObject=(Float)oneParameterObject;

}

}

}

而对于采用参数的强制类型转换编程实现中所可能产生出的运行时的错误,Java编译器并不能及时地检测出可能存在的错误,而只有程序代码在运行时才抛出异常错误,这将

会存在一个很严重的程序代码的安全隐患。

(2)应用泛型编程实现功能代码的一个主要优点

应用泛型的好处是能够在源代码被编译时就能够进行类型安全方面的检查和识别,并且所有参数的强制类型转换工作都是自动和隐式的——由运行系统自动地完成。因此不仅可以提高代码的可重用率,而且也能够提高程序代码的质量。

(3)应用泛型编程实现功能代码的另一个优点

应用泛型技术不仅能够减少编码量、也能够提高程序代码的通用性——由于泛型技术其实是一种程序模板技术,因此能够大大地简化程序代码的结构,并能够产生出通用的功能实现代码,相应地也就能够提高项目中的代码实现的开发效率。

1.1.2泛型技术在应用中的规则及基本的语法定义

1、Java泛型技术在应用中的规则和限制

为了能够让编译系统能够正确地识别参数的最终数据类型,读者在应用Java泛型技术时也应该要遵守一定的规则和按照特定的要求编程。具体的要求如下:

1)泛型定义中的类型参数只能适用于“类”的类型(包括开发人员的自定义类)、不

能应用于简单的数据类型;

2)泛型的参数类型可以使用extends语句,例如。习惯上成为“有

界类型”来加以限定其应用的范围;

3)泛型的参数类型也还可以是通配符类型的定义形式,例如Class classType =

Class.forName(https://www.doczj.com/doc/328922877.html,ng.Integer);

4)但泛型类不可以继承Exception类,即泛型类不可以作为异常抛出的定义;

5)不可以定义泛型数组、也不可以用泛型定义构造具体的对象实例(即object = new

G();这样的对象创建的语句是错误的),并且在static方法中也不可以使用泛型、泛型变量也不可以用static关键字来修饰——请见下面的代码片段示例:

public static T getSingleInstance() {

// 不通过

}

public static T singleInstance; // 不通过

2、泛型的基本语法定义

(1)泛型的基本语法定义方式是通过在某个类名后面的<>括号中指定

下面【例1-20】中的示例代码说明了如何进行泛型定义,其中用G代替在实际中将会使用到的类名(当然也可以使用其它别的符号名称,习惯上在这里使用大写的G,表示GenericType的含义)。然后就可以采用该G符号进行参数的定义。

通过在类名后面使用一对尖括号<>、并且在其中放一个称为类型参数的“G”来定义泛型,该符号“G”其实是“类型占位符”或者称为“类型参数”。

(2)泛型的基本语法定义的代码示例

【例1-20】泛型的基本语法定义的代码示例

public class TestGenerics {

Collection oneCollection;

public void doSomeThing(G oneElement) {

oneCollection.add(oneElement);

// ... 其它的功能实现代码,在此省略

}

}

3、Java语言中的泛型与C++语言中的模板在应用方面的不同点

(1)Java语言中的泛型很相似于C++语言中的模板、但和类模板还是有一定区别的Java语言中的泛型技术无论是语法定义还是应用的环境等方面都类似于C++语言中的模板技术,但是它们的这种相似性仅限于表面层次。Java 语言中的泛型是由Java编译器本身实现的、并由编译器执行参数的数据类型检查和类型推断工作,然后最终再生成普通的非泛型定义的类字节码的程序代码。

这种实现技术在Java的泛型技术中称为擦除(Erasure)——编译器使用泛型的数据类型信息来保证数据类型在应用方面的安全性,然后在生成最终的目标字节码之前再将它们清除掉;因此,在最终的*.class类文件中是不存在与泛型定义有关的各种描述内容——因为Java中的“泛型技术”目前还只是一种“编译期的技术”,也就是只能在编译期有效。(2)在Java虚拟机中是没有泛型数据类型的(所有对象定义都属于Java中的标准类型)开发人员无论在程序代码的何处定义了一个泛型类型,相应的原始数据类型都会被自动地提供和最终使用——原始数据类型的类型名字就替代了类型参数定义中的泛型定义的占位符名字。类型变量被擦除时如果存在限定的类型,则用其第一个限定的类型名替换;如果不存在限定的数据类型变量则用通用的Object基类的数据类型替换。

用其第一个限定类型SomeOneBaseClass替换的代码片段示例:

public class First GenericTypeDemo

无限定的数据类型定义时用Object类型替换的代码片段示例:

public class Some GenericTypeDemo {

private G someOneAttribute;

public G getSomeOneAttribute() {

return this.someOneAttribute;

}

public void setSomeOneAttribute(G someOneAttribute) {

this. someOneAttribute= someOneAttribute;

}

}

1.2Java泛型类、接口、方法及在项目中的应用

1.2.1Java泛型类及应用示例

1、Java泛型类及具体的应用示例

(1)Java泛型类——可以把泛型类看成是类的模板

由于泛型类声明也是一种类的定义声明,因此仍然需要为它提供具体的数据类型后才能构成最终的具体数据类型——因为Java语言是一种强类型的语言,在定义类、接口或者方法时, 必须指定它们的具体数据类型。

但由于泛型是一种模板类型的定义,因此在声明泛型类、接口或者方法和变量时是不需要指定它们的具体类型, 而是用一个类型参数的占位符号来代替。但在具体应用这个类、接口或者方法、变量时必须要为这个泛型类型参数最终指定一个具体的数据类型、并最终被实际的数据类型所代替。

在下面【例1-21】所示的Java泛型类的应用示例中类名后面带有,表明了这个GenericTypeDemo类是一个泛型类, 其中的GenericType被称为泛型的类型参数。因此,在使用这个GenericTypeDemo泛型类时,泛型的类型参数GenericType是需要被替换为某种实际类的数据类型名。

(2)Java泛型类的应用示例

在下面的【例1-21】中的Java泛型类的代码示例中,利用泛型的类型参数GenericType 可以定义泛型类、也可以定义泛型成员变量和定义泛型方法变量,但在最终的对象声明或

者方法变量具体赋值时要给出具体的数据类型——请注意【例1-21】中的黑体标识的代码。【例1-21】Java泛型类的代码示例

package com.px1987.generictype;

public class GenericTypeClassDemo {

private GenericType oneObject; //利用该泛型类型参数定义泛型成员属性变量

public void showGenericTypeClassTyep() {

System.out.println("GenericType的实际类型是: " +

oneObject.getClass().getName());

}

public void setOneObject(GenericType oneObject) {

this.oneObject = oneObject;

} //利用该泛型类型参数来定义泛型方法变量

public static void main(String[] args) {

GenericTypeClassDemo oneGenericTypeDemo=

new GenericTypeClassDemo();

oneGenericTypeDemo.setOneObject("给定String类型的参数");

oneGenericTypeDemo.showGenericTypeClassTyep ();

}

}

使用泛型的类型参数来声明一个泛型类名称后,就可以把泛型的类型参数当作一种数据类型的定义符号来声明成员属性变量、方法的参数变量和方法的返回值类型——因为泛型类的类型参数的有效性是作用于整个类。当然泛型的类型参数仅仅是个数据类型的符号名字而已。

但在创建泛型类的对象实例时, 和创建普通的对象实例基本类似——只是需要提供具体的Java类的类型名称(【例1-21】中的示例为https://www.doczj.com/doc/328922877.html,ng.String类型)来替代泛型的类型参数。下图1.12为【例1-21】中的程序代码示例的最终执行结果的截图。

图1.12 【例1-21】中的代码示例的执行结果截图

2、不应用Java泛型类实现的代码示例

对于【例1-21】中的程序代码示例其实也可以不应用Java泛型类来实现,但此时一般是将对象的类型设置为Object类型。在Java SE 1.5版之前的JDK系统库中,为了让某个类在具体应用时能够具有一定的通用性,往往是将程序代码中的参数类型、返回值类型等设置为https://www.doczj.com/doc/328922877.html,ng.Object通用基类的数据类型。然后在获取这些方法的返回值时,再将其“强制”类型转换为最终的目标类或接口等类型,最后才可以真正地调用该对象中的目标方法,否则将会出现类型转换的运行时异常错误。

下面的【例1-22】中的程序代码为体现这种应用状况的代码示例,在对参数最终应用时需要进行强制类型的转换,而且还需要保证转换的目标数据类型与参数的原始类型要保持一致性——请读者参见其中的黑体所标识的代码,否则将会出现运行时的异常错误。【例1-22】不应用Java泛型类实现的代码示例

package com.px1987.generictype;

public class NoUserGenericTypeClassDemo {

private Object oneObject; //定义Object类型的成员变量

public void setOneObject(Object oneObject) {

this.oneObject = oneObject;

}

public void showAttributeObjectClassTyep() { //下面需要进行强制类型转换System.out.println("Object的实际类型是:" +

((String)oneObject).getClass().getName());

}

public static void main(String[] args) {

NoUserGenericTypeClassDemo oneObjectTypeDemo=

new NoUserGenericTypeClassDemo();

oneObjectTypeDemo.setOneObject("给定String类型的参数");

oneObjectTypeDemo.showAttributeObjectClassTyep ();

}

}

【例1-22】中的代码示例的执行结果请见下图1.13所示的截图,本示例的参数的最终

类型为https://www.doczj.com/doc/328922877.html,ng.String类型——在运行过程中最终由Java虚拟机进行替换。

图1.13 【例1-22】中的代码示例的执行结果截图

该方式在具体编程实现时有可能存在下面的一些主要问题:首先强制类型转换是件很麻烦的、并且读者需要事先知道各个Object参数的具体类型是什么,读者才能做出正确的转换——如果在程序中有多处要应用该Object参数时,将使得程序代码变得太复杂;其次,一旦转换的目标数据类型与Object参数的原始数据类型不一致时,该程序代码在被编译时不会出现任何的语法错误,而在程序运行时将会出现https://www.doczj.com/doc/328922877.html,ng.ClassCastException类型的异常抛出——请见下图1.14所示的异常抛出的截图。

下图1.14是将【例1-22】中的代码示例中的oneObjectTypeDemo.setOneObject("给定String类型的参数");一条语句代码改变为oneObjectTypeDemo.setOneObject("给定String类型的参数");然后再次执行后的结果截图——Java虚拟机抛出了https://www.doczj.com/doc/328922877.html,ng.ClassCastException 类型的异常错误。

图1.14 对【例1-22】中的代码示例错误地进行参数类型转换后的执行结果截图因此,采用Object类型定义的编程方式将导致程序代码的可读性和可靠性都将会降低——读者对比【例1-21】和【例1-22】中的两种不同的编程实现方式的代码示例,明显地可以了解到应用Java泛型技术的意义不仅能够帮助开发人员减低代码的耦合度、减少代码的重复编程实现(应用代码模板技术提高可重用性),也能够保证代码的质量和可靠性。

1.2.2Java泛型接口及应用示例

1、Java泛型接口的语法定义

在Java SE 1.5版以后的Java语言中,不仅允许开发人员声明泛型类, 也可以声明泛型接口。当然,声明泛型接口和声明泛型类的语法其实基本上是一致的, 也是在泛型接口名称后面加上泛型的类型参数。同样也要求读者在具体应用该泛型接口时,也必须要给定最终的具体实际类型。

2、Java泛型接口的代码示例

作者在下面的【例1-23】中给出了一个Java泛型接口的代码示例,在该接口中定义了一个方法doQuerySomeData,并且该方法带有一个泛型方法的变量。

【例1-23】定义一个Java泛型接口的代码示例

package com.px1987.generictype;

public interface GenericTypeInterface{

public void doQuerySomeData(GenericType oneQueryParameter);

}

下面的【例1-24】中的GenericTypeInterfaceImple类的程序代码是对【例1-23】中的泛型接口的具体实现类程序代码,并在其中的main方法中以两种不同的数据类型的方式应用【例1-23】中的泛型接口——请读者注意其中的黑体所标识的代码。另外,也请读者注意对泛型方法的调用与普通的方法的调用是相同的

【例1-24】对泛型接口GenericTypeInterface的实现类代码示例

package com.px1987.generictype;

public class GenericTypeInterfaceImple implements

GenericTypeInterface { public void doQuerySomeData(GenericType oneQueryParameter) {

System.out.println("GenericType的实际类型是: " +

oneQueryParameter.getClass().getName());

}

public static void main(String[] args) {

GenericTypeInterface oneGenericTypeInterfaceImple=

new GenericTypeInterfaceImple();

oneGenericTypeInterfaceImple.doQuerySomeData("给定String类型的参数");

GenericTypeInterface twoGenericTypeInterfaceImple=

new GenericTypeInterfaceImple();

twoGenericTypeInterfaceImple.doQuerySomeData(new Integer(100));

}

}

请读者注意,泛型接口的类型参数也同样是作用于整个接口的定义中。下图1.15为【例1-24】中的泛型接口的实现类GenericTypeInterfaceImple的执行结果的截图。因此,通过应用泛型接口同样也能够达到通用和模板形式的接口定义的效果——提高了接口在应用方面的可重用性。

图1.15 【例1-24】中的泛型接口的实现类代码的执行结果的截图

1.2.3Java泛型方法及在项目中的应用

1、应用Java泛型方法的主要目的

应用Java泛型方法能够大大地提高程序代码编程实现的灵活性和通用性,可以达到不被限定到哪个具体的类或接口的编程应用的效果,并能够使得开发人员只需要关注泛型方法的业务功能实现方面的问题——因为泛型方法使得该方法能够独立于其所在的类或接口的类型而独自地产生变化。

比如,开发人员可能需要实现一种通用的数据累加的功能实现,此时不需要具体地考虑这两个数的具体的数据类型;再比如,在数据库查询访问方法的设计中,利用泛型方法可以实现一种通用的查询方法,而不需要具体地关注该查询方法所接收的方法参数的数据类型——因此,合理地应用Java泛型方法编程可以提高代码的可重用度。

请读者参考前面的【例1-21】中的泛型类中的setOneObject方法的定义、【例1-23】中的泛型接口中的doQuerySomeData方法的定义代码——它们都应用了Java泛型方法。

2、泛型方法所在的类可以是泛型类也可以为普通的类(不是泛型类)

是否能够拥有泛型方法的定义与该泛型方法所在的类或者接口是否是泛型定义没有直接的必然关系。但在具体应用泛型方法时,请读者注意下面的一些问题:

1)如果泛型方法所在的类为泛型类时,必须要在创建该泛型类的对象实例时指定具体

的类型参数的值——也就是具体的目标数据类型。请读者参考【例1-21】、【例1-24】中的程序代码示例;

2)读者在使用泛型方法时,通常不必指明参数的具体类型,因为Java编译器会自动

地根据对该泛型方法实际调用的实参的数据类型而给出最终具体的数据类型。

这称编程机制在Java泛型技术中称为类型参数推断(Type Argument Inference)。因此,读者可以像调用普通类中的方法一样调用泛型方法,但又能够达到重载方法(Overriding Method)编程应用相同的效果——请读者参考【例1-24】中的代码示例。

3、Java中的泛型方法的定义语法

泛型方法其实是在某个类(泛型类或者普通类)的声明中包括了泛型的类型形参的方法,泛型方法可以在类或接口声明中进行声明,这些类或接口本身可以是泛型的或是非泛型(普通的类)。泛型方法的一般定义形式(方法的参数或者返回值为泛型定义)如下: return-type methodName(parameter-list){

}

如在【例1-24】中的泛型类中的doQuerySomeData泛型方法的定义中,带有一个“GenericType oneQueryParameter”的泛型方法参数。请见下面的代码示例中的黑体代码:public void doQuerySomeData(GenericType oneQueryParameter){

}

相关主题
文本预览
相关文档 最新文档