Java进阶知识学习:泛型

目录,更新ing,学习Java的点滴记录

  目录放在这里太长了,附目录链接大家可以自由选择查看--------Java学习目录

为什么要使用泛型

  1. 一般的类和方法,只能使用具体的类型:要么是基本类型要么是自定义类.如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大
  2. 在面向对象编程语言中,多态算是一种泛化机制.例如,你可以将方法的形参类型设为基类,那么该方法就可以接受从这个基类导入的任何类作为参数.这样的方法更加通用,应用场合也比较多.在类的内部也是如此,凡是需要说明类型的地方,如果都使用基类,确实能够具备更好地灵活性.
  3. 此外,拘泥于单继承体系,也使得程序受限很多.如果方法的形参为一个接口,而不是具体的类,这种限制又放松了很多.因为任何实现了该接口的类都能够满足该方法.
  4. 可是有时候,即便使用了接口,对程序的约束还是太强.因为一旦指明了接口,他就要求你的代码必须使用特定的接口.而我们希望编写更加通用的代码,要使得代码能够应用于"某种不具体的类型",而不是一个具体的接口或类.
  5. 这就是JavaSE5的重大变化之一------泛型.泛型实现了参数化类型的概念,使代码可以应用于多种类型.“泛型"这个术语的意思是"适用于许许多多的类型”.并且在你创建一个参数化类型的实例时,编译器会为你负责转型操作,并且保证类型的正确性.
  6. 泛型正是我们需要的程序设计手段.使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性.泛型对于集合类尤其有用
  7. 简单总结一句:泛型意味着编写的代码可以被很多不同类型的对象所重用

简单泛型的使用

  1. 先看一个持有单个对象的类,该类可以明确指定其持有对象的类型
    在这里插入图片描述 但是该类的重用性就很差了,只能持有一种类型的对象
  2. 在Java5之前,可以让这个类直接持有Object类型的对象
    在这里插入图片描述
     现在的Genericity可以存储任何类型的对象.但是获取值的时候必须强制类型转换
  3. 有些情况下,我们确实希望容器能够同时持有多种类型的对象.但是,通常来说,我们会使用容器来存储一种类型的对象.泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性.
  4. 因此,我们采用类型参数,用尖括号扩住,放在类名后面.在使用这个类的时候,再用实际的类型替换此类型参数.类型参数的魅力在于:使得程序具有更好的可读性和安全性
    在这里插入图片描述
     这样以后创建Genericity对象,必须指明想要持有什么类型的对象,将其置于尖括号内.以后,你就只能在Genericity中存入该类型机及其子类(因为多态和泛型不冲突),并且在取出对象时,会自动进行类型转换.
     Java7之后的版本就可以在构造函数中省略泛型参数了,如上图main中第一行,编译器也可以很好的利用这个信息,调用get时,不需要类型转换,编译器就知道返回值类型为Car.
  5. 这就是泛型的核心概念:告诉编译器想使用什么类型,然后编译器帮你处理一切细节

定义泛型类

  1. 一个泛型类就是具有一个或多个类型参数的类.
  2. 引入一个Pig类
    在这里插入图片描述
  3. Pig类引入了一个类型参数T,用尖括号<>括起来,并放在类名后面.泛型类可以有多个类型参数.例如,可以定义Pig类,其中第一个域和第一个域使用不同的类型
    在这里插入图片描述
  4. 类定义中还可以用类型参数指定方法的返回值类型以及域和局部变量的类型
    在这里插入图片描述
    类型参数使用大写形式,且比较短.在Java类库中,使用变量E表示集合的元素类型,K和V分别表示键与值类型.T(需要时可以选择U和S)表示"任意类型"
  5. 用具体类型替换类型参数就可以实例化泛型类型
    在这里插入图片描述
  6. 扩展:表面上看,Java的泛型类类似于C++的模板类.但是Java中没有template关键字,但是二者有着本质的区别.

定义泛型方法

  1. 前面介绍的都是应用在类上,但是同样可以在类中包含参数化方法,而这个方法所在的类可以是泛型类,也可以不是泛型类.总之,是否拥有泛型方法,与其所在的类是否是泛型没有关系
  2. 泛型方法能够使方法独立于类而变化.基本指导原则:无论何时,就应该尽量使用泛型方法,也就是说,如果使用泛型方法可以取代将整个类泛型化,就应该只用泛型方法.
  3. 定义泛型方法:只需要将类型参数放在方法返回值之前即可
    在这里插入图片描述 在该例子中,类并不是泛型化的,只有方法test具有类型参数,这也是由该方法的返回类型前面的类型参数指明的
  4. 泛型类和泛型方法指定类型参数的不同
     当使用泛型类的时候,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指定参数类型,因为比那一起会为我们找出具体的类型,这称为类型参数推断.因此,我们可以像调用普通方法一样调用test(),看上去test()被无限次重载过.并且调用test(0时传入基本类型,自动打包机制就会介入其中,将基本类型的值包装为对应的对象.

定义泛型接口

  1. 泛型也可以应用于接口,开发中可能用到的生成器就是一个样例
  2. 定义格式:和泛型类很相似,直接在接口后面添加就可以
    在这里插入图片描述
  3. 泛型接口中T的类型决定因素
     1) 该接口的实现类在定义时指定T类型
      在这里插入图片描述  在这里插入图片描述
     2) 实现类在定义时,如果不指定泛型T的类型,那么该实现类也必须是泛型类,最终类型决定取决于创建该实现类对象的时候去指定
      在这里插入图片描述  在这里插入图片描述

类型参数的限定

  1. 案例—查找一个数组中最小值
    在这里插入图片描述 分析一下代码,方法接收的形参是一个T类型的数组,首先对数组进行判断,看是否为null或者空数组,其次将数组第一个元素赋值给T类型的small,最后通过一个for循环遍历所有元素,在调用small的compareTo方法去比较大小,每次将最小值赋值给small,再将small返回,看似没有什么问题?仔细一想,T类型是不固定的,那么它一定持有compareTo方法吗?当然是不一定,这里就是问题点
     解决方法就是:对类型参数加以限定,使之必须要满足一定条件,这里将T限制为实现了Comparable接口的类
      在这里插入图片描述
  2. 上面示例中有一点很有意思,可能也让你感到迷惑,既然要对T类型限制为是一个实现Comparable接口的类,为啥不是用T implements Comparable却使用了extends关键字,在之前的学习中显然extends是用来表示继承关系的.在下面这种记法中(T extends BoundingType),表示T应该是绑定类型的子类型.T和绑定类型可以是类也可以是接口.选择extends关键字的原因是更接近与子类的概念.
  3. 对类型参数的限定可以有多个
    在这里插入图片描述

类型擦除

  1. 无论何时定义一个泛型类型,都自动提供了一个相应的原始类型.
     原始类型的类名字就是删去类型参数后的泛型类的类名.
     擦除类型参数,如果有进行类型限定的话,就替换为限定类型中的第一个(因此可以对一个T进行多个类型限定),无限定的变量用Object,看文字可能不明白,下面给出两个例子,一个未进行类型限定,一个进行了类型限定
  2. 未进行类型限定的原始类型
     泛型类
      在这里插入图片描述
     对应原始类
      在这里插入图片描述
  3. 进行类型限定之后的原始类型
     泛型类
      在这里插入图片描述
     原始类型
      在这里插入图片描述
  4. 继续看下面这个例子,你觉得二者输出结果一样吗?
    在这里插入图片描述 一开始我个人理解也是ArrayList与ArrayList很容易被认为是不同的类型,因为传递的泛型都不一样,但是上面的程序会认为他们是相同的类型
    在这里插入图片描述
     一个残酷的现实就是:在泛型代码内部,无法获得任何有关泛型参数类型的信息---无法知道用来创建某个特定实例的实际的类型参数.
  5. 总结:Java泛型使用擦除来实现的,这意味着当你使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象,因此List和List在运行时事实上都是相同的类型,这两种类型都被擦除为他们的原生类型—List.

约束与局限性(两个常见的)

  1. 不能用类型参数代替基本类型
     不能用类型参数代替基本类型.因此,没有Pig,只有Pig.原因就是类型擦除.擦除之后,Pig类含有Object类型的域,而Object不能存储double值.
  2. 运行时类型查询只适用于原始类型
     虚拟机中的对象总有一个特定的非泛型类型.因此,所有的类型查询只产生原始类型.比如:
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

通配符类型

  1. 固定的泛型类型系统使用起来还不够灵活,解决方案就是:通配符类型
  2. 个人理解通配符就是一些约定俗称的符号,之前我们用到的T,已经表示键值的KV等
     T表示某个具体的类型,?表示不确定的Java类型
  3. 无界通配符,无界通配符在方法中声明形参时比较重要
    在这里插入图片描述
     从代码中可以发现,runOne方法只能接受泛型为Animal类型的参数,而runTwo使用了无界通配符,可以接收所有类型作为参数,更加灵活
     所以,对于不确定的类型或者某个类的继承结构中的子类的类型,可以使用无界通配符 ,表示可以持有任何类型。在runTwo方法中,使用了?之后就不会关心具体传入的参数了
  4. 上界
    extends:参数化的类型可能是所指定的类型,或者是此类型的子类
    在这里插入图片描述
     使用了extends的位置,可以传递Animal或者其任何子类的参数,并且,extends指定的上界可以有多个,中间用逗号分开
  5. 下界
    super:表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41649001/article/details/106679941