黑马程序员-泛型的理解

来源:互联网 发布:伺服电机怎么编程控制 编辑:程序博客网 时间:2024/04/30 09:35

---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------

 

泛型用于解决安全问题,JDK1.5版本后出现的

泛型的定义格式:

定义在类名:示例:class Demo<T>{}    

泛型符号放在类名的后面,在实例化的时候指定泛型类型

定义在方法:分非静态方法、静态方法

1)非静态方法:

示例public<T>void Demo<T> (){}

在类上定义的泛型会作用到非静态方法中,两者是一致的

2)静态方法:

示例:publicstatic<T> void Demo<T>(){}

在类上定义的泛型不会作用到静态方法中,如果类上也是用T,静态方法上也可以用T但是两者都是独立的

泛型符号放在返回值类型的前面

定义在接口上:interface Demo<T>

类实现接口时:class Demo<T>implements Car<Q>

定义在接口上的泛型何时指定?

答:有三种情况

1)在类实现该接口的时候,指定泛型的类型

2)如果在类实现实现接口的时候没有指定泛型的类型,那么默认是Object类型

3)在类实现接口的时候没有指定泛型类型,想要在接口实现类的对象时,确定接口的泛型的泛型类型,那么也必须在类上声明该泛型类型(类的泛型和接口的一致)

4)编译器不允许创建参数类型的数组,即是说,创建数组示例的时候,不能使用泛型,如下写法是错误的

ArrayList[]<String>a1 = new ArrayList[10];

 

注意:泛型的所用的符号“T”,可以用别的符号代替,大小写有区别,但是不能关键字,T也有作用范围,定义在方法上的泛型,出了方法范围就不起作用,和局部变量的使用原则一样

 

以下格式注意:

1ArrayList list = new ArrayList<String>()

ArrayList<String>list = new ArrayList ()

以上两种写法编译可以通过,但是list的返回值类型还是Object,还是避免不了强转,但是为什么java语法允许这种写法?因为要兼容老版本,泛型是JDK1.5才出现的

2ArrayList<Object>list = new ArrayList<String>()

ArrayList< String>list = new ArrayList< Object>()

上面两种写法错误,泛型不存在多态的写法,即不考虑继承的关系

在定义的写法上不考虑继承,但是在实际操作中

ArrayList< Object > list = new ArrayList< Object>()

list.add(“abcd”);//可以添加String类型的数据,因为String也属于Object

 

3:

ArrayList a1 = newArrayList<String>();

ArrayList<Object>a2 = a1;

上述代码可以通过编译,因为编译器是按照一行一行的顺序检查语法错误,上述两条代码单独检查,都正确,编译器不考虑程序运行时的结果


泛型的好处:

提高了程序的安全性

把运行时可能出现的异常提前到编译时期

避免了强制类型转换的麻烦

泛型类的出现优化了程序设计

 

什么时候要定义泛型:

当类中要操作的引用数据类型不确定的时候,可以定义成泛型类

早期的时候时通过Object来完成拓展,现在定义泛型类来拓展

 

泛型可以定义在类上

示例:

class Haha<T>//定义要操作的数据{public void show(T t){System.out.println(t);}public void print(T t){System.out.println(t);}}class  HaDemo{public static void main(String[] args) {Haha<String> a = new Haha<String>();//此时已经明确了要操作的数据类型了,已经固定不能更改a.show("hiahia");a.print("haha");}}


 

 

输出结果:

"show" hiahia

"print"haha             

如果类中方法操作的数据都是固定一种数据时,可以把泛型定义在类名上,但是,如果类中的方法,需要操作不同的数据,但是也想要泛型的功能,把错误提前到编译时期怎么办

此时可以把泛型定义在类中的方法上

示例

class Haha//定义要操作的数据{public <T> void show(T t){System.out.println("show:"+t);}public <Q>void print(Q q){System.out.println("print:"+q);}}class  HaDemo1{public static void main(String[] args) {Haha a = new Haha();a.show("hiahia");//此时同一个方法可以接受不同类型的参数a.show(43);a.print("haha");a.print(12);}}


输出结果

show:hiahia

show:43

print:haha

print:12

 

两者混合使用也可以

示例:

class Haha<T>//定义要操作的数据{public void show(T t){System.out.println(t);}public <Q> void print(Q q)//此时这个方法和定义在类上的T没关系,泛型T和Q的作用是独立的{System.out.println(q);}}class  HaDemo{public static void main(String[] args) {Haha<String> a = new Haha<String>();//此时的String泛型限定只作用于show()a.show("hiahia");a.print(4566435);}}


 

输出结果

hiahia

4566435

 

需要注意的地方:

如果静态方法引用的数据类型不确定,可以把泛型定义在方法上,但是!!静态方法不能使用类上定义的泛型,因为静态先于类先加载进内存,但是静态方法可以自定义泛型。

泛型的还有一个占位符<?>,这个与<T>有什么区别呢?

当定义在方法上的时候,两者的对比

Public static<T>void print<T>(){}

Public static voidprint<?>(){}

除了格式上的区别,还有使用上的区别,<T>中的T是代表一个实际的类型,那么在传入参数的时候,传入StringT就等同于String,传入int类型T就等同于int,后续可以用T接收对象和引用,示例:T t = it.next();it.next()返回一个String类型的对象时,T可以接收

 

但是<?>中的?不代表具体的类型,即使传入参数后也不能当做数据类型使用,有局限性

但是也可以给<?>做拓展

Class person{}

Class studentextends person{}

,假设studentPerson的子类,person是父类
如果只想要接收personperson的子类,那么可以给一个类型限定<? extendsperson>  向下限定
如果只想要接收student和它的父类型,那么可以给一个类型限定

<? super student>向上限定(间接父类也可以吗?可以)

定义一个方法,用于答应出任意参数化类型的集合中的所有数据

public static void printCollection(Collection<Object> cois){For(Object obj:cois){    System.out.println(obj);}cois.add(“abcd”);//正确}//但是如果使用通配符?public static void printCollection(Collection<?> cois){For(Object obj:cois){    System.out.println(obj);}cois.add(“abcd”);//错误,不能添加,因为不知道未来一定匹配String}

 

通配符的使用注意:

Class<?> a = Class<String>b         //写法正确

Class<String>b = Class<?>a          //写法错误

总结:任意类型可以接收某种类型因为任意任意类型也包括了某种类型,但是不能某种类型不能接收任意类型

注意的问题:

 

泛型里面接收的都是引用数据类型,基本数据类型不行

TreeMap<char,int> tm = new TreeMap<char,int> ()//应该这样找到其基本数据类型的包装类TreeMap<Character,Integer> tm = new TreeMap< Character,Integer > ()//注:可以接收String//以下也是不正确的Public static void main(String[] args){Int arr = {2,3,4,5};//修改为Integer arr = {2,3,4,5}编译可以通过Print(arr);}Public static void print(T[] arr){}

 

编译错误,修改为Integer arr = {2,3,4,5}编译可以通过

因为泛型里面接收的必须是引用数据类型,不能是基本数据类型

原因:

为什么传入int变量可以被泛型接收,而int[]数组却不行,因为int会被自动装箱成对象,但是int[]数组本来就已经是一个对象,不能被装箱

 

泛型进阶

泛型是提供给java编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带泛型说明的集合时会去除掉泛型信息,使程序运行效率不受影响,对参数化的泛型类型,getClass()方法的返回值和原始类型完全一样,由于编译生成的字节码会去掉泛型的类型信息,也就是说,只要能够跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可(因为直接得到字节码,而字节码已经去掉了泛型信息)

示例:

ArrayList<Integer> collection = new ArrayList<Integer>();//现在在源代码中定义了集合collection,只能装Integer类型的对象现在通过反射加入String类型的数据collection.getClass().getMethod(“add”,Object.class).invoke(collection,”abcdefg”)System.out.println(collection.get(0));


 

结果:abcdefg

 

泛型的一个应用,根据传入参数的类型确定泛型的类型

这里的泛型在使用之前,参数传进来之前,泛型是不确定的

泛型依赖参数,此类泛型可以由编译器进行类型推断(类型推断下面讲解)

public static <T> T add(T x,T y)

{

return null;

}

add(3,3.5)--à由于传入参数一个是int一个是float,所以,最终T所表示的类型是两者的交集,Numberl类型

add(4.5,3.5)--à传入参数都是float型,所以T的类型是float

add(3,”abc”)--à由于传入参数一个是int类型一个是String类型,所以两者的交集是Object类型

 

关于泛型的类型推断

需求1:把任意参数类型的集合中的数据安全地复制到相应类型的数组中

需求2:把任意类型的数组中的数据安全的复制到另一个相应类型的数组中去

Public static <T> voidcopy_1(Collectiong<T> src,T[] dest)

{}

Public static <T> void copy_2(T[]src,T[] dest)

{}

Copy_1(new Vector<String>,newString[10]);

这种写法正确,此时T的值为String

Copy_1(new Vector<Date>,newString[10]);

这种写法错误,参数的代表的T值不相等,此时编译器不进行类型推断

 

Copy_2(newDate[10],newString[10])

这种写法正确,虽然此时参数代表的T值不相等,前者为Date,后者为String

此时编译器会进行类型推断,最终的类型取两者的交集Object

解疑:为什么有时候编译器对泛型类型推断,有时候不类型推断

答:仔细观察copy_1(Collectiong<T> src,T[] dest)copy_2(T[]src,T[] dest)

对于copy_1,因为Collectiong<T>这个是定义泛型类型(有“<>”),代表着这里定义了一个泛型类型,所以后面的T[]的类型与此时定义的一致,T[]中这个T可以理解为引用

 

对于copy_2,因为这里T[] src,T[] dest不是使用“<>”定义泛型,那么这里的泛型也是不确定的(或者说没有明确指定),那么可以理解为泛型的确定需要依赖参数,那么此时就编译器就会对泛型进行类型推断

 

总结:何时会类型推断

当泛型的类型通过<>被指定为某一确定的类型时,此时泛型类型已经确定,编译器不需要类型推断

当泛型类型没有通过<>指定为某一确定的类型时,那么此时泛型所表示的类型就不确定,需要依赖参数类型反过来确定泛型,此时编译器会进行类型推断,并且如果参数的类型不一致,编译器推断的原理为,取交集

 

通过反射得到泛型的实际类型参数

Vector<Person> v = new Vector<Person>()

问题:能不能通过获得v.getClass()或者字节码,然后再获得里面的泛型实际参数呢?

答:不行,因为字节码是通过编译器了,编译器阶段会进行泛型的类型檫除,把泛型信息去除了,所以不能通过对象的字节码得到任何泛型信息

但是sun公司提供一个方法去获取泛型信息

例:获取apply这个方法传入的参数Vector<Date> v的泛型信息

Class Demo{

public static void apply(Vector<Date> v)

{}

}

此时通过以下语句可以达到目的

Method applyMethod= v.getClass.getMethod(“apply”, Vector.Class)

//获取这个apply这个方法

Type[] types =applyMethod.getGenericParameterTypes();

//获取泛型的方法参数类型

ParameterizedTypepType = (ParameterizedType)type[0];

System.out.println(pType.getRawType());

//获得参数类型

System.out.println(pType.getActualTypeArguments());

//获得参数所接受的泛型类型

输出结果:

classjava.util.Vector

classjava.util.Date

 

 

 

---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------

详细请查看:http://edu.csdn.net 

原创粉丝点击