为什么需要泛型?

 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. vue——vue-resource

    get请求 getSearch () { return this.$http.get('https://xxx.xxx.com/xxx.json', {params: {name: this.sear ...

  2. [转] 一张图理解prototype、proto和constructor的三角关系

    前面的话 javascript里的关系又多又乱.作用域链是一种单向的链式关系,还算简单清晰:this机制的调用关系,稍微有些复杂:而关于原型,则是prototype.proto和constructor ...

  3. 【AtCoder】ARC076

    ARC076 C - Reconciled? 如果\(N = M\) 答案是\(2N!M!\) 如果\(|N - M| = 1\) 答案是\(N!M!\) 否则答案是0 #include <bi ...

  4. Javascript 中调参数的脚本onclick="select(this)" this 怎么解释

    解释1. this,指当前的onclick所在的节点本身. 比如: <div onclick='select(this)"></div> 则当点击div时,this就 ...

  5. sqlldr的用法 (这个最完整)

    转自:http://blog.chinaunix.net/uid-23622436-id-2394093.html 一:在 Oracle 数据库中,我们通常在不同数据库的表间记录进行复制或迁移时会用以 ...

  6. Python交互图表可视化Bokeh:3. 散点图

    散点图 ① 基本散点图绘制② 散点图颜色.大小设置方法③ 不同符号的散点图 1. 基本散点图绘制 import numpy as np import pandas as pd import matpl ...

  7. 054 kafka内部机制

    一:数据格式与数据存储 1.总结 存储在磁盘文件中(index+log) 顺序读写的 基于offset偏移量来管理数据的(主要是读操作) 由分区器根据key值决定数据分布到哪个分区,默认使用hash ...

  8. 记Ubuntu Mongodb 和 Mysql的安装与使用

    安装mongodb 参考链接 https://www.cnblogs.com/shileima/p/7823434.html https://blog.csdn.net/xlengji/article ...

  9. react学习三

    三点运算符  (...)的用法 1:展开运算符 let a=[1,2,3]; let b=[0,...a,4];//[0,1,2,3,4] let obj ={a:1,b:2}; let obj2 = ...

  10. Django 中bootstrap的引用

    bootstrap的优越性 如果你有基本的HTML+CSS,bootstrap其实就是在标签中加入具体的class来实现样式.和原生态的HTML+CSS需要先在head标签的style写样式或者引入外 ...