java基础(七)之快速理解泛型

来源:互联网 发布:知乎网址 编辑:程序博客网 时间:2024/06/05 02:37

引言:
泛型在日常开发中并不常见,但是在很多开发框架中却非常常见,学习泛型最好的方法就是阅读jdk源码,泛型在集合这部分大量应用。

一.泛型的出现原因

下面从两段段代码中来进行理解泛型出现的原因:

import java.util.ArrayList;public class GenericTest {public static void main(String[] args) {    ArrayList list=new ArrayList();    list.add(2);    list.add("3");    int str=(Integer)list.get(1);    System.out.println(str);}}

这段代码执行后会报ClassCastException异常,即类型转换异常,默认集合中元素类型都是Object类型,当你对集合中元素进行操作时,必须知道该元素是什么类型,再进行转型,不然会导致类型转换出错。这段代码在编译阶段是不进行错误检查的,只有在运行时才能报错。

//超级会员class SuperUser{    String authority="";    String name="";    int credit=0;}//普通用户class CommonUser{    String authority="";    String name="";}//封装的的普通用户的数据列表class SuperUserData{    int status=0;    List<SuperUser> su=null;}//封装的的普通用户的数据列表class CommonUserData{    int status=0;    List<CommonUser> su=null;}

我们发现代码的重用性存在问题,最后封装的数据类中集合元素只是类型不同而已。因此我们就希望能否向参数化形参一样,能否参数化类型参数化形参的出现是为了提高代码的重用性。那么参数化类型部分为了提高代码的可重用性。
可以将上述的2个数据封装类,写成通用的数据容器类

class UserData<T>{int status=0;//参数化数据类型T data=null;}

泛型的好处:

1.用来在代码编译阶段就强制进行数据类型检查,避免出现类型转化问题。同时也避免了大量使用类型转换这样的可能出错的代码段。
2.提高程序的可重用性

二.泛型的机制
只有理解泛型的机制才能更好的使用泛型,泛型只是java提供的一层经过包装的语法糖。

1.类型擦除
Java的泛型是伪泛型。为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉。正确理解泛型概念的首要前提是理解类型擦除(type erasure)。Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

public class MyTest {      public static void main(String[] args) {          ArrayList<String> arrayList1=new ArrayList<String>();          arrayList1.add("abc");          ArrayList<Integer> arrayList2=new ArrayList<Integer>();          arrayList2.add(123);          System.out.println(arrayList1.getClass()==arrayList2.getClass());      }  }  

如在代码中定义的List和List等类型,在编译后都会编程List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。

2.类型擦除后保留的原始类型
原始类型(raw type)就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除,并使用其限定类型(无限定的变量用替换).

class Pair<T> {      private T value;      public T getValue() {          return value;      }      public void setValue(T  value) {          this.value = value;      }  }

Pair的原始类型为:

class Pair {      private Object value;      public Object getValue() {          return value;      }      public void setValue(Object  value) {          this.value = value;      }  } 

因为在Pair中,T是一个无限定的类型变量,所以用Object替换。其结果就是一个普通的类,如同泛型加入java变成语言之前已经实现的那样。在程序中可以包含不同类型的Pair,如Pair或Pair,但是,擦除类型后它们就成为原始的Pair类型了,原始类型都是Object。

3.类型转换
因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。这样就引起了一个问题,既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换。这部分其实jvm暗中已经帮我们做了。这点可以从java集合中的源码就可以知道。
看下ArrayList和get方法:

 public E get(int index) {      RangeCheck(index);      return (E) elementData[index];      }

三.泛型基本使用

1.泛型接口

interface Iuser<T>{//泛型接口中泛型方法public T getUser();}

2.泛型类

class  MyUser<T>{T data=null;int id=0;//泛型类中泛型方法public T getUser(){return data;}}

3.普通类中泛型方法

class  MyUser{int id=0;//普通类中泛型方法,<T>是类型的定义public <T> T getUser(){T data=null;return data;}}

注:这里的T仅仅是一个占位符,可以使用任何的字母符号替代。但一般程序开发都用K,V,E,T这四个最常用的符号。

4.泛型通配符 ?

public static void fun(List<Object> list){System.out.println(list);}List<String> list1 = new ArrayList<String>();List<Integer> list2 = new ArrayList<Integer>();fun(list1);//编译不通过fun(list2);//编译不通过

下面通配符就能出场了。

public static void fun(List<?> list) {System.out.println(list);}

下面介绍几种通配符用法:

1)通配符只能出现在引用的定义中,而不能出现在创建对象中。

//不能出现在创建对象中,无法编译通过List<?> list=new ArrayList<?>(),//引用可以ArrayList<?> list = null

2)带有下边界的通配符

//即“?”只能被赋值为Number或其子类型。List<? extends Number> list;

3)带有上边界的通配符

//即“?”只能被赋值为Integer或其父类型。List<? super Integer> list;

四.源码分析
我们以ArrayList实现为例来说明泛型的应用。

ArrayList的继承关系如下:

public class ArrayList<E> extends AbstractList<E> implements List<E>{}

add方法如下

    public boolean add(E e) {        ensureCapacityInternal(size + 1);  // Increments modCount!!        elementData[size++] = e;        return true;    }

下面就是找到elementData到底是啥?

   private transient Object[] elementData;

原来底层就是Object数组。

下面我们看下get方法

    public E get(int index) {        rangeCheck(index);        return elementData(index);    }

重点就在于elementData()方法,继续找到该方法

    E elementData(int index) {        return (E) elementData[index];    }

原来,底层就是用Object数组实现的,当使用相应的类型时,最后还是进行类型转换,只不过java提供了语法糖,底层为我们进行了类型转换。

原创粉丝点击