Java基础学习——Java集合(四)泛型、泛型类与泛型方法、泛型的通配符

一、泛型的引入 

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的父类,也就是不是?的父类
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值