【Java 数据结构】泛型

🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!

欢迎志同道合的朋友一起加油喔🦾🦾🦾


目录

一.泛型的引入

1.什么是泛型(Generic)

2.没有泛型的时候使用集合:

3. 泛型的使用

4. 泛型总结:

二.自定义泛型结构

1.泛型类,泛型接口

【1】泛型类的定义和实例化:

【2】继承情况:

【3】泛型应用场合:

 【4】泛型使用细节:

2.泛型方法

3.泛型参数存在继承关系的情况

4.通配符

【1】在没有通配符的时候:

【2】引入通配符:

【3】使用通配符:

【4】查看API中应用位置

5.通配符的使用场景

1.方法参数中使用通配符:

​​​​​​2.返回值中使用通配符:

3.局部变量中使用通配符:

6.使用通配符后的细节

7. 泛型受限

(1)泛型上限

(2)泛型下限

三.泛型的擦除机制

1. 泛型擦除的概念:

2 由于泛型擦除出现的错误举例:



一.泛型的引入

1.什么是泛型(Generic)

泛型就相当于标签
形式:<>  
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,
JDK1.5之 后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。
Collection<E>, List<E>, ArrayList<E> 这个<E>就是类型参数,即泛型。

2.没有泛型的时候使用集合:

一般我们在使用的时候基本上往集合中存入的都是相同类型的数据--》便于管理如果不使用泛型的话,什么引用数据类型都可以存入集合,不方便!

public class Test01 {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //创建一个ArrayList集合,向这个集合中存入学生的成绩:
        ArrayList al = new ArrayList();
        al.add(98);
        al.add(18);
        al.add(39);
        al.add(60);
        al.add(83);
        al.add("丽丽");
        //对集合遍历查看:
        for(Object obj:al){
            System.out.println(obj);
        }
    }
}

3. 泛型的使用

public class Test01 {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //创建一个ArrayList集合,向这个集合中存入学生的成绩:
        //加入泛型的优点:在编译时期就会对类型进行检查,不是泛型对应的类型就不可以添加入这个集合。
        ArrayList<Integer> al = new ArrayList<Integer>();
        al.add(98);
        al.add(18);
        al.add(39);
        al.add(60);
        al.add(83);
        /*al.add("丽丽");
        al.add(9.8);*/
        //对集合遍历查看:
        /*for(Object obj:al){
            System.out.println(obj);
        }*/
        for(Integer i:al){
            System.out.println(i);
        }
    }
}

4. 泛型总结:

(1)JDK1.5以后
(2)泛型实际就是 一个<>引起来的 参数类型,这个参数类型  具体在使用的时候才会确定具体的类型。

(3)使用了泛型以后,可以确定集合中存放数据的类型,在编译时期就可以检查出来
(4)使用泛型你可能觉得麻烦,实际使用了泛型才会简单,后续的遍历等操作简单。
(5)泛型的类型:都是引用数据类型,不能是基本数据类型
(6)ArrayList<Integer> al = new ArrayList<Integer>();在JDK1.7以后可以写为:
ArrayList<Integer> al = new ArrayList<>();  --<>  ---钻石运算符


二.自定义泛型结构

1.泛型类,泛型接口

【1】泛型类的定义和实例化:

/**
 * GenericTes就是一个普通的类
 * GenericTest<E> 就是一个泛型类
 * <>里面就是一个参数类型,但是这个类型是什么呢?这个类型现在是不确定的,相当于一个占位
 * 但是现在确定的是这个类型一定是一个引用数据类型,而不是基本数据类型
 */
public class GenericTest<E> {
    int age;
    String name;
    E sex;
    public void a(E n){
    }
    public void b(E[] m){
    }
}
class Test{
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //GenericTest进行实例化:
        //(1)实例化的时候不指定泛型:如果实例化的时候不明确的指定类的泛型,那么认为此泛型为Object类型
        GenericTest gt1 = new GenericTest();
        gt1.a("abc");
        gt1.a(17);
        gt1.a(9.8);
        gt1.b(new String[]{"a","b","c"});
        //(2)实例化的时候指定泛型:---》推荐方式
        GenericTest<String> gt2 = new GenericTest<>();
        gt2.sex = "男";
        gt2.a("abc");
        gt2.b(new String[]{"a","b","c"});
        
    }
}

【2】继承情况:

(1)父类指定泛型:

如果指定父类泛型,那么子类就不需要再指定泛型了,可以直接使用

class SubGenericTest extends GenericTest<Integer>{
}
class Demo{
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //指定父类泛型,那么子类就不需要再指定泛型了,可以直接使用
        SubGenericTest sgt = new SubGenericTest();
        sgt.a(19);
    }
}

  (2)父类不指定泛型:

如果父类不指定泛型,那么子类也会变成一个泛型类,那这个E的类型可以在创建子类对象的时候确定:

class SubGenericTest2<E> extends GenericTest<E>{
}
class Demo{
    //这是main方法,程序的入口
    public static void main(String[] args) {
        SubGenericTest2<String> s = new  SubGenericTest2<>();
        s.a("abc");
        s.sex = "女";
    }
}

【3】泛型应用场合:

源码经常使用泛型

 【4】泛型使用细节:

(1)泛型类可以定义多个参数类型

 (2)泛型类的构造方法的写法:

 (3)不同的泛型的引用类型不可以相互赋值:

 (4)泛型如果不指定,那么就会被擦除,反应对应的类型为Object类型:

 (5)泛型类中的静态方法不能使用类的泛型:

 (6)不能直接使用E[]的创建:

2.泛型方法

1.什么是泛型方法:
  不是带泛型的方法就是泛型方法
  泛型方法有要求:这个方法的泛型的参数类型要和当前的类的泛型无关
2.泛型方法定义的时候,前面要加上<T>
      原因:如果不加的话,会把T当做一种数据类型,然而代码中没有T类型那么就会报错
3.T的类型是在调用方法的时候确定的
4.泛型方法可以是静态方法

public class TestGeneric<E> {
    //不是泛型方法 (不能是静态方法)
    public static void a(E e){
    }
    //是泛型方法
    public static <T>  void b(T t){
    }
}
class Demo{
    //这是main方法,程序的入口
    public static void main(String[] args) {
        TestGeneric<String> tg = new TestGeneric<>();
        tg.a("abc");
        tg.b("abc");
        tg.b(19);
        tg.b(true);
    }
}

3.泛型参数存在继承关系的情况

4.通配符

通配符可以在方法参数、返回值以及局部变量的类型中使用,但不能用于类定义或泛型方法定义的类型参数。通配符的主要目的是为了提供更大的灵活性,特别是在处理泛型集合时。

【1】在没有通配符的时候:

下面的a方法,相当于方法的重复定义,报错

public class Test {
    /*public void a(List<Object> list){
    }
    public void a(List<String> list){
    }
    public void a(List<Integer> list){
    }*/
}

【2】引入通配符:

发现: A 和 B是子类父类的关系,G<A>和G<B>不存在子类父类关系,是并列的
加入通配符?后,G<?>就变成了 G<A>和G<B>的父类

public class Demo {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<Integer> list3 = new ArrayList<>();
        List<?> list = null;
        list = list1;
        list = list2;
        list = list3;
    }
}

【3】使用通配符:

public class Test {
    /*public void a(List<Object> list){
    }
    public void a(List<String> list){
    }
    public void a(List<Integer> list){
    }*/
    public void a(List<?> list){
        //内部遍历的时候用Object即可,不用?
        for(Object a:list){
            System.out.println(a);
        }
    }
}
class T{
    //这是main方法,程序的入口
    public static void main(String[] args) {
        Test t = new Test();
        t.a(new ArrayList<Integer>());
        t.a(new ArrayList<String>());
        t.a(new ArrayList<Object>());
    }
}

【4】查看API中应用位置

5.通配符的使用场景

1.方法参数中使用通配符:

您可以在方法参数的泛型类型中使用通配符,以允许更广泛的类型传递给方法。例如:

public void processElements(List<? extends Number> numbers) {
    // ...
}

 这个方法接受一个包含Number类型或其子类型的List。使用通配符使得这个方法可以接受诸如List<Integer>List<Double>等类型的参数。

​​​​​​2.返回值中使用通配符:

在某些情况下,您可能需要返回泛型类型,并希望提供一定的灵活性。例如:

public List<? extends Number> getNumbers() {
    // ...
}

这个方法返回一个包含Number类型或其子类型的List。这意味着该方法可能返回List<Integer>List<Double>等类型的实例。

3.局部变量中使用通配符:

您还可以在局部变量的类型中使用通配符,例如:

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

 这里,我们将一个Integer类型的ArrayList赋值给一个包含Number类型或其子类型的List变量。

总之,通配符可以在方法参数、返回值以及局部变量的类型中使用,为泛型提供更大的灵活性。然而,它们不能用于类定义或泛型方法定义的类型参数。 

6.使用通配符后的细节

7. 通配符上界和下界    

(1)通配符上界

语法:

类/接口<? extends T>

上界使用关键字extends来表示。它表示类型参数必须是某个类(或接口)的子类(或实现类)。

这里的?是一个通配符,表示任意类型。extends T表示该类型必须是类型参数T或T的子类。

(2)通配符下界

语法:

类/接口<? super T>

下界使用关键字super来表示。它表示类型参数必须是某个类(或接口)的父类

public static void processIntegers(List<? super Integer> integers) {
    // ...
}

这个方法可以接受如List<Integer>List<Number>List<Object>等参数,因为Number和Object都是Integer的父类。 

8.泛型上界 

注意泛型只有上界没有下界,在Java泛型中,类型参数只能设置上界(使用extends关键字),而不能设置下界。

语法:类/接口<T extends 类/接口>

上界使用关键字extends来表示。它表示类型参数必须是某个类(或接口)的子类(或实现类)。

三.泛型的擦除机制

1. 泛型擦除的概念:

 类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。

简单理解:在编译期间,所有的泛型信息都会被擦除掉。例如代码中定义的List<Object>和List<String>等类型,在编译后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。

2 由于泛型擦除出现的错误举例:

 类型擦除的原因:这是因为擦除后两个重载的方法都变成了一样,发生了冲突所以报错

  public MyGeneric(ArrayList  arrayList) {        
    }

完美解释此处问题,但是却勾起小伙伴的另外一个问题,类型擦除了,为什么不同泛型之间不能相互赋值!!

因为检查机制的存在,编译器的工作是这样子滴:首先进行类型检查,检查类型不同,报错!如果类型相同,再进行类型擦除啦!!!(即进入擦除阶段,需要通过检查那一关)

举个例子,购买的衣服,需要相关人员先进行质量合格等方面的检查,检查通过,进入商城,消费者购买完撕掉了吊牌。。。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

书生-w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值