博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java基础加强总结(二)——泛型
阅读量:6186 次
发布时间:2019-06-21

本文共 12547 字,大约阅读时间需要 41 分钟。

一、体验泛型

  JDK1.5之前的集合类中存在的问题——可以往集合中加入任意类型的对象,例如下面代码:

1 package cn.gacl.generic.summary; 2  3 import java.util.ArrayList; 4  5 public class GenericTest { 6      7     public static void main(String[] args) { 8         /** 9          * 不使用泛型之前ArrayList容器可以存储任意类型的对象10          */11         ArrayList collection1 = new ArrayList();12         collection1.add(1);//存储Integer对象13         collection1.add(1L);//存储Long对象14         collection1.add("xdp");//存储String对象15         /**16          * 这里会报异常: JAVA.LANG.CLASSCASTEXCEPTION: 17          * JAVA.LANG.LONG CANNOT BE CAST TO JAVA.LANG.INTEGER18          * 19          */20         int i = (Integer) collection1.get(1);21     }22 }

  JDK1.5之后的集合类希望你在定义集合时,明确表示你要向集合中装哪种类型的数据,无法加入指定类型之外的数据,例如下面的代码:

/**         * 使用泛型限定ArrayList容器只能存储字符串类型的对象         */        ArrayList
collection2 = new ArrayList
(); collection2.add("孤傲苍狼"); //collection2.add(1);//报错,因为限制了collection2只能存储String类的对象,不能加入Integer类型的对象 //collection2.add(1L);//报错,因为限制了collection2只能存储String类的对象,不能加入Long类型的对象 //由于已经指明集合中存储的是字符串类型的对象,因此这里不用再强制转型了 String element = collection2.get(0);

  泛型是提供给Javac编译器看的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带参数类型说明的集合时会去去除掉“类型”信息,使程序运行不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样由于编译生成的字节码会去掉泛型的类型信息,因此只要能跳过编译器,就可以往某个泛型集合中加入其他类型的数据。

  例如下面的代码就演示了"使用反射得到集合,然后调用add方法往原本只能存储Integer对象的集合中存储一个String类型的对象"

1 ArrayList
collection3 = new ArrayList
();2 //对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样3 System.out.println(collection3.getClass());//结果为:java.util.ArrayList4 System.out.println(collection3.getClass() == collection2.getClass());//结果为true5 //使用反射得到集合,然后调用add方法往原本只能存储Integer对象的集合中存储一个String类型的对象6 collection3.getClass().getMethod("add", Object.class).invoke(collection3, "abc");7 System.out.println(collection3.get(0));//输出的结果为:abc,这证明字符串对象确实是存储到了原本只能存储Integer对象的集合中

备注:

  1. 泛型是JDK1.5的所有新特性中最难深入掌握的部分,没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储进同一个集合中,使用泛型集合,可以将一个集合中的元素限定为一个特定类型,这样集合中就只能存储同一类型的对象,这样更安全;并且当从集合中获取一个对象时,编译器也知道这个对象的类型,不需要对对象进行强制类型转换,这样更方便。
  2. 在JDK1.5之后,你还可以按原来的方式将各种不同类型的数据放到同一个集合中,但是编译时会报一个unChecked警告
  3. 泛型中的类型参数严格说明集合中装载的数据类型是什么和可以加入什么类型的数据,记住:Collection<String>和Collectin<Object>是两个没有转换关系的参数化的类型

二、了解泛型

  • ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
    • 整个称为ArrayList<E>泛型类型
    • ArrayList<E>中的E称为类型变量或类型参数
    • 整个ArrayList<Integer>称为参数化类型
    • ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
    • ArrayList<Integer>中的<>是“typeof”
    • ArrayList称为原始类型
  • 参数化类型与原始类型的兼容性:
    • 参数化类型可以引用一个原始类型的对象,编译时编译器会报警告,例如:Collection<String> c = new Vector();
    • 原始类型可以引用一个参数化类型的对象,编译时编译器会报警告,例如:Collection c = new Vector<String>();
  • 参数化类型不考虑类型参数的继承关系:
    • Vector<String> v = new Vector<Object>();//错误,语法上不通过
    • Vector<Object> v = new Vector<String>();//错误,语法上不通过

  假设Vector<String> v = new Vector<Object>;可以的话,那么以后从v中取出的对象当作String用,而v实际指向的集合中可以加入任意类型的对象,

  假设Vector< Object > v = new Vector< String >;可以的话,那么以后可以向v中加入任意类型的对象,而v实际指向的集合中只能装String类型的对象

思考:下面的代码会报错吗?(不会报错

  • Vector v1 = new Vector<String>();//参数化类型的对象可以给原始类型的引用
  • Vector<Object> v=v1;//参数化类型的引用可以指向原始类型的对象

三、泛型中的?通配符

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

错误的定义:

1     /** 2      * Collection中的Object只是说明Collection实例对象中的方法接收的参数是Object 3      * Collection是一种具体的类型,new HashSet
也是一种具体的类型,两者没有兼容性问题 4 * @param collection 5 */ 6 public static void printCollection(Collection
collection){ 7 for(Object obj:collection){ 8 System.out.println(obj); 9 }10 collection.add("abc");//没错11 collection=new HashSet
();//会报告错误12 }

正确的定义:

1     /**这里的Collection
中的?表示可以传人任意的类型参数 2 * Collection
cols可以匹配任意参数化的类型,但是到底匹配的是什么类型,只有以后才知道 3 * 所以 cols=new ArrayList
和cols = new ArrayList
都可以 4 * 但是cols.add("abc")或cols.add(new Date())都不行 5 */ 6 public static void printCollection(Collection
collection){ 7 for(Object obj:collection){ 8 System.out.println(obj); 9 }10 //collection.add("abc");//报错,因为collection不知道未来匹配的一定是String类型11 collection.size();//不报错,此方法与参数类型没有关系12 collection=new HashSet
();//这是可以的13 }

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

四、泛型中的?通配符的扩展

1.限定通配符?的上边界

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

   这里指的是?所代表的参数化类型必须是继承Number类的,如这里的?所代表的Integer类型就是继承Number类的

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

2.限定通配符?的下边界

  • 正确的写法:Vector<? super Integer> y = new Vector<Number>();

  这里指的是?所代表的参数化类型必须是Integer类的父类,如这里的?所代表的Number类型就是Integer类的父类

  • 错误的写法:Vector<? super Integer> y = new Vector<Byte>();

五、泛型的综合应用

1 package cn.itcast.day2; 2 import java.util.HashMap; 3 import java.util.HashSet; 4 import java.util.Map; 5 import java.util.Set; 6 /** 7  * 此类是用来演示泛型的应用的 8  *  9  * @author 孤傲苍狼10  * 11  */12 public class GenericCase {13     public static void main(String[] args) {14         HashMap
maps = new HashMap
();15 maps.put("lhm", 35);16 maps.put("flx", 33);17 /**18 * 变量的命名技巧:如果以后不知道一个变量该如何命名,就可以以方法名的形式来命名,19 * 如果要定义变量接收返回值,如果此时不知道如何定义变量名时,就直接定义成returnValue20 */21 Set
> entrySet = maps.entrySet();// 这里的变量名直接以方法名的形式定义22 // 使用增强的for循环迭代Map容器中的key和value23 //这里的Entry是Map类的一个内部类,map类中存储的key和value都是封装在这个Entry内部类中的24 //这个Entry内部类提供了getKey方法取出键,getValue方法取出值25 for (Map.Entry
entry : entrySet) {26 System.out.println(entry.getKey() + ":" + entry.getValue());27 }28 }29 }

在JSP页面中也经常要使用迭代标签<c:forEach>对Set或Map集合进行迭代:

1 
2 ${entry.key}:${entry.value}3

六、自定义泛型方法

1 package cn.itcast.day2; 2 import java.io.Serializable; 3 /** 4  * 此类是用来演示如何定义和使用泛型方法的 5  *  6  * @author 孤傲苍狼 7  *  8  */ 9 public class GenericMethod {10     public static void main(String[] args) {11         add(3, 5);12         Number x1 = add(3.5, 5);// Integer类型和Double类型的交集就是Number类,Number类是这两个类的父类,所以可以定义Number类型的变量来接收返回值13         Object x2 = add(3, "abc");// Integer类型和String类型的交集就是Object类,因为Object类是所有类的父类,所以可以定义Object类型的变量来接收返回值14         /**15          * swap(new String[]{"abc","123","xdp"},1,2);的执行结果如下: 16          * abc 123 xdp 17          * abc xdp 12318          * 从结果来看,索引为1的元素和索引为2的元素的确是交换了位置19          */20         swap(new String[] { "abc", "123", "xdp" }, 1, 2);// 调用自定义泛型方法,传入的实际参数必须是引用类型的数组21         // swap(new int[]{1,2,3,4,5},1,3);//只有引用类型才能作为泛型方法的实际参数,这里的int[]数组是属于基本类型,不能作为泛型方法的参数,所以这样会报错22         printArray(new Integer[]{1,2,3});//可以传入Integer类型的数组,因为Integer类型的数组是属于引用类型的23         //printArray(new int[]{10,2,5});不能传入非引用类型的数组作为泛型方法的实际参数24     }25     /**26      * 泛型方法的定义语法: 这里定义的就是一个泛型方法 方法的返回值类型是T,即任意的类型 返回值的具体类型由传入的类型参数来定27      * 28      * @param 
29 * 代表任意的类型30 * @param x31 * @param y32 * @return33 */34 private static
T add(T x, T y) {35 return null;36 }37 /**38 * 定义一个泛型方法来交换数组中指定索引位置的两个元素 这里传入的数组可以是任意类型的数组39 * 传入泛型方法的实际参数类型必须是引用类型的数组,不能是基本类型的数组40 * 41 * @param
42 * @param a43 * @param i44 * @param j45 */46 private static
void swap(T[] a, int i, int j) {47 // 数组中元素位置未交换前的打印结果48 printArray(a);49 T temp = a[i];50 a[i] = a[j];51 a[j] = temp;52 System.out.println();53 // 数组中元素位置交换后的打印结果54 printArray(a);55 }56 /**57 * 定义打印任意引用数组类型的方法58 * 59 * @param
60 * @param array61 */62 private static
void printArray(T[] array) {63 for (T t : array) {64 System.out.print(t + "\t");65 }66 }67 /**68 * 定义有extends限定符,并且具有多个上边界的泛型方法,各个边界使用&符号分隔69 * @param
70 */71 public
void method(){}72 }

  普通方法,构造方法和静态方法都可以使用泛型

七、泛型方法练习题

  1. 编写一个泛型方法,自动将Object类型对象转换为其他类型
  2. 定义一个泛型方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象
  3. 采用自定泛型方法的方式打印出任意参数化类型的集合中的所有内容。
  4. 定义一个泛型方法,把任意参数类型的集合中的数据安全地复制到相应类型的数组中
  5. 定义一个泛型方法,把任意参数类型的一个数组中的数据安全地复制到相应类型的另一个数组中去
1 /** 2      * 1.编写一个泛型方法,自动将Object类型对象转换为其他类型 3      * @param 
4 * @param obj 5 * @return 6 */ 7 private static
T autoConvert(Object obj){ 8 return (T)obj; 9 }10 /**11 * 2.定义一个泛型方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象12 * @param
13 * @param array14 * @param obj15 */16 private static
void fillArray(T[] array,T obj){17 for(int i=0;i
25 * @param collection26 */27 private static
void printCollection(Collection
collection){28 System.out.println(collection.size());29 for(Object obj:collection){30 System.out.println(obj);31 }32 }33 /**34 * 4.定义一个泛型方法,把任意参数类型的集合中的数据安全地复制到相应类型的数组中35 * @param
36 * @param srcCollection37 * @param descArray38 */39 private static
void CollectionCopyToarray(Collection
srcCollection,T[] descArray){40 Iterator
it = srcCollection.iterator();41 int recordElementPostion=0;42 while(it.hasNext()){43 descArray[recordElementPostion]=it.next();44 recordElementPostion++;45 }46 printArray(descArray);47 }48 /**49 * 5.定义一个泛型方法,把任意参数类型的一个数组中的数据安全地复制到相应类型的另一个数组中去50 * @param
51 * @param srcArray52 * @param descArray53 */54 private static
void srcArrayToDescArray(T[] srcArray,T[] descArray){55 for(int i=0;i
void printArray(T[] array) {61 for (T t : array) {62 System.out.print(t + "\t");63 }64 }

八、自定义泛型类

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

1 package cn.itcast.day2; 2 import java.util.Set; 3 import cn.itcast.day1.ReflectField; 4 /** 5  * DAO:Data Access Object(数据访问对象) 6  * 数据访问:CRUD,即增删改查 7  * @author 孤傲苍狼 8  * 此类是用来演示如何定义泛型类 9  * 此泛型类中的
中的E代表实际操作的类型10 * 指明了操作类型E之后,GenericDAO类中定义的CRUD方法就都是针对于指定的类型11 */12 public class GenericDAO
{13 private E field1; //定义泛型类型的成员变量14 public
void add(E x){15 }16 public
E findById(int id){17 return null;18 }19 public void delete(E obj){20 }21 public void delete(int id){22 }23 public void update(E obj){24 }25 //public static void update(E obj){}这样定义会报错,静态方法不允许使用泛型参数26 public static
void update2(E obj){}//这样定义就可以,此时的这个静态方法所针对的类型和GenericDAO
中指定的类型是两个不同的类型27 public Set
findByConditions(String where){28 return null;29 }30 public static void main(String[] args) {31 GenericDAO
dao = new GenericDAO
();//这里指定泛型类的操作类型是ReflectField32 dao.add(new ReflectField(1,3));33 ReflectField rf = dao.findById(1);34 GenericDAO
dao1=null;35 new GenericDAO
();36 }37 }

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

    GenericDAO<String> dao=null;

    new GenericDAO<String>();

注意:

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

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

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

1 package cn.itcast.day2; 2 import java.lang.reflect.Method; 3 import java.lang.reflect.ParameterizedType; 4 import java.lang.reflect.Type; 5 import java.util.Date; 6 import java.util.Vector; 7 /** 8  * 此类是用来演示如何通过反射获得泛型的实际类型参数 9  * Hibernate中的源代码就有这样的写法10  * @author 孤傲苍狼11  * 12  */13 public class UseReflectGetGenericParameter {14     public static void main(String[] args) throws Exception {15         /**16          * 通过这种方式得到的字节码中是没有办法得到泛型类的实际类型参数的,17          * 因为在编译这个泛型类时就已经把这个泛型类的实际参数给去掉了18          * Vector
v = new Vector
();19 * v.getClass();20 */21 Method applyMethod = UseReflectGetGenericParameter.class.getMethod(22 "applyVector", Vector.class);23 //得到泛型类型的参数化类型数组,Type类是Class类的父类24 Type[] types = applyMethod.getGenericParameterTypes();25 /**26 * ParameterizedType这个类是一个参数化类型类,types数组中存储的都是参数化类型的参数,27 * 这里取出第一个数组元素,并强制转换成ParameterizedType类型28 */29 ParameterizedType pType = (ParameterizedType) types[0];30 System.out.println(pType.getRawType()/*得到原始类型,输出的结果为:class java.util.Vector*/);31 System.out.println(pType.getActualTypeArguments()[0]/*获得泛型的实际类型参数,输出的结果为:class java.util.Date*/);32 }33 /**34 * 利用反射可以得到这个方法的参数列表的类型35 * 通过这个变量v是没有办法知道定义它的那个类型的36 * 但是当把这个变量交给一个方法作为参数或者返回值去使用,37 * Method类中提供了一系列方法可以获得方法的参数列表38 * 并且是以泛型的那种形式来获得参数列表39 * @param v40 */41 public static void applyVector(Vector
v) {42 }43 }

转载地址:http://amada.baihongyu.com/

你可能感兴趣的文章
基于Spring Boot2 + Spring Security OAuth2 实现单点登陆(一)
查看>>
跟我一起来用C++写web服务器吧(二)
查看>>
获取图片的旋转角度信息
查看>>
句柄泄漏和Handler的底层机制
查看>>
Refresh Token的使用场景以及如何与JWT交互
查看>>
聊聊jvm的CompressedClassSpace
查看>>
未来几年,BCH超越BTC的路径是什么?
查看>>
import和require的区别
查看>>
一个离开学校三年java架构师
查看>>
页面优化小总结 (图片类型)
查看>>
mysql中sum()与if()联合使用
查看>>
vue-resource安装与应用
查看>>
React编程规范
查看>>
iOS KVC与KVO
查看>>
秋招总结:一篇文章搞定秋招学习规划
查看>>
antd Form组件方法getFieldsValue获取自定义组件的值
查看>>
python爬虫系列(3.2-lxml库的使用)
查看>>
SEO提高网站排名快速见效的方法
查看>>
(十五) 构建springmvc+mybatis+dubbo分布式平台-window安装dubbo管控台
查看>>
Mvp官方示例
查看>>