Java中的泛型,本质上来说,就是是参数化类型,就是说所操作的数据类型被指定为一个参数,而不是确定的某种类型。这种数据类型可以用在类、接口和方法创建中。即泛型类、泛型接口、泛型方法。这样说可能不够生动,来举些例子说明一下。

例子一


我们通过 例子一 来简单看看泛型的应用场景。下面的情况中,没有使用泛型:

public class FanXingTest {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
Integer integer1 = 1;
arrayList.add(integer1);
//你记得这个元素的类型,那么没问题,通过index取出来
Integer integer2 =(Integer) arrayList.get(0);
System.out.println(integer2);
//假如你记错了arraylist里面存的类型了,这时编译器也不会报错,但是运行时会报错
String string =(String) arrayList.get(0);
System.out.println(string);
}
}

ArrayList里存放的是Object类。这样的好处是:通过对类型Object的引用来实现可以存放任意类(Java的多态机制),但也带来了缺点:需要进行强制类型转换。

可以看到,在没有使用泛型的情况下,你若记得 arrayList 里面存了什么类型的东西,那么最好不过了,可以直接通过index来取出来,并进行类型转换即可。

而假如你在不知道的情况下强制转换写错,编译器也不会提示错误,在运行时才会发现,会报一个错: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String 。实际情况中程序员不可能对每个元素都记得其类型,所以这样有明显的不合理性。

泛型的引入可以解决上述问题,先来看看针对上述情景使用泛型的情况:

public class FanXingTest {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<Integer>();
Integer integer1 = 1;
arrayList.add(integer1);
//直接取出来,无需强制类型转换
Integer integer2 = arrayList.get(0);
System.out.println(integer2);
//编译器会检查出错误,根本不允许你把类型搞混了,下面两种情况编译器都直接提示不能类型转换
String string1 =(String) arrayList.get(0);
String string2 = arrayList.get(0);
}
}

上面的代码中我们使用了泛型,就是在创建 arrayList 的时候,就限定了里面存放的只能是 Integer 类型的数据,那么编译时就可以检查类型安全,所有的强制类型转换是自动和隐式的,提高代码重用率。我们在取出数据的时候,就无需进行强制类型转换,而且你把类型搞错,编译器会提示错误。

例子二


那么泛型是如何实现的呢?自己如何创建泛型类?我们通过 例子二 来看一看。先自己来创建一个泛型类,如下所示:

//自己新建一个泛型类:MyFan
class MyFan<T>{
private T t1;
//构造函数
public MyFan(T t1){
this.t1=t1;
}
//测试泛型的操作数据类型
public void showInfo(){
System.out.println("所操作数据类型是:"+t1.getClass().getName());
//通过反射机制,可以知道T的各种成员变量和方法的信息
Method m[]=t1.getClass().getDeclaredMethods();
for (int i = 0; i < m.length; i++) {
System.out.println(m[i].getName());
}
}
}

自己创建泛型类的时候,先假设这样一个处境:我希望我的这个类可以处理多种类型的数据。这就需要把类型"参数化",也就是当成一个"参数"传进来。所以定义类的时候,在类名的后面跟了一个<T>: class MyFan<T> ,这个T只是一个象征意义的符号,可以是任意符号。T 就是个类型"参数",我们之后可以传任意类型的类进来。上面的 showInfo() 方法中,我们可以利用反射机制,来得到传进来的这个数据类型是什么,也可以得到这个类里面有些个什么方法。接下来,我们利用我们自己创建的泛型类 MyFan<T> 来做个试验:

public class FanXingTest {
public static void main(String[] args) {
// TODO Auto-generated method stub MyFan<String> ft1=new MyFan<String>("黄黄");
ft1.showInfo();
MyFan<Integer> ft2=new MyFan<Integer>(1);
ft2.showInfo(); //输出两个实例对象的类型,结果是一样的
System.out.println(ft1.getClass());
System.out.println(ft2.getClass()); }
}

通过创建我们自己的泛型类的实例, ft1 里面的 t1 变量指向一个String类实例, ft2 里面的 t1 变量指向一个Integer类实例,那么上述程序的输出如下:

所操作数据类型是:java.lang.String...String的各个成员方法略去
所操作数据类型是:java.lang.Integer...Integer的各个成员方法略去
class MyFan
class MyFan

最后两行的输出,我们是想来通过 ft1/ft2.getClass() 来看看创建的这两个实例的类型会不会不一样,结果可以看到,两个都是属于 MyFan类的实例变量,并没有因为传入类型的不同而引起不同。

例子三


现在应该知道了泛型是如何运作的,我们在自己定义泛型类或者泛型接口的时候,<T>都是跟在类名的后面: class MyFan<T>{...} ,然后实例化的时候<T>也跟在类名的后边即可: MyFan<T> fan1 = new MyFan<T>()。

有些时候我们还会遇到更为复杂的情况,这几天我在工具类Collections里面,看到了sort方法,如下:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}

我们需要知道extends后面跟的类型表示泛型的上限(含自己),super表示的是泛型的下限(含自己)。如<T extends 类/接口>,表示T必须是该指定类/接口的子类(实现了某接口或继承自某类,都算"子类")。

我们来看 <T extends Comparable<? super T>> ,首先规定了T必须是 Comparable<? super T> 的一个子类,也就是说,T 必须是实现了 Comparable<? super T> 这个接口的。然后<? super T>表示Comparable<>中的类型下限为T(就是至少得是T的父类,包括T)!我们可以通过 GregorianCalendar 类和 Calendar 类来加深理解。先看下两个类的定义:

//Calendar类实现了Comparable<Calendar>接口
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>{
...
@Override
public int compareTo(Calendar anotherCalendar) {
return compareTo(getMillisOf(anotherCalendar));
}
}
//GregorianCalendar类继承自Calendar类
public class GregorianCalendar extends Calendar{...}

那我们现在假设有个ArrayList,调用上面的sort方法:

ArrayList<GregorianCalendar> arrayList = new ArrayList<GregorianCalendar>();
Collections.sort(arrayList);

此时不会报错,因为相当于 <GregorianCalendar extends Comparable<Calendar>> 是成立的,我们结合两个类的定义可以知道:Calendar为GregorianCalendar 的父类,并且Calendar实现了Comparable<Calendar>,于是GregorianCalendar 也实现了Comparable<Calendar>接口。所以泛型的限制范围 <T extends Comparable<? super T>> 是满足的。接下来我们自己定义一个sort函数:

public static <T extends Comparable<T>> void sort(ArrayList<T> list) {
list.sort(null);
}

此时调用我们的sort函数 sort(new ArrayList<GregorianCalendar>()); ,编译器就会报错,因为GregorianCalendar类只是实现了Comparable<Calendar>接口,并没有实现Comparable<GregorianCalendar>接口。于是传入的参数不满足泛型的限制范围,所以会报错

自己的一些思考


先看看下面的Collections类里面的两个sort方法

public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}

注意到上面除了方法参数(比如List<T> list)之外,都是在返回类型(void)之前对方法参数类型进行了限定(比如static <T extends Comparable<? super T>> void)。

其实泛型就是”可有可无”,它的存在就是限制了你将要使用的参数类是什么范围内,extends/super分别是泛型的上下界(含)。就比如上面第一个sort,限定了<T extends Comparable<? super T>>,第二个就是没限定,任意的<T>;但是还有参数这块,第一个的参数就是之前限定好的参数T即可;第二个的参数呢,在Comparator<? super T>这里限定了一个下届,得是T的父类。

再往下说,为什么是<T extends Comparable<? super T>>而不是<T extends Comparable<T>>呢。这其实考虑到了子类若是继承自父类,而父类已经实现了Comparable接口,子类可以拿来用,所以就有<T extends Comparable<? super T>>的效果,举个例子就是上面的GregorianCalendar类和Calendar类,等效于子类GregorianCalendar也实现了Comparable接口。这时候若是<T extends Comparable<T>>就不合适了。

其实一般自己实现的Comparable接口的话,都是 class MyTest implements Comparable<MyTest>这种,估计也不会class MyTest implements Comparable<MyTest的父类>,上面的<T extends Comparable<? super T>>只是考虑到了比如ManmanTest继承自MyTest,那么ManmanTest也就实现了Comparable<MyTest>接口的情况。

Java中泛型的理解的更多相关文章

  1. Java中泛型 类型擦除

    转自:Java中泛型是类型擦除的 Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类 ...

  2. java中ThrealLocal的理解

    目录 java中threadlocal的理解 一.threadlocal的生命周期和ThreadLocalMap的生命周期 二.ThreadLocal的作用 三.threadlocal示例 四.Inh ...

  3. java中threadlocal的理解

    [TOC] #java中threadlocal的理解##一.threadlocal的生命周期和ThreadLocalMap的生命周期可以吧TreadLocal看做是一个map来使用,只不过这个map是 ...

  4. 谈谈我对Java中CallBack的理解

    谈谈我对Java中CallBack的理解 http://www.cnblogs.com/codingmyworld/archive/2011/07/22/2113514.html CallBack是回 ...

  5. Java中泛型使用

    Java中泛型使用 泛型作用: 泛型:集合类添加对象不用强转 反射机制:将泛型固定的类的所有方法和成员全部显示出来 核心代码: ArrayList<Ls> ff=new ArrayList ...

  6. 沉淀再出发:关于java中的AQS理解

    沉淀再出发:关于java中的AQS理解 一.前言 在java中有很多锁结构都继承自AQS(AbstractQueuedSynchronizer)这个抽象类如果我们仔细了解可以发现AQS的作用是非常大的 ...

  7. Java中hashcode的理解

    Java中hashcode的理解 原文链接http://blog.csdn.net/chinayuan/article/details/3345559 怎样理解hashCode的作用: 以 java. ...

  8. Java中泛型在集合框架中的应用

    泛型是Java中的一个重要概念,上一篇文章我们说过,当元素存入集合时,集合会将元素转换为Object类型存储,当取出时也是按照Object取出的,所以用get方法取出时,我们会进行强制类型转换,并且通 ...

  9. Java 中泛型的全面解析(转)

    Java泛型(generics) 是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter).声明的类型参数在使用时用具体的类型来替换.泛型最主要的应用是在J ...

随机推荐

  1. cf 323A A. Black-and-White Cube 立体构造 不知道为什么当k为奇数时构造不出来 挺有趣的题目吧

    A. Black-and-White Cube time limit per test 1 second memory limit per test 256 megabytes input stand ...

  2. jvm内存JVM学习笔记-引用(Reference)机制

    在写这篇文章之前,xxx已经写过了几篇关于改jvm内存主题的文章,想要了解的朋友可以去翻一下之前的文章 如果你还不了解JVM的基本概念和内存划分,请阅读JVM学习笔记-基础知识和JVM学习笔记-内存处 ...

  3. 方法控件[置顶] Asp.net总结(一)

    本文朋友在青岛逛街的时候突然想到的...这两天就有想写几篇关于方法控件的博客,所以回家到之后就奋笔疾书的写出来发表了 纵观 Asp.net的学习只会之路,可以发现 Asp.net可以分为以下几大块 控 ...

  4. 类classthe Meta-Object Compiler (moc)

    本文是一篇关于类class的帖子 the Meta-Object Compiler (moc) 元对象编译器是处理Qt的C++扩展的程序. moc工具读取C++头文件,如果它找到一个或者多个类声明包含 ...

  5. 怎么在Windows下安装Linux虚拟机

    前提:①电脑有安装好VMware Workstation Pro虚拟机,没有的话点此下载并安装.②下载好CentOS-7-x86_64-DVD-1503-01镜像文件,没有点此下载密码:lomg. 1 ...

  6. import,include

    1. #import导入头文件,即:导入头文件中的内容到当前类 2. #import ""导⼊自定义类,#import <>导入类库中的头文件. 3.功能类似C语言中的 ...

  7. 驱动07.USB驱动程序

    1 了解USB识别的过程 eg:在Windows系统下的一个现象:把手机的USB设备接到PC 1. 右下角弹出"发现android phone" 2. 跳出一个对话框,提示你安装驱 ...

  8. Jenkins配置和使用

    之前整理了Jenkins的下载和安装过程,有需要的可以参考我的博客,地址:   http://www.cnblogs.com/luchangyou/p/5981884.html 接下来整理一下Jenk ...

  9. 第一百二十二节,JavaScript表单处理

    JavaScript表单处理 学习要点: 1.表单介绍 2.文本框脚本 3.选择框脚本 为了分担服务器处理表单的压力,JavaScript提供了一些解决方案,从而大大打破了处处依赖服务器的局面. 一. ...

  10. db2 常用配置

    db2set配置: db2set DB2_ENABLE_LDAP=NO db2set DB2_ALTERNATE_GROUP_LOOKUP=GETGROUPLIST db2set DB2_RESTOR ...