首先说一下什么是泛型
泛型,就是参数化类型。我们都知道在定义方法的时候,可以传入形参,在使用的时候实参,这是我们经常使用的,但是什么是参数化类型呢?顾名思义,就是将类型进行参数化,一般我们经常见到的分为三种:泛型类(class)、泛型接口(interface)、泛型方法(method)。
我们为什么使用泛型
可以通过如下代码说明:
- 将错误发现在编译器
List list = new ArrayList(); list.add("first"); list.add("second"); list.add(3); for (int i = 0; i < list.size(); i++) { System.out.println("name:"+(String) list.get(i)); }
以上代码在编译器不会出现问题,但是在运行的时候会抛出异常ClassCastException,是因为在向list集合放入数据的时候,list不会记录放入数据的类型,但是在取出的时候会恢复之前的类型,如果不小心将非String类型放入集合,在运行时就会出现问题。
相同的代码运行不同的数据类型,代码简化
public float add (float a, float b){ return a+b; } public double add(double a, double b){ return a+b; }
看到上面的代码就不多做解释了,如果还有需要int类型的加法,是不是还要新增加一个方法呢
泛型的定义方式
泛型类,示例如下:
public class Generic<T,K> { public T mData; public K mResult; public Generic(T data,K result){ mData = data; mResult = result; } public void print(){ System.out.println("data:"+mData); System.out.println("result:"+mResult); } }
引用类型变量T、K,在类名后面,用<>包裹,个数不限,类型变形一般使用大写字母,如T、E、K、V等
- 泛型接口,与泛型类相似:
public interface GenericInterface<T> { T next(); }
泛型方法:
public <T, K> T test_2(Generic<T, K> generic) { return generic.mData; }
在public与返回值之间定义泛型类型,并用<>包裹这是必不可少的,并且T、K可以出现在泛型方法的任何位置。以下方法并不是泛型方法,仅仅是返回值为泛型而已:
//不是泛型方法 public T next() { return data; }
泛型类的约束和局限性
public class Restrict<T> { }
- 不能使用基本类型
//不能使用基本类型 Restrict<double> restrict = new Restrict<>(); Restrict<Double> restrict1 = new Restrict<>();
- 运行时的类型检查仅支持原始类型
```java //运行时类型查询仅支持原始类型 Restrict<String> restrict2 = new Restrict<>(); //如下两种是错误的判断方式 System.out.println(restrict2 instanceof Restrict<Double>); System.out.println(restrict2 instanceof Restrict<T>); //正确的方式 Restrict<Integer> restrict3 = new Restrict<>(); System.out.println(restrict2.getClass() == restrict3.getClass()); ```
- 静态方法或者静态代码块中不能引用泛型类型变量
```java //泛型类的静态上下文失效 public K data; static { System.out.println(data); } public static void testF(){ System.out.println(data); } ``` 不能在静态域或方法中引用类型变量。因为泛型是要在对象创建的时候才知道是什么类型的,而对象创建的代码执行先后顺序是static的部分,然后才是构造函数等等。所以在对象初始化之前static的部分已经执行了,如果你在静态部分引用的泛型,那么毫无疑问虚拟机根本不知道是什么东西,因为这个时候类还没有初始化。
- 不能实例化类型变量
```java T data; public Restrict(){ this.data = new T(); } ```
泛型类的继承关系
public class Employee { } public class Worker extends Employee { } public class Pair<T> { }
以下创建对象方式是不允许的:
Pair<Employee> pair = new Pair<Worker>();
但是可以继承或者或者泛型类,如ArrayList和List:
public class ExtentPair extends Pair { } Pair pair1 = new ExtentPair();
通配符类型
通过上面的例子,Pair和Pair没有继承关系,就会出现如下错误:
public class Fruit { public String getColor(){ return "fruit"; }; } public class Orange extends Fruit { @Override public String getColor() { return "Orange"; } } public class Apple extends Fruit { @Override public String getColor() { return "Apple"; } } public class HongFuShi extends Apple { @Override public String getColor() { return "HongFuShi"; } } public class GenericType<T> { T data; public T getData() { return data; } public void setData(T data){ this.data = data; } }
public void print(GenericType<Fruit> fruitGenericType){ System.out.println(fruitGenericType.getData().getColor()); }
如下调用编译是不通过的:
//此种调用允许 GenericType<Fruit> genericType = new GenericType<>(); print(genericType); //此种调用不允许 GenericType<Orange> orangeGenericType = new GenericType<>(); print(orangeGenericType);
为了解决如上问题,引入了类型通配符 “?”
? extends X 表示类型的上界,类型参数是X的子类
? super X 表示类型的下界,类型参数是X的超类
这两种 方式从名字上来看,特别是super,很有迷惑性,下面我们来仔细辨析这两种方法。
? extends X
表示传递给方法的参数,必须是X的子类(包括X本身)
public void print2(GenericType<? extends Fruit> fruitGenericType){ System.out.println(fruitGenericType.getData().getColor()); }
如下调用则可以通过:
GenericType<Orange> orangeGenericType = new GenericType<>(); print2(orangeGenericType);
但是对泛型类GenericType来说,如果其中提供了get和set类型参数变量的方法的话,set方法是不允许被调用的,会出现编译错误,get方法则没问题,会返回一个Fruit类型的值。
为何?
道理很简单,? extends X 表示类型的上界,类型参数是X的子类,那么可以肯定的说,get方法返回的一定是个X(不管是X或者X的子类)编译器是可以确定知道的。但是set方法只知道传入的是个X,至于具体是X的那个子类,不知道。
总结:主要用于安全地访问数据,可以访问X及其子类型,并且不能写入非null的数据。
? super X
示传递给方法的参数,必须是X的超类(包括X本身)
但是对泛型类GenericType来说,如果其中提供了get和set类型参数变量的方法的话,set方法可以被调用的,且能传入的参数只能是X或者X的子类,get方法只会返回一个Object类型的值。
? super X 表示类型的下界,类型参数是X的超类(包括X本身),那么可以肯定的说,get方法返回的一定是个X的超类,那么到底是哪个超类?不知道,但是可以肯定的说,Object一定是它的超类,所以get方法返回Object。编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,但是X和X的子类可以安全的转型为X。
总结:主要用于安全地写入数据,可以写入X及其子类型。
限定类型变量 extend
public <T extends Comparable> T compare(T a,T b){
if(a.compareTo(b)>0) return a;else return b;
}
T为Comparable的子类,extend的左右均可以有多个,且可以为类或者接口,但是如果是具体的实现类必须放在右面的第一个,且只能extend一个类:
public <T,K extends Comparable & Serializable> T compare(T a, T b)