java泛型从浅到深学习

以下是本文的目录大纲:
  一泛型设计初衷
  二.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);   
}

使用方法有两种:

  1. 子类实现接口时候继续保留泛型
  2. 子类实现接口时确定好泛型类型

代码如下所示:
//子类实现接口时候继续保留泛型

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 泛型总结:

  1. 适用于多种数据类型执行相同的代码;
  2. 泛型中的类型在使用时指定,且只能是引用类型;
  3. 泛型归根到底就是“模版”。

四、类型擦除(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>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值