为什么需要泛型?

 public class GenericTest {

     public static void main(String[] args) {
List list = new ArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100); for (int i = 0; i < list.size(); i++) {
String name = (String) list.get(i); //
System.out.println("name:" + name);
}
}
}

  定义一个List类型的集合,先增加2个String类型的值,再增加1个Integer类型的值。这是完全允许的,因为此时list默认的元素类型为Object类型。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值,出现上面1中的错误。编译时正常,而运行时出现java.lang.ClassCastException。因此,此类错误在编码过程中不易被发现。

  增加一个对象到集合中,集合不会记住此对象的类型。从集合中取出此对象时,该对象的编译时类型(表面类型)是Object类型,但其运行时类型(实际类型)是本身类型。因此,上面1处取出集合元素时需要人为地强制类型转换到具体的目标类型,容易出现java.lang.ClassCastException。

  使用泛型达到这一目的——使集合能够记住集合内元素类型,只要编译时不出现问题,运行时就不会出现java.lang.ClassCastException。

  什么是泛型?

  泛型指参数化类型。将原来的具体类型参数化,类似于方法中的形参,类型定义成了参数形式即类型形参,然后在使用时传入具体的类型即类型实参。

  使用泛型来修改上面的代码:

 public class GenericTest {

     public static void main(String[] args) {
/*
List list = new ArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100);
*/ List<String> list = new ArrayList<String>();
list.add("qqyumidi");
list.add("corn");
//list.add(100); // 1 提示编译错误 for (int i = 0; i < list.size(); i++) {
String name = list.get(i); //
System.out.println("name:" + name);
}
}
}

  采用泛型写法后,在上面1处增加1个Integer对象时会出现编译错误。List<String>中String是类型实参,直接限定了集合中只能含有String类型的元素,从而上面2处无需进行强制类型转换。也就是说,集合记住了元素的类型,编译器确认了它是String类型。

  List接口的定义:

 public interface List<E> extends Collection<E> {

     int size();

     boolean isEmpty();

     boolean contains(Object o);

     Iterator<E> iterator();

     Object[] toArray();

     <T> T[] toArray(T[] a);

     boolean add(E e);

     boolean remove(Object o);

     boolean containsAll(Collection<?> c);

     boolean addAll(Collection<? extends E> c);

     boolean addAll(int index, Collection<? extends E> c);

     boolean removeAll(Collection<?> c);

     boolean retainAll(Collection<?> c);

     void clear();

     boolean equals(Object o);

     int hashCode();

     E get(int index);

     E set(int index, E element);

     void add(int index, E element);

     E remove(int index);

     int indexOf(Object o);

     int lastIndexOf(Object o);

     ListIterator<E> listIterator();

     ListIterator<E> listIterator(int index);

     List<E> subList(int fromIndex, int toIndex);
}

  List接口采用了泛型定义,<E>中的E表示类型形参,用于接收具体的类型实参。出现E的地方均表示接收外部相同的类型实参。

  作为List接口的实现类,ArrayList的定义形式:

 public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable { public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
} //...省略掉其他具体的定义过程 }

  

  自定义泛型接口、泛型类和泛型方法

  泛型类和泛型方法定义实例:

 public class GenericTest {

     public static void main(String[] args) {

         Box<String> name = new Box<String>("corn");
System.out.println("name:" + name.getData());
} } class Box<T> { private T data; public Box() { } public Box(T data) {
this.data = data;
} public T getData() {
return data;
} }

  常见的如T、E、K、V等形式的参数通常表示类型形参,接收外部传入的类型实参。<T>表示泛型方法,T是方法的返回类型。

  对于传入的不同类型实参,相应生成的对象实例的类型是否一样?

 public class GenericTest {

     public static void main(String[] args) {

         Box<String> name = new Box<String>("corn");
Box<Integer> age = new Box<Integer>(712); System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box
System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box
System.out.println(name.getClass() == age.getClass()); // true } }

  在使用泛型类时,虽然传入了不同的类型实参,但没有生成不同的类型。可以传入不同类型实参的泛型类在内存上只有一个,即原来的基本类型(本实例中为Box)。因为泛型只是作用于编译阶段,通过编译的class文件是不包含任何泛型信息的。

  类型通配符

  根据上面的实例,Box<Number>和Box<Integer>实际上都是Box类型。Box<Number>和Box<Integer>是否可以看成具有父子关系的泛型类型呢?

 public class GenericTest {

     public static void main(String[] args) {

         Box<Number> name = new Box<Number>(99);
Box<Integer> age = new Box<Integer>(712); getData(name); //The method getData(Box<Number>) in the type GenericTest is
//not applicable for the arguments (Box<Integer>)
getData(age); // } public static void getData(Box<Number> data){
System.out.println("data :" + data.getData());
} }

  上面1处出现了错误提示信息:The method getData(Box<Number>) in the t ype GenericTest is not applicable for the arguments (Box<Integer>)。Box<Number>不能视为Box<Integer>的父类。

  为什么?

 public class GenericTest {

     public static void main(String[] args) {

         Box<Integer> a = new Box<Integer>(712);
Box<Number> b = a; //
Box<Float> f = new Box<Float>(3.14f);
b.setData(f); // } public static void getData(Box<Number> data) {
System.out.println("data :" + data.getData());
} } class Box<T> { private T data; public Box() { } public Box(T data) {
setData(data);
} public T getData() {
return data;
} public void setData(T data) {
this.data = data;
} }

  上面1处和2处肯定会出现错误提示。使用反证法来说明:

  假设Box<Number>可以视为Box<Integer>的父类,上面1处和2处将不会有错误提示。通过getData方法取出的数据到底是什么类型?Integer? Float? 还是Number?由于编程过程的顺序不可控性,在必要的时候判断类型,进行强制类型转换。这与泛型的理念矛盾。因此,Box<Number>不能视为Box<Integer>的父类。

  类型通配符用来表示Box<Integer>和Box<Number>的父类的引用类型。

  类型通配符使用?代替具体的类型实参。Box<?>是Box<Integer>和Box<Number>等所有Box<类型实参>的父类。

  实例如下:

 public class GenericTest {

     public static void main(String[] args) {

         Box<String> name = new Box<String>("corn");
Box<Integer> age = new Box<Integer>(712);
Box<Number> number = new Box<Number>(314); getData(name);
getData(age);
getData(number);
} public static void getData(Box<?> data) {
System.out.println("data :" + data.getData());
} }

  限制类型实参只能是Number类及其子类,需要用到类型通配符上限:

 public class GenericTest {

     public static void main(String[] args) {

         Box<String> name = new Box<String>("corn");
Box<Integer> age = new Box<Integer>(712);
Box<Number> number = new Box<Number>(314); getData(name);
getData(age);
getData(number); //getUpperNumberData(name); //
getUpperNumberData(age); //
getUpperNumberData(number); //
} public static void getData(Box<?> data) {
System.out.println("data :" + data.getData());
} public static void getUpperNumberData(Box<? extends Number> data){
System.out.println("data :" + data.getData());
} }

  上面1处调用会出现错误,而上面2处和3处调用正常。

  总之,类型通配符上限为Box<? extends Number>形式,类型通配符下限为Box<? super Number>形式,其含义与类型通配符上限相反。

  注意:Java中没有泛型数组。

  参考资料

  Java总结篇系列:Java泛型

  <T>和T的区别

Java 泛型 介绍的更多相关文章

  1. Java泛型介绍!!!

    Java总结篇系列:Java泛型  转自:http://www.cnblogs.com/lwbqqyumidi/p/3837629.html 一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下 ...

  2. java泛型介绍

    一.泛型初衷 Java集合不会知道我们需要用它来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要就具有很好的通用性.但这样做也带来两个问题: –集合对元素类型没有任何限制,这样可能引 ...

  3. Java泛型介绍——HashMap总结

    今天在编程中,需要使用到Hashmap来存储和传递数据,发现自己学习Java这么久,实际上对泛型依旧知之甚少,搜索整理了一下HashMap的使用. HashMap的声明初始化,因为泛型的原因,起两个参 ...

  4. java泛型探索——介绍篇

    1. 泛型出现前后代码对比 先来看看泛型出现前,代码是这么写的: List words = new ArrayList(); words.add("Hello "); words. ...

  5. java泛型(一)、泛型的基本介绍和使用

    现在开始深入学习java的泛型了,以前一直只是在集合中简单的使用泛型,根本就不明白泛型的原理和作用.泛型在java中,是一个十分重要的特性,所以要好好的研究下. 泛 型的定义:泛型是JDK 1.5的一 ...

  6. Java泛型一:基本介绍和使用

    原文地址http://blog.csdn.net/lonelyroamer/article/details/7864531 现在开始深入学习java的泛型了,以前一直只是在集合中简单的使用泛型,根本就 ...

  7. java泛型 7 泛型的基本介绍和使用

    现在开始深入学习Java的泛型了,以前一直只是在集合中简单的使用泛型,根本就不明白泛型的原理和作用.泛型在java中,是一个十分重要的特性,所以要好好的研究下. 一.泛型的基本概念 泛型的定义:泛型是 ...

  8. Java泛型的历史

    为什么Java泛型会有当前的缺陷? 之前的章节里已经说明了Java泛型擦除会导致的问题,C++和C#的泛型都是在运行时存在的,难道Java天然不支持“真正的泛型”吗? 事实上,在Java1.5在200 ...

  9. java泛型基础

    泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 这种参数类型可以用在类.接口和方法的创建中, 分别称为泛型类.泛型接口.泛型方法.  Ja ...

随机推荐

  1. Aws云服务EMR使用

    Aws云服务EMR使用 创建表结构 创建abc库下的abc_user_i表字段s3://abc-server/abc-emr/shell/ABC_USER_HIVE.q: EXTERNAL 指定为外部 ...

  2. JavaBean toString() - 将bean对象打印成字符串

    JavaBean toString方式 https://www.cnblogs.com/thiaoqueen/p/7086195.html //方法一:自动生成 @Override public St ...

  3. MySQL应用异常问题解决

    MySQL错误:Every derived table must have its own alias 派生表都必须有自己的别名 一般在多表查询时,会出现此错误. 因为,进行嵌套查询的时候子查询出来的 ...

  4. Java 中 static 和 volatile 关键字的区别?

    static指的是类的静态成员,实例间共享 volatile跟Java的内存模型有关,线程执行时会将变量从主内存加载到线程工作内存,建立一个副本,在某个时刻写回.valatile指的每次都读取主内存的 ...

  5. python全栈开发day64-模板-变量和(.)的使用,filters和自定义filter

    一.上周内容回顾 day64 内容回顾: 1. 所有的django命令 1. 安装 pip install django==1.11.14 pip install -i 源 django==1.11. ...

  6. markdown 语法小结

    1 标题 # 一级标题 ## 二级标题 2  字体加粗和斜体 *斜体* **加粗** 3.引用 > 4.换行 空行 或两个空格+tab 5.无序列表 + 第一个 + 第二个 - 第一个 - 第二 ...

  7. Python_模块介绍

    模块:一组或者一个.py文件实现了某个功能的代码集合 模块分为三种: 自定义模块 内置标准模块(又称标准库):Python自带的模块 开源模块:自己写的模块,有可以供人使用的功能 开源模块的集散地:P ...

  8. Python交互图表可视化Bokeh:7. 工具栏

    ToolBar工具栏设置 ① 位置设置② 移动.放大缩小.存储.刷新③ 选择④ 提示框.十字线 1. 位置设置 import numpy as np import pandas as pd impor ...

  9. 关系网络数据可视化:2. Python数据预处理

    将数据中导演与演员的关系整理出来,得到导演与演员的关系数据,并统计合作次数 import numpy as np import pandas as pd import matplotlib.pyplo ...

  10. 051 Kafka的安装

    后来重新复习的时候,发现这篇文章不错:https://www.cnblogs.com/z-sm/p/5691760.html 一:前提 1.安装条件 Java   Scala zookeeper Ka ...