文章目录
泛型是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);
}
}