泛型Generics


泛型是JDK1.5及以上才可以使用的特性/语法,它的本质是 类型参数化(Parameterized by types).

1.理解泛型

在声明一个类、接口、方法的时候,需要涉及到到一个问题:
要给属性确定一个类型,或者给方法的返回值确定一个类型,
或者给方法的参数确定一个类型。

之前,定义类、接口、方法的时候,上面所描述的类型都是直接写死,不会变化的。

现在,希望这些类型都不写死,在将来使用的时候,可以通过传参的方式,
最后确定到底这些属性、方法的返回值、方法的参数是什么类型。

例如:

//之前的写法
			public class Student{
    
    
				//属性id是Long类型,固定写死不会变的。
				private Long id;
			}

			//现在的写法(泛型):
			public class Student<T>{
    
    
				//属性id的类型是不确定,需要将来使用Student类的时候,通过传参的方式再确定这个T到底代表的是什么类型
				private T id;
			}
			
			//s1对象中的id属性就是Long类型
			Student<Long>	 s1 = new Student<Long>();
			//s2对象中的id属性就是Integer类型
			Student<Integer> s2 = new Student<Integer>();
			//s3对象中的id属性就是String类型
			Student<String>  s3 = new Student<String>();

2.感受泛型

public void test1(){
    
    
				//集合中运行存放不同类型的数据
				//使用了泛型之后,集合中只能存放指定类型的数据
				//编译器会检查这个操作中使用的数据类型是不是符合泛型的要求
				//编译器会做类型的安全检查
				List<Integer> list = new ArrayList<Integer>();
				list.add(1);
				list.add(2);
				list.add(3);
				//list.add("hello");
				
				//使用了泛型之后,从集合中取数据的时候,也不需要做类型转换
				//编译器可以确定这个集合中的类型
				//这里就不存在类型转换的代码,也就不会出现类型转换异常的信息
				for(Integer i : list){
    
    
					//Integer i = (Integer)obj;
					System.out.println(i.intValue());
				}
			}
这里使用泛型之后:
			1)可以让编译器帮我们做类型安全检查
			2)不需要做类型的强制转换,也就不会有类型转换异常了
			3)提供代码的可读性

			4)List等泛型接口的使用更加灵活,可以通过传参来确定集合中元素的类型是什么。

3.定义一个泛型类和使用它

定义:
public class Point<T>{
    
    

	private T x;
	private T y;

	public Point(){
    
    }

	public Point(T x,T y){
    
    
		this.x = x;
		this.y = 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 String toString(){
    
    
		return "Point[x="+x+",y="+y+"]";
	}

}


使用:
public void test2(){
    
    
	//当使用一个泛型类,却指定泛型的类型的时候
	//那么这个泛型默认就是Object
	//Point p = new Point(1,"2");
	//System.out.println(p);
	
	//编译报错,因为泛型的类型只能使用引用类型,不能使用基本类型
	//Point<int> p = new Point<int>(1,2);

	//Point<Integer> p = new Point<Integer>(1,2);
	//Point<Long> p = new Point<Long>(1L,2L);
	//Point<Double> p = new Point<Double>(1.5,2.5);
	Point<String> p = new Point<String>("A1.5","B1.6");
	System.out.println(p);


}

4.泛型类、泛型接口、泛型方法

如果泛型参数定义在类上面,那么这个类就是一个泛型类
如果泛型参数定义在接口上面,那么这个接口就是一个泛型接口
如果泛型参数定义在方法上面,那么这个方法就是一个泛型方法
//泛型类
public class Point<T>{
    
    ...}

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

//泛型方法
public class Student{
    
    

	public <T> T test(T t){
    
    
		//..
	}
}


注意,<>就是用来声明的泛型的标识

理解泛型方法:
能否定义一个方法,方法的有一个参数,有返回值,要求方法的参数类型必须和返回类型一致?

类似于这样的情况:

public int test(int a);
public String test(String a);
public Object test(Object a);

但是这里的类型都是写死的,需要使用泛型,把这些类型给参数化。

//泛型方法
//将来调用的test方法的时候,才能确定类型参数T到底是什么类型
public <T> T test(T a);

//调用test方法时候,确定泛型T的具体类型:
t.test(1);  //这时候T就确定下来了,是Integer
t.test("1");//这时候T就确定下来了,是String
t.test(new Student());//这时候T就确定下来了,是Student

5.使用泛型后的子类型问题

//父类型的引用,执行子类对象
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>();


注意,=号俩边的所指定的泛型类型,一定是一样的,不可能出现俩边是不同的泛型类型。
注意,这里说的泛型类型,指的是<>中所指定的类型

总结:虽然Integer是Object的子类型,但是ArrayList<Integer>和ArrayList<Object>之间没有子父类型的关系,就是俩个没有关系的不同的类型
所以:
//编译通过
Object o = new Integer(1);
//编译报错
ArrayList<Object> list = new ArrayList<Integer>();

6.通配符?

例如:

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 test4(Collection<?> c){
    
    
			
}

注意,这时候test4方法中的参数类型,使用了泛型,
并且使用问号来表示这个泛型的类型,这个问号就是通配符,
可以匹配所以的泛型类型

所以,test4方法可以接收 泛型是任意类型的 集合对象

例如:
	t.test4(new ArrayList<String>());
	t.test4(new ArrayList<Integer>());
	t.test4(new ArrayList<Object>());
	t.test4(new ArrayList<任意类型>());

使用通配符?所带来的问题:
注意,使用?通配之后,就不能在往集合中添加数据了。

Collection<?> c  = null;

c = new ArrayList<String>();

//编译报错
//因为变量c所声明的类型是Collection,同时泛型类型是?
//那么编译器也不知道这个?将来会是什么类型,因为这个?是一个通配符
//所以,编译器不允许使用变量c来向集合中添加新数据。
c.add("hello");

//编译通过
//但是有一个值是可以添加到集合中的,null
//集合中一定存的是引用类型,null是所有引用类型共同的一个值,所以一定可以添加进去。
c.add(null);
总结:虽然使用?号通配符,可以让一个引用指向所有的 泛型类型的集合对象:
Collection<?> c = new ArrayList<任意类型>();
同时这个?也带来了新的问题:
//编译报错
//原因:编译器不知道?号将来到底是什么类型
c.add(任意类型数据);

//编译通过
//原因:null是所有引用的一个共同的值
c.add(null);

7.泛型的边界(上限和下限)

上限,指定泛型最大类型是什么类型
下限,指定泛型类型最小是什么类型

使用extends关键字可以设置泛型的上限

?表示任意类型,如何让?的范围变小呢?比如只接收泛型为Integer的集合
通过<? extends Number>就能实现,它表示将来接受的泛型类型只能是Number类型或Number子类型

使用super关键字可以设置泛型的下限

通过<? super Number>,它表示将来接受的泛型类型只能是Number类型或Number的父类型

实例:

package com.chenhao.day33;
import java.util.*;

public class GenericTest{
    
    
	public static void main(String[] args){
    
    
		GenericTest t=new GenericTest();
		List<String> list1=new ArrayList<String>();
		List<Integer> list2=new ArrayList<Integer>();
		list1.add("hello1");
		list1.add("hello2");
		
		list2.add(1);
		list2.add(2);
		t.test1(list1);
		t.test1(list2);
		
		// 不兼容的类型: List<String>无法转换为Collection<? extends Number>
		//t.test2(list1);

		t.test2(list2);
		// 错误: 不兼容的类型: List<Integer>无法转换为Collection<? super Number>
		//t.test3(list2);
		List<Number> list3=new ArrayList<Number>();
		//编译通过
		t.test3(list3);
		//Number继承自Object并实现了Serializable接口
		List<Object> list4=new ArrayList<Object>();
		//编译通过
		t.test3(list4);

		List<java.io.Serializable> list5=new ArrayList<>();
		//编译通过
		t.test3(list5);
	}
	
	//使用通配符打印出所有类型的泛型
	public void test1(Collection<?> c){
    
    
		for(Object obj:c){
    
    
			System.out.println(obj);
		}
	}

	//上限,指定泛型最大类型是什么类型
	//只打印出泛型为Integer的集合
	//泛型的边界,? extends Number表示将来接受的泛型类型只能是Number类型或Number子类型
	public void test2(Collection<? extends Number> c){
    
    
		/**for(Object obj:c){
			System.out.println(obj);
		}*/
		this.test1(c);
	}
	
	//下限,指定泛型类型最小是什么类型
	//表示将来接受的泛型类型只能是Number类型或Number父类型
	public void test3(Collection<? super Number> c){
    
    
		this.test1(c);
	}
}

总结:

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

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

8.泛型中extends和super的使用场景

extends

注意,泛型中使用了?通配后,就不能添加数据了

1).可以在声明泛型或者泛型接口的时候使用

public class Point<T extends Number>{
    
    
	private T x;
	private T y;
	}

public interface Action<T extends Person>{
    
    
	public void test(T t);
}

2).可以在声明泛型方法的时候使用

public <T extends Action> void test(T t);
注意,在泛型中使用extends的时候,后面可以跟接口类型。当前这个例子就表示,将来这个泛型T所接受的只能是Action类型或Action的子类型
public <T extends Action> void test4(T t){
    
    
		
	}
	
	//内部接口
	private interface Action{
    
    
		
	}

	private interface SubAction extends Action{
    
    
	
	}
main:
	Action a=null;
	SubAction b=null;
	t.test4(b);

3).可以在声明变量的时候使用

List<? extends Number> list=new ArrayList<>();

在定义方法参数列表的时候,也是在声明变量,这个时候也可以使用super

public void test(List<? super Number> list){
    
    }

super限定泛型的下限

1).在声明泛型类型和接口中不能使用super

//编译报错
public class Point<T extends Number>{
    
    
	private T x;
	private T y;
	}
//编译报错
public interface Action<T extends Person>{
    
    
	public void test(T t);
}

2)在声明泛型方法的时候不能使用super

//编译报错
public <T super Action> void test4(T t){
    
    
		
}

3)在声明泛型类型的变量时【可以】使用super

List<? super Number> list;
list=new ArrayList<Number>();
list=new ArrayList<Object>();

在定义方法参数列表的时候,也是在声明变量,这个时候也可以使用super
public void test(List<? super Number> list){
    
    }

总结,extends和super其实都是给泛型的类型指定一个限定的范围,因为如果不指定的话,一个泛型的类型将来就可以使用java中任意一个类型,不还控制它

public void test6(){
    
    
		Point<Number> p=new Point<Number>();
		List<Integer> list=new ArrayList<Integer>();
		list.add(1);
		list.add(3);
		p.test(list);
		System.out.println(p);
}

//如果T是Number,则test函数中所传list得泛型可以是Integer,Long,Double等数值型包装类
//如果T是Integer,则test函数中所传list得泛型只能是Integer
	private class Point<T>{
    
    
		private T x;
		private T y;

		public void test(List<? extends T> list){
    
    
			if(list.size()==2){
    
    
				this.x=list.get(0);
				this.y=list.get(1);
			}
		}


		public String toString(){
    
    
			return "point[x="+x+",y="+y+"]";
		}
	}

9.raw type(原始类型的泛型)

作用是兼容在以前JDK中编写的代码

//使用泛型接口和泛型类,同时指定泛型的类型
List<String> list =new ArrayList<String>();

//使用泛型接口泛型类,使用原始的泛型类型
List list=new ArrayList();
注意,泛型的原始类型就是Object,所以在指定泛型的时候,类中的泛型就会默认使用Object来表示

10.泛型类型信息擦除(Type Erasure)

泛型信息只存在于编译期间的源码中,编译成class文件后,class文件中将会擦出所有的泛型类型相关的信息
所以,ArrayList<String>和ArrayLIst<Integer>在编译期间是不兼容的,因为编译器会根据泛型的信息作
类型的安全检查,编译成class文件后,这两个ArrayLIst是同种类型

例如:

public void run(List<String> list){
    
    
	
	}

public void run(List<Integer> list){
    
    
	
}

这两个方法编译时会报错
编译时这两个方法是不同的,而编译完后由于泛型类型信息被擦除,两个方法相同了,所以会报错
在这里插入图片描述

而以下这两个方法编译时报错的信息是这样的

public void run(List list){
    
    
	
}

public void run(List list){
    
    
	
}

在这里插入图片描述

ArrayList<String> list1=new ArrayList<String>();
ArrayList<Integer> list2=new ArrayList<Integer>();

//判断list1所指向的实际类型是否与list2指向的实际类型相同
//true,因为编译完后,泛型类型信息被擦除了,这两个都是指向ArrayList
System.out.println(list1.getClass()==list2.getClass());
1.声明
Point.java
public Point<T>{
	
}
2.编译
Point.java-->Point.class
3.使用
Test.java
main:
		Pioint<Object> Point<String>  Point<Integer>
		...
		在编译期间,以上类型是相互不兼容的‘
		有没有一种类型,可以和以上所有类型兼容?
		那就是Point<?>
		Point<Object> p=new Pont<String>();
		编译完成后,以上这些类型都是同一种类型,都对应Point类

11.类型安全代码(type-safe Code)

代码编译后编译器没有发出警告,也没有进行强转就叫类型安全代码

对于原始类型,编译器没有提供足够的信息用于类型检查,因此会产生【未经检查】或【不安全】警告。

12.比较复杂的泛型例子,注意是如何调用的

public void test8(){
    
    
		//1.确定调用者
		Run<String,Integer> r1=null;
		//public <V> Run<T,V> getInstance(Map<? extends T,? super V> map);
		//public <V> Run<String,V> getInstance(Map<? extends String,? super V> map);
		
		//3.根据返回值泛型中的v定义map,map可以是如下三种
		Map<String,Long> map=new HashMap<>();
		//Map<String,Number> map=new HashMap<>();
		//Map<String,Object> map=new HashMap<>();
		
		//2.确定返回值类型r2的泛型,则确定了v
		Run<String ,Long> r2=r1.getInstance(map);
		//public <Long> Run<String,Long> getInstance(Map<? extends String,? super Long> map);
		
	}

	private interface Run<T,R>{
    
    
		public R test(T t);

		public <V> Run<T,V> getInstance(Map<? extends T,? super V> map);
		
		public <V> V go(List<? super V> list);
		
	}

再来个简单的

package com.chenhao.day33;
import java.util.*;

public class GenericTest2{
    
    
	public static void main(String[] args){
    
    
		//1.确定调用者
		Test<String,Number> t=null;
		//public <V> V run(Map<? super String,? extends Number> map);

		//3.确定参数map的泛型
		Map<String,Long> map=new HashMap<>();
		
		//2.确定返回值V
		String str=t.run(map);
	}

	private interface Test<T,R>{
    
    
		public <V> V run(Map<? super T,? extends R> map);
	}
}

猜你喜欢

转载自blog.csdn.net/qq_37924213/article/details/105285353