一、泛型的引入
1.编写一段存入学生成绩的的代码
import java.util.ArrayList;
public class Demo04 {
public static void main(String[] args) {
//创建一个存放学生成绩的集合
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(100);
list.add(80);
list.add(98);
list.add(68);
list.add("小明")
//对成绩进行遍历
for (Object o:list){
System.out.println(o);
}
}
}
可以看到上面的最后一行代码,不是integer类型的成绩,但是代码不会报错。
但是一般我们在使用集合时,存入的都是相同类型的数据,对于这种错误,我们要进行避免,所以我们要引入泛型。泛型机制的目标是为了可以把发现 bug 的时机提前到编译时,而不是运行时。这样可以节省大量的调试Java程序的时间,泛型仅仅只存在于编译时。
2.泛型的格式
<引用类型>
将上面的代码添加泛型
ArrayList<Integer> list = new ArrayList<Integer>();
可以看到效果如图:
3.泛型的作用
1)确认集合中存放的数据类型,在编译期可以进行检查
2)遍历等代码编写更准确
二、泛型类/泛型接口
泛型类和泛型接口的定义方法一样,这里学习泛型类就好。
1.泛型类
我们可以直接编写泛型类,在定义类的时候不指定泛型,使用<E>代替,这里的E只是一个占位,是还没有确定的引用数据类型,源码中经常这么定义。
然后在对这个类进行实例化的时候再指定泛型。
GenericTest<String> genericTest2 = new GenericTest();
//自定义泛型类
//在类的后面加上<E>,这里的E只是一个占位,是还没有确定的引用数据类型
class GenericTest<E> {
//定义一些E代表的类型的属性和方法
int age;
E sex;
//定义方法的参数为E代表的类型
public void a(E n){
}
public void b(E[] m){
}
}
public class Demo05 {
public static void main(String[] args) {
//对上面定义的泛型类进行实例化
//不指定泛型实例化类的话,可以传入任意类型数据,相当于泛型没有效果,所以一般不使用
//1.可以不指定泛型,,此时就是默认的类型Object
GenericTest gt1 = new GenericTest();
//调用方法可以输入任意引用类型的数据
gt1.a(111);
gt1.b(new String[]{"a", "b"});
//2.指定泛型为String,那么定义为E的属性和方法的参数类型均为指定泛型为String
GenericTest<String> gt2 = new GenericTest();
//genericTest2.a(111); //不是String类型,报错
gt2.b(new String[]{"a", "b"});
}
}
2.继承泛型类的子类
在创建一个子类继承父类时,如果父类指定了泛型,子类就只能继承父类的泛型。
如果父类没有指定泛型(用E代替),那么子类必须也指定泛型为(E),并且在实例化的时候指定泛型,否则就是默认的Object类型
package com.rzd.no03collection;
//自定义泛型类
//在类的后面加上<E>,这里的E只是一个占位,是还没有确定的引用数据类型
class GenericTest<E> {
//定义一些E代表的类型的属性和方法
int age;
E sex;
//定义方法的参数为E代表的类型
public void a(E n){
}
public void b(E[] m){
}
}
//GenericTest的子类
class SubGenericTest1 extends GenericTest<Integer>{//直接指定父类的泛型为Integer
}
class SubGenericTest2<E> extends GenericTest<E>{
//不指定父类的泛型,用E代替时就必须也必须指定子类的泛型为E,且不能为其他A、B或String
}
public class Demo05 {
public static void main(String[] args) {
//指定了父类的泛型后,父类不指定泛型时直接继承父类的泛型
SubGenericTest1 sgt1 = new SubGenericTest1();
//subGenericTest.a("abc"); //不是Integer类型,报错
sgt1.a(111);
sgt1.b(new Integer[]{1, 2, 3});
//不指定了父类的泛型,子类就要指定泛型,否则是继承了父类的默认Object类型
SubGenericTest2<Integer> sgt2 = new SubGenericTest2();
//subGenericTest.a("abc"); //不是Integer类型,报错
sgt2.a(111);
sgt2.b(new Integer[]{1, 2, 3});
}
}
泛型类和泛型接口的
3.泛型类和泛型接口的应用
可以看到源码,例如我们在创建集合时,经常要使用到的ArrayList就是一个泛型类
并且类中的方法的参数也定义为了E类型
所以我们在调用类中的方法时才会可以输入任意引用类型的数据
List list1 = new ArrayList(); list1.add(18); list1.add("abc"); list1.add("15"); list1.add("true"); list1.add("7.8");
4.泛型类的使用注意点
1)可以定义多个泛型类
2)泛型的类构造器不能定义泛型
3)不同泛型的引用类型不可以相互赋值
4)如果实例化时不指定泛型,那么就是默认的Object类型,所以一定要指定泛型
5)泛型类中的静态属性和方法不能使用泛型,因为泛型要在实例化的时候才可以确认,而static与类一起先加载,此时泛型的类型还没有确定
6)不能直接使用泛型创建数组
三、泛型方法
1.泛型方法格式
泛型方法的类型与当前类是否是泛型类,当前类的泛型类型都无关
public <T> void b(T t){ System.out.println("泛型方法"); }
class GenericTest3<E>{
public void a(E e){
System.out.println("不是泛型方法");
}
//泛型方法的泛型类型与当前类是否是泛型类,当前类的泛型类型都无关
//泛型方法前面必须加上<T>,因为如果不加的话会把T当做普通的参数类型,但是java中没有T类型,会报错
public <T> void b(T t){
System.out.println("泛型方法");
}
}
2.调用泛型方法
调用泛型方法,泛型方法的具体类型,在调用方法的时候才确认,具体类型为传入参数的类型。
泛型方法可以是静态(static)方法。
package com.rzd.no03collection;
class GenericTest3<E>{
public void a(E e){
System.out.println("不是泛型方法");
}
//泛型方法的泛型类型与当前类是否是泛型类,当前类的泛型类型都无关
//泛型方法前面必须加上<T>,因为如果不加的话会把T当做普通的参数类型,但是java中没有T类型,会报错
public static <T> void b(T t){
System.out.println("泛型方法");
}
}
public class Demo07 {
public static void main(String[] args) {
GenericTest3<String> gt3 = new GenericTest3<>();
gt3.b("abc");
gt3.b(111);
gt3.b(9.8);
gt3.b(true);
}
}
三、泛型的引用类型之间不存在继承关系
在Java中,例如Object和String之间是存在父类和子类的继承关系的。但是当把Object和String放入<>中后就不存在继承关系。不能使用多态:父类的引用指向子类的对象
package com.rzd.no03collection;
import java.util.ArrayList;
import java.util.List;
public class Demo08 {
public static void main(String[] args) {
//引用类型
Object obj = new Object();
String str = new String();
obj=str; //多态,父类的引用指向子类的对象
//相当于Object obj =new String();
//数组
Object[] objArr=new Object[10];
String[] strArr=new String[10];
objArr=strArr; //原理同上
//集合
List<Object> objlist = new ArrayList<>();
List<String> strlist = new ArrayList<>();
//objlist=strlist; //报错
/*原因是:ArrayList底层是一个Object类型的数组。
这里的泛型,只是对编译期,存入集合的数据类型进行限制,
strlist的底层仍然是Object类型的数组。
那么objlist与strlist底层就是并列的关系,而不是父类子类的继承关系
*/
}
}
四、泛型通配符<?>
1.<?>的编写格式
class GenericTest4 {
public void a(List<?> list){
//遍历方式
for (Object o:list){
System.out.println(o);
}
}
2.可以把泛型中T和?放在一起来理解
List<T> , 这个 T 是一个形参,可以理解为一个占位符,被使用时,会在程序运行的时候替换成具体的类型,比如替换成String,Integer之类的
List<?>, 这个 ? 是一个实参,这是Java定义的一种特殊类型,比Object更特殊。比如List<Object>和List<String>不是父子关系的,而是并列关系,是两个类型,但List<?> 是 List<String>的父类
也就是说:? 表示了集合【所有Java类型,String,Integer等系统定义的,或者用户定义的Foo等类型】这个整体;而 T 表示了集合【所有Java类型,String,Integer等系统定义的,或者用户定义的Foo等类型】中的一个成员
可以和上面的泛型的引用类型(T)之间不存在继承关系结合到一起看:
public static void main(String[] args) {
List<Object> objlist = new ArrayList<>();
List<String> strlist = new ArrayList<>();
//objlist=strlist; //报错,并列关系
ArrayList<?> objects = new ArrayList();
ArrayList<String> strings = new ArrayList();
ArrayList<Integer> integers = new ArrayList();
objects=strings; //父子关系
}
3.T与?的区别与使用场景
1)T可以写入数据:list.add(),?不可以
import java.util.ArrayList;
import java.util.List;
class GenericTest4 {
public void a(List<?> list){
//遍历方式
for (Object o:list){
System.out.println(o);
}
/*写入数据:不能写入数据,因为对?的类型不确定,不可能定义一个? e这种参数
并且在创建对象时,只可能将?定义为一种数据类型,这里不可能知道是哪种类型,String?Integer?
因此不能保证这里添加的数据是正确的,所以不能添加数据
*/
//list.add();
//读取数据:可以读取数据,因为不确认类型,只能使用Object类型来接收
Object o=list.get(0);
}
public <T> void b(List<T> t,T e){
//而T可以写入数据,因为可以创建T e这种参数
t.add(e);
}
}
public class Demo09 {
public static void main(String[] args) {
GenericTest4 gt4 = new GenericTest4();
gt4.a(new ArrayList<String>());
}
}
2)使用extends限定类型子集的时候,?不能多重继承,T 可以(用到再学)
3)在不需要处理数组里的元素的时候,使用?更方便简单,如图所示,只需要写一处?
五、泛型受限
可以对泛型的上限和下限进行定义
上限:List<? extends 类名>
下限:List<? super 类名>
package com.rzd.no03collection;
import java.util.ArrayList;
import java.util.List;
//定义父类
class Person extends Object{
}
//定义子类
class Student extends Person{
}
public class Demo10 {
public static void main(String[] args) {
//定义3个集合
List<Object> a = new ArrayList<>();
List<Person> b = new ArrayList<>();
List<Student> c = new ArrayList<>();
//泛型受限
//泛型的上限:extends Person表示泛型?的上限就是Person。
List<? extends Person> list1= null;
//所以使用多态的时候,父类的引用指向子类的对象
// list1=a; //a是Object类,不是Person的子类,也就是不是?的子类
list1=b;
list1=c;
//泛型下限:super Person表示泛型?的下限就是Person。
List<? super Person> list2=null;
list2=a;
list2=b;
//list2=c; //c是Studen类,不是Person的父类,也就是不是?的父类
}
}