转载请申明出处:http://blog.csdn.net/xmxkf/article/details/9944389

36.入门泛型的基本应用

体验泛型:

Jdk1.5以前的集合类中存在什么问题?

ArrayList collection1 = new ArrayList();

collection1.add(1);

collection1.add(1L);

collection1.add(“abc”);

int i = (Integer) collection1.get(1);  //编译要潜质类型转换且运行时类型转换出错

Jdk1.5的集合类希望你在定义集合时,明确表示你要向集合中装那种类型的数据,无法加入指定类型以外的数据

ArrayList collection2 = new ArrayList();

collection2.add(1);

/*collection2.add(1L);

collection2.add(“abc”); */

//这两句代码编译时就报告了语法错误(将运行是错误提前到编译期)

int i = collection2.get(0);

//获取集合中一个对象时,编译器可以知道这个对象的类型;不需要进行类型转换

理解:泛型是提供给javac编译器使用的,可以先定集合中输入类型,让编译器挡住源程序中的非法输入,编译器编译完后会去掉有类型说明的集合的“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据;例如用反射的到集合,在调用其ADD方法即可。

在jdk1.5中,你还可以按原来的方式将各种不同类型的数据装到一个集合中,但是编译器会报告unckecked警告(行号后面的小警告号,也可以通过@SuppressWarnings(“deprection”)取消警告,dos命令javac编译时没有警告,而不是eclipse工具没有警告符号)。

ArrayList<String> collection3 =
new ArrayList<String>();

System.out.println(collection2.getClass()==collection3.getClass()); //true

//collection3.add("acds");

//通过反射可去掉泛型类型信息(编译后就去掉了),可加入字符串等各种类型对象

collection3.getClass().getMethod("add", Object.class).invoke(collection3,"acds");

System.out.println(collection3.get(0));

37.泛型的内部原理及更深应用

了解泛型:

1、ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:

整个称为ArrayList<E>泛型类型;ArrayList<E>中的E称为类型变量或类型参数;

整个ArrayList<Integer>称为参数化的类型;

ArrayList<Integer>中Integer称为类型参数的实例或者实际类型参数;

ArrayList<Integer>中<>念做typeOf;ArrayList称为原始类型。

2、参数化类型与原始类型的兼容性:

参数化类型可以引用一个原始类型的对象,编译报告警告,例如:

Collection<String> c = new Vector();    //通不通过就是编译器一句话的事

原始类型可以引用一个参数化类型的对象,编译报告警告,例如:

Collection c = new Vector<String>();     //原来的方法接受一个集合参数,新的类型也要能传递进去

3、参数化类型不考虑类型参数的继承关系:

Vector<String> v = new Vector<Object>();   //错误

Vector< Object> v = new Vector< String >();   //也错误

4、在创建数组实例时,数组的元素不能使用参数化的类型。例如,下面语句有错误:

Vector<Integer>  vectorArray = new Vector< Integer >[10];

思考:下面代码会出错吗?

Vector v1 = new Vector<String>();     //参数化类型给原始类型

Vector<Object> v2= v1;          //原始类型给参数化类型

不会出错,因为编译器是严格按照语法检查的工具,不考虑运行时效果,只是一行行翻译代码,看有没有错。

38.泛型的通配符扩展应用

问题:定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,如何定义?

错误方式:

public static void printCollection(Collection<Object> cols){

for(Object obj : cols){

       System.out.println(obj);

}

cols.add(“string”);   //没错,因为cols可以接受任意类型对象

cols = new HashSet<Date>();

//会报错,Collection<Object> cols = new HashSet<Date>()

}

正确方式:

public static void printCollection(Collection<?> cols){

for(Object obj : cols){

       System.out.println(obj);

}

cols.add(“string”);   //错误,因为不知道自己未来匹配就一定是String

cols.size();      //没错,此方法与类型参数没有关系

cols = new HashSet<Date>();  //不会报错,因为指定?为Date

}

Cols<Object>中的Object只是说明Cols<Object>实例对象中的方法接受的参数是Object;Cols<Object>是一种具体的类型,new
HashSet<Date>()也是一种具体类型,两者没有兼容性问题。

 

总结:使用?通配符可以引用其他各种参数化的类型。?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。

泛型中的?通配符的扩展:

限定通配符的上边界: 只能指向实际类型参数为Number子类的参数化类型

正确:Vector<? extends Number> x = new Vector<Integer>();

错误:Vector<? extends Number> x = new Vector<String>();

限定通配符的下边界: 只能指向实际类型参数为Integer父类的参数化类型

正确:Vector<? super Integer > x = new Vector< Number >();

错误:Vector<? super Integer > x = new Vector<Byte>();

提示:限定通配符总是包括自己。

Class x1 = String.class.asSubclass(Number.class);

Class<?> y;

Class<String> x2 ;
//   Class<?> xw =  Class.forName("java.lang.String");

y=x2;  
//正确

x2=y;  
//错误

39.泛型集合的综合应用案例

1、出下面代码代表掌握了Java的泛型集合类:

HashMap<String, Integer> hm = new HashMap<String,Integer>();

hm.put("zhangsan", 23);

hm.put("lisi", 28);

hm.put("wanger", 35);

//泛型的实际参数类型又是一个参数化类型

Set<Map.Entry<String, Integer>> entrySet = hm.entrySet();

for(Map.Entry<String, Integer> me : entrySet)

{

System.out.println(me.getKey()+":"+me.getValue());

}

、对在jsp网页中也经常要对Set或Map集合进行迭代:

<c: forEach items=”${map}”
var=”entry”>

${entry.key}:${entry.value}

</c:forEach>

40.自定义泛型方法及其应用

1、由C++的模板函数引入自定义泛型:

如下函数的结构很相似,仅类型不同:

int add(int x, int y){ return x+y; }

float add(float x, float y){ return x+y; }

double add(double x, double y){ return x+y; }

C++用模板函数解决,只写一个通用方法,它可以适应各种类型,示意代码如下:

template<class T> T add(T x, T y){ return (T)(x+y);}

2、Java中的泛型类型(或者泛型)类似于C++中的模板。但是这种相似性仅限于表面。Java语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除掉)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为Java厂商升级其JVM造成难以逾越的障碍。所以java的泛型采用了可以完全在编译器中实现的擦除方法。

3、定义泛型方法:

Java的泛型方法没有C++模板函数功能强大,java中的如下代码无法通过编译:

<T> T add(T x, T y){ return (T)(x+y); //return null; 不是所有类型数据都能加法运算}

交换数组中的两个元素的位置的泛型方法语法定义如下:

Static <E> void swap (E[] a, int i ,int j){

//E为引用类型,此方法接受引用数据类型数组

E t = a[i];

a[i] = a[j];

a[j] = t;   }

思考:只有引用类型才能作为泛型方法的实际参数,对于add方法,使用基本类型的数据进行测试没问题,这时因为自动装箱和拆箱了。swap(new int[3],3 ,5);会报编译错误,因为编译器不会对new
int[3]中的int元素自动拆装箱,因为new int[]本身已经是对象了,你想要的有可能就是int数组呢?它装箱岂不成了Integer[]弄巧成拙?

Numbernum =
add(3,5);         //泛型可以取两个参数共同的交集类作为T

Numbernum1
add(3.5,5);

Object obj = add(3,"abc");

privatestatic <T> T add(T x,T y)

{

returnnull;

}

注意:

a于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和方法返回值类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。

b有引用类型才能作为泛型方法的实际参数,swap(new int[3],3 ,5);语句会报错。

c除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用。例如: Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如:<V
extends Serializable & cloneable> void method(){};

d通方法,构造方法,静态方法都可以使用泛型。编译器也不允许创建类型变量的数组。

e泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号隔开,如下:

Public static <K,V> V getValue(K key){ return map.get(key);}

f也可以用类型变量表示异常,称为参数化的异常。可用于方法的throws列表中,但是不能用于catch子句中。

例:用下面的代码说明对异常如何采用泛型:

privatestatic <Textends
Exception>voidsayHello()
throws T

{

ry{}

atch(Exception e)  //不能写catch(T
e)

{

hrow(T)e ;

}

}

41.自定义泛型方法的练习与类型推断总结

、编写一个泛型方法,自动将Object类型的对象转换成其他类型:

privatestatic <T> T autoConvert(Object obj)

{

return(T)obj;

}

、定义一个方法,可以将任意类型的数组中的元素填充为相应类型的某个对象:

privatestatic
<T>voidautoConvert(T[] a, T t)

{

for(int i=0;i<a.length;i++)

{

a[i] = t;

}

}

、采用自定义泛型方式打印出任意参数化类型的集合中的所有内容:

在这两种情况下,通配符方案比泛型方法更有效。当一个类型变量用来表达两个参数之间或者参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候使用,才需要使用泛型方法。

//通配符

publicstaticvoid printCollection(Collection<?> cols)

{

//  cols.add(“string”);   //错误,因为不知道自己未来匹配就一定是String

System.out.println(cols.size());  //没错,此方法与类型参数没有关系

for(Object obj : cols)

{

System.out.println(obj);

}

cols = new HashSet<Date>(); //不会报错,因为指定?为Date

}

//泛型方法

publicstatic <T>void
printCollection2(Collection<T> cols,T t)

{

cols.add(t);

System.out.println(cols.size());

for(Object obj : cols)

{

System.out.println(obj);

}

cols = (Collection<T>)new HashSet<Date>();

}

、定义一个方法,把任意参数类型的集合中的数据安全的复制到相应的数组中:

定义一个方法,把任意参数类型的数组中的数据安全的复制到相应的数组中:

privatestatic <T>voidcopy1(Collection<T>
src,T[] dest)
{}

privatestatic <T>voidcopy2(T[]
src,T[] dest)
{}

copy1(new Vector<String>(),new String[10]); //正确

copy2(new Date[10],new String[10]);   //正确 
会认为T是Date和String的公共父类

copy1(new Vector<Date>(),new String[10]);  //错误 
Vector<Date>直接确定了T是Date

****类型参数的(参数)类型推断:

编译器判断泛型方法的实际类型参数的过程称为类型推断。类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。

根据调用泛型方法时实际传递的参数类型或者返回值的类型来判断,规则如下:

A、当某个类型变量只在整个参数列表中的所有参数和返回值的的一处被用,那么根据调用方法时该处的实际应用类型来确定,这很容易判断出,即直接根据调用方法时传递的参数或返回值来决定泛型参数的类型:

swap(new String[3], 3, 4)  --------àstatic<E>
void swap(E[],int i,int ,j)

B、当某个类型变量在整个参数列表中的所有参数和返回值中的多处被使用了

如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易推断出:

add(3, 5)   -------àstatic<T>
T add(T a,T b);

如果调用方法时这多处的实际应用类型都对应了不同的类型,且没有返回值,这时取多个参数中的最大交集类型,例如下面语句T实际对应的类型就是Number了,编译没问题,只是运行时出问题:

fill(new Integer[3], 3.5f) ------àstatic
<T> void fill(T[] a, T v)

如果调用方法时这多处的实际应用类型都对应了不同的类型,且使用返回值,这时优先考虑返回值的类型,例如下面语句实际对应类型就是Integer,编译将报告错误,将变量x类型改为Float,对比eclipse报告的错误提示,接着再将变量x的类型改为Number就没错误了:

int x = add (3, 3.5f)  -------àstatic <T> T add(T a, T b)

C、
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没问题;而第二种则根据参数化的Vector类实例将类型变量直接确定为String类型(因为Vector后面有<>直接确定T),编译将出问题:

copy(new Integer[5], new String[5])-------àstatic <T> void copy(T[] a ,T[] b)

copy(new Vector<String>(), new Integer[5])

-------àstatic <T> void copy(Collection<T> a ,T[] b)

42.自定义泛型类的应用

定义泛型类型:

1、如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:

public class GenericDao<T>{

private T field1;

public void save(T obj){}

public T getById(int id){}

}

2、类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:

GenericDao<String> dao = null;

new GenericDao<String>();

3、注意:

在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。

当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为静态成员时被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。

* dao : data access  object数据访问对象  
——>crud增删改查*/

publicclass GenericDao <E>     //操作E类型对象的类

{

publicvoid add(E obj){  //增加对象

}

public <T> T findById(int id){  //查找对象

returnnull;

}

public Set<E> findByConditions(String where){

returnnull;

}

public E findByUserName(String name){

returnnull;

}

publicvoid delete(E obj){   //删除对象

}

publicvoid delete(int
id){    //删除对象

}

publicvoid updat1e(E obj){   //修改对象

}

publicstatic <E>void
update2(E obj){

//静态方法不能访问类上的泛型,只能自己重新声明泛型

}

43.

java.lang.reflect

接口 ParameterizedType 表示参数化类型   1.5开始

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

publicstaticvoid applyVector(Vector<Date> v)

{

}

//Vector<Date> v = new Vector<Date>();

//通过获取参数化类型的Class对象是不能获取到实际类型参数的,编译时会擦除掉

Method applyMethod = GenericTest.class.getMethod("applyVector",
Vector.class);

//1.5新方法,获取一个方法对象的形参类型(包括参数化类型)

Type[] types = applyMethod.getGenericParameterTypes();

//获取这个数组中的参数化类型对象

ParameterizedType pType = (ParameterizedType)types[0];

System.out.println(pType); // java.util.Vector<java.util.Date>

//获取原始类型

System.out.println(pType.getRawType()); //class java.util.Vector

//获取此类型实际类型参数的数组(只有一个,可能有几个Map<K,V>)

System.out.println(pType.getActualTypeArguments()[0]); //class java.util.Date

java--加强之 Java5的泛型的更多相关文章

  1. Java笔记(五)泛型

    泛型 一.基本概念和原理 泛型将接口的概念进一步延申,“泛型”的字面意思是广泛的类型. 类.接口和方法都可以应用于非常广泛的类型,代码与它们能够操作 的数据类型不再绑定到一起,同一套代码可以应用到多种 ...

  2. Java编程的逻辑 (35) - 泛型 (上) - 基本概念和原理

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  3. Java基础之多态和泛型浅析

    Java基础之多态和泛型浅析 一.前言: 楼主看了许多资料后,算是对多态和泛型有了一些浅显的理解,这里做一简单总结 二.什么是多态? 多态(Polymorphism)按字面的意思就是“多种状态”.在面 ...

  4. Java 加解密技术系列文章

    Java 加解密技术系列之 总结 Java 加解密技术系列之 DH Java 加解密技术系列之 RSA Java 加解密技术系列之 PBE Java 加解密技术系列之 AES Java 加解密技术系列 ...

  5. Java 8 新特性之泛型的类型推导

    1. 泛型究竟是什么? 在讨论类型推导(type inference)之前,必须回顾一下什么是泛型(Generic).泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据 ...

  6. 160829、Java加解密与数字签名

    ** Java加解密 ** 实现方式:JDK实现,CC,BC JDK提供比较基础的底层的实现:CC提供一些简化的操作:BC提供补充 一.Base64加密 非常简单,加密解密就一个函数. 代码如下: 二 ...

  7. Java加解密与数字签名

    ** Java加解密 ** 实现方式:JDK实现,CC,BC JDK提供比较基础的底层的实现:CC提供一些简化的操作:BC提供补充 一.Base64加密 非常简单,加密解密就一个函数. 代码如下: 二 ...

  8. 11.Java 加解密技术系列之 总结

    Java 加解密技术系列之 总结 序 背景 分类 常用算法 原理 关于代码 结束语 序 上一篇文章中简单的介绍了第二种非对称加密算法 — — DH,这种算法也经常被叫做密钥交换协议,它主要是针对密钥的 ...

  9. 10.Java 加解密技术系列之 DH

    Java 加解密技术系列之 DH 序 概念 原理 代码实现 结果 结束语 序 上一篇文章中简单的介绍了一种非对称加密算法 — — RSA,今天这篇文章,继续介绍另一种非对称加密算法 — — DH.当然 ...

随机推荐

  1. 10 GridView 样式属性

    GridView 样式属性: 1,android:numColumns="auto_fit" 显示的列数 如果android:numColumns不设置那么自动每行1列 如下图 2 ...

  2. mysql-workbench工具update(更新)失败的解决办法

    是因为安全模式的保护,所以我们需要设置一下: 如下:windows下是edit–>preferences–>SQL Editor 把右边的最后一行,"safe update&qu ...

  3. Android View框架总结(四)View布局流程之Measure

    View树的measure流程 View的measures时序图 View布局流程之measure measure过程 View的measure过程 ViewGroup的measure过程 Frame ...

  4. ROS_Kinetic_24 使用catkin_create_qt_pkg快速创建qt-ros功能包

    使用catkin_create_qt_pkg快速创建qt-ros功能包 参考网址: qt_create:http://wiki.ros.org/qt_create qt_ros:https://git ...

  5. Hadoop:Hadoop基本命令

    http://blog.csdn.net/pipisorry/article/details/51223877 常用命令 启用hadoop start-dfs.sh start-hbase.sh 停止 ...

  6. StarUML中InteractionOperation的画法

    StarUML画InteractionOperation的方法:http://stackoverflow.com/questions/16152278/using-alt-in-sequence-di ...

  7. [GitHub]第二讲:GitHub客户端

    文章转载自http://blog.csdn.net/loadsong/article/details/51591456 Git 是一个分布式的版本控制工具,即使我不联网,也可以在本地进行 git 的版 ...

  8. web安全认证机制知多少

    如今web服务随处可见,成千上万的web程序被部署到公网上供用户访问,有些系统只针对指定用户开放,属于安全级别较高的web应用,他们需要有一种认证机制以保护系统资源的安全,本文将探讨五种常用的认证机制 ...

  9. μC/OS-II与RT-Thread对比——任务调度

           在任务调度器的实现上,μC/OS-II和RT-Thread都采用了位图调度(bitmap scheduling),任务优先级的值越小则代表具有越高的优先级,主要区别在于实现形式,是采用多 ...

  10. ios swift模仿qq登陆界面,xml布局

    给大家推荐两个学习的地址: 极客学院的视频:http://www.jikexueyuan.com/path/ios/ 一个博客:http://blog.csdn.net/lizhongfu2013/a ...