黑马程序员——JAVA基础---使用泛型

时间:2021-08-13 12:39:59

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

在介绍泛型之前,先来看一个例子。
【例】 在项目中创建Test类,该类中使基本类型向上转型为Object类型。


public class Test {
private Object b; //定义Object类型成员变量
public Object getB() { //设置相应getXXX()方法
return b;
}
public void setB(Object b) { //设置相应setXXX()方法
this.b = b;
}
public static void main(String[] args) {
Test t=new Test();
t.setB(new Boolean(true)); //向上转型操作
System.out.println(t.getB())
t.setB(new Float(12.3));
Float f=(Float)(t.getB()); //向下转型操作
System.out.println(f);
}
}

运行本实例,运行结果如图所示。

黑马程序员——JAVA基础---使用泛型

 图  使基本类型向上转型为Object类型
在本实例中,Test类中定义了私有的成员变量b,它的类型为Object类型,同时相应地为其定义了setXXX()与getXXX()方法。在类主方法中,将new Boolean(true)对象作为setB()方法的参数,由于setB()方法的参数类型为Object,这样就实现了“向上转型”的操作,同时,在调用getB()方法时,将getB()方法返回的Object对象以相应的类型返回,这个就是“向下转型”操作。问题通常就会出现在这里,我们知道“向上转型”是安全的,而“向下转型”时,就容易出现问题,例如:

t.setB(new Float(12.3));
Integer f=(Integer)(t.getB());
System.out.println(f);
在上述代码中,并不存在语法错误,所以可以被编译器所接受,但在执行时会出现ClassCast- Exception异常。从上述代码可以看出,“向下转型”操作通常会出现问题,泛型机制能有效地解决了这一问题。

一 定义泛型类

Object类为最上层的父类,很多程序员为了使程序更为通用,设计程序时通常使传入的值与返回的值都以Object类型为主。当需要使用这些实例时,必须正确地将该实例转换为原来的类型,否则在运行时将会发生ClassCastException异常。

下面介绍泛型机制,其语法如下:
类名
其中,T代表一个类型的名称。
将前面的例子进行改写,在定义类时使用泛型的形式,关键代码如下所示。
在项目中创建OverClass类,该类定义了泛型类。

public class OverClass<T>{                      //定义泛型类
private T over; //定义泛型成员变量
public T getOver() { //设置getXXX()方法
return over;
public void setOver(T over) { //设置setXXX()方法
this.over = over;
public static void main(String[] args) {
//实例化一个Boolean型的对象
OverClass<Boolean> over1=new OverClass<Boolean>();
//实例化一个Float型的对象
OverClass<Float> over2=new OverClass<Float>();
over1.setOver(true); //不需要进行类型转换
over2.setOver(12.3f);
Boolean b=over1.getOver(); //不需要进行类型转换
Float f=over2.getOver();
System.out.println(b);
System.out.println(f);
}
}

运行上述代码,运行结果与图所示的运行结果一致。在例中定义类时,在类名后添加了一个“<T>”,便是使用了泛型机制,可以将OverClass类称为泛型类,同时返回和接受的参数使用T这个类型。最后,在主方法中可以使用Over<Boolean>形式返回一个Boolean型的对象,使用OverClass<Float>形式返回一个Float型的对象,使这两个对象分别调用setOver()方法就不需要进行显式“向上转型”操作,setOver()方法直接接受相应类型的参数,而调用getOver()方法时,不需要进行“向下转型”操作,而直接将getOver()方法返回的值赋予相应的类型变量即可。
从例中可以看出,使用泛型定义的类在声明该类对象时,可以根据不同的需求指定<T>真正的类型,而在使用类中的方法传递或返回数据类型时,将不再需要进行类型转换操作,而是使用在声明泛型类对象时<>符号中设置的数据类型。
使用泛型这种形式将不会发生ClassCastException异常,因为在编译器中就可以检查类型匹配是否正确。

在项目中定义泛型类。


OverClass<Float> over2=new OverClass<Float>();
over2.setOver(12.3f);
//Integer i=over2.getOver(); //不能将Boolean型的值赋予Integer变量
在上例中,由于over2对象在实例化时已经指定类型为Float,而最后一条语句却将该对象获取的Float类型值赋予Integer类型,所以编译器会报错。这种问题如果使用“向下转型”操作,就会在运行上述代码时才会发生问题。

说明:
在定义泛型类时,一般将类型名称使用T来表达,而容器的元素使用E来表达,具体的设置读者可以参看API。

二 泛型的常规用法

1.定义泛型类时声明多个类型
在定义泛型类时,可以声明多个类型,语法如下:
MutiOverClass

public class ArrayClass <T>{
private T[] array; //定义泛型数组
public void SetT(T[] array){ //设置SetXXX()方法为成员数组赋值
this.array=array;
}
public T[] getT(){ //获取成员数组
return array;
}
public static void main(String[] args) {
ArrayClass<String> a=new ArrayClass<String>();
String[] array={"成员1","成员2","成员3","成员4","成员5"};
a.SetT(array); //调用SetT()方法
for(int i=0;i<a.getT().length;i++){
System.out.println(a.getT()[i]);//调用getT()方法返回数组中的值
}
}
}

在Eclipse中运行本实例,运行结果如图所示。
黑马程序员——JAVA基础---使用泛型

图 定义泛型类时声明数组类型
由上可见,可以在使用泛型机制时声明一个数组,但是不可以使用泛型来建立数组的实例,如下面的代码就是错误的:

public class ArrayClass <T>{
//private T[] array=new T[10]; //不能使用泛型来建立数组的实例
...
}

3.集合类声明容器的元素
可以使用K和V两个字符代表容器中的键值和与键值相对应的具体值。

【例】 在项目中创建MutiOverClass类,在该类中使用了集合类声明容器的元素。

public class MutiOverClass <K,V>{
public Map<K,V> m=new HashMap<K,V>(); //定义一个集合HashMap实例
public void put(K k,V v){//设置put()方法,将具体值与对应具体值的键值放入集合中
m.put(k, v);
}
public V get(K k){ //根据键值获取键值
return m.get(k);
}
public static void main(String[] args) {
//实例化泛型类对象
MutiOverClass<Integer,String> mu=new MutiOverClass<Integer,
String>();
for(int i=0;i<5;i++){
//根据集合的长度循环将键值与具体值放入集合中
mu.put(i,"我是集合成员"+i);
}
for(int i=0;i<mu.m.size();i++){
//调用get()方法获取集合中的值
System.out.println(mu.get(i));
}
}
}

在Eclipse中运行本实例,运行结果如图所示。

黑马程序员——JAVA基础---使用泛型

图 集合类声明容器的元素
其实在例中定义的泛型类MutiOverClass类属于多此一举,因为在Java中这些集合框架已经都被泛型化了,可以在主方法中直接使用public Map


public class AnyClass{
public static void main(String[] args) {
//定义ArrayList容器,设置容器内的值类型为Integer
ArrayList<Integer> a=new ArrayList<Integer>();
a.add(1); //为容器添加新值
for(int i=0;i<a.size();i++){
//根据容器的长度循环显示容器内的值
System.out.println(“获取ArrayList容器的值:”+a.get(i));
//定义HashMap容器,设置该容器的键值与具体内容的类型分别为Integer与String型
Map<Integer,String> m=new HashMap<Integer, String>();
for(int i=0;i<5;i++){
m.put(i,"成员"+i); //为容器填充键值与内容
}
for(int i=0;i<m.size();i++){
System.out.println(“获取Map容器的值”+m.get(i));
//根据键值获取容器的内容
//定义Vector容器,使容器中的内容为String型
Vector<String> v=new Vector<String>();
for(int i=0;i<5;i++){
v.addElement(“成员”+i); //为Vector容器添加内容
for(int i=0;i<v.size();i++){
//显示容器中的内容
System.out.println("获取Vector容器的值"+v.get(i));


public class AnyClass{
public static void main(String[] args) {
//定义ArrayList容器,设置容器内的值类型为Integer
ArrayList a=new ArrayList();
a.add(1); //为容器添加新值
for(int i=0;i

三 泛型的高级用法

泛型的高级用法包括限制泛型可用类型、使用类型通配符等。
1.限制泛型可用类型
默认可以使用任何类型来实例化一个泛型类对象,但是Java中也对泛型类实例的类型做了限制,语法如下:
class 类名称
其中,anyClass是指某个接口或类。
使用泛型限制后,泛型类的类型必须实现或继承了anyClass这个接口或类。无论anyClass是接口或类,在进行泛型限制时,都必须使用extends关键字。
【例】 在项目中创建LimitClass类,在该类中限制泛型类型。


public class LimitClass<T extends List>{ //限制泛型的类型
public static void main(String[] args) {
LimitClass<ArrayList> l1=new LimitClass<ArrayList>();
//可以实例子化实现List接口的类
LimitClass<LinkedList> l2=new LimitClass<LinkedList>();
//这句是错误的,因为HashMap没有实现List()接口
//LimitClass<HashMap> l3=new LimitClass<HashMap>();
}
}
在例中,将泛型做了限制,设置泛型类型必须实现List接口,例如,ArrayList和LinkedList都实现了List接口,而HashMap没有实现List接口,所以在这里不能实例化HashMap类型的泛型对象。
当没有使用extends关键字限制泛型类型时,默认Object类下的所有子类都可以实例化泛型类对象。如图所示的两个语句是等价的。

黑马程序员——JAVA基础---使用泛型

图 等价的两个泛型类

2.使用类型通配符
在泛型机制中,提供了类型通配符,其主要作用是在创建一个泛型类对象时,限制这个泛型类的类型实现或继承某个接口或类的子类。要声明这样一个对象,可以使用“?”通配符来表示,同时,依然需要使用extends关键字来对泛型加以限制。
使用泛型类型通配符的语法如下:
泛型类名称

List<String> l1=new ArrayList<String>();            //实例化一个ArrayList对象
l1.add("成员"); //在集合中添加内容
List<?> l2=l1; //使用通配符
List<?> l3=new LinkedList<Integer>();
System.out.println(l2.get(0)); //获取集合中第一个值

在例中,List<?>类型的对象可以接受String类型的ArrayList集合,也可以接受Integer类型的LinkedList集合。也许有的读者会有疑问,List<?> l2=l1语句与List l2=l1存在何种本质区别?这里需要注意,使用通配符声明的名称实例化的对象不能对其加入新的信息,只能获取或删除,例如:

l1.set(0, "成员改变"); //没有使用通配符的对象调用set()方法,
//l2.set(0, "成员改变");
//使用通配符的对象调用set()方法,不能被调用
//l3.set(0, 1);

l2.get(0); //可以使用l2的实例获取集合中的值
l2.remove(0); //根据键值删除集合中的值
从上述代码中可以看到,由于对象l1是没有使用A<?>这种形式初始化出来的对象,所以它可以调用set()方法改变集合中的值,但l2与l3则是通过使用通配符的方式创建出来的,所以不能改变集合中的值。

技巧:
泛型类型限制除了可以向下限制之外,还可以向上限制,只要在定义时使用super关键字。例如,A

public class ExtendClass<T1>{
//...
}
class SubClass<T1,T2,T3> extends ExtendClass<T1>{
//...
}
如果在SubClass类继承ExtendClass类时保留父类的泛型类型,需要在继承时指明,如果没有指明,直接使用extends ExtendsClass语句进行继承操作,则SubClass类中的T1、T2、T3都会自动变为Object,一般情况下,都将父类的泛型类型保留。

定义的泛型接口也可以被实现。
在项目中创建一个类文件,在该类中实现泛型接口。

interface i<T1>{
//...
}
class SubClass2<T1,T2,T3> implements i<T1>{
//...
}

四 泛型总结

泛型的使用方法如下:
泛型的类型参数只能是类类型,不可以是简单类型,如A这种泛型定义就是错误的。
泛型的类型个数可以是多个。
可以使用extends关键字限制泛型的类型。
可以使用通配符限制泛型的类型。