一、泛型的介绍
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。Collection接口、List接口这个就是类型参数,即泛型。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。Java语言引入泛型的好处是安全简单。
在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
二、泛型引入
泛型是JDK1.5中引⼊的⼀个新特性,其本质是参数化类型,把类型作为参数传递。
public class Cache {
Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
public class Demo {
public static void main(String[] args) {
Cache cache = new Cache();
cache.setValue(134);
int value = (int) cache.getValue();
cache.setValue("hello");
String value1 = (String) cache.getValue();
System.out.print(value1);
}
}
执行上述代码,其输出内容为:
hello
当Cache类的value属性的类型定义为Object时,我们在使用这个属性的时候其方法也很简单,只要我们做正确的强制类型转换就好了。 但是泛型却给我们带来了不一样的编程体验,我们使用泛型将上述Cache类进行改造,如下所示:
public class Cache<T> {
T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
public class Demo {
public static void main(String[] args) {
Cache<String> cache1 = new Cache();
cache1.setValue("123");
String value1 = cache1.getValue();
System.out.println(value1);
Cache<Integer> cache2 = new Cache();
cache2.setValue(456);
int value2 = cache2.getValue();
System.out.println(value2);
}
}
执行上述代码,其输出结果为:
123
456
这就是泛型,它将value这个属性的类型也参数化了,这就是所谓的参数化类型。
泛型的特性:
- 只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除
- 在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
三、泛型的三种使用方式
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。
3.1 泛型类
如果一个类被<T>的形式定义,那么它就被称为是泛型类,前面的例子就是属于泛型类。
public class Generic<T> {
// field这个成员变量的类型为T,T的类型由外部指定
private T field;
// 泛型构造方法形参field的类型也为T,T的类型由外部指定
public Generic(T field) {
this.field = field;
}
// 泛型方法getField的返回值类型为T,T的类型由外部指定
public T getField() {
return field;
}
}
此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
例如将T修改为E,如下所示:
public class Cache<E> {
E value;
public E getValue() {
return value;
}
public void setValue(E value) {
this.value = value;
}
}
public class Demo {
public static void main(String[] args) {
Cache<String> cache1 = new Cache<>();
cache1.setValue("123");
String value1 = cache1.getValue();
System.out.println(value1);
Cache<Integer> cache2 = new Cache<>();
cache2.setValue(456);
int value2 = cache2.getValue();
System.out.println(value2);
}
}
执行上述代码,其输出结果为:
123
456
虽然T可以写为其它的标识,但是一般情况下还是建议使用常见的一些形式,并且按照其约定的释义去使用相关的形式参数。
T:代表一般的任何类
E:代表 Element
K:代表 Key 的意思
V:代表 Value 的意思,通常与K一起配合使用
3.2 泛型接口
定义一个泛型接口Generator:
public interface Generator<T> {
public T get();
}
未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中。
public class ColorGenerator<T> implements Generator<T>{
@Override
public T get() {
return null;
}
}
未传入泛型实参时,如果不声明泛型,编译器会报错,如下图所示:
传入泛型实参时,在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型。
import java.util.Random;
public class ColorGenerator implements Generator<String> {
private String[] colors = {"red","blue","green"};
@Override
public String get() { //方法返回值 T 要换成 String
Random random = new Random();
return colors[random.nextInt(3)];
}
}
public class Demo {
public static void main(String[] args) {
Generator<String> generator = new ColorGenerator();
String color = generator.get();
System.out.print(color);
}
}
执行上述代码,其输出结果为:
red
3.3 泛型方法
3.3.1 泛型方法-普通使用
public class GenericMethod<T>{
public <T> void demo1(T t){
if(t instanceof String){
System.out.println("字符串");
}else if(t instanceof Integer){
System.out.println("int类型");
}else {
System.out.println("其它类型");
}
}
/**
* 声明的类型参数<E>,其实也是可以当作返回值的类型的。
* 为了避免混淆,如果在一个泛型类中存在泛型方法,那么两者的类型参数最好不要同名。
*/
public <E> E demo2(E e){
return e;
}
private T field;
// 此方法为泛型类的成员方法,但不是泛型方法!
public T getField() {
return field;
}
}
- demo1方法的public与返回值(void)中间的<T>非常重要,可以理解为声明此方法为泛型方法。
- 只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
- <T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
- 与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
- 当调用泛型方法时,传入参数,编译器就会确定类型,这个类型包括形参的类型和返回值类型(如果返回值类型也是使用泛型)。
public class Demo {
public static void main(String[] args) {
GenericMethod<String> genericMethod=new GenericMethod<>();
genericMethod.<Integer>demo1(123456);
String value=genericMethod.demo2("张三");
System.out.println(value);
}
}
执行上述代码,其输出内容如下:
int类型
张三
泛型类,是在实例化类的时候指明泛型的具体类型。泛型方法,是在调用方法的时候指明泛型的具体类型。
3.3.2 泛型方法-可变参数
public class GenericMethod{
public <T> void demo1(T...args){
for (T arg : args) {
System.out.println(arg);
}
}
}
public class Demo {
public static void main(String[] args) {
GenericMethod genericMethod=new GenericMethod();
genericMethod.demo1("张三","李四","王五");
}
}
执行上述代码,其输出内容如下:
张三
李四
王五
3.3.3 泛型方法-静态方法
public class StaticGeneric<T> {
// 类加载在类实例化之前,获得类上定义的泛型需要类实例化
public static void show(T t){};
}
上述代码,在IDEA编辑的时候就会报错(如下图所示),泛型类中的使用了泛型的成员方法前面由于使用了static,这就要求在进行加载时就要获取到定义的参数类型,显然这个时候是做不到的。