Java 基础知识——泛型

泛型

泛型是 JDK5.0 增加的新特性。泛型的本质是参数化类型,即所操作的数据类型(例如String、Interger)被指定为参数。可以用在类、接口和方法的创建,分别成为泛型类、泛型接口、泛型方法。


一、 为什么使用泛型

在 JDK5.0之前,在没有泛型的情况下,通过对类型 Object 的引用来实现参数的 “任意化”,带来的缺点是:将所有类型数据都当作 object 类型存放:① 使用具体数据类型时需要强制类型转换;② 容易造成类型转换错误
对于强制类型转换错误的情况,编译器可能不提示错误,但在运行时会出现异常,是一个安全隐患。

  • 示例一

    List list = new ArrayList ();
    list.add("hello");
    list.add(6);
    
    for (object o : list) {
    	System.out.println((String) o);  //  转换错误:  Integer 不能转换成 String 类型
    }
    
  • 示例二

    public class NoType {
    	private Object obj;    				// 定义 Object 通用类型变量
    	public Notype(Object obj){			// 构造函数
    		this.obj = obj;
    	}
    	public void set(Object obj) {		// set 方法
    		this.obj = obj;
    	}
    	public Object getO(){					// get 方法
    		return obj;			
    	}
    }	
    
    public class Test {
    	public static void main(String[] args){
    		NoType intObj = new NoType(new Integer(66));   // 创建一个传入整数的对象
    		NoType strObj = new NoType("Hello world");     // 创建一个传入字符串的对象
    		int i = (Integer)intObj.get();				   //  1. 执行此句必须强制类型转换,否则报错
    		intObj = strObj;		// 2. 此句是对象赋值,语法上正确,但语义上错误,只有在运行时才会有错
    	}
    }
    

    值得注意的时,

    1. int i = (Integer)intObj.get(); 在使用时必须强制类型转换,因为get方法返回的时对象类型。
    2. intObj = strObj; 此句是对象赋值,语法上正确,但语义上错误,只有在运行时才会出错

    使用泛型能够很好的解决上述问题。

    // T 为类型参数,下面的就是引用该类型参数,类似方法中
    public class NoType<T> {
    	private T obj;    				// 定义泛型成员变量
    	public NoType(T obj){			// 构造函数
    		this.obj = obj;
    	}
    	public void set(T obj) {		// set 方法
    		this.obj = obj;
    	}
    	public T get(){					// get 方法
    		return obj;			
    	}
    }		
    
    public class Test {
    	public static void main(String[] args){
    		NoType intObj = new NoType(new Integer(66));   // 创建一个传入整数的对象
    		NoType strObj = new NoType("Hello world");     // 创建一个传入字符串的对象
    		int i = intObj.get();				   //  1. 无需强制类型转换
    		intObj = strObj;		// 2. 执行时将提示错误,编译无法通过
    	}
    }
    

二、 泛型的定义与使用

1. 泛型类
  • 1.1 泛型类定义:

    泛型类型参数只能是类类型,不能是基本数据类型。( 因此是integer而非int )
    泛型的类型参数可以有多个

    // 给类增加泛型,相当于给类增加了过滤
    // G 类型形参,G 用作传递给 FxClass 的实际类型的占位符,声明类型参数时,目标类型替换G即可
    // G 为类型参数,下面的就是引用该类型参数,类似方法中
    public class FxClass<G> {  
    	private G g;
    	public G getG() {
    		return g;
    	}
    	public void setG(G g) {
    		this.g = g;
    	}
    }
    
  • 1.2 泛型类的继承

    // 使用泛型类:使用时会根据泛型定义对操作进行可一些过滤
    public class Main {
    	public static void main(String[] args) {
    		FxClass<String> fxClass = new FxClass<>();
    		fxClass.setG("自定义泛型类");
    		System.out.println(fxClass.getG());
    		Test test = new Test();
    	}
    
    // 泛型类的继承
    class Test extends FxClass<String> {  // 继承泛型类的时候要指定具体的类型实参
    	
    }
    
    // 原始类型
    // 不写类型实参被称为原始类型等价于 class Test extends FxClass<object>
    class Test extends FxClass {  
    }
    
    List list = new ArrayList();  // 没有使用泛型
    // 使用了泛型:没有生成新的 List 本质上还是上一个 List,只不过泛型做了规定只能装载特定的数据
    List<String> stringlist = new ArrayList<>();  
    
2. 限定类型
  • 有时需要对类型参数的取值进行一定程度的限制,使数据具有可操作性。
    例如,在制定参数类型时可以使用 extends 关键字限制此参数类型必须继承自制定父类或父类本身。
    // 限定只能时number 类型
    public class BoundType<T extends Number> { 
    	T[] array;    							// 定义泛型成员变量
    	public BoundType(T[] array){			// 构造函数
    		this.array = array;
    	}
    	public double Sum(){					// 计算总和
    		double sum = 0.0;
    		for(T element : array){
    			sum+=element.doubleValue();		// 该方法只能将 Number 类型的值转化为 double
    											// 没有 extends Number 会报错
    		}
    		return sum;
    	}
    }	
    
    public class Test{
    	Integer []a = {1,2,3,4};
    	BoundType<Integer> intObj = new BoundType<Integer>(a);  // 实参制定为整型
    	sum1 = intObj.sum();
    
    	Number []b = {1, 3.0f, 1.3, 5};       // float、double、int只要是Number类型均可计算
    	BoundType<Number> numObj = new BoundType<Number>(b);  
    	sum2 = numObj.sum();
    }
    
3. 通配符
  • 3.1 通配符的使用

    当某一方法所传参数是泛型类时,为了更明显,该如何去标识该参数是泛型类而不是普通类呢?

    // 定义了一个泛型类(将该泛型类作为下述方法的参数)
    public class NoType<T> {
        private T obj;    				// 定义泛型成员变量
        public NoType(T obj){			// 构造函数
            this.obj = obj;
        }
        public void set(T obj) {		// set 方法
            this.obj = obj;
        }
        public T get(){					// get 方法
            return obj;
        }
    }
    

    如果我们想用该泛型类作为参数传到下面的方法里,并标什该参数是泛型类,会想到用Object标识。测试类:

    在这里插入图片描述

    此时,会产生一个编译错误,试图将NoTpye<Integer> 类型传递给NoType<object>类型,类型不匹配导致编译错误。这种情况下我们就可以使用通配符解决,通配符由 " ? " 来表示,代表一个未知类型。

    当然,参数直接写为NoType 不限定类型也会通过不报错,但是对于该参数是一个泛型类不明显,因此我们用通配符来标识。

    在这里插入图片描述

    输出:
    在这里插入图片描述

  • 3.2 通配符的限定类型

    在通配符的使用过程中,也可以用 extends 关键字限定通配符界定的类型参数的范围。
    上述代码中:

    public class Main {
        public static void func(NoType<? extends Number> t){  // 通配符限定类型
            System.out.println(t);
        }
        public static void main(String[] args) {
            NoType<Object> obj = new NoType<Object>(12);
            func(obj);   									// 这里会产生一个编译错误
            NoType<Integer> intobj = new NoType<Integer>(12);
            func(intobj);
        }
    }
    
4. 泛型接口
  • 定义泛型接口
    interface MyList<E> {  // E 作为类型形参
    	// 打印泛型实际类型实参的类型名称
    	void showTypeName(E e)
    }
    
  • 实现一个泛型接口
    // 通过 MyListImp 类实现一个泛型接口
    public class MyListImp<E> implents MyList<E>{
    	public void showTypeName(E e) {
    		System.out.println(e.getClass().getName());
    	}
    }
    
  • 测试
    public static void main(String[] args) {
    	MyList<String> myList = new MyListImp<>();
    	myList.showTypeName("hello");
    }
    
5. 泛型方法
修饰符 <T,E, … 形参> 返回值类型 方法名称 (形参列表) {
          方法体,具体的逻辑实现
}

一般地,E:在集合框架中,K V:含有映射关系使用(Map),N:用于数字,T S U V:通用类型。

public class Test {
	public static void main(String[] args) {
		// 普通方法
		public void doS(String s) {
			System.out.println(s.getClass().getName());
		}
	
		// 泛型方法
		public <T> void doSS(T t) {  // <T> T 形参
			System.out.println(T.getClass().getName());
		}
	}
}	

三、泛型的局限性

Java 并没有真正实现泛型,是编译器在编译的时候在字节码上做了手脚,即类型擦除,这种实现理念造成 Java 泛型本身有很多漏洞。为规避这些问题,Java 对其使用做了一些约束,但还有一些由类型擦除引起地问题存在导致泛型的局限性

1. 泛型参数只能是类类型

泛型类型参数只能是类类型,不能是基本数据类型
擦除类型后原先的类型参数被 Object 或者限定类型替换,而基本类型是不能被对象所存储的。通常可以使用基本类型的包装类来解决此问题。( 例如用 int 的包装类 Integer )

2. 泛型类型不能被实例化
  • 不能建立泛型对象
// 非法
public class NoType<T> {
    private T obj;    			
    public NoType(){
        obj = new T();   // 不能建立泛型对象
        				 // 擦除类型将泛型T替换成new Object()
    }
}
  • 不能建立泛型数组
public <T> T[] build(T[] a){
	T[] arrays = new T[2];   // 非法,不能建立泛型数组(不能实例化泛型数组)
							 // 擦除类型会让该方法总是构造一个Objec[2]的数组
}

可以通过Class.newInstance 和 Array.newInstance 方法,利用反射构造泛型对象和数组。

  • 不能创建一个限定类型泛型类的数组。除非使用通配符
NoType<String> []arrays = new NoType<String>[100];  // × 非法
NoType<?> []arrays = new NoType<?>[100];            // √
3. 静态属性/方法不能引用类型参数

在泛型类中,不能在静态变量或方法中引用类型参数。

 // T为类型参数,下面的就是引用该类型参数,类似方法中
public class NoType<T> {
    
    // 静态变量不能引用类型参数。
    static T obj;    			
    
    // 静态方法不能引用类型参数
    static T getobj(){
        return obj   
    }
}

尽管不能在类中的静态变量或静态方法中引用类型参数,但是可以声明静态泛型方法。

4. 泛型在异常中的限制
  • 不能自定义泛型异常
public class FxException <T> extends Exception{
		// 	泛型类无法继承 Throwable,非法
}
  • 不能捕获类型参数异常。 即不能在catch中使用类型参数。下面方法不能编译:
    在这里插入图片描述

  • 可以抛出泛型异常。可以在异常声明中使用类型参数。例如下面方法合法:
    在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值