为什么我们需要泛型?
用处一:能让多种数据类型复用相同的代码
用处二:泛型在使用过程中不需要进行强制转换,泛型能使代码在编译期间就知道程序的错误
泛型类和泛型接口的定义
定义:参数化的类型,例如 void mMethod(int x, int y),其中x、y就是int类型的参数,而泛型就是在你编写函数的时候把具体的参数类型进行参数化了,只有在调用该函数或实例化对象时才知道具体传入类型变量
//定义泛型类 类名<T>,T为类型化参数
public class MGeneric<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
}
//定义泛型接口
public interface MGenericInterface<T> {
public T next();
}
}
//实现方式一
public class MGenericImpl<T> implements MGenericInterface<T> {
@Override
public T next() {
// TODO Auto-generated method stub
return null;
}
}
}
//实现方式二
public class MyGenericImpl1 implements MGenericInterface<String>{
@Override
public String next() {
// TODO Auto-generated method stub
return null;
}
}
}
泛型方法辨析
可以独立声明泛型方法,而不一定在泛型类里面声明
声明格式:
public <T> 返回类型 methodName(T a){
}
}
泛型方法声明是不受限制的,既可以定义到普通类中,也可以定义到泛型类里面。但定义到泛型类里面有点是需要注意的,就是泛型类里面的T,和泛型方法里面的T是不同类型的
//定义泛型类 类名<T>,T为类型化参数
public class MGeneric<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public <E> E getValue(E e) {
return e;
}
public static <E> void show(E e) {
System.out.println(e.toString());
}
public <T> void setData1(T data) {
// this.data = data;//注意:这样会报错,因为泛型方法上的T,与泛型类的T不是同一种类型
//如果非要这么写,可以不同于泛型类定义的字母来定义泛型方法,然后通过强制转换的形式进行赋值,例如setData2()方法
}
public <E> void setData2(E data) {
this.data = (T) data;
}
}
}
如何辨别是不是泛型方法,主要看调用该方法时传入的参数类型是否够广泛(这么描述没毛病,嘿嘿)
限定类型变量
声明泛型方法是可以通过<T extends 类/接口> 的定义来限定类型,例如:
public <T extends Comparable<T>> T min(T a, T b) {
T ret = a.compareTo(b) > 0 ? a : b;
return ret;
}
注意了:Java语言是单继承多实现的规则的,这个规则同样适用于泛型限定类型变量,但是如果同时限定类和接口的情况下,类必须写在接口前面,泛型类和泛型方法都可以使用这种限定类型变量,例如:
//ArrayList类排前面,后面可以加一个或多个接口
public <T extends ArrayList&Comparable&Serializable> T min(T a, T b) {
a.add(6);
T ret = a.compareTo(b) > 0 ? a : b;
return a;
}
泛型中的约束和局限性
泛型并不是无所不能的。例如:
//不能实例化类型变量
public MGeneric() {
//this.data = new T();
}
//静态域或方法里不能引用类型变量
// private static T instance;
// private static T method(T a) {return a}
//静态域或方法里不能引用类型变量
// private static T instance;
// private static T method(T a) {return a}
- 不能实例化类型变量。如:
new T()
是不允许的 - 静态域或者方法里面不能引用类型变量。为什么不允许静态域或方法引用类型变量:泛型定义里说了,只有在调用该函数或实例化对象时才知道具体传入类型变量,而Java中静态变量是先于类构造器执行的,虚拟机压根不知道你静态引用的T是什么。所以静态域或方法是不允许引用类型变量
- 静态方法本身是泛型方法可行
- 泛型类型不能是基础类型,例如int/float...之类的基础类型都是不行的,只能传对象
- 泛型不允许使用 instanceof 的,例如:
if(restrict instanceof Restrict<Double>)
是会报错的 - 泛型类不能extends Exception/Throwable,不能捕获泛型类对象
泛型类型的继承规则
/* 泛型类可以继承或者扩展其他泛型类,比如List和ArrayList */
private static class ExtendMGeneric<T> extends MGeneric<T>{
}
public static void main(String[] args) {
//注意:虽然Fruit为Apple的基类,但是,fruitGen和appleGen是没有任何关系的
MGeneric<Fruit> fruitGen = new MGeneric<Fruit>();
MGeneric<Apple> appleGen = new MGeneric<Apple>();
//MGeneric<Fruit> fruitGenTest = new MGeneric<Apple>();//这是会报错的
MGeneric<String> gen = new ExtendMGeneric<>();
}
}
由于类型间的继承关系对泛型类对象没有任何影响,为了能使其有影响,所以引入通配符 ?
通配符类型 ?
public static void useExtends() {
//注意:虽然Fruit为Apple的基类,但是,fruitGen和appleGen是没有任何关系的
MGeneric<Fruit> fruitGen = new MGeneric<Fruit>();
MGeneric<Apple> appleGen = new MGeneric<Apple>();
//MGeneric<Fruit> fruitGenTest = new MGeneric<Apple>();//这是会报错的
print(fruitGen); //可以
//print(appleGen);//不可以
print2(appleGen);//可以
print2(fruitGen);//可以
// extends 修饰的,确保其能安全读取本身或子类数据
MGeneric<? extends Fruit> temp = new MGeneric<>();
//temp.setData(new Fruit());//报错。因为<? extends Fruit> 主要是限制了安全访问类型的上界
Fruit fruit = temp.getData();
}
public static void print(MGeneric<Fruit> p) {
System.out.println(p.getData());
}
public static void print2(MGeneric<? extends Fruit> p) {//表示传入MGeneric 类型参数为 Fruit子类或Fruit本身
System.out.println(p.getData());
}
public static void print(MGeneric<Fruit> p) {
System.out.println(p.getData());
}
public static void print2(MGeneric<? extends Fruit> p) {//表示传入MGeneric 类型参数为 Fruit子类或Fruit本身
System.out.println(p.getData());
}
public static void useSuper() {
MGeneric<Fruit> fruitGen = new MGeneric<Fruit>();
MGeneric<Apple> appleGen = new MGeneric<Apple>();
MGeneric<Hongfushi> hfsGen = new MGeneric<>();
//print3(hfsGen);//Apple子类被限制使用
print3(fruitGen);
print3(appleGen);
//注意了,super 修饰的,确保其能安全写入其本身或子类数据
MGeneric<? super Apple> x = new MGeneric<>();
//x.setData(new Fruit());//setData只能是Apple或其子类,因为Apple及其子类能安全转型为Apple类,而其父类,例如Fruit不能保证其能转型为Apple类
x.setData(new Apple());
x.setData(new Hongfushi());
Object data = x.getData(); //x的最大上界类为Object
}
public static void print3(MGeneric<? super Apple> p) {
System.out.println(p.getData());
}
}
public static void print3(MGeneric<? super Apple> p) {
System.out.println(p.getData());
}
Java泛型在虚拟机上使用的方式是类型擦除,意思是使用Object代替T。如果<T extends ArrayList&Comparable> 这类的就是使用ArrayList来代替T,需要使用到Comparable里面方法的时候就会进行强制转换