知识目标
1. 理解Java泛型的应用场景、泛型类和泛型方法。
2. 掌握Java泛型类、泛型接口和泛型方法的使用。
能力目标
熟练使用Java泛型类、泛型接口和泛型方法等编写相应的应用程序。
素质目标
1. 能够阅读科技文档和撰写分析文档。
2. 能够查阅JDK API。
3. 增强学生团队协作能力。
应用场景
使用Java泛型:能够对整型数组、字符串数组甚至其他任何类型的数组进行排序
使用Java泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序:在使用框架SSH(Struts+Spring+Hibernate)开发一个应用系统中,常使用DAO(Date AccessObject)来访问数据库对象,完成数据库中的数据和Java对象里的一种关联关系的一系列操作CRUD。数据库中的对象有很多,每一个对象都写一个DAO,显得很烦琐,每一个DAO都要写CRUD操作,这样代码的重复率高,如果使用泛型,代码的复用得到了很好的应用,提高了代码的效率。
相关知识
泛型的概念
泛型就是“宽泛的数据类型”,任意的数据类型。泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用。使用泛型可以很好地解决“代码复用”问题。
泛型的定义和使用
1.定义泛型类
在定义带类型参数的类时,在紧跟类名之后的<>内,指定一个或多个类型参数的名字,同时也可以对类型参数的取值范围进行限定,多个类型参数之间用“,”号分隔。定义完类型参数后,可以在定义位置之后的类的几乎任意地方(静态块、静态属性、静态方法除外)使用类型参数,就像使用普通的类型一样。注意,父类定义的类型参数不能被子类继承。
punlic class TestClassDefine<T,S extends T>{
..........
}
2.泛型方法
public <T,S extends T> T testGenericMethodDefine(T t,S s){
。。。。
}
3.泛型接口
先定义泛型接口:
public interface Generator<T>{
public T next();
}
然后定义这个实现类来实现这个接口:
public class GeneratrImp1 implements Generator<String>{
@override
public String next(){
..............
}
}
相关概念
1.通配符
类型通配符一般是使用“?”代替具体的类型参数,对类型参数赋予不确定值。例如List<?>在逻辑上是List<String>、List<Integer>等所有List<具体类型实参>的父类。
public class GenericTest{
public static void main(String[] args){
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("zhangsan");
age.add(18);
number.add(341);
getData(name);
getData(age);
gatData(number);
}
public static void getData(Liat<?> data){
System.out.println("data:"+data.get(0));
}
}
//data:zhangsan
data:18
data:341
2.上下边界:限制使用泛型类别
如果想限制使用泛型类别时,只能用某个特定类型或者是其子类型才能实例化该类型时,可以在定义类型时,使用extends关键字指定这个类型必须是继承某个类,或者实现某个接口,也可以是这个类或接口本身。
(1)类型通配符上限通过List来定义,如此定义就是通配符泛型值接受Number及其下层子类类型。
public class GenericTest{
public static void main(String[] args){
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("zhangsan");
age.add(18);
number.add(341);
getData(name);
getData(age);
gatData(number);
getUperNumber(name);//........1
getUperNumber(age);//.........2
getUperNumber(number);//......3
}
public static void getData(Liat<?> data){
System.out.println("data:"+data.get(0));
}
public static void getUperNumber(List<? extends Number> data){
System.out.println("data:"+data.get(0));
}
}
//1处报错
解析:在//1处会出现错误,因为getUperNumber()方法中的参数已经限定了参数泛型上限为Number,所以泛型为String不在这个范围之内,所以会报错。
(2)类型通配符下限通过形如List<? extendsNumber>来定义,表示类型只能接受Number及其三层父类类型,如Objec类型的实例。
3.擦除
ava中的泛型基本上都是在编译器这个层次来实现的。生成的Java字节码中,是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,编译器在编译的时候去掉,这个过程就称为类型擦除。如在代码中定义List<Integer>和List<String>等类型,在编译后都会编成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。
泛型的好处
(1)类型安全
通过知道使用泛型定义的变量的类型限制,编译器可以更有效地提高Java程序的类型安全。
(2)消除强制类型转换
消除源代码中的许多强制类型转换。这使得代码可读性更强,并且减少了出错机会。所有的强制转换都是自动和隐式的。
(3)提高性能
List list1 = new ArrayList();
list1.add("wangzhikang");
String str1 = (String)list.get(0);
List<String> list2 = new ArrayList<String>();
list2.add("wangzhikang");
String str2 = list2.get(0);
对于上面的两段程序,由于泛型所有工作都在编译器中完成,Javac编译出来的字节码是一样的(只是更能确保类型安全),那么何谈性能提升呢?是因为在泛型的实现中,编译器将强制类型转换插入生成的字节码中,但是更多类型信息可用于编译器这一事实,为未来版本的JVM的优化带来了可能.
泛型使用中的注意事项
(1)泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
(2)泛型的类型参数可以有多个
任务实施:
任务一:泛型类的定义和使用
1.任务需求
先来看没有泛型的情况下的容器类如何定义:
package 泛型;
//没有泛型的容器类
public class Boxs {
private String key;
private String value;
public Boxs(String key,String value){
this.key = key;
this.value = value;
}
public String getKey(){
return key;
}
public void setKey(String key){
this.key = key;
}
public String getValue(){
return value;
}
public void setValue(String value){
this.value = value;
}
}
####缺点:Box类保存了一对key-value键值对,但是类型是定死的,也就说如果我想要创建一个String-Integer类型的键值对,当前这个Box是做不到的,还必须要另外重写一个Box,这明显重用性就非常低,代码得不到复用,使用泛型可以很好地解决这个问题。
2.任务分析
重新定义Box类,类图如图4-1所示。
3.任务实现
Box.java
package 泛型;
//带泛型的容器类
public class NewBoxs<K,V> {
private K key;
private V value;
public NewBoxs(K k,V v){
this.key = k;
this.value = v;
}
public K getKey(){
return key;
}
public void setkey(K k){
this.key = k;
}
public V getValue(){
return value;
}
public void setValue(V v){
this.value = v;
}
}
在编译期是无法知道K和V具体是什么类型的,只有在运行时才会真正根据类型来构造和分配内存。这样我们的Box类便可以得到复用,我们可以将T替换成任何我们想要的类型。实例化泛型类的时候,我们只需要把类型参数换成具体的类型即可。可以看一下现在Box类对于不同类型的支持情况
TestBox.java
package 泛型;
public class TestBox {
public static void main(String[] args) {
NewBoxs<String,String> b1 = new NewBoxs<>("name","zhangsan");
NewBoxs<Integer,String> b2 = new NewBoxs<>(1,"lisi");
NewBoxs<Double,Double> b3 = new NewBoxs<>(1.2,1.3);
System.out.println(b1.getKey()+":"+b1.getValue());
System.out.println(b2.getKey()+":"+b2.getValue());
System.out.println(b3.getKey()+":"+b3.getValue());
}
}
说明
通过public class Container<K, V> {}定义泛型类,在实例化该类时,必须指明泛型K、V的具体类型,例如:Container<String,String> c1 = new Container<String,String>("name", "Messi");,指明泛型K的类型为String,泛型V的类型为String。
任务二:泛型方法的定义和使用
1.任务需求
定义和使用泛型方法。
2.任务分析
声明一个泛型方法很简单,只要在返回类型前面加上一个类似<K, V>的形式就行了。其类图如图4-3所示。
3.任务实现
Pair.java
package 泛型;
public class Pair<K,V> {
private K key;
private V value;
public Pair(K k,V v){
this.key = k;
this.value = v;
}
public K getKey(){
return key;
}
public V getValue(){
return value;
}
public void setKey(K k){
this.key = k;
}
public void setValue(V v){
this.value = v;
}
}
Util.java
package 泛型;
public class Util {
public static <K,V> boolean compare(Pair<K,V> p1,Pair<K,V> p2){
return p1.getKey().equals(p2.getKey())&&p1.getValue().equals(p2.getValue());
}
}
TestUtil.java
package 泛型;
public class TestUtil {
public static void main(String[] args) {
//TODO Auto-generated method stub
Pair<String,String> p1 = new Pair<>("name","zhangsan");
Pair<String,String> p2 = new Pair<>("name","lisi");
System.out.println("计较p1,p2:"+Util.compare(p1,p2));
Pair<String,Integer> p3 = new Pair<>("age",18);
Pair<String,Integer> p4 = new Pair<>("age",19);
System.out.println("计较p3,p4:"+Util.compare(p3,p4));
Pair<Integer,String> p5 = new Pair<>(1,"lisi");
Pair<Integer,String> p6 = new Pair<>(2,"wangwu");
System.out.println("计较p5,p6:"+Util.compare(p5,p6));
}
}
说明
在调用泛型方法的时候,在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一个父类的最小级,直到Object。在指定泛型的时候,该方法中的几种类型必须是该泛型实例类型或者其子类。
任务三 泛型接口的定义和使用
1.任务需求
对任两个数求和。这两个数可以是整数、浮点数和字符串。
2.任务分析
定义泛型接口Calculator,其中定义泛型方法and,用来求和。
3.任务实现
Calculator接口
package 泛型.泛型接口的定义和使用;
//
public interface Calculator <T>{
public T and(T a,T b);
}
定义类CalculatorInteger实现Calculator接口。
package 泛型.泛型接口的定义和使用;
public class CalculatorInteger implements Calculator<Integer>{
@Override
public Integer and(Integer a,Integer b){
//TODO Auto-generated method stub
return a+b;
}
}
定义类CalculatorString实现Calculator接口。
package 泛型.泛型接口的定义和使用;
public class CalculatorString implements Calculator<String>{
@Override
public String and(String a, String b) {
//TODO Auto-generated method stub
return a+b;
}
}
定义测试类TestCalculator,进行测试。
package 泛型.泛型接口的定义和使用;
public class TestCalculator {
public static void main(String[] args) {
//TODO Auto-generated method stub
CalculatorInteger ci = new CalculatorInteger();
Integer and1 = ci.and(10, 20);
System.out.println(and1);
CalculatorString cs = new CalculatorString();
String and2 = cs.and("湖北", "武汉");
System.out.println(and2);
}
}
运行结果:
说明
通过public interface Calculator<T> {}定义泛型接口,在实现该接口时,必须指明泛型T的具体类型,例如:public classCalculatorInteger implementsCalculator<Integer>{},指明泛型T的类型为Integer,实例化该类时无须指定泛型类型。
参考书籍:java高级程序设计实战教程