Java基础day15笔记:泛型限定

2018-12-27  本文已影响0人  楠楠喜欢泡枸杞

    12-集合框架(泛型限定)

        接下来说一说泛型的高级应用。

        例子:

        将al传进printColl中打印:

        将al1传入printColl打印:

        发现挂掉了:

        必须挂的,因为左右两边类型不匹配:

        可是我们又需要打印Integer类型的数据,难道要再写一个方法吗?好麻烦呢。

        那有没有办法打印任意类型的数据呢?

        有这样一种方法:当我们的对象类型不确定时,我们可以通过一个通配符来表示:

        虽然也可以这样写(比如早期没出现泛型的时候都是这样写):

        这样写即使传入使用泛型的对象也是可以的,但是这样写不够严谨,我们还是在这里写一个占位符更严谨。

        现在我们再试一下:

        全部都顺利打出来了:

        也可以不用<?>用,但是方法名前面一定也要写上哦:

        那<?>和有什么区别吗?

        有的,不过不是很大。

        是一个具体类型的话,就可以接收并操作这个类型,比如这样:

        但是如果写成<?>,就无法进行这个操作。

        代表一个具体类型,传什么类型就是什么类型,而?是不明确类型,叫做占位符,告诉你我这里有泛型,但是泛型是什么不知道。

        不明确具体类型的情况下用<?>表示。

        我们这里依然用<?>来表示,现在有一个问题,我们是否可以直接打印长度?

        不可以。

        为什么呢?

        因为代表不明确类型,而.length()是一个具体类型的方法。如果传Integer,它就没有这个方法。

        所以用泛型有好处,可以提高扩展性,但是它不可以使用类型特有方法。

        但是toString()方法就可以,因为所有类型都具备这个方法:

        总之,类型调用方法这里我们要注意一下。就像多态,提高了扩展性,但是也有弊端:不能预先使用子类中的特有方法。

        新的例子:

        Person类:

        定义一个容器al,存入Person对象:

        运行,一切正常:

        现在来了一个学生类,它继承了Person类:

        然后我们继续定义一个新的容器并存入Student:

        编译:

        失败了。

        主函数中最后一句代码:

        printColl(al1);

        就相当于:

        ArrayList al=new ArrayList();

        这个是不允许的。

        为什么不允许呢?Student不是Person的子类吗?

        拿动物和猪(猪是动物的子类)打个比方吧:

        ArrayLis<动物> al=new ArrayList<猪>();

        我们定义的是要存动物(等号左边),而真正的窝里面存的是猪(等号右边)。我们考虑到了定义的集合是可以存储动物的(等号左边),于是找了条狗想要存进去,可是窝里面只能装猪(等号右边),狗进去之后类型就冲突了,再往出取的时候就很容易出现类型安全问题。(还是不太懂,之前讲多态的时候,这样不是都没有问题吗?而且我理解的是这次存的是猪,下次存的是狗,并不是一次存入不同的,感觉也不矛盾?

        道理其实很简单,把狗扔到猪圈里,猪就疯了。

        那如果这样写呢:        

        ArrayList al=new ArrayList();

        这样也不行。

        总之,左右两边一定要一致。

        那我们这样写是不是就可以了:

        这样既可以接收Person对象,也可以接收Student对象。

        但是这样一来,除了Person和Student对象,其他的什么类型的对象也都可以进来了。而我们现在就只想用这个方法来打印Person和Person的子类,其他类我们不想受理。

        这个时候就来了泛型限定:

        它表示可以接收Person类型和Person的子类类型。

        运行:

        OK了。

        这就是我们所说的泛型限定,而泛型限定分为两种:上限和下限。

         总结一下:

        ?:通配符,也可以理解为占位符。

        ? extends E:可以接收E类型或者E的子类型。这是上限。

        ? super E:可以接收E类型或者E的附类型。这是下限。

        什么时候用到下限呢,我们在TreeSet的构造方法中发现了:

        这是什么意思呢?

        举个例子:

        也可以这样写:

        Person可以接收Student吗?

        可以。

        我们如果比较this.getName()的话,Person也具备这个方法:

        比较器:

        我们再返回看一下构造函数:

        那写Person也可以:

        这样写之后,我们依然可以接收Student对象:

        所以不管是传Student类型还是Person类型都可以,它们都可以接收Student对象。

        这就是下限的例子。

        在这个方法中,既可以接收Person也可以接收Person的子类型,限定了上限:

        而比较的时候,我们限定了下限,既能比较Student,也能比较Person:

        我们可以在TreeSet中看到几个方法:

        这个的意思是,往里面添加的时候,只要是E或者E的子类型,都可以操作E,多态嘛。

        而这个也是一样,比较的时候,比较的类型、或者比较的父类型,都能够接收子类对象进来。

        这里还有个的例子:

        这个方法上并没有定义泛型,而是用<?>来表示占位符。而泛型定义在类上了:

        上面的意思是,我要比较另一个集合的时候,这个集合不确定,传进去什么集合就是什么集合。不写<?>也可以,但是在1.5版本以后,Colletion后面都带着<>,所以还是写个占位符放在里面,类型不明确的情况下,用?来表示就可以了。

    13-集合框架(泛型限定2)

        再一个例子。

        Person类和它的子类Student类:

        但是这样打印是没有意义的,因为学生对象不具备比较性。

        我们现在要写一个比较器:

        再看一下TreeSet接口,它传入的是:

        构造方法中,可以接收E类型和E的父类型:

        所以比较器写是对的。

        继续:

        将比较器传进去:

        运行:

        OK了。

        接下来Worker类继承了Person类:

        新写了一个Worker比较器:

         主函数中存入Worker对象并打印: 

        运行也是没有问题的:

        但是好麻烦,每次搞一个对象都要搞一个比较器。

        我们可以直接在比较其中写它们的父类型(因为构造函数中可以接收E及E的父类型):

        它的子类们直接使用这一个比较器就可以了:

        运行都是OK的,图略。

        但是有一点要注意,比较器中使用的只能是父类的方法。有扩展性就有局限性。

上一篇 下一篇

猜你喜欢

热点阅读