为什么需要泛型?

 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. 通过ModelForm实现主机添加和编辑

    通过ModelForm实现主机添加和编辑 ModelForm这是一个神奇的组件,通过名字我们可以看出来,这个组件的功能就是把model和form组合起来:在使用Model和Form时,都需要对字段进行 ...

  2. GoogleTest入门

    Googletest入门 来源:https://github.com/google/googletest/blob/master/googletest/docs/primer.md P.S. gmoc ...

  3. 部署Asp.net core & Nginx,通过nginx转发

    部署Asp.net core & Nginx,通过nginx转发 CentOS 7 x64 1.vs2017 建立Asp.net core项目,并发布到目录 2.通过FTP工具,将程序copy ...

  4. bzoj2018年5月赛

    题解: 老早之前看的并没有写题解.. t1: 我刚开始想的是线段树来维护.. 看了题解发现直接二分就行了 很容易发现因数只会有30个 那么我们就统计每一种因数在这段区间的个数 然后开个vector记录 ...

  5. iTOP4412环境搭建:arm-linux-gcc: 没有那个文件或目录

    系统:vmware下的ubuntu14.04 对于iTOP4412自己搭建环境,在source /root/.bashrc后发现还是没有正常,调用arm-linux-gcc -v提示没有那个文件或目录 ...

  6. java中可以对时间进行加减处理,有时候不用在sql语句中处理

    String ssny = (String) pd.get("ssny");   SimpleDateFormat simpleDateFormat=new SimpleDateF ...

  7. Kafka生产者案例报警告SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".

    一.SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". 这个报警告的原因简单来说时因为slf4j的版本 ...

  8. hdu 3078 Network (暴力)+【LCA】

    <题目链接> 题目大意:给定一颗带点权的树,进行两种操作,k=0,更改某一点的点权,k!=0,输出a~b路径之间权值第k大的点的点权. 解题分析:先通过RMQ的初始化,预处理pre[]数组 ...

  9. Callable与Runable接口 submit与execute区别

    execute(Runnable x) 没有返回值.可以执行任务,但无法判断任务是否成功完成. submit(Runnable x) 返回一个future.可以用这个future来判断任务是否成功完成 ...

  10. Alpha(7/10)

    鐵鍋燉腯鱻 项目:小鱼记账 团队成员 项目燃尽图 冲刺情况描述 站立式会议照片 各成员情况 团队成员 学号 姓名 git地址 博客地址 031602240 许郁杨 (组长) https://githu ...