一、体验泛型

  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对象的集合中

备注:

  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;//参数化类型的引用可以指向原始类型的对象

三、泛型中的?通配符

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

错误的定义:

     /**
* 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(){}
}

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

七、泛型方法练习题

  1. 编写一个泛型方法,自动将Object类型对象转换为其他类型
  2. 定义一个泛型方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象
  3. 采用自定泛型方法的方式打印出任意参数化类型的集合中的所有内容。
  4. 定义一个泛型方法,把任意参数类型的集合中的数据安全地复制到相应类型的数组中
  5. 定义一个泛型方法,把任意参数类型的一个数组中的数据安全地复制到相应类型的另一个数组中去
 /**
* 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基础加强总结(二)——泛型的更多相关文章

  1. Java基础语法<十二> 泛型程序设计

    1 意义 泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用. 常见应用 : ArrayList 2 K T V E ? object等的含义 类型变量使用大写形式 E – Element ( ...

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

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

  3. java基础语法(二)--单列模式

    java基础语法(二)--单列模式 /** * 功能:单列模式 * @author Administrator * */ public class SingletonTest { public sta ...

  4. java基础解析系列(二)---Integer

    java基础解析系列(二)---Integer 前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现bug的时候,才能处理更加从容. 目录 java基础解析 ...

  5. Java基础拾遗(二)

    (尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/76358523冷血之心的博客) 马上就要秋招了,新的一轮笔试面试马上 ...

  6. java基础知识点补充---二维数组

    #java基础知识点补充---二维数组 首先定义一个二维数组 int[][] ns={ {1,2,3,4}, {5,6,7,8}, {9,10,11,12}, {13,14,15,16} }; 实现遍 ...

  7. Java基础反射(二)

    原文地址http://blog.csdn.net/sinat_38259539/article/details/71799078 反射是框架设计的灵魂 (使用的前提条件:必须先得到代表的字节码的Cla ...

  8. JAVA基础-集合(二)

    一.Map整体结构体系 Map是集合的另一大派系,与Collection派系不同的是Map集合是以键值对儿的形式存储在集合的.两个键为映射关系,其中第一个键为主键(主键是唯一的不可重复),第二个键为v ...

  9. Java基础学习笔记二十八 管家婆综合项目

    本项目为JAVA基础综合项目,主要包括: 熟练View层.Service层.Dao层之间的方法相互调用操作.熟练dbutils操作数据库表完成增删改查. 项目功能分析 查询账务 多条件组合查询账务 添 ...

随机推荐

  1. 机器学习 Python实践-K近邻算法

    机器学习K近邻算法的实现主要是参考<机器学习实战>这本书. 一.K近邻(KNN)算法 K最近邻(k-Nearest Neighbour,KNN)分类算法,理解的思路是:如果一个样本在特征空 ...

  2. $()与document.getElementById

    $('#a')是返回一个jquery对象 $('#a')[0]是一个element对象 document.getElementById('a') return 一个element对象

  3. shell 中>/dev/null 2>&1含义

    shell中可能经常能看到:>/dev/null 2>&1 命令的结果可以通过%>的形式来定义输出 分解这个组合:“>/dev/null 2>&1” 为五 ...

  4. 破损的键盘(UVa 11988)

    s[] 数组用来保存原有的字符序列 nex[] 数组记录打印的下标顺序 C++11 代码如下: #include<iostream> #include<cstring> usi ...

  5. c++模板与泛型编程基础

    (1)定义函数模板(function template) 函数模板是一个独立于类型的函数,可以产生函数的特定类型版本. // implement strcmp-like generic compare ...

  6. 【HackerRank】How Many Substrings?

    https://www.hackerrank.com/challenges/how-many-substrings/problem 题解 似乎是被毒瘤澜澜放弃做T3的一道题(因为ASDFZ有很多人做过 ...

  7. 【LOJ】#2292. 「THUSC 2016」成绩单

    题解 神仙dp啊><(也有可能是我菜) 我们发现,想要拿一段区间的话,只和这个区间的最大值和最小值有关系,那么我们考虑,如果一个区间[l,r]我们拿走了一些数后,使它的最小值是a,最大值是 ...

  8. python 字符串截断

    >>> s = '%20and%201=2%20union%20select%201,group_concat%28table_name%29,3,4,5,6,7,8,9,10,11 ...

  9. Action(8):Error -27728:Step download timeout(120 seconds)has expired when downloading

    Action(8):Error -27728:Step download timeout(120 seconds)has expired when downloading   出现如下图所示对话框上的 ...

  10. java.lang.NoClassDefFoundError: javax/persistence/EntityListeners

    在使用 Hibernate 进行数据库操作的时候,在启动 Tomcat 服务器后,Console 控制台可能会打印出这样的异常:java.lang.NoClassDefFoundError: java ...