目录
概念
只要你想要接触Java,你就不可能不接触泛型,就好比你不可能不用List一样。所以泛型到底是个什么玩意儿呢?泛型,概括来说,就是把类型参数化。当我们去定义个方法的时候,最熟悉的情况就是需要定义一个形参,然后调用此方法时传递实参。而泛型就是将类型由原来的具体类型参数化,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
一个最TM简单的栗子
List generic = new ArrayList();
generic.add("string");
generic.add(123);
for (int i = 0; i < generic.size(); i++) {
System.out.println((String) generic.get(i));
}
这里我们没有指定泛型,list里存放了string类型和int类型,来看下运行结果。
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
毫无疑问地报错了。
Arraylist可以存放任意类型的数据,这种错误在运行阶段才被发现,为了将这种错误提前扼杀在摇篮里,这个时候泛型就派上用场了!使用了泛型,这种问题在编译阶段就会被解决!!
IDE会直接提示你,你TM的应该放String类型的数据进去。
在实际工作中,泛型类或者方法能够很大程度地给代码瘦身,但是必须指定泛型来保证数据类型的一致。
泛型只在编译期有效,过了编译期,就会进行泛型擦除!
不多逼逼,直接上证据!
List<String> generic = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
System.out.println(generic.getClass().equals(integers.getClass()));
上述的结果为true,两个的类型都是class java.util.ArrayList!
泛型类
泛型有几种使用姿势:泛型类、泛型接口、泛型方法
泛型类最典型的就是上述的ArrayList
泛型类的使用姿势:
class Point<T>{// 此处可以随便写标识符号
private T x ;
private T y ;
public void setX(T x){//作为参数
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//作为返回值
return this.x ;
}
public T getY(){
return this.y ;
}
};
//IntegerPoint使用
Point<Integer> p = new Point<Integer>() ;
p.setX(new Integer(100)) ;
System.out.println(p.getX());
//FloatPoint使用
Point<Float> p = new Point<Float>() ;
p.setX(new Float(100.12f)) ;
System.out.println(p.getX());
此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。一般类型就是T表示(TYPE)。
这个T表示派生自Object类的任何类,比如String,Integer,Double等等。这里要注意的是,T一定是派生于Object类的。为方便起见,大家可以在这里把T当成String,即String在类中怎么用,那T在类中就可以怎么用!所以定义变量,作为返回值,作为参数传入的定义就很容易理解了。
泛型类构造实例的时候一定要加个尖括号传入具体类型,这点用过List就再熟悉不过了。
泛型类还可以支持多泛型!
类似于下面这样:
class MorePoint<T,U>{
}
假如我们定义了一个类,里面有多个字段,字段的类型希望可以用泛型去指定,且各不相同,此时就可以用到多泛型。
class MorePoint<T,U> {
private T x;
private T y;
private U name;
public void setX(T x) {
this.x = x;
}
public T getX() {
return this.x;
}
public void setName(U name){
this.name = name;
}
public U getName() {
return this.name;
}
}
//使用
MorePoint<Integer,String> morePoint = new MorePoint<Integer, String>();
morePoint.setName("harvic");
你甚至可以定义5个,10个,一万个泛型!
泛型接口
泛型接口的定义和泛型类是一样的:
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
public void setVar(T x);
}
这里需要注意的是泛型接口的实现类
class InfoImpl implements Info<String>{ // 定义泛型接口的子类
private String var ; // 定义属性
public InfoImpl(String var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
@Override
public void setVar(String var){
this.var = var ;
}
@Override
public String getVar(){
return this.var ;
}
}
像这种直接指定了泛型的(String),就不是泛型类,因为此时已经把T给填充了。
class InfoImpl<T> implements Info<T>{ // 定义泛型接口的子类
private T var ; // 定义属性
public InfoImpl(T var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
这种不指定T的才是实现泛型接口的泛型类。
泛型方法
不多逼逼,直接上泛型方法的定义代码
public class StaticFans {
//静态函数
public static <T> void StaticMethod(T a){
Log.d("harvic","StaticMethod: "+a.toString());
}
//普通函数
public <T> void OtherMethod(T a){
Log.d("harvic","OtherMethod: "+a.toString());
}
}
上面定义了一个泛型的静态方法,一个泛型的非静态方法;看出来泛型方法和泛型类定义的不同了吗?
泛型方法必须在返回类型前面加一个<T>
再来看下泛型方法的使用:
//静态方法
StaticFans.StaticMethod("adfdsa");//使用方法一
StaticFans.<String>StaticMethod("adfdsa");//使用方法二
//常规方法
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
方法一,可以像普通方法一样,直接传值,任何值都可以(但必须是派生自Object类的类型,比如String,Integer等),函数会在内部根据传进去的参数来识别当前T的类别。但尽量不要使用这种隐式的传递方式,代码不利于阅读和维护。因为从外观根本看不出来你调用的是一个泛型函数。
方法二,与方法一不同的地方在于,在调用方法前加了一个<String>来指定传给<T>的值,如果加了这个<String>来指定参数的值的话,那函数里所有用到的T类型也就是强制指定了是String类型。这是建议使用的泛型方式。
泛型数组
我们也可以定义泛型数组,泛型数组用T[]来表示
public static void main(String[] args) {
Integer[] result = generateArray(1,2,3);
Integer template[] = generateArray(4,5,6);
}
public static <T> T[] generateArray(T...args) {
return args;
}
Java可变长参数请自行去了解。
声明泛型上下边界
上面的例子你们看了现在应该都清楚一件事,即泛型T实际上是指所有Object下的子类,那如果想要缩小泛型的边界呢?比如我只想类型是Number类的,不要给我用来装什么乱七八糟的String类型,那这个时候就需要我们自己去约数泛型的边界。
不多逼逼,直接上代码:
class Point<T extends Number> {
private T x ;
private T y ;
public void setX(T x){//作为参数
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//作为返回值
return this.x ;
}
public T getY(){
return this.y ;
}
};
这样的话,这个类的泛型就只能指定为Number的子类,否则就会报错。
绕过泛型检测
上面说过,泛型检查只在编译期,运行期是会被擦除的,所以理所当然我们能通过反射绕过泛型的检查。通过invoke方法进行不同数据类型的插入。
List<String> generic = new ArrayList<>();
generic.add("abc");
Class<? extends List> clazz = generic.getClass();
Method m = clazz.getMethod("add", Object.class);
m.invoke(generic, 123);
System.out.println(generic);
总结
说白了,泛型变量,就是把类型也变成一个可以传值的参数,它跟String,Integer,Double等等的类的使用上没有任何区别,T只是一个符号,可以代表String,Integer,Double……这些类的符号,在泛型函数使用时,直接把T看到String,Integer,Double……中的任一个来写代码就可以了。唯一不同的是,要在函数定义的中在返回值前加上<T>标识泛型。