泛型的本质
# 从数据结构角度看:
泛型是一种效果,是不同的存储方式,执行的操纵是一样的效果。
使用泛型,一旦定义了一个算法,就独立于任何特定的数据类型,而且不需要额外的操作,就可以将这个算法应用到各种
数据类型中。许多 算法 不论运用哪一种数据类型,他们在逻辑上是一样的。
泛型很强大,以至于从 根本上 改变了java代码的 编写方式。
# 从java角度看:
使用泛型,可以建立以 类型安全模式 处理 各种数据 的类、接口和方法。
泛型是一种安全机制。
举例说明:在集合中存入数据的时候,强行规定存入的数据类型,就可以避免安全问题。
#######################################################################################
java的泛型:::::::
下面看看具体的泛型:
# 泛型在本质上是指 类型参数化 。
类型参数化:是指用来 声明 数据的类型的 本身,也是可以改变的,有实际的参数来决定。
一般情况下,实际参数决定了形式参数的 值 。而类型参数化,则是实际参数的类型决定的形式参数的 类型 。
举例说明:Integer max(Integer a,Integer b){
return a>b?a:b;
}
如果比较的换成了double或float,不得不再次重新写代码。泛型就是一种不必确定参数的 类型 ,
等到调用的时候在确定参数的 类型 。
在泛型出现之前用Object声明 参数类型 变通,但是需要强制类型转换,有了泛型就方便多了。
所有的强制转化变为自动或者隐式的。
泛型的具体java应用
也可以说在 类型参数化的时候使用泛型。下面的应用都是可以使用类型参数化的时候使用泛型;
特点:避免强转、安全性(强行规定集合的数据类型)、扩展性
《一》泛型类
例子:
//声明一个泛型类
public class Generic<T>{
T ob;//ob的类型是T,需要到创建时候对象才能确定。
Generic(T o){
ob = o;
}
T getOb(){
return ob;
}
void showType(){
System.out.println("Type of T is:"+ob.getClass().getName() );
}
}
//下面使用上面的泛型
public class demoGeneic{
public static void main(String args[]){
//声明一个Integer的Generic变量
Generic<Integer> iobj;
//创建一个Integer的Generic对象
iobj = new Generic<Integer>(100);
iobj.showType();
int k = iobj.getOb();
System.out.println("k = "+ k);
//声明一个String的Generic变量
Generic<String> sobj;
//创建一个String的Generic对象
iobj = new Generic<String>("hello");
sobj.showType();
String s = sobj.getOb();
System.out.println("s = "+ s);
}
}
结果:Type of T is:java.lang.Integer
k = 100
Type of T is:java.lang.String
s = hello
注意:类型参数T不能使用在静态方法中,并且不能出现<int>。
《二》泛型方法
创建一个泛型方法常用形式:
[访问权限] [static][final]<类型参数列表> 返回值类型 方法名([形式参数列表])
必须这样写, 泛型方法可以写在一个泛型类中,也可以写在普通方法中,由于在泛型类中
的任何方法,本质上都是泛型方法,所以在实际使用中,很少会在泛型类中
再用上面的形式来定义泛型方法。
类型参数可以用在方法体中修饰局部变量,也可以用在方法的参数表中,修
饰形参。
泛型方法可以是实例方法或者静态方法。类型参数可以使用在静态方法中,
这是与泛型类的重要区别。
使用一个泛型方法: <对象名|类名>.<实际类型>方法名(实际参数类型) ;
和
<对象名|类名>.方法名(实际参数表);
如果泛型方法是实例方法。要使用对象名作为前缀,如果是静态方法,则可以
使用对象名或类名作为前缀。
如果是在类的内部调用,且采用的是第二种,那么前缀都可以省略。
看看例子吧:
public class demoGenMethods{
//定义泛型方法,有一个形式参数用类型参数T来定义
public static <T> void showGenMsg(T ob,int n){
T localOb = ob;//局部变量也可以用类型参数T来定义
System.out.println(localOb.getClass().getClass().getName());
}
public static <T> void showGenMsg(T ob){
System.out.println(ob.getClass().getName());
}
public static void main(String args[]){
String str = "parameter";
Integer k = new Integer(123);
//用两种方法调用泛型方法
demoGenMethods.<Integer>showGenMsg(k,1);
showGenMsg(str);
showGenMsg(k);//这就是泛型方法的好处,同一个方法接受的参数类型在同一个类中可以不同。
}
}
注意:静态方法不可以访问类中的泛型。泛型也是一个对象,从某种意义上说。
因为泛型在创建对象的时候才执行,而静态方法在加载的时候,对象不存在。
如果静态方法操作的数据类型不确定,可以将泛型定义在方法上。注意泛型方法的格式。
《三》泛型接口
interface 接口名<类型参数表>
看例子:
创建接口:interface MinMax<T extends Comparable<T>>
实现接口:class MyClass<T extends Comparable<T> > implements MinMax<T>
看不同: MinMax<T extends Comparable<T>>和MinMax<T> 如果写法还是一样就不能通过编译。
至于class MyClass<T extends Comparable<T> >也要写成泛型,是因为:一个类实现了一
个泛型接口则此类也是泛型类。但是如果父类的是这种 implements MinMax<T> 形式就不必
在这样class MyClass<T extends Comparable<T> >写啦。
《四》泛型类的继承
泛型类的子类必须将泛型父类所需要的类型参数,沿着继承链向上传递。这与
构造方法 参数 必须沿着继承链向上传递的方式类似。
1、以 泛型类 为父类
public class derivedGen <T> extends superGen <T>
一定要这样写,意味着传递给derivedGen的实际类型也会传递给superGen。
看一个例子:public class derivedGen<T ,U> extends superGen<T>{
U dob;
public derivedGen(T ob1,U ob2){
super(ob1);//传递参数给父类!!!!!!!!!!!
dob = ob2;//为自己的成员赋值
}
public U getDob(){
return dob;
}
}
2、以 非泛型类 为父类
此时,不需要传递类型参数给父类,所有的类型参数都是为自己准备的。
技巧
一、上界使用:
class Stats<T extends Comparable & Serializable>
例子:求数组中元素的平均值(有int double )
class Stats<T>{
T [] nums;
Stats (T [] obj){
nums = obj;
}
double average(){
double sum = 0.0;
for(int i = 0;i<nums.length;i++)
sum += nums[i].doubleValue();//这里有错误
return sum/nums.length;
}
}
分析: nums[i].doubleValue()所有的Number都有这个方法,为甚系统还找不到这个方法?
原因就是,使用了泛型,没有告诉系统说参数一定要是Integrt 或 Double,要是String
也可以。解决这个问题就要用到下面的 有界类型。
在例子:class Stats<T extends Number>{
T [] nums;
Stats (T [] obj){
nums = obj;
}
double average(){
double sum = 0.0;
for(int i = 0;i<nums.length;i++)
sum += nums[i].doubleValue();//这里正确!
return sum/nums.length;
}
}
还有<? super E>可以传父类的对象,局限性就是这个对象只能用父类的方法。
二、通配符参数
看下面的例子就又会出现问题:
class Stats<T>{
.....
void doSomething(Stats <T> ob){
Systemout.println(ob.getClass().getName());
}
}
如果在使用时:
Integer inums[] = {1,2,3,4,5};
Stats <Integer> iobj = new Stats<Integer>(inums);
Double dnums[] = {1.1,2.2,3.3};
Stas <Double> dobj = new Stats<Double>(dnums);
dobj.doSomething(iobj);//错误
分析: void doSomething(Stats <T> ob)是这样声明的所以iobj和dobj的类型要一致。
要解决必须使用通配符啦!再看改过的例子:
class Stats<T>{
.....
void doSomething(Stats <?> ob){
Systemout.println(ob.getClass().getName());
}
}
如果在使用时:
Integer inums[] = {1,2,3,4,5};
Stats <Integer> iobj = new Stats<Integer>(inums);
Double dnums[] = {1.1,2.2,3.3};
Stas <Double> dobj = new Stats<Double>(dnums);
dobj.doSomething(iobj);//正确
通配符的使用:Stats <? extends Integer> ob
也可以将方法定义为泛型方法。区别:用泛型方法的情况可以用T接受;
***************************************************
运行时类型识别:
可以采用反射机制和传统的方法。
需要注意的,在JVM中,泛型类的对象总是一个特定的类型,此时,他不再是泛型。所以,
所有的类型查询都只会产生原始类型。
a instanceof Generic <?> 或者 a instanceof Generic <> "?"被忽略
但是不能写为 a instanceof Generic <Integer>
擦拭:程序员不必知道有关java编译器将源代码转化为class文件的细节。但是知道更好呵呵!
工作原理:当Java代码被编译时,全部泛型类型的信息会被删除,也就是使用类型参数来代替他们的
界限限制,默认的是Object ,然后运用相应的强制转化以维持与类型兼容,编译器会强制
这种类型兼容。对于泛型来说这种方法意味着在运行时不存在类型参数,他们仅仅只是
一种源代码机制。
擦拭会带来错误:1、静态成员共享问题
在泛型类中可以有静态的属性或者方法。静态方法不能使用参数类型。那么,其中
的静态成员也不可以使用参数类型或者是本泛型类的对象。
2、重载冲突问题
void conflict(T o){}
void conflict(Object o){}
在编译时,T会被Object所取代,所以实际上他们两个是一样的,就会出现错误。
另一种:public int conflict(foo<Integer>){}
public int conflict(foo<String>){}
编译器会怀疑他们有冲突,还可以更正:
public int conflict(foo<Integer>i){}
public int conflict(foo<String>s){}
3、接口实现问题
由于接口也可以是泛型接口,而一个类又可以实现多个泛型接口,所以会引发
冲突。例如:
class foo implements Comparable<Integer>,Comparable<Long>
由于会擦拭所以这两个接口还以一样的在编译期间。 要实现泛型接口
只能实现具有不同擦拭效果的接口,否则用:
class foo<T> implements Comparable<T>
泛型的局限:
1、不能使用基本类型
2、不能使用泛型类 异常
3、不能使用泛型数组
4、不能实例化参数类型对象