java基础(七)之快速理解泛型
2017-09-22 15:58
411 查看
引言:
泛型在日常开发中并不常见,但是在很多开发框架中却非常常见,学习泛型最好的方法就是阅读jdk源码,泛型在集合这部分大量应用。
一.泛型的出现原因
下面从两段段代码中来进行理解泛型出现的原因:
这段代码执行后会报ClassCastException异常,即类型转换异常,默认集合中元素类型都是Object类型,当你对集合中元素进行操作时,必须知道该元素是什么类型,再进行转型,不然会导致类型转换出错。这段代码在编译阶段是不进行错误检查的,只有在运行时才能报错。
我们发现代码的重用性存在问题,最后封装的数据类中集合元素只是类型不同而已。因此我们就希望能否向参数化形参一样,能否参数化类型?参数化形参的出现是为了提高代码的重用性。那么参数化类型部分为了提高代码的可重用性。
可以将上述的2个数据封装类,写成通用的数据容器类
泛型的好处:
1.用来在代码编译阶段就强制进行数据类型检查,避免出现类型转化问题。同时也避免了大量使用类型转换这样的可能出错的代码段。
2.提高程序的可重用性
二.泛型的机制
只有理解泛型的机制才能更好的使用泛型,泛型只是java提供的一层经过包装的语法糖。
1.类型擦除
Java的泛型是伪泛型。为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉。正确理解泛型概念的首要前提是理解类型擦除(type erasure)。Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。
如在代码中定义的List和List等类型,在编译后都会编程List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。
2.类型擦除后保留的原始类型
原始类型(raw type)就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除,并使用其限定类型(无限定的变量用替换).
Pair的原始类型为:
因为在Pair中,T是一个无限定的类型变量,所以用Object替换。其结果就是一个普通的类,如同泛型加入java变成语言之前已经实现的那样。在程序中可以包含不同类型的Pair,如Pair或Pair,但是,擦除类型后它们就成为原始的Pair类型了,原始类型都是Object。
3.类型转换
因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。这样就引起了一个问题,既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换。这部分其实jvm暗中已经帮我们做了。这点可以从java集合中的源码就可以知道。
看下ArrayList和get方法:
三.泛型基本使用
1.泛型接口
2.泛型类
3.普通类中泛型方法
注:这里的T仅仅是一个占位符,可以使用任何的字母符号替代。但一般程序开发都用K,V,E,T这四个最常用的符号。
4.泛型通配符 ?
下面通配符就能出场了。
下面介绍几种通配符用法:
1)通配符只能出现在引用的定义中,而不能出现在创建对象中。
2)带有下边界的通配符
3)带有上边界的通配符
四.源码分析
我们以ArrayList实现为例来说明泛型的应用。
ArrayList的继承关系如下:
add方法如下
下面就是找到elementData到底是啥?
原来底层就是Object数组。
下面我们看下get方法
重点就在于elementData()方法,继续找到该方法
原来,底层就是用Object数组实现的,当使用相应的类型时,最后还是进行类型转换,只不过java提供了语法糖,底层为我们进行了类型转换。
泛型在日常开发中并不常见,但是在很多开发框架中却非常常见,学习泛型最好的方法就是阅读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<Int db0a eger>(); 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提供了语法糖,底层为我们进行了类型转换。
相关文章推荐
- JAVA基础:Java泛型编程快速入门
- Java基础:Java泛型编程快速入门
- Java 基础快速掌握 TCP 3次握手和4次握手的理解
- JAVA基础:Java泛型编程快速入门
- Java基础之深入理解泛型
- Java基础:Java泛型编程快速入门
- java基础理解(泛型)
- Java基础:Java泛型编程快速入门
- Java基础:Java泛型编程快速入门
- 黑马程序员---java基础加强---jdk1.5新特性之泛型
- 关于Java泛型深入理解小总结
- Java多线程 -- 深入理解JMM(Java内存模型) --(一)基础
- java基础之泛型
- JAVA常用基础知识点[继承,抽象,接口,静态,枚举,反射,泛型,多线程...]
- Java基础学习总结(25)——Log4j快速入门教程
- Java泛型编程快速入门
- Java中泛型的基础到提高《精简》
- java基础之快速排序
- Java基础-四大特性理解(抽象、封装、继承、多态)
- JAVA中泛型的理解