看完这篇你一定懂什么是Java泛型!

目录

概念

一个最TM简单的栗子 

泛型类

泛型接口

泛型方法

泛型数组

声明泛型上下边界

绕过泛型检测

总结


概念

只要你想要接触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>标识泛型。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值