Java基础day15笔记:泛型限定
12-集合框架(泛型限定)
接下来说一说泛型的高级应用。
例子:
![](https://img.haomeiwen.com/i14399848/c58903a8b98fda44.png)
将al传进printColl中打印:
![](https://img.haomeiwen.com/i14399848/115508e0f458fbda.png)
将al1传入printColl打印:
![](https://img.haomeiwen.com/i14399848/03ac664ff1529fdf.png)
发现挂掉了:
![](https://img.haomeiwen.com/i14399848/906ebd100cd0b4c8.png)
必须挂的,因为左右两边类型不匹配:
![](https://img.haomeiwen.com/i14399848/b83130620f928565.png)
可是我们又需要打印Integer类型的数据,难道要再写一个方法吗?好麻烦呢。
那有没有办法打印任意类型的数据呢?
有这样一种方法:当我们的对象类型不确定时,我们可以通过一个通配符来表示:
![](https://img.haomeiwen.com/i14399848/f4bfe969c753866b.png)
虽然也可以这样写(比如早期没出现泛型的时候都是这样写):
![](https://img.haomeiwen.com/i14399848/63ad94cc37ef24bd.png)
这样写即使传入使用泛型的对象也是可以的,但是这样写不够严谨,我们还是在这里写一个占位符更严谨。
现在我们再试一下:
![](https://img.haomeiwen.com/i14399848/98f4abba7d81a28f.png)
全部都顺利打出来了:
![](https://img.haomeiwen.com/i14399848/cae1309f5bead01a.png)
也可以不用<?>用,但是方法名前面一定也要写上哦:
![](https://img.haomeiwen.com/i14399848/40b7c4cbeaca783f.png)
那<?>和有什么区别吗?
有的,不过不是很大。
是一个具体类型的话,就可以接收并操作这个类型,比如这样:
![](https://img.haomeiwen.com/i14399848/5c38352abdabfd04.png)
但是如果写成<?>,就无法进行这个操作。
代表一个具体类型,传什么类型就是什么类型,而?是不明确类型,叫做占位符,告诉你我这里有泛型,但是泛型是什么不知道。
不明确具体类型的情况下用<?>表示。
我们这里依然用<?>来表示,现在有一个问题,我们是否可以直接打印长度?
![](https://img.haomeiwen.com/i14399848/aa0fbd77a5df585f.png)
不可以。
为什么呢?
因为代表不明确类型,而.length()是一个具体类型的方法。如果传Integer,它就没有这个方法。
所以用泛型有好处,可以提高扩展性,但是它不可以使用类型特有方法。
但是toString()方法就可以,因为所有类型都具备这个方法:
![](https://img.haomeiwen.com/i14399848/c8da3ab9bf283f5f.png)
总之,类型调用方法这里我们要注意一下。就像多态,提高了扩展性,但是也有弊端:不能预先使用子类中的特有方法。
新的例子:
Person类:
![](https://img.haomeiwen.com/i14399848/7bc1a7c9fe447397.png)
定义一个容器al,存入Person对象:
![](https://img.haomeiwen.com/i14399848/8d084f65b2556fdb.png)
运行,一切正常:
![](https://img.haomeiwen.com/i14399848/de632f34e1977b0e.png)
现在来了一个学生类,它继承了Person类:
![](https://img.haomeiwen.com/i14399848/192aeb97ddea2917.png)
然后我们继续定义一个新的容器并存入Student:
![](https://img.haomeiwen.com/i14399848/f45ab5216cb7c7f5.png)
编译:
![](https://img.haomeiwen.com/i14399848/23d64cd4905d12f3.png)
失败了。
主函数中最后一句代码:
printColl(al1);
就相当于:
ArrayList al=new ArrayList();
这个是不允许的。
为什么不允许呢?Student不是Person的子类吗?
拿动物和猪(猪是动物的子类)打个比方吧:
ArrayLis<动物> al=new ArrayList<猪>();
我们定义的是要存动物(等号左边),而真正的窝里面存的是猪(等号右边)。我们考虑到了定义的集合是可以存储动物的(等号左边),于是找了条狗想要存进去,可是窝里面只能装猪(等号右边),狗进去之后类型就冲突了,再往出取的时候就很容易出现类型安全问题。(还是不太懂,之前讲多态的时候,这样不是都没有问题吗?而且我理解的是这次存的是猪,下次存的是狗,并不是一次存入不同的,感觉也不矛盾?)
道理其实很简单,把狗扔到猪圈里,猪就疯了。
那如果这样写呢:
ArrayList al=new ArrayList();
这样也不行。
总之,左右两边一定要一致。
那我们这样写是不是就可以了:
![](https://img.haomeiwen.com/i14399848/448beabb0e1d3708.png)
这样既可以接收Person对象,也可以接收Student对象。
但是这样一来,除了Person和Student对象,其他的什么类型的对象也都可以进来了。而我们现在就只想用这个方法来打印Person和Person的子类,其他类我们不想受理。
这个时候就来了泛型限定:
![](https://img.haomeiwen.com/i14399848/20ddcfa2b2df839f.png)
它表示可以接收Person类型和Person的子类类型。
运行:
![](https://img.haomeiwen.com/i14399848/aeab5a6b4a261e69.png)
OK了。
这就是我们所说的泛型限定,而泛型限定分为两种:上限和下限。
总结一下:
?:通配符,也可以理解为占位符。
? extends E:可以接收E类型或者E的子类型。这是上限。
? super E:可以接收E类型或者E的附类型。这是下限。
什么时候用到下限呢,我们在TreeSet的构造方法中发现了:
![](https://img.haomeiwen.com/i14399848/f49e5d6f2dbfb929.png)
这是什么意思呢?
举个例子:
![](https://img.haomeiwen.com/i14399848/55e810a52e0fdbcc.png)
也可以这样写:
![](https://img.haomeiwen.com/i14399848/3429932332811f24.png)
Person可以接收Student吗?
可以。
我们如果比较this.getName()的话,Person也具备这个方法:
![](https://img.haomeiwen.com/i14399848/b4bc32b790959400.png)
比较器:
![](https://img.haomeiwen.com/i14399848/8a3346d859f3801a.png)
我们再返回看一下构造函数:
![](https://img.haomeiwen.com/i14399848/31925243780b82b6.png)
那写Person也可以:
![](https://img.haomeiwen.com/i14399848/ed07e031f556df38.png)
这样写之后,我们依然可以接收Student对象:
![](https://img.haomeiwen.com/i14399848/f517d52dee1f9548.png)
所以不管是传Student类型还是Person类型都可以,它们都可以接收Student对象。
这就是下限的例子。
在这个方法中,既可以接收Person也可以接收Person的子类型,限定了上限:
![](https://img.haomeiwen.com/i14399848/1363f981df8755f4.png)
而比较的时候,我们限定了下限,既能比较Student,也能比较Person:
![](https://img.haomeiwen.com/i14399848/7d83a2e7017ddedf.png)
我们可以在TreeSet中看到几个方法:
![](https://img.haomeiwen.com/i14399848/89a5dc0f27af2daa.png)
这个的意思是,往里面添加的时候,只要是E或者E的子类型,都可以操作E,多态嘛。
![](https://img.haomeiwen.com/i14399848/0aae79a56cc84f64.png)
而这个也是一样,比较的时候,比较的类型、或者比较的父类型,都能够接收子类对象进来。
这里还有个的例子:
![](https://img.haomeiwen.com/i14399848/2450698bbf96e150.png)
这个方法上并没有定义泛型,而是用<?>来表示占位符。而泛型定义在类上了:
![](https://img.haomeiwen.com/i14399848/4fd3dfcffe35fdf8.png)
上面的意思是,我要比较另一个集合的时候,这个集合不确定,传进去什么集合就是什么集合。不写<?>也可以,但是在1.5版本以后,Colletion后面都带着<>,所以还是写个占位符放在里面,类型不明确的情况下,用?来表示就可以了。
13-集合框架(泛型限定2)
再一个例子。
Person类和它的子类Student类:
![](https://img.haomeiwen.com/i14399848/962ba0846494a065.png)
![](https://img.haomeiwen.com/i14399848/611d303d397de64e.png)
但是这样打印是没有意义的,因为学生对象不具备比较性。
我们现在要写一个比较器:
![](https://img.haomeiwen.com/i14399848/0c5d60b7c08c24b3.png)
再看一下TreeSet接口,它传入的是:
![](https://img.haomeiwen.com/i14399848/4df133934adc9283.png)
构造方法中,可以接收E类型和E的父类型:
![](https://img.haomeiwen.com/i14399848/72d0a3ef85332fc4.png)
所以比较器写是对的。
继续:
![](https://img.haomeiwen.com/i14399848/393807c6560b1f92.png)
将比较器传进去:
![](https://img.haomeiwen.com/i14399848/22e4e60ef0637bf7.png)
运行:
![](https://img.haomeiwen.com/i14399848/f4c94522fc231b62.png)
OK了。
接下来Worker类继承了Person类:
![](https://img.haomeiwen.com/i14399848/1136b02908108baa.png)
新写了一个Worker比较器:
![](https://img.haomeiwen.com/i14399848/e2c5478de50623d6.png)
主函数中存入Worker对象并打印:
![](https://img.haomeiwen.com/i14399848/ebf5d264810718dd.png)
运行也是没有问题的:
![](https://img.haomeiwen.com/i14399848/d86297a81a4bbc97.png)
但是好麻烦,每次搞一个对象都要搞一个比较器。
我们可以直接在比较其中写它们的父类型(因为构造函数中可以接收E及E的父类型):
![](https://img.haomeiwen.com/i14399848/754672e320051381.png)
它的子类们直接使用这一个比较器就可以了:
![](https://img.haomeiwen.com/i14399848/9406c2e1607e6a28.png)
运行都是OK的,图略。
但是有一点要注意,比较器中使用的只能是父类的方法。有扩展性就有局限性。