2.6 泛型
泛型的本质是参数化类型,泛型提供了编译时类型的安全检测机制,该机制允许程序在编译时检测非法的类型,比如要实现一个能够对字符串(String)、整形(Int)、浮点型(Float)、对象(Object)进行大小比较的方法,就可以使用Java泛型。
在不使用泛型的情况下,我们可以通过引用Object类型来实现参数的任意化,因为在Java中Object类是所有类的父类,但在具体使用时需要进行强制类型转换。强制类型转换要求开发者必须明确知道实际参数的引用类型,不然可能引起前置类型转换错误,在编译期无法识别这种错误,只能在运行期检测这种错误(即只有在程序运行出错时才能发现该错误)。而使用泛型的好处是在编译期就能够检查类型是否安全,同时所有强制性类型转换都是自动和隐式进行的,提高了代码的安全性和重用性。
2.6.1 泛型标记和泛型限定:E、T、K、V、N、?
在使用泛型前首先要了解有哪些泛型标记,如表2-2所示。
表2-2
类型通配符使用“? ”表示所有具体的参数类型,例如List<? >在逻辑上是List<String>、List<Integer>等所有List<具体类型实参>的父类。
在使用泛型的时候,若希望将类的继承关系加入泛型应用中,就需要对泛型做限定,具体的泛型限定有对泛型上线的限定和对泛型下线的限定。
1.对泛型上限的限定:<? extends T>
在Java中使用通配符“? ”和“extends”关键字指定泛型的上限,具体用法为<? extends T>,它表示该通配符所代表的类型是T类的子类或者接口T的子接口。
2.对泛型下限的限定:<? super T>
在Java中使用通配符“? ”和“super”关键字指定泛型的下限,具体用法为<? super T>,它表示该通配符所代表的类型是T类型的父类或者父接口。
2.6.2 泛型方法
泛型方法指将方法的参数类型定义为泛型,以便在调用时接收不同类型的参数。在方法的内部根据传递给泛型方法的不同参数类型执行不同的处理方法,具体用法如下:
public static void main(String[] args) {
generalMethod("1",2, new Wroker());
}
//定义泛型方法generalMethod, printArray为泛型参数列表
public static < T > void generalMethod( T ... inputArray )
{
for ( T element : inputArray ){
if (element instanceof Integer) {
System.out.println("处理Integer类型数据中...");
} else if (element instanceof String) {
System.out.println("处理String类型数据中...");
} else if (element instanceof Double) {
System.out.println("处理Double类型数据中...");
} else if (element instanceof Float) {
System.out.println("处理Float类型数据中...");
} else if (element instanceof Long) {
System.out.println("处理Long类型数据中...");
} else if (element instanceof Boolean) {
System.out.println("处理Boolean类型数据中...");
} else if (element instanceof Date) {
System.out.println("处理Date类型数据中...");
}
else if (element instanceof Wroker) {
System.out.println("处理Wroker类型数据中...");
}
}
}
以上代码通过public static < T > void generalMethod( T ... inputArray )定义了一个泛型方法,该方法根据传入数据的不同类型执行不同的数据处理逻辑,然后通过generalMethod("1",2, new Wroker())调用该泛型方法。注意,这里的第1个参数是String类型,第2个参数是Integer类型,第3个参数是Wroker类型(这里的Wroker是笔者自定义的一个类),程序会根据不同的类型做不同的处理。
2.6.3 泛型类
泛型类指在定义类时在类上定义了泛型,以便类在使用时可以根据传入的不同参数类型实例化不同的对象。
泛型类的具体使用方法是在类的名称后面添加一个或多个类型参数的声明部分,在多个泛型参数之间用逗号隔开。具体用法如下:
//定义一个泛型类
public class GeneralClass<T> {
public static void main(String[] args) {
//根据需求初始化不同的类型
GeneralClass<Integer> genInt =new GeneralClass<Integer>();
genInt.add(1);
GeneralClass<String> genStr =new GeneralClass<String>();
genStr.add("2");
}
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
在以上代码中通过public class GeneralClass<T>定义了一个泛型类,可根据不同的需求参数化不同的类型(参数化类型指编译器可以自动定制作用于特定类型的类),比如参数化一个字符串类型的泛型类对象:
new GeneralClass<String>()。
2.6.4 泛型接口
泛型接口的声明和泛型类的声明类似,通过在接口名后面添加类型参数的声明部分来实现。泛型接口的具体类型一般在实现类中进行声明,不同类型的实现类处理不同的业务逻辑。具体的实现代码如下:
//定义一个泛型接口 public interface IGeneral<T> { public T getId(); } //定义泛型接口的实现类 public class GeneralIntergerImpl implements IGeneral<Integer>{ @Override public Integer getId() { Random random = new Random(100); return random.nextInt(); } public static void main(String[] args) { //使用泛型 GeneralIntergerImpl gen = new GeneralIntergerImpl(); System.out.println(gen.getId()); } }
以上代码通过public interface IGeneral<T>定义了一个泛型接口,并通过public class GeneralIntergerImpl implements IGeneral<Integer>定义了一个Integer类型的实现类。
2.6.5 类型擦除
在编码阶段采用泛型时加上的类型参数,会被编译器在编译时去掉,这个过程就被称为类型擦除。因此,泛型主要用于编译阶段。在编译后生成的Java字节代码文件中不包含泛型中的类型信息。例如,编码时定义的List<Integer>和List<String>在经过编译后统一为List。JVM所读取的只是List,由泛型附加的类型信息对JVM来说是不可见的。
Java类型的擦除过程为:首先,查找用来替换类型参数的具体类(该具体类一般为Object),如果指定了类型参数的上界,则以该上界作为替换时的具体类;然后,把代码中的类型参数都替换为具体的类。