Java基础加强总结(二)——泛型
一、体验泛型
JDK1.5之前的集合类中存在的问题——可以往集合中加入任意类型的对象,例如下面代码:
package cn.gacl.generic.summary; import java.util.ArrayList; public class GenericTest { public static void main(String[] args) {
/**
* 不使用泛型之前ArrayList容器可以存储任意类型的对象
*/
ArrayList collection1 = new ArrayList();
collection1.add(1);//存储Integer对象
collection1.add(1L);//存储Long对象
collection1.add("xdp");//存储String对象
/**
* 这里会报异常: JAVA.LANG.CLASSCASTEXCEPTION:
* JAVA.LANG.LONG CANNOT BE CAST TO JAVA.LANG.INTEGER
*
*/
int i = (Integer) collection1.get(1);
}
}
JDK1.5之后的集合类希望你在定义集合时,明确表示你要向集合中装哪种类型的数据,无法加入指定类型之外的数据,例如下面的代码:
/**
* 使用泛型限定ArrayList容器只能存储字符串类型的对象
*/
ArrayList<String> collection2 = new ArrayList<String>();
collection2.add("孤傲苍狼");
//collection2.add(1);//报错,因为限制了collection2只能存储String类的对象,不能加入Integer类型的对象
//collection2.add(1L);//报错,因为限制了collection2只能存储String类的对象,不能加入Long类型的对象
//由于已经指明集合中存储的是字符串类型的对象,因此这里不用再强制转型了
String element = collection2.get(0);
泛型是提供给Javac编译器看的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带参数类型说明的集合时会去去除掉“类型”信息,使程序运行不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样,由于编译生成的字节码会去掉泛型的类型信息,因此只要能跳过编译器,就可以往某个泛型集合中加入其他类型的数据。
例如下面的代码就演示了"使用反射得到集合,然后调用add方法往原本只能存储Integer对象的集合中存储一个String类型的对象"
ArrayList<Integer> collection3 = new ArrayList<Integer>();
//对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样
System.out.println(collection3.getClass());//结果为:java.util.ArrayList
System.out.println(collection3.getClass() == collection2.getClass());//结果为true
//使用反射得到集合,然后调用add方法往原本只能存储Integer对象的集合中存储一个String类型的对象
collection3.getClass().getMethod("add", Object.class).invoke(collection3, "abc");
System.out.println(collection3.get(0));//输出的结果为:abc,这证明字符串对象确实是存储到了原本只能存储Integer对象的集合中
备注:
- 泛型是JDK1.5的所有新特性中最难深入掌握的部分,没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储进同一个集合中,使用泛型集合,可以将一个集合中的元素限定为一个特定类型,这样集合中就只能存储同一类型的对象,这样更安全;并且当从集合中获取一个对象时,编译器也知道这个对象的类型,不需要对对象进行强制类型转换,这样更方便。
- 在JDK1.5之后,你还可以按原来的方式将各种不同类型的数据放到同一个集合中,但是编译时会报一个unChecked警告
- 泛型中的类型参数严格说明集合中装载的数据类型是什么和可以加入什么类型的数据,记住: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;//参数化类型的引用可以指向原始类型的对象
三、泛型中的?通配符
问题:定义一个方法,该方法可以打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
错误的定义:
/**
* Collection<Object>中的Object只是说明Collection<Object>实例对象中的方法接收的参数是Object
* Collection<Object>是一种具体的类型,new HashSet<Date>也是一种具体的类型,两者没有兼容性问题
* @param collection
*/
public static void printCollection(Collection<Object> collection){
for(Object obj:collection){
System.out.println(obj);
}
collection.add("abc");//没错
collection=new HashSet<Date>();//会报告错误
}
正确的定义:
/**这里的Collection<?>中的?表示可以传人任意的类型参数
* Collection<?> cols可以匹配任意参数化的类型,但是到底匹配的是什么类型,只有以后才知道
* 所以 cols=new ArrayList<Integer>和cols = new ArrayList<String>都可以
* 但是cols.add("abc")或cols.add(new Date())都不行
*/
public static void printCollection(Collection<?> collection){
for(Object obj:collection){
System.out.println(obj);
}
//collection.add("abc");//报错,因为collection不知道未来匹配的一定是String类型
collection.size();//不报错,此方法与参数类型没有关系
collection=new HashSet<Date>();//这是可以的
}
总结:使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数无关的方法,不能调用与参数有关的方法
四、泛型中的?通配符的扩展
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>();
五、泛型的综合应用
package cn.itcast.day2;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* 此类是用来演示泛型的应用的
*
* @author 孤傲苍狼
*
*/
public class GenericCase {
public static void main(String[] args) {
HashMap<String, Integer> maps = new HashMap<String, Integer>();
maps.put("lhm", 35);
maps.put("flx", 33);
/**
* 变量的命名技巧:如果以后不知道一个变量该如何命名,就可以以方法名的形式来命名,
* 如果要定义变量接收返回值,如果此时不知道如何定义变量名时,就直接定义成returnValue
*/
Set<Map.Entry<String, Integer>> entrySet = maps.entrySet();// 这里的变量名直接以方法名的形式定义
// 使用增强的for循环迭代Map容器中的key和value
//这里的Entry是Map类的一个内部类,map类中存储的key和value都是封装在这个Entry内部类中的
//这个Entry内部类提供了getKey方法取出键,getValue方法取出值
for (Map.Entry<String, Integer> entry : entrySet) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
}
在JSP页面中也经常要使用迭代标签<c:forEach>对Set或Map集合进行迭代:
<c:forEach items=”${map}” var=”entry”>
${entry.key}:${entry.value}
</c:forEach>
六、自定义泛型方法
package cn.itcast.day2;
import java.io.Serializable;
/**
* 此类是用来演示如何定义和使用泛型方法的
*
* @author 孤傲苍狼
*
*/
public class GenericMethod {
public static void main(String[] args) {
add(3, 5);
Number x1 = add(3.5, 5);// Integer类型和Double类型的交集就是Number类,Number类是这两个类的父类,所以可以定义Number类型的变量来接收返回值
Object x2 = add(3, "abc");// Integer类型和String类型的交集就是Object类,因为Object类是所有类的父类,所以可以定义Object类型的变量来接收返回值
/**
* swap(new String[]{"abc","123","xdp"},1,2);的执行结果如下:
* abc 123 xdp
* abc xdp 123
* 从结果来看,索引为1的元素和索引为2的元素的确是交换了位置
*/
swap(new String[] { "abc", "123", "xdp" }, 1, 2);// 调用自定义泛型方法,传入的实际参数必须是引用类型的数组
// swap(new int[]{1,2,3,4,5},1,3);//只有引用类型才能作为泛型方法的实际参数,这里的int[]数组是属于基本类型,不能作为泛型方法的参数,所以这样会报错
printArray(new Integer[]{1,2,3});//可以传入Integer类型的数组,因为Integer类型的数组是属于引用类型的
//printArray(new int[]{10,2,5});不能传入非引用类型的数组作为泛型方法的实际参数
}
/**
* 泛型方法的定义语法: 这里定义的就是一个泛型方法 方法的返回值类型是T,即任意的类型 返回值的具体类型由传入的类型参数来定
*
* @param <T>
* 代表任意的类型
* @param x
* @param y
* @return
*/
private static <T> T add(T x, T y) {
return null;
}
/**
* 定义一个泛型方法来交换数组中指定索引位置的两个元素 这里传入的数组可以是任意类型的数组
* 传入泛型方法的实际参数类型必须是引用类型的数组,不能是基本类型的数组
*
* @param <T>
* @param a
* @param i
* @param j
*/
private static <T> void swap(T[] a, int i, int j) {
// 数组中元素位置未交换前的打印结果
printArray(a);
T temp = a[i];
a[i] = a[j];
a[j] = temp;
System.out.println();
// 数组中元素位置交换后的打印结果
printArray(a);
}
/**
* 定义打印任意引用数组类型的方法
*
* @param <T>
* @param array
*/
private static <T> void printArray(T[] array) {
for (T t : array) {
System.out.print(t + "\t");
}
}
/**
* 定义有extends限定符,并且具有多个上边界的泛型方法,各个边界使用&符号分隔
* @param <T>
*/
public <T extends Serializable & Cloneable> void method(){}
}
普通方法,构造方法和静态方法都可以使用泛型
七、泛型方法练习题
- 编写一个泛型方法,自动将Object类型对象转换为其他类型
- 定义一个泛型方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象
- 采用自定泛型方法的方式打印出任意参数化类型的集合中的所有内容。
- 定义一个泛型方法,把任意参数类型的集合中的数据安全地复制到相应类型的数组中
- 定义一个泛型方法,把任意参数类型的一个数组中的数据安全地复制到相应类型的另一个数组中去
/**
* 1.编写一个泛型方法,自动将Object类型对象转换为其他类型
* @param <T>
* @param obj
* @return
*/
private static <T> T autoConvert(Object obj){
return (T)obj;
}
/**
* 2.定义一个泛型方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象
* @param <T>
* @param array
* @param obj
*/
private static <T> void fillArray(T[] array,T obj){
for(int i=0;i<array.length;i++){
array[i]=obj;
}
printArray(array);
}
/**
* 3.采用自定泛型方法的方式打印出任意参数化类型的集合中的所有内容
* @param <T>
* @param collection
*/
private static <T> void printCollection(Collection<T> collection){
System.out.println(collection.size());
for(Object obj:collection){
System.out.println(obj);
}
}
/**
* 4.定义一个泛型方法,把任意参数类型的集合中的数据安全地复制到相应类型的数组中
* @param <T>
* @param srcCollection
* @param descArray
*/
private static <T> void CollectionCopyToarray(Collection<T> srcCollection,T[] descArray){
Iterator<T> it = srcCollection.iterator();
int recordElementPostion=0;
while(it.hasNext()){
descArray[recordElementPostion]=it.next();
recordElementPostion++;
}
printArray(descArray);
}
/**
* 5.定义一个泛型方法,把任意参数类型的一个数组中的数据安全地复制到相应类型的另一个数组中去
* @param <T>
* @param srcArray
* @param descArray
*/
private static <T> void srcArrayToDescArray(T[] srcArray,T[] descArray){
for(int i=0;i<srcArray.length;i++){
descArray[i]=srcArray[i];
}
printArray(descArray);
}
private static <T> void printArray(T[] array) {
for (T t : array) {
System.out.print(t + "\t");
}
}
八、自定义泛型类
如果类的实例对象中有多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时就要采用泛型类型的方式定义,也就是类级别的泛型,语法格式如下:
package cn.itcast.day2;
import java.util.Set;
import cn.itcast.day1.ReflectField;
/**
* DAO:Data Access Object(数据访问对象)
* 数据访问:CRUD,即增删改查
* @author 孤傲苍狼
* 此类是用来演示如何定义泛型类
* 此泛型类中的<E>中的E代表实际操作的类型
* 指明了操作类型E之后,GenericDAO类中定义的CRUD方法就都是针对于指定的类型
*/
public class GenericDAO<E> {
private E field1; //定义泛型类型的成员变量
public <E> void add(E x){
}
public <E> E findById(int id){
return null;
}
public void delete(E obj){
}
public void delete(int id){
}
public void update(E obj){
}
//public static void update(E obj){}这样定义会报错,静态方法不允许使用泛型参数
public static<E> void update2(E obj){}//这样定义就可以,此时的这个静态方法所针对的类型和GenericDAO<E>中指定的类型是两个不同的类型
public Set<E> findByConditions(String where){
return null;
}
public static void main(String[] args) {
GenericDAO<ReflectField> dao = new GenericDAO<ReflectField>();//这里指定泛型类的操作类型是ReflectField
dao.add(new ReflectField(1,3));
ReflectField rf = dao.findById(1);
GenericDAO<String> dao1=null;
new GenericDAO<String>();
}
}
类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下的两种方式都可以:
GenericDAO<String> dao=null;
new GenericDAO<String>();
注意:
1.在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型
2.当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用,因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
九、通过反射获得泛型的实际类型参数
package cn.itcast.day2;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.Vector;
/**
* 此类是用来演示如何通过反射获得泛型的实际类型参数
* Hibernate中的源代码就有这样的写法
* @author 孤傲苍狼
*
*/
public class UseReflectGetGenericParameter {
public static void main(String[] args) throws Exception {
/**
* 通过这种方式得到的字节码中是没有办法得到泛型类的实际类型参数的,
* 因为在编译这个泛型类时就已经把这个泛型类的实际参数给去掉了
* Vector<Date> v = new Vector<Date>();
* v.getClass();
*/
Method applyMethod = UseReflectGetGenericParameter.class.getMethod(
"applyVector", Vector.class);
//得到泛型类型的参数化类型数组,Type类是Class类的父类
Type[] types = applyMethod.getGenericParameterTypes();
/**
* ParameterizedType这个类是一个参数化类型类,types数组中存储的都是参数化类型的参数,
* 这里取出第一个数组元素,并强制转换成ParameterizedType类型
*/
ParameterizedType pType = (ParameterizedType) types[0];
System.out.println(pType.getRawType()/*得到原始类型,输出的结果为:class java.util.Vector*/);
System.out.println(pType.getActualTypeArguments()[0]/*获得泛型的实际类型参数,输出的结果为:class java.util.Date*/);
}
/**
* 利用反射可以得到这个方法的参数列表的类型
* 通过这个变量v是没有办法知道定义它的那个类型的
* 但是当把这个变量交给一个方法作为参数或者返回值去使用,
* Method类中提供了一系列方法可以获得方法的参数列表
* 并且是以泛型的那种形式来获得参数列表
* @param v
*/
public static void applyVector(Vector<Date> v) {
}
}
Java基础加强总结(二)——泛型的更多相关文章
- Java基础语法<十二> 泛型程序设计
1 意义 泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用. 常见应用 : ArrayList 2 K T V E ? object等的含义 类型变量使用大写形式 E – Element ( ...
- Java基础之多态和泛型浅析
Java基础之多态和泛型浅析 一.前言: 楼主看了许多资料后,算是对多态和泛型有了一些浅显的理解,这里做一简单总结 二.什么是多态? 多态(Polymorphism)按字面的意思就是“多种状态”.在面 ...
- java基础语法(二)--单列模式
java基础语法(二)--单列模式 /** * 功能:单列模式 * @author Administrator * */ public class SingletonTest { public sta ...
- java基础解析系列(二)---Integer
java基础解析系列(二)---Integer 前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现bug的时候,才能处理更加从容. 目录 java基础解析 ...
- Java基础拾遗(二)
(尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76358523冷血之心的博客) 马上就要秋招了,新的一轮笔试面试马上 ...
- java基础知识点补充---二维数组
#java基础知识点补充---二维数组 首先定义一个二维数组 int[][] ns={ {1,2,3,4}, {5,6,7,8}, {9,10,11,12}, {13,14,15,16} }; 实现遍 ...
- Java基础反射(二)
原文地址http://blog.csdn.net/sinat_38259539/article/details/71799078 反射是框架设计的灵魂 (使用的前提条件:必须先得到代表的字节码的Cla ...
- JAVA基础-集合(二)
一.Map整体结构体系 Map是集合的另一大派系,与Collection派系不同的是Map集合是以键值对儿的形式存储在集合的.两个键为映射关系,其中第一个键为主键(主键是唯一的不可重复),第二个键为v ...
- Java基础学习笔记二十八 管家婆综合项目
本项目为JAVA基础综合项目,主要包括: 熟练View层.Service层.Dao层之间的方法相互调用操作.熟练dbutils操作数据库表完成增删改查. 项目功能分析 查询账务 多条件组合查询账务 添 ...
随机推荐
- php 的swoole 和websocket 连接wss
1. 下载证书 $serv = new swoole_server('0.0.0.0', 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); $s ...
- 洛谷P2296寻找道路
传送门啦 题目中有一个条件是路径上的所有点的出边所指向的点都直接或间接与终点连通. 所以我们要先判断能否走这一个点, $ bfs $ 类似 $ spfa $ 的一个判断,打上标记. 在这我反向建图,最 ...
- springMVC源码分析--HttpMessageConverter写write操作(三)
上一篇博客springMVC源码分析--HttpMessageConverter参数read操作中我们已经简单介绍了参数值转换的read操作,接下来我们介绍一下返回值的处理操作.同样返回值的操作操作也 ...
- c语言双向循环链表
双向循环链表,先来说说双向链表,双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱.所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继 ...
- 【TensorFlow】一文弄懂CNN中的padding参数
在深度学习的图像识别领域中,我们经常使用卷积神经网络CNN来对图像进行特征提取,当我们使用TensorFlow搭建自己的CNN时,一般会使用TensorFlow中的卷积函数和池化函数来对图像进行卷积和 ...
- 学习python绘图
学会python画图 # 使用清华的pip源进行安装sklearn # pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -U sciki ...
- UML中的6大关系(关联、依赖、聚合、组合、泛化、实现)
UML定义的关系主要有六种:依赖.类属.关联.实现.聚合和组合.这些类间关系的理解和使用是掌握和应用UML的关键,而也就是这几种关系,往往会让初学者迷惑.这里给出这六种主要UML关系的说明和类图描述, ...
- Android 中.aar文件生成方法与用法
https://i.cnblogs.com/EditPosts.aspx?opt=1 无论是用Eclipse还是用Android Studio做android开发,都会接触到jar包,全称应该是:Ja ...
- mongo 运维管理学习
1 如何在线修改chunk大小 https://docs.mongodb.com/manual/tutorial/modify-chunk-size-in-sharded-cluster/ 2 chu ...
- git status中文文件名编码问题解决
在默认设置下,中文文件名在工作区状态输出,中文名不能正确显示,而是显示为八进制的字符编码. 通过将git配置变量 core.quotepath 设置为false,就可以解决中文文件名称在这些Git命令 ...