以下是本文的目录大纲:
一泛型设计初衷
二.Object参数任意化
三. 泛型:本质是“参数化类型”
若有不正之处,请多多谅解并欢迎批评指正。
一.泛型设计初衷:
为解决容器无法记忆元素类型的问题。
jdk5引入的类型机制。
一个例子如下:
import java.util.ArrayList;
import java.util.List;
public class Demo2_1 {
public static void main(String[] args) {
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
System.out.print("泛型测试,"+"item = " + item);
}
}
}
毫无疑问,程序的运行结果会以崩溃结束:泛型测试,item = aaaaException in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Stringat Demo2_1.main(Demo2_1.java:13…从上面代码可以看出,ArrayList能够存放任意类型,例子中先添加了一个String类型对象“aaaa”,再添加了一个Integer类型,在使用时都以String的方式使用,因此程序崩溃了。
二、Object参数任意化
使用Object作为参数,什么都能存放,可以接解决上面的问题。如下代码,我们不考虑过多异常情况,简单自定义一个MyArrayList类:
class MyArrayList{
private Object[] elementDatas;
private static final int DEFAULT_CAPACITY = 10;
private int size;
public MyArrayList(int capacity){
if(capacity<=0){
capacity = DEFAULT_CAPACITY;
}
elementDatas = new Object[capacity];
this.size =0;
}
public boolean add(Object e) {
elementDatas[size++] = e;
return true;
}
public Object get(int index) {
return elementDatas[index];
}
public int size() {
return this.size;
}
}
public class Demo2_2 {
public static void main(String[] args) {
MyArrayList arrayList = new MyArrayList(5);
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
Object obj = arrayList.get(i);
if(obj instanceof String){
String item = (String)obj;
System.out.print("泛型测试,"+"item = " + item);
}else if(obj instanceof Integer){
Integer item = (Integer)obj;
System.out.print("泛型测试,"+"item = " + item);
}
}
}
}
使用Object作为参数,似乎解决了上述问题,但它存在以下问题:
1),可以向集合添加了不相同类型元素,不能限定元素类型;
2) 编译时不检查类型的异常,运行时检查类型的异常;
3) 取出元素时,太多强制转换,增加了代码复杂度,维护起来困难。
为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
泛型的优势:简单安全,具体如下:
1). 让容器记住元素类型,取出元素时无需强制类型转换;
2). 限定元素类型:一旦指定元素类型,容器不能再添加其他类型元素;
3) 运行时不检查类型的异常,编译时检查类型的异常;运行时不会产生ClassCastException
4) 代码简洁,易于维护。
三、 泛型:本质是“参数化类型”
将类型定义成参数形式T(可以称之为类型形参),表示不确定类型。
在使用/调用时传入具体的类型(类型实参)。
类型形参仅是一个占位符,使用大写英文字母,默认含义有如下:
E:element; 常用在java Collection里,如:List,Iterator,Set
V:value:Hash相关容器中 K:Key:Hash相关容器中 N:Number T:Type S:Subtype。
S、V、U:表示第二、第三个参数、第四个参数
参数化类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
3.1 泛型类
Demo设置点坐标
//设置Integer类型的点坐标
class IntegerPoint{
private Integer x ; // 表示X坐标
private Integer y ; // 表示Y坐标
public void setX(Integer x){
this.x = x ;
}
public void setY(Integer y){
this.y = y ;
}
public Integer getX(){
return this.x ;
}
public Integer getY(){
return this.y ;
}
}
//设置Float类型的点坐标
class FloatPoint{
private Float x ; // 表示X坐标
private Float y ; // 表示Y坐标
public void setX(Float x){
this.x = x ;
}
public void setY(Float y){
this.y = y ;
}
public Float getX(){
return this.x ;
}
public Float getY(){
return this.y ;
}
}
他们除了变量类型不一样,一个是Integer一个是Float以外,其它并没有什么区别!如果利用Object参数任意化:
Object参数任意化设置点坐标
class ObjectPoint{
private Object x ;
private Object y ;
public void setX(Object x){
this.x = x ;
}
public void setY(Object y){
this.y = y ;
}
public Object getX(){
return this.x ;
}
public Object getY(){
return this.y ;
}
}
测试:
public class TestObjectPoint {
public static void main(String[] args){
ObjectPoint integerPoint = new ObjectPoint();
integerPoint.setX(new Integer(100));
Integer integerX=(Integer)integerPoint.getX();
ObjectPoint floatPoint = new ObjectPoint();
floatPoint.setX(new Float(100.12f));
Float floatX = (Float)floatPoint.getX();
String floatX = (String)floatPoint.getX(); //error
}
}
泛型类的定义及使用
class Point<T>{// 此处可以随便写标识符号
private T x ;
private T y ;
public void setX(T x){//作为参数
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//作为返回值
return this.x ;
}
public T getY(){
return this.y ;
}
}
public class TestObjectPoint {
public static void main(String[] args){
//IntegerPoint使用
Point<Integer> pInt = new Point<Integer>() ;
pInt.setX(new Integer(100)) ;
System.out.println(pInt.getX());
//FloatPoint使用
Point<Float> pFloat = new Point<Float>() ;
pFloat.setX(new Float(100.12f)) ;
System.out.println(pFloat.getX());
}
}
泛型类Point中,T的作用域就是整个Point类;泛型的具体的类型(类型实参),不可以使用基本数据类型,只能使用引用类型。
3.2 泛型方法
如果定义了一个泛型(类、接口),那么Java规定,不能在所有的静态方法、静态初块等所有静态内容中使用泛型的类型参数。例如:
public class Point {
public static void getX(T t){//报错,编译不通过
}
}
泛型方法的作用:
在静态内容静态方法中使用泛型。更一般的情况是,如果类(或者接口)没有定义成泛型,但是想在其中某几个方法中运用泛型。
普通类中,静态泛型方法的定义及使用,如下代码:
public class Point {
public static <T> T getY(T t){
return t;
}
}
只有声明了的方法才是泛型方法,后面的T为方法返回类型。
Demo 泛型方法及泛型类的成员方法
public class Point<T> {
public T getX(T t){//泛型成员方法
return t;
}
public <T> T getY(T t){//泛型方法
return t;
}
}
3.3 泛型接口
泛型接口定义代码如下:
interface Information<T> {
T fun(T t);
}
使用方法有两种:
- 子类实现接口时候继续保留泛型
- 子类实现接口时确定好泛型类型
代码如下所示:
//子类实现接口时候继续保留泛型
class InterfaceImp1<T> implements Information<T> {
@Override
public T fun(T t) {
return t;
}
}
//子类实现接口时确定好泛型的类型
class InterfaceImp2 implements Information<String>{
@Override
public String fun(String s) {
return s;
}
}
3.4 泛型总结:
- 适用于多种数据类型执行相同的代码;
- 泛型中的类型在使用时指定,且只能是引用类型;
- 泛型归根到底就是“模版”。
四、类型擦除(type erasure)
泛型是一个编译时的概念。擦除是泛型的类型参数在编译期擦除,将java 文件编译为 class 文件时擦除。所以,生成的Java字节代码中是不包含泛型中的类型信息。
public class Foo {
public void listMethod(List<String> stringList){
}
public void listMethod(List<Integer> intList) {
}
}
public class Demo2_4{
public static void main(String[] args) {
List<String> ls = new ArrayList<String>();
List<Integer> li = new ArrayList<Integer>();
System.out.println(ls.getClass() == li.getClass());
}
}
4.1 类型擦除的主要过程
类型擦除的主要过程如下:
例如: 对于Point、Point,Jvm只看到Point,仅生成一个Point.class文件;
class Point,原始类型为Object; 擦除后的类型为Point; class Point;原始类型为Comparable;擦除后的类型为Point。
问题:擦除与Object参数任意化的区别?
1擦除能够通过类型实参进行类型限定,Object参数任意化不能;
2擦除通过jvm实现强制类型转换;Object参数任意化必须由程序员主动实现。
问题:为什么java采用类型擦除?
类型擦除能保持Java语言向后兼容(Jdk1.5才有泛型)。
问题:既然类型擦除了,如何保证只能使用泛型变量限定的类型?
java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,再进行编译的。
五、 通配符
5.1为什么需要通配符?
Java没有实现真正的泛型,只能使用类型擦除来实现伪泛型
1)泛型类不能通过T使用具体方法。
Demo 2_5
class HasF {
public void f() {
System.out.println("HasF.f()");
}
}
public class Manipulator<T> {
private T obj;
public Manipulator(T obj) {
this.obj = obj;
}
public void manipulate() {
obj.f(); //无法编译 找不到符号 f()
}
public static void main(String[] args) {
HasF hasF = new HasF();
Manipulator<HasF> manipulator = new Manipulator<>(hasF);
manipulator.manipulate();
}
}
上面的 Manipulator 是一个泛型类,内部用一个泛型化的变量 obj,在 manipulate 方法中,调用了 obj 的方法 f(),但是这行代码无法编译。因为类型擦除,编译器不确定 obj 是否有 f() 方法。
解决这个问题的方法是给 T 一个边界:
class Manipulator2<T extends HasF> {
private T obj;
public Manipulator2(T x) { obj = x; }
public void manipulate() { obj.f(); }
}
现在 T 的类型是 ,这表示 T 必须是 HasF 或者 HasF 的导出类型。这样,调用 f() 方法才安全。HasF 就是 T 的边界,因此通过类型擦除后,所有出现 T 的地方都用 HasF 替换。这样编译器就知道 obj 包含有方法 f() 。
2) 擦除导致泛型不可变性
不可变:不论 A B 有什么关系,A 的容器和 B 的容器都没有父子关系。
如:A extends B, 不能有ArrayList extends ArrayList
5.2 为什么需要通配符?
1)在泛型中需要使用特定方法
2)解决泛型导致不可变性
public class GernericTest {
public static void main(String[] args) throws Exception{
List<Integer> listInteger =new ArrayList<Integer>();
List<String> listString =new ArrayList<String>();
printCollection(listInteger);//error
printCollection(listString);//error
}
//打印出任意参数化类型的集合中的所有数据
public static void printCollection(List<Object> list){
for(Object obj:list){
System.out.println(obj);
}
}
}
错误分析:泛型不可变性。将上述方法修改为:
public static void printCollection(List<?> list){
for(Object obj:list){
System.out.println(obj);
}
}
注意:<?>允许所有类型的引用调用,等价于<? Extends Object>