泛型
学习目标:
- 掌握泛型的基本
原理
及应用
- 掌握泛型
通配符
的使用 - 指定泛型操作中的
上限及下限
- 在
接口
上应用泛型 - 掌握
泛型方法
及泛型数组
的使用
这里针对的是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 类名称<泛型类型标识1,泛型类型标识2,...泛型类型标识n>{
[访问权限] 泛型类型标识 变量名称;
[访问权限] 泛型类型标识 方法名称(){};
[访问权限] 返回值类型声明 方法名称(泛型类型标识 变量名称){};
}
泛型对象定义:
类名称<具体类型> 对象名称 = 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<String>,如果不指定类型,就会出现不安全操作的警告信息。
案例:
不指定泛型类型
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); } }
程序在编译时会报错。
注意:尽管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类。
格式:
-
设置上限:
- 声明对象:
类名称<? extends 类> 对象名称
- 定义类:
[访问权限] 类名称<泛型标识 extends 类>{}
- 声明对象:
-
设置下限:
- 声明对象:
类名称<? super 类> 对象名称
- 定义类:
[访问权限] 类名称<泛型标识 extends 类>{}
- 声明对象:
1.泛型的上限
案例:
假设一个方法中能够接收的泛型对象只能是数字(Byte、Short、Long、Integer、Float、Double)类型,此时在定义方法参数接收对象时,就必须指定泛型的上限。所有的数字包装类都是Number类型的子类,
设置方法只能接收泛型为Number或Number类型的子类
class Info<T>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } } public class Demo01 { public static void main(String[] args) { Info<Integer> i1 = new Info<Integer>(); //声明Integer的泛型对象 Info<Float> i2 = new Info<Float>(); //声明Float的泛型对象 i1.setVar(30); i2.setVar(30.1f); fun(i1); fun(i2); } // 接收Info对象,范围上限设置为Number,所以只能接收数字类型 public static void fun(Info<? extends Number> temp){ System.out.print(temp + "、"); } }
输出:
30、30.1、
注意:以上程序中的fun()方法只能接收数字类型的Info类的泛型对象,如果传递的是一个String类的泛型对象,就会编译错误。
class Info<T>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } } public class Demo01 { public static void main(String[] args) { Info<String> i1 = new Info<String>(); //声明String的泛型对象 i1.setVar("Hello"); fun(i1); } // 接收Info对象,范围上限设置为Number,所以只能接收数字类型 public static void fun(Info<? extends Number> temp){ System.out.print(temp + "、"); } }
也可以直接在类的声明处指定泛型的上限范围⭐⭐⭐
class Info<T extends Number>{ //此处泛型只能是数字类型 private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } }
以上代码Info类中的泛型范围是所有的数字,如果此时声明的泛型对象是Number的子类,不会出现问题;如果不是Number的子类就会出现问题。
正确声明:
public class Demo01 { public static void main(String[] args) { Info<Integer> i1 = new Info<Integer>(); //声明Integer的泛型对象 System.out.println("内容: " + i1); } }
错误的声明:
public class Demo01 { public static void main(String[] args) { Info<String> i1 = new Info<String>(); //声明String的泛型对象 System.out.println("内容: " + i1); } }
2.泛型的下限
应用场景:
当使用的泛型只能在本类及其父类类型上应用时,就必须使用泛型的范围下限进行配置。
案例:
class Info<T>{ //此处泛型只能是数字类型 private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } } public class Demo01 { public static void main(String[] args) { Info<Object> i1 = new Info<Object>(); //声明Object的泛型对象 Info<String> i2 = new Info<String>(); //声明String的泛型对象 i1.setVar(new Object()); i2.setVar("Java"); fun(i1); fun(i2); } // 只能接收String或Object类型的泛型 public static void fun(Info<? super String> temp){ System.out.println("内容:" + temp); } }
在fun()方法中,Info进行了下限的配置,只能接收泛型是String和Object类型的引用。
5.泛型与子类继承的限制
注意:
一个类的子类可以通过对象的多态性为其父类实例化。
但是
在泛型操作中:子类的泛型类型是无法使用父类的泛型类型接收的。
例如: Info<String>
不能使用 Info<Object>
接收。
下面的程序编译时会报错:
public class Demo01{ public static void main(String[] args){ Info<String> i1 = new Info<String>(); //泛型类型为String Info<Object> i2 = null; //泛型类型为Object i2 = i1; //两个Info对象进行转换,Info<String> --> Info<Object> } }
问题:这里为什么不能使用向上转型。
回答:如果将子类泛型变为父类泛型,则表示扩大了子类的内容。
6.泛型接口
可以在接口上声明泛型,声明格式在接口名称后面加<T>即可,格式:
泛型接口: [访问权限] interface 接口名称<泛型标识>{}
泛型接口的实现方式:
- 直接在子类后声明泛型
- 直接在子类实现的接口中明确的给出泛型类型
案例:
直接在子类的定义上声明泛型
interface Info<T>{ //在接口上定义泛型 public T getVar(); } class InfoImpl<T> implements Info<T>{ //定义泛型接口的子类 private T var; //定义属性 public InfoImpl(T var){ //通过构造方法设置属性内容 this.var = var; } @Override public T getVar() { return var; } public void setVar(T var) { this.var = var; } }
以上程序泛型接口的子类声明了与接口中同样的泛型标识
使用泛型接口的子类
public class Demo01 { public static void main(String[] args) { Info<String> info = null; //定义接口对象 info = new InfoImpl<String>("Java"); //通过子类实例化Info对象 System.out.println("内容:" + info.getVar()); } }
以上程序使用了泛型,但是依然可以使用对象的多态性通过一个子类为接口实例化。
案例:
直接在子类实现的接口中明确的给出泛型类型
interface Info<T>{ //在接口上定义泛型 public T getVar(); } class InfoImpl implements Info<String>{ //定义泛型接口的子类,指定类型为String private String var; //定义属性 public InfoImpl(String var){ //通过构造方法设置属性内容 this.var = var; } @Override public String getVar() { return var; } public void setVar(String var) { this.var = var; } }
以上程序,泛型接口的子类在实现接口时,直接在实现的接口处指定了具体的泛型类型String。
使用泛型接口的子类
public class Demo01 { public static void main(String[] args) { Info<String> info = null; //定义接口对象 info = new InfoImpl("Java"); //通过子类实例化Info对象,不用指定泛型 System.out.println("内容:" + info.getVar()); } }
7.泛型方法
可以在类中定义泛型化的方法。
注意:泛型方法所在的类可以是泛型类,也可以不是泛型类。
1.定义泛型方法
注意:泛型方法中可以定义泛型参数,参数的类型就是传入的数据类型。
格式: [访问权限] <泛型标识> 泛型标识 方法名称([泛型标识 参数名称])
案例:定义一个泛型方法。
class Demo{ public <T> T fun(T t){ //可以接收任意类型的数据 return t; } } public class Demo01 { public static void main(String[] args) { Demo demo = new Demo(); String str = demo.fun("Java"); //传递字符串 int i = demo.fun(30); //传递数字,自动装箱 System.out.println(str); System.out.println(i); } }
<T>的意思是:方法中传入或返回的泛型类型由调用方法时所传入的参数类型决定。
2.通过泛型方法返回泛型类实例
案例:通过方法返回泛型类实例
class Info<T extends Number>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } } public class Demo01 { public static void main(String[] args) { Info<Integer> i = fun(30); System.out.println(i.getVar()); } public static <T extends Number> Info<T> fun(T param){ Info<T> temp = new Info<T>(); //根据传入的数据类型实例化Info对象 temp.setVar(param); return temp; //返回实例化对象 } }
注意: <T extends Number>的意思是,方法中传入或返回的泛型类型由调用方法时所传入的参数类型决定。
3.使用泛型统一传入的参数类型
传入一个方法的泛型对象的泛型类型必须一致
案例:统一传入对象的泛型类型
class Info<T>{ private T var; public T getVar() { return var; } public void setVar(T var) { this.var = var; } public String toString(){ return this.var.toString(); } } public class Demo01 { public static void main(String[] args) { Info<String> info1 = new Info<String>(); //设置String为泛型类型 Info<String> info2 = new Info<String>(); info1.setVar("HELLO"); info2.setVar("kang"); add(info1, info2); } public static <T> void add(Info<T> info1,Info<T> info2){ System.out.println(info1.getVar() + " " + info2.getVar()); } }
注意:add()方法中的两个Info对象的泛型类型必须一致。
8.泛型数组
使用泛型方法时也可以传递或返回一个泛型数组。
案例:接收和返回泛型数组。
public class Demo01 { public static void main(String[] args) { Integer[] i = fun1(1,2,3,4,5,6); //返回泛型数组 fun2(i); //输出数组内容 } public static <T> T[] fun1(T...arg){ //接收可变参数,返回泛型数组 return arg; //返回泛型数组 } public static <T> void fun2(T[] param){ //接收泛型数组 System.out.println("接收泛型数组:"); for (T t : param) { System.out.print(t + "、"); } System.out.println(); } }
9.泛型的嵌套设置
在一个类的泛型中指定另一个类的泛型
案例: 定义两个泛型类
class Info<T,V>{ //指定两个泛型类型 private T var; private V value; public Info(T var, V value) { this.var = var; this.value = value; } public T getVar() { return var; } public void setVar(T var) { this.var = var; } public V getValue() { return value; } public void setValue(V value) { this.value = value; } } class Demo<S>{ private S info; public Demo(S info){ this.setInfo(info); } public S getInfo(){ return info; } public void setInfo(S info){ this.info = info; } }
Info类需要指定两个泛型类型,Demo类需要指定一个泛型类型。可以将Info设置成Demo的泛型类型。
设置嵌套泛型
public class Demo01 { public static void main(String[] args) { Demo< > demo = null; //将Info设置成Demo的泛型类型 Info<String, Integer> info = null; //指定Info类的两个泛型类型 info = new Info<String,Integer>("工资",30000); demo = new Demo<Info<String,Integer>>(info); //在Demo类中设置Info类对象 System.out.println("内容一:" + demo.getInfo().getVar()); System.out.println("内容二:" + demo.getInfo().getValue()); } }
输出:
内容一:工资
内容二:30000