Java是一种强类型的语言,定义一个变量时需要指明其类型。Object是所有类的基类,在Java 1.5之前,为了让类具有通用性,通常使用Object来实现参数的任意化,如将String、Double等存储为Object类型,这个过程叫做自动装箱或向上转型。但是问题在于取数据时,必须做强制类型转换,将Object向下转型为String或Double等类型。向下转型存在很大的风险,需要事先知道具体的下转型类型是什么,一旦忘记或写错,在运行必然抛出异常,但在编译期间却不易发现,因此存在极大的安全隐患,应该尽量避免使用向下转型。
下面是使用Object的一个示例:
public class ObjectFoo {
private Object foo;
public ObjectFoo(Object f) {
this.foo = f;
}
public Object getFoo() {
return foo;
}
public void setFoo(Object foo) {
this.foo = foo;
}
}
具体调用如下:
ObjectFoo strFoo1 = new ObjectFoo("hello Object");
ObjectFoo intFoo1 = new ObjectFoo(100);
// 此处需要强制类型转换为String
String strRst = (String) strFoo1.getFoo();
// 此处需要强制类型转换为int
int intRst = (int) intFoo1.getFoo();
System.out.println("strFoo1.getFoo:" + strRst);
System.out.println("intFoo1.getFoo:" + intRst);
正因为这个过程麻烦且容易出错,在Java 1.5中推出了泛型这个新特性,其本质是参数化类型。泛型在使用时会指明具体的类型,因此不需要向下转型,编译的时候也会检查类型安全,泛型还可以提高代码重用性,概括来说就是简单易用并且安全,泛型可用在类、接口、方法中,分别叫做泛型类、泛型接口和泛型方法,下面会一一介绍。
依然以上述场景为例,先看一下利用泛型类是如何实现的:
public class GenericsFoo<T> {
private T foo;
public GenericsFoo(T f) {
this.foo = f;
}
public T getFoo() {
return foo;
}
public void setFoo(T foo) {
this.foo = foo;
}
}
泛型类的调用:
// 使用泛型时必须指定具体类型,如String,不再需要类型转换
GenericsFoo strFoo2 = new GenericsFoo<>("Hello Generics");
// 使用泛型时必须指定具体类型,如Integer,不再需要类型转换
GenericsFoo intFoo2 = new GenericsFoo<>(100);
System.out.println("strFoo2.getFoo:" + strFoo2.getFoo());
System.out.println("intFoo2.getFoo:" + intFoo2.getFoo());
从上面代码可以看到,跟普通的类相比,多出了
符号,T是自定义的标识符,也作为参数,用来传递数据类型,称之为类型参数。T可以理解为数据类型的占位符,在运行时会被替换为真正的数据类型,上面示例中T会被替换为String和Integer。
。
,叫做限制泛型。如果有多个类和接口,写法为
,但只能存在一个类,因为Java只能继承一个类,多个接口需写在类的后面。Class> classType = Class.forName("java.lang.String")
。使用泛型方法时不必指明参数类型,编译器会根据传递的参数自动查找具体的类型,这一点与泛型类不同。泛型方法与其所在类是不是泛型类没有关系,普通类也可拥有泛型方法,要定义一个泛型方法,只需要将类型参数放在修饰符之后、返回值之前即可。一旦定义了类型参数,就可以在参数列表、方法体及返回值中使用了。
下面代码是在上述泛型类的基础上增加了一个泛型方法:
public class GenericsFoo<T> {
private T foo;
public GenericsFoo(T f) {
this.foo = f;
}
public T getFoo() {
return foo;
}
public void setFoo(T foo) {
this.foo = foo;
}
// 泛型方法
public void getValue(V f) {
System.out.println("The value is:" + f);
}
}
泛型方法除了定义不同,调用方法就跟普通方法一样:
GenericsFoo strFoo2 = new GenericsFoo<>("Hello");
strFoo2.getValue(strFoo2.getFoo());
strFoo2.getValue("Generics Method");
strFoo2.getValue(255);
输出结果:
The value is:Hello
The value is:Generics Method
The value is:255
泛型接口跟泛型类定义很像,直接放上示例代码,相信大家都能看懂。
定义泛型接口:
public interface Info<T> {
public T getVar();
}
泛型接口的实现类:
public class InfoImp<T> implements Info<T> {
private T var;
public InfoImp(T var) {
this.var = var;
}
public void setVar(T var) {
this.var = var;
}
@Override
public T getVar() {
return var;
}
}
调用示例:
public class GenericsInterface {
public static void main(String[] args) {
Info strObj = new InfoImp<>("Hello Generics Interface");
System.out.println("The string value is:" + strObj.getVar());
Info intObj = new InfoImp<>(1024);
System.out.println("The integer value is:" + intObj.getVar());
}
}
输出结果:
The string value is:Hello Generics Interface
The integer value is:1024
上面泛型类的示例中public class GenericsFoo
并没有限制类型参数T的类型,实际上这里相当于Object,可以是任何类型。有时候我们需要对此处的类型做特定限制,这就是限制泛型了,听起来有点抽象,举个例子很容易就明白了。
限制泛型的基本语法为
,这里的extends需要广义理解,接口也是用它,这就表示T的类型只能是该SuperClass的子类或者是任何实现了这个接口的类的类型,这样就对泛型做了限制。
假设定义一个父类Geometry:
public class Geometry {
public String name;
public double area;
}
再定义3个类,其中Circle、Square继承自Geometry,Keyboard没有继承Geometry。
public class Circle extends Geometry {
}
public class Square extends Geometry {
}
public class Keyboard {
}
限制泛型类的定义如下:
public class GenericsRestrict<T extends Geometry> {
}
如上所示,泛型类型仅能是Geomerty或其子类,其他类型则会报错。
调用测试,如果是限制之外的类型就会报错:
public class TestClass {
public static void main(String[] args) {
GenericsRestrict square = new GenericsRestrict<>();
GenericsRestrict circle = new GenericsRestrict<>();
// 因为Keyboard没有继承Geometry,下面语句报错
GenericsRestrict keyboard = new GenericsRestrict<>();
}
}
当然extends后面可以是接口或类与多个接口,规范及原理相同,这里不再赘述。
为了解决类型被限制死了而不能动态根据需要来确定的缺点,引入了“通配符泛型”, 如 extends Collection>
,?代表未知类型,这个类型可以是Collection接口的所有实现类。使用通配符有以下两点需要注意:
>
,而没有extends,则可以是任意类型。 super Number>
表示只能接受 Number 及其父类,可接受的最低类型为Number。上限限制则是使用上面提到的extends关键字。如果在使用泛型时没有指定具体的数据类型,就会擦除泛型类型,并向上转型为 Object,在获取数据时必须向下强制类型转换,这与不使用泛型是一样的。