泛型
学习目标:
- 掌握泛型的基本原理及应用
- 掌握泛型通配符的使用
- 指定泛型操作中的上限及下限
- 在接口上应用泛型
- 掌握泛型方法及泛型数组的使用
这里针对的是JDK1.5本身的泛型特性,JDK1.5之后在类集和反射机制中已经大量使用泛型,需要结合类集框架及反射机制。
什么是泛型:
就是指在对象创建时不指定类中属性的具体类型,而由外部在声明和实例化对象时指定具体的类型。
1.为什么要使用泛型
引入案例:
要求设计一个可以表示坐标点的类,坐标由X和Y组成,坐标的表示方法有以下三种:
- 整数表示:x = 10,y = 20
- 小数表示:x = 10.5,y = 20.6
- 字符串表示:x = "东经180度", y = "北纬210度"
案例分析:
- 首先要创建一个表示坐标点的类:Point
- Point类中有两个属性,分别表示x坐标和y坐标
- x和y坐标的数据类型有3种:(int、float、String)
- 需要使用一种类型同时接收3种类型数据,只能使用object,使用Object类接收任何数据类型,都会自动向上转型。
- int --> 自动装箱成Integer --> 向上转型使用Object接收
- float --> 自动装箱成Float --> 向上转型使用Object接收
- String --> 向上转型使用Object接收
代码设计:
Point类- class Point{
- private Object x;
- private Object y;
-
- public void setX(Object x){
- this.x = x;
- }
-
- public void setY(Object y){
- this.y = y;
- }
-
- public Object getX(){
- return this.x;
- }
-
- public Object getY(){
- return this.y;
- }
- }
复制代码 使用整数表示坐标:- public class GenericsDemo{
- public statci void main(String[] args){
- Point p = new Point();
- p.setX(10); //利用自动装箱操作:int --> Integer -->Object
- p.setY(20); //利用自动装箱操作:int --> Integer -->Object
- int x = (Integer)p.getX(); //取出数据先变为Integer,之后自动拆箱
- int y = (Integer)p.getY(); //取出数据先变为Integer,之后自动拆箱
- System.out.println("整数表示,X坐标为:" + x);
- System.out.println("整数表示,Y坐标为:" + y);
- }
- }
复制代码 使用小数表示坐标- public class GenericsDemo {
- public static void main(String[] args) {
- Point p = new Point();
- p.setX(10.5f); //利用自动装箱操作:float --> Float -->Object
- p.setY(20.6f); //利用自动装箱操作:float --> Float -->Object
- float x = (Float) p.getX();//取出数据先变为Float,之后自动拆箱
- float y = (Float) p.getY();//取出数据先变为Float,之后自动拆箱
- System.out.println("小数表示,X坐标为:" + x);
- System.out.println("小数表示,Y坐标为:" + y);
- }
- }
复制代码 使用字符串表示坐标- public class GenericsDemo {
- public static void main(String[] args) {
- Point p = new Point();
- p.setX("东经180度"); //String --> Object
- p.setY("北纬210度"); //String --> Object
- String x = (String) p.getX(); //取出数据
- String y = (String) p.getY(); //取出数据
- System.out.println("字符串表示,X坐标为:" + x);
- System.out.println("字符串表示,Y坐标为:" + y);
- }
- }
复制代码 以上三个程序已经证明Point类符合要求,但是是存在问题的。
可以把X设置成数字,Y设置为字符串,程序在编译的时候不会出现错误,但是运行的时候会报错。
- public class GenericsDemo {
- public static void main(String[] args) {
- Point p = new Point();
- p.setX(10);
- p.setY("北纬210度");
- int x = (Integer) p.getX();
- int y = (Integer) p.getY();
- System.out.println("整数表示,X坐标为:" + x);
- System.out.println("整数表示,Y坐标为:" + y);
- }
- }
复制代码 程序出现类转换异常,因为String类无法向Integer类转换。出现这个问题的原因就是Point类中使用了Object类型接收属性,造成了类型安全问题,要绝解决这个问题就需要使用泛型。
2.泛型的应用
1.基本应用
泛型可以解决数据类型的安全问题,原理:是在类声明时通过一个标识标识类中某个属性的类型或者某个方法的返回值及参数类型。这样类在声明或者实例化时只要指定好需要的具体类型即可。
泛型类定义:
[访问权限] class 类名称{
[访问权限] 泛型类型标识 变量名称;
[访问权限] 泛型类型标识 方法名称(){};
[访问权限] 返回值类型声明 方法名称(泛型类型标识 变量名称){};
}
泛型对象定义:
类名称 对象名称 = new 类名称();
声明泛型:
- class Point<T>{ //此处可以是任意的标识符号
- private T var; //此变量的类型由外部决定
-
- public T getVar(){ //返回值的类型由外部指定
- return var;
- }
-
- public void setVar(T var){ //设置的类型由外部指定
- this.var = var;
- }
- }
- public class Demo01 {
- public static void main(String[] args) {
- Point<Integer> integerPoint = new Point<Integer>(); //里面的var类型为Integer类型
- integerPoint.setVar(30); //设置数字,自动装箱
- System.out.println(integerPoint.getVar()*2); //计算结果按数字取出。
- }
- }
复制代码 关于整型设置的问题:
只能使用包装类;在泛型的指定中是无法使用基本数据类型的,必须设置成一个类,这样在设置一个数字时就必须使用包装类。
注意:
如果传进来的值和泛型所指定的类型不一致,则在编译的时候会报错。
加入泛型后使得程序的操作更加安全,避免了类型转换异常(ClassCastException)的发生。
2.泛型中的构造方法
定义: [访问权限] 构造方法([泛型类型 参数名称]){}
与普通的构造方法并无不同,只是参数类型使用泛型表示。
案例:
- class Point<T>{ //此处可以是任意的标识符号
- private T var; //此变量的类型由外部决定
- public Point(T var){ //构造方法
- this.var = var;
- }
- public T getVar(){ //返回值的类型由外部指定
- return var;
- }
- public void setVar(T var){ //设置的类型由外部指定
- this.var = var;
- }
- }
- public class Demo01 {
- public static void main(String[] args) {
- Point<String> p = null;
- p = new Point<String>("Java");
- System.out.println(p.getVar());
- }
- }
复制代码 3.指定多个泛型类型
如果一个类中有多个属性需要使用不同的泛型声明,则可以在声明类时指定多个泛型类型。
案例:
设置多个泛型类型- class Notepad<K,V>{
- private K key;
- private V value;
- public K getKey() {
- return key;
- }
- public void setKey(K key) {
- this.key = key;
- }
- public V getValue() {
- return value;
- }
- public void setValue(V value) {
- this.value = value;
- }
- }
- public class Demo01 {
- public static void main(String[] args) {
- Notepad<String, Integer> notepad = null;
- notepad = new Notepad<String, Integer>();
- notepad.setKey("工资");
- notepad.setValue(30000);
- System.out.println("资金来源:" + notepad.getKey());
- System.out.println("金额:" + notepad.getValue());
- }
- }
复制代码 3.泛型的安全警告
泛型在应用中最好在声明和实例化类对象时,指定好其内部的数据类型,如Info,如果不指定类型,就会出现不安全操作的警告信息。
案例:
不指定泛型类型- class Info<T>{
- private T var;
- public T getVar() {
- return var;
- }
- public void setVar(T var) {
- this.var = var;
- }
- }
- public class Demo01 {
- public static void main(String[] args) {
- Info info = new Info(); //警告没有指定泛型类型
- info.setVar("Java");
- System.out.println("内容:" + info.getVar());
- }
- }
复制代码 以上程序会在编译的时候出现警告,但是并不影响程序的运行。
如果没有指定泛型类型,则所有的类型统一使用Object进行接收
以上程序的var属性实际就变成了Object类型,也就是相当于在定义时将泛型擦除了。
以上程序相当于下面这个程序:- public class Demo01 {
- public static void main(String[] args) {
- Info<Object> info = new Info<Object>(); //指定Object为泛型类型
- info.setVar("Java");
- System.out.println("内容:" + info.getVar());
- }
- }
复制代码 
4.通配符
在泛型操作中可以通过通配符接收任意指定泛型类型的对象(不需要设置一个固定的类型)。
1.匹配任意类型的通配符
在泛型类的操作中,进行引用传递时泛型类型必须匹配才可以传递,否则是无法传递的。
案例:
使用泛型声明后的对象引用传递问题- class Info<T>{
- private T var;
- public T getVar() {
- return var;
- }
- public void setVar(T var) {
- this.var = var;
- }
- }
- public class Demo01 {
- public static void main(String[] args) {
- Info<String> stringInfo = new Info<String>(); //指定String为泛型类型
- stringInfo.setVar("Java"); //为属性赋值
- fun(stringInfo); //错误无法传递
- }
-
- public static void fun(Info<Object> temp){ //此处可以接收Object泛型类型的Info对象
- System.out.println("内容:" + temp);
- }
- }
复制代码 程序在编译时会报错。
<img alt="image-20220718152739892" loading="lazy">
注意:尽管String是Object类的子类,但是在进行引用传递时也同样无法进行操作。
可以将方法fun()中定义的Info(Object)修改为Info,即不指定泛型。
如下:- class Info<T>{
- private T var;
- public T getVar() {
- return var;
- }
- public void setVar(T var) {
- this.var = var;
- }
- }
- public class Demo01 {
- public static void main(String[] args) {
- Info<String> stringInfo = new Info<String>(); //指定String为泛型类型
- stringInfo.setVar("Java"); //为属性赋值
- fun(stringInfo);
- }
- public static void fun(Info temp){ //此处可以接收Info对象
- System.out.println("内容:" + temp);
- }
- }
复制代码 但是这样做时,Info中并没有指定任何的泛型类型,是不妥当的。为了解决这个问题可以使用Java中的通配符"?"
通配符:
"?"表示可以接收此类型的任意泛型对象
使用通配符"?"- class Info<T>{
- private T var;
- public T getVar() {
- return var;
- }
- public void setVar(T var) {
- this.var = var;
- }
- }
- public class Demo01 {
- public static void main(String[] args) {
- Info<String> stringInfo = new Info<String>(); //指定String为泛型类型
- stringInfo.setVar("Java"); //为属性赋值
- fun(stringInfo);
- }
- public static void fun(Info<?> temp){ //此处可以接收Object泛型类型的Info对象
- System.out.println("内容:" + temp);
- }
- }
复制代码 注意:
- fun()方法中使用Info的代码形式,表示可以使用任意的泛型类型对象。
- 如果使用"?"接收泛型对象,则不能设置被泛型指定的内容。
- public class Demo01 {
- public static void main(String[] args) {
- Info<?> stringInfo = new Info<String>(); //使用"?"接收泛型
- stringInfo.setVar("Java"); //错误,无法设置
- }
- }
复制代码- 可以设置为null值
- public class Demo01 {
- public static void main(String[] args) {
- Info<?> stringInfo = new Info<String>(); //使用"?"接收泛型
- stringInfo.setVar(null); //null,可以设置
- }
- }
复制代码
2.受限泛型
引用传递中,在泛型操作中也可以设置一个泛型对象的范围上限和范围下限。
- 范围上限:使用extends关键字声明,表示泛型的类型可能是所指定的类型或者此类型的子类。
- 范围下限:使用super关键之声明,表示泛型的类型可能是所指定的类型,或者此类型的父类,或是Object类。
格式:
<ol>设置上限:<ol>
声明对象:类名称 |