关于泛型的一些理解(通俗易懂)

简介:用通俗图文和比喻去理清一下泛型的结构和语法以及一些特点。
参考文章:https://www.cnblogs.com/yangming1996/p/9199557.html
目录:
1 泛型概述
2 定义泛型类/接口/方法
3 通配符
4 一些特点

泛型概述

泛型我个人把他理解成『泛化的类型』。而泛化其实有点像继承的近义词,比如特殊泛化成一般子类泛化成父类红色、蓝色、绿色泛化成颜色。那么泛型是干嘛的呢?

举一个例子

如下图所示,现在有一个房子,不论是男人,女人,婴儿,残疾人还是外星人,都可以住在里面。突然有一天,我们想给残疾人发放补贴,但因为房子里什么人都有,我们就不能直接见人就给钱,而是要去判断一下,这个人是不是残疾人。
在这里插入图片描述
为了方便起见,我们就规定了一个房子,这里面只能住残疾人(如下图),如果住进其他人,我们在编译阶段就不给他通过。这样,我们就可以放心地对这个房子里的人进行操作——发补贴,而不用去判断他到底是不是残疾人。
在这里插入图片描述
这些事情其实对于JVM来说,并没有任何影响,因为泛型只用作编译期检查,在编译后会被擦除,所以泛型只是为了让我们的代码可读性更高,操作更方便并在编译阶段去预防一些错误的发生。

泛型的定义

对于类(或接口),我们一般这样定义一个泛型:

public class Demo1 <T>
public interface IDemo1 <T>

这里的这个T,是我们自己定义的,符合规范即可,一般常用取名方式有:T,E,K,V。在定义过后,这个类就可以使用这个T当做数据类型,比如:

 public class Demo1 <T>  {
 	private T number;
 }

这里正好可以顺便提一下『类型擦除』。意思就是上面的代码,在JVM眼中,其实是这样的:

 public class Demo1  {
 	private Object number;
 }

在大部分情况下,泛型类型都会被Object给擦除,不过也有特例,这个在后面『通配符』中会提到。
在泛型类中,除了定义泛型属性,还可以去定义泛型方法:

public class Demo1 <T> {
    public T f(T number){
        return number;
    }
}

但如果一个类上并没有声明泛型,我们还想声明泛型函数,就需要在函数前定义想要用的泛型,比如:

public class Demo2 {
    public <T> T f(T number){
        return number;
    }
}

使用方式:
对象.<String>f(...);

通配符

我们以前是选择一个具体的类型传进去,比如:
ArrayList<number> list = new ArrayList<>();或者
ArrayList<String> list = new ArrayList<>();
现在,我们不写具体的类型,而用?代替,它就代表了任意一种类型。
ArrayList<?> list = new ArrayList<>();
那我们的通配符「『?』和定义的泛型『T』,就感觉很相似了,但其实是有本质上的不同的。我们可以把通配符理解成某种“具体”的类型,只不过这种类型,是一个很笼统与抽象的,可以海纳百川的。(也就是说,我们的『?』,就和String,Integer是同一级别的)

举一个例子

我们先创建一个简单的泛型类——Node,里面只有一个属性——T Data,并在类中编写相应的Setter/Getter方法以及无参和全参的构造方法。然后在测试类中运行如下代码:
在这里插入图片描述
我们可以看到,因为在f函数中,形参的类型要求是Node<Number>,所以f(intergerNode)会编译错误。(虽然Integer是Number的子类,但可以看出,在泛型中,子类并不能被当做一种特殊的父类)
而如果我们使用通配符,把f函数的参数改为Node<?> node,问题就迎刃而解了:
在这里插入图片描述

上下边界

这个其实非常好理解,依然看上面的例子,我们修改一下f函数的参数:
在这里插入图片描述
这其实就是泛型的上边界,可以看到Number类和其子类Interger类都没有报错,而String类依然编译不通过。而下边界自然就是:
在这里插入图片描述
这就是泛型中边界的问题。
还记得前面在类型擦除的时候提到过说:泛型在编译后,会被Object类代替(擦除),而有一种特殊情况就是使用了『上边界』,这时替代者就不是Object,而是extends的那个类了。比如:

public class Demo1 <T extends String> {
    private T str;
}

这里,就是用String去擦除,而不是Object了。

一些特点

参数类型的父类和子类、通配符的局限性、泛型与数组、泛型与静态方法

参数类型的父类和子类

我们以前的认知中,父类能出现的地方,子类是一定可以出现。但在泛型中并不是如此。比如:
在这里插入图片描述
我们普通的类中,父类指针是可以指向子类对象的,但是即使Integer是Number的子类,a2依然不能赋给a1。

通配符的局限性

如果用通配符定义了一个类型,那么这个变量是只能读取,而不能被修改的。比如:
在这里插入图片描述
可以看到我们是不能往集合list中去添加任何元素。如果再回到我们前面的那个例子:
在这里插入图片描述
可以看出我们在f函数里,对形参node进行设置时,编译并不能通过,也就是不允许我们去修改。

创建泛型类数组

在这里插入图片描述
这里可以看到创建泛型数组是会报错的。但可以通过下面两种方式去声明。(这一块具体的内容暂时还没有去探索,先表明一下有这种现象。)

静态方法不可以访问类上的泛型参数,需要自己重新定义

如图:
在这里插入图片描述
虽然在类上定义了T,但依然报错,只能在方法前自行定义:
在这里插入图片描述
这是因为我们并不可以直接使用指定泛型的类名去调用静态函数,如:

Node<Integer>.f()

是不可以的,所以我们静态方法里面的泛型,就不能依赖类的定义,而要自己去定义。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值