java基础:泛型

泛型

1、概述

1.1 定义

(1)本质是“类型参数化”。
(2)在声明一个类、接口、方法的时候,需要涉及到到一个问题:要给属性确定一个类型,或者给方法的返回值确定一个类型,或者给方法的参数确定一个类型。之前,定义类、接口、方法的时候,上面所描述的类型都是直接写死,不会变化的。而类型参数化,顾名思义,就是将原来的具体的类型进行参数化,类似于方法中的参数变量,不过这里是将类型作为参数,在使用时会传入具体的类型。

1.2 泛型的使用

public class Point{
	int x;
	int y;
}

这里Pint类里面的属性x和y的类型已经定义好了,都是int类型。如果希望x属性和y属性的类型更加灵活一点,就可以为这两个属性添加泛型参数T,如下:

public class Point<T>{
	T x;
	T y;
}

(1)T表示泛型参数,表示一种数据类型,具体是什么类型,需要将来使用Point的时候进行传参来确定。
(2)如果将来Point在使用的时候,没有给泛型参数T传值,那么T默认就表示为Object类型。
(3)T是泛型参数的名字,也就是相当于形参,名字随便起,但是一般用一个有意义的大写字母,例如一般E用在集合中。

Print的使用如下

public static void main(String[] args){
	//p1对象中x和y的属性类型都是Integer
	Point<Integer> p1 = new Point<Integer>();
	p1.x = 1;
	p1.y = 2;
	
	 //p2对象中x和y的属性类型都是String
	Point<String> p2 = new Point<String>();
	p2.x = "1";
	p2.y = "2";

	//p3对象中x和y的属性类型都是Object
	Point p3 = new Point();
	p3.x = new Object();
	p3.y = new Object();
	
}

可以看到,Point类的中x和y属性的类型,是可以根据我们在使用时所传的参数,进行临时变化的。
如果没有传这个泛型参数,那么这个参数T就默认是Object类型。

注意:给泛型参照传的值,只能是引用类型,不能是基本类型:Point编译报错

1.2 为什么要用泛型

有如下代码:

import java.util.ArrayList;
import java.util.List;

public class Demo01 {
	public static void main(String[] args) {
		//创建一个ArrayList集合
		List list = new ArrayList();
		
		//添加String类型的数据
		list.add("aaaa");
		//添加Integer类型的数据(int会自动包装为Integer)
		list.add(100);
		
		//第一种方式,用Object类型接收输出
//		for(int i = 0; i<list.size(); i++) {
//		 Object object = list.get(i);
//		 System.out.println(object);
//		}
		
		//第二种方式,如果指定了用String类型接收
		for(int i = 0; i<list.size(); i++) {
			String string = (String) list.get(i);
			System.out.println(string);
		}

	}
}

第一种方式输出的结果:
aaaa
100

第二种方式的输出结果:
aaaa
Exception in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at Test9.test01.Demo01.main(Demo01.java:24)

可以看到,第二种方式报错了,ClassCastException,类型转换异常,为什么会报这个异常呢?因为这个list数组中存的是两种不同类型(String和Integer)的数据,如果你指定用其中某一种类型接收,如String类型(而不用object类型接收),那使用时就只能用String类型的数据,另外的一种类型(Integer)就不能被接收和使用,此时就会发生类型转换异常。在这种情况下,就要使用泛型了,对集合传入的数据进行类型限制

在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。说人话就是限制所传入数据的类型。
对上面的案例使用泛型,代码如下:

import java.util.ArrayList;
import java.util.List;

public class Demo01 {
	public static void main(String[] args) {
		//创建一个ArrayList集合
		List<String> list = new ArrayList<String>();
		
		//添加String类型的数据
		list.add("aaaa");
		//添加Integer类型的数据(int会自动包装为Integer)
//		list.add(100);  //这句代码报错了
		
		//指定用String类型接收
		for(int i = 0; i<list.size(); i++) {
			String string = list.get(i);
			System.out.println(string);
		}
	}
}

可以看到,在List list=new ArrayList();这句代码两边多出了两个,这就是表示为这个集合类型添加了泛型类型,且这个泛型类型指定为String类型。表示这个集合只能添加String类型的数据了,如果添加了其他类型的数据就会报错。

后面那对尖括号中的泛型类型是可以省略的,即可以写成下面这样子

List<String> list = new ArrayList<>();

2、泛型的使用

2.1 泛型类

泛型参数定义在类上面,那这个类就是泛型类。

//泛型类
public class Point<T>{...}

//创建泛型类对象
public static void main(String[] args){
	Point<String> p = new Point<String>();
	}

代码样例:


public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;

	//泛型构造方法形参key的类型也为T,T的类型由外部指定
    public Generic(T key) { 
        this.key = key;
    }

	//泛型方法getKey的返回值类型为T,T的类型由外部指定
    public T getKey(){ 
        return key;
    }
}
//在实例化泛型类时,必须指定T的具体类型
//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic<Integer> integer = new Generic<Integer>(123456);

//传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic<String> string = new Generic<String>("lalala");

System.out.println(integer.getkey());
System.out.println(string.getkey());

注意:不能对确切的泛型类型使用instanceof操作
如下是错误的
if(Num instanceof Generic){ } //Number是所有与数字相关的父类

2.2 泛型接口

泛型接口,如果泛型参数定义在接口上面,那么这个接口就是一个泛型接口。
使用其实和泛型类差不太多。。。
如下:

//泛型接口
public interface Action<T>{...}

public static void main(String[] args){
	//使用匿名内部类实现接口,且指定了这个接口的类型为String
	Action<String> a = new Action<>(){
		//...
	};
}

2.3 泛型方法

泛型参数定义在方法上面,那么这个方法就是泛型方法

//泛型方法
public class Test{
	public <T> T test(T t){
		//...
	}
}

//泛型方法中的返回类型和参数类型都得是泛型才能叫泛型方法,否则只是带泛型参数的普通方法或者返回值为泛型的普通方法
public static void main(String[] args){
	Test t = new Test();    
	String str = t.test("hello");    
	Integer i = t.test(1);    
	Double d = t.test(10.5D);
}

3、泛型的类型

先观察下面的代码

//编译通过,父类型的引用,指向子类对象
	Object o = new Integer(1);
	
//编译通过,Object[]类型兼容所有的【引用】类型数组,arr可以指向任意 引用类型 数组对象
	Object[] arr = new Integer[1];
	
//编译失败,类型不兼容,int[] 是基本类型数组
Object[] arr = new int[1];

//编译失败,错误信息:ArrayList<Integer>无法转为ArrayList<Object>,原因:在编译期间,ArrayList<Integer>和ArrayList<Object>是俩个不同的类型,并且没有子父类型的关系
ArrayList<Object> list = new ArrayList<Integer>();

俩个类型,如果是当做泛型的指定类型的时候,就没有多态的特点了

4、泛型通配符

4.1 怎么用

public void test1(Collection<Integer> c){}
public void test2(Collection<String> c){}
public void test3(Collection<Object> c){}

test1方法【只能】接收泛型是Integer类型的集合对象
test2方法【只能】接收泛型是String类型的集合对象
test3方法【只能】接收泛型是Object类型的集合对象
原因:由于泛型的类型之间没有多态,所以=号俩边的泛型类型必须一致
在这种情况下,就可以使用通配符(?)来表示泛型的父类型,如下:

public void test(Collection<?> c){}

这个问号就是统配符,可以匹配所有的泛型类型

4.2 使用通配符"?"带来的问题

有如下代码:

Collection<?> c;        
	c = new ArrayList<String>();
	
	c.add("hello");
	//编译报错
	//因为变量c所声明的类型是Collection,同时泛型类型是通配符(?),那么编译器也不知道这个?将来会是什么类型,因为这个?只是一个通配符,所以,编译器不允许使用变量c来向集合中添加新数据。
	
	c.add(null);
	//编译通过
	//集合中一定存的是引用类型,null是所有引用类型共同的一个值,所以一定可以添加进去。

5、泛型边界

使用extends和super关键字对泛型的类型进行限制。如规定泛型的上限和下限。

5.1 上限

例如:List<? extends Number> list
将来引用list就可以接收泛型是Number或者Number子类型的List集合对象

public static void main(String[] args) {    
	List<? extends Number> list;    
	
	//list可以指向泛型是Number或者Number【子】类型的集合对象    
	list = new ArrayList<Number>();    
	list = new ArrayList<Integer>();    
	list = new ArrayList<Double>();   
	
	 //编译报错,因为String不是Number类型,也不是Number的子类型    
	 //list = new ArrayList<String>();

5.2 下限

例如:List<? super Number> list
将来引用list就可以接收泛型是Number或者Number父类型的List集合对象

public static void main(String[] args) {    
	List<? super Number> list;    
	
	//list可以指向泛型是Number或者Number【父】类型的集合对象    
	list = new ArrayList<Number>();    
	list = new ArrayList<Serializable>();    
	list = new ArrayList<Object>();   

	//编译报错,因为String不是Number类型,也不是Number的父类型    
	//list = new ArrayList<String>();       
	
	//编译报错,因为Integer不是Number类型,也不是Number的父类型    
	//list = new ArrayList<Integer>();}

5.3 对比extends和super

使用extends可以定义泛型的【上限】,这个就表示将来泛型所接收的类型【最大】是什么类型。可以是这个最大类型或者它的【子类型】。
使用super可以定义泛型的【下限】,这个就表示将来泛型所接收的类型【最小】是什么类型。可以是这个【最小类型】或者它的【父类型】。

5.4 extends和super作为上限和下限使用场景

对于extends:
1、在声明泛型类或者泛型接口的时候【可以】使用
2、在声明泛型方法的时候【可以】使用
3、在声明变量的时候【可以】使用

对于super:
1、在声明泛型类或者泛型接口的时候【不能】使用
2、在声明泛型方法的时候【不能】使用
3、在声明变量的时候【可以】使用

6、泛型特性:泛型擦除

泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份字节码。

定义一个泛型类如下:

class Generic<T> {    
	private T obj;    	
	public Generic(T o) {        
	obj = o;   
}    
	public T getObj() {        
	return obj;
   }
}

Java编译后的字节码中Generic相当于这样的:

class Generic {    	
	private Object obj;   
	public Generic(Object o) {
	  	obj = o;   
	}    
	public Object getObj() {        
	return obj;   
	}
}

泛型信息被擦除后,所有的泛型类型都会统一变为原始类型:Object
泛型使编译器可以在编译期间对类型进行检查以提高类型安全减少运行时由于对象类型不匹配引发的异常.

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值