10-集合(Collections-sort)
集合框架我们基本都讲完了,就剩下了这一部分,工具类:
首先说一下Collections,它的出现有什么特点呢?
作为一个工具类,首先它里面的方法都是静态的。
它没有对外提供构造函数,是不需要创建对象的,因为它的对象当中并未去封装特有数据,都是共享型的情况下,定义成静态最方便。
它的里面有一堆方法,都有什么用呢?
我现在有一堆元素,不需要保证唯一,用哪个集合?List。
可是我又想对这里面的元素进行排序。但Tree是Set集合中的,List集合中没有直接排序的方式。
那么集合框架也想到了,它为我们提供了这样一个工具来完成这个动作。
Colletions这个工具类是专门用来对集合进行操作的。
那么,它里面是由n多静态方法所组成的,其中有一个看上去就很爽:
它可以对List集合进行排序。
我们先分析一下红框这一部分:
1.4版本的写法:
1.5开始,就带泛型了:
List集合中的元素想要排序是不是需要进行比较?
所以这个T必须是Comparable的子类:
Comparable这个接口也带泛型,所以写个T:
但是为了扩展性更强一些,里面传T的父类也可以:
OK,现在整体都理解了。
接下来我们来用一下。
代码:
结果:
List是数组结构,数组结构出现重复元素后依然可以排序,因为它有索引,例:
接下来我们想改一下排序规则,想对集合中的字符串进行长度排序。
我们发现还有一个带比较器的sort方法:
代码实现:
11-集合(Collections-max)
接下来我们看一下Collections中的max方法:
试一下:
上面这个是按自然顺序排。
下面这个是按照自定义的长度顺序排:
12-集合(Collections-binarySearch)
试一下:
试着找一下集合中不存在的"aaaa":
这个2是什么呢?
我们点进binarySearch方法中看一下:
所以这里返回的是-1-1=-2。
下面写一下binarySearch的原理:
验证一下,找一下"aaa":
再试着找一下"aaaa":
我们的返回值不够专业,修改一下,返回-(插入点)-1:
OK啦:
如果元素不具备比较性怎么办?
那就用这个方法,具有比较器的:
这个方法的原理,将上面写的那部分原理代码拿下来,做两处修改即可:
做这两处修改是因为,原先是str.compare....,但是str有可能不具备比较性,或者它的比较方式不是我们所需要的,这个时候就指定了一个比较器cmp,cmp中有一个compare方法,用这个方法来对str和key做比较。
而比较器的compare方法就是之前写的按长度来比的:
现在试一下使用它:
13-集合(Collections-替换反转)
接下来演示一下fill方法:
演示:
这个方法将集合中的元素全部都替换成了指定的元素。
接下来做一个小练习:fill方法可以将list集合中的所有元素替换成指定元素,我们现在来实现一下将list集合中的部分元素替换成指定元素。
思路:
接下来看一下replaceAll方法:
演示:
14-集合(Collections-reverseOrder)
用迭代器打印出TreeSet集合中的元素:
现在我们想将它倒序打印出来,是不是不再用它默认的比较方式,而是传一个比较器。
比较器:
但是老需要这么写还是很麻烦,我们要想一想,集合框架中是否提供了这样的方法?没有的话我们才需要自己手动来写。
在Collections中就有这样一个方法:
所以我们直接把这个逆向比较器传入就可以了:
实现了元素的逆序输出:
我们之前写了一个按长度来排序的比较器,排序出来的结果是从短到长,我们现在想实现从长到短的排序,该怎么做呢?
Collections中还有一个方法,可以将一个现有的比较器强行逆转,就是刚刚那个逆向比较器的重载方法,刚刚那个逆向比较器没有参数,而这个方法可以传一个比较器:
这样即可:
OK了:
15-集合(Collections-SynList)
集合中那么多的对象,它们都有一个共同的特点:线程不安全。万一真的被多线程操作了,就会出问题。
那该怎么办呢?
自己加锁?
很麻烦的。
添加有一个方法,删除有一个方法,我们需要将添加和删除封装到同一个锁里面去,也就是说,在某一时刻,只能有一个线程进行添加或者删除。
怎么办呢?
Collecitons就替我们想到了解决方法:
我们只要将不安全的集合给它,它就会给我们安全的集合。
这个不需要演示,我们更想知道它底层是怎么实现的。
下面要介绍源码:
这里有个SynchronizedList类,我们来看一下:
我们找到它的添加和删除方法:
这就是它的实现原理。添加和删除是两个动作,这两个动作要放在一个锁里面才行。
顺便再说一下swap方法:
reverse方法在反转的时候需要调换元素在集合中的位置,其实调换的时候就是调用的swap方法来完成。
将它暴露出来,就可以直接置换List集合中两个元素的位置。
演示一下:
顺便再提一下shuffle这个方法:
对列表中元素位置进行随机置换,听起来很厉害的样子,我们试一下:
两次运行的结果是不同的,都是随机的。
其实和洗牌、摇色子一样,以后写相关的代码可以用到。
16-集合(Arrays)
Arrays:用于操作数组的工具类。
里面都是静态方法。
接下来演示一些方法。
将数组变成字符串:
将数组变成List集合(传进来一个数组,返回的是List):
演示:
把数组变成List集合有什么好处?
以前我们要判断一个元素在数组中是否存在,需要遍历这个数组,然后写判断方式,自己完成这个功能就比较麻烦:
而把数组变成集合之后,我们就可以使用集合的思想和方法来操作数组中的元素:
数组的方法比较少,集合的方法比较多。
但是注意,我们执行这句代码:
运行的时候会报错,不支持的操作异常:
为什么会这样呢?
注意:将数组变成集合之后,不可以使用集合的增删方法,因为数组的长度是固定的。如果使用了增删方法,就会发生UnsupportedOperationException。
我们可以使用集合中诸如contains、get、indexOf()、subList()等方法。
我们再试一下这个:
我们发现打印出来的是一个数组的哈希值。
为什么把字符串往里面一扔就变成集合了呢?
如果数组中的元素都是对象,那么变成集合时,数组中的元素就直接转换成集合中的元素。
如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。
我们重新试一下:
OK了:
17-集合(集合转成数组)
说完了数组变集合,接下来说集合变数组。
该用什么方法呢?
Collection接口中的toArray方法。
这里有两个toArray方法:
我们该用哪一个呢?
第一个是返回Object类型的数组,第二是返回指定类型的数组,显然第二种更方便一些,就不用强转了。
我们演示一下:
为什么传0呢?我们传1试一试:
运行结果没有什么区别:
传5试试:
有区别了:
总结一下:
1,指定类型的数组到底要定义多长呢?
当指定类型的数组长度小于集合的size,那么该方法内部会创建一个新的数组,长度为集合的size。
当指定类型的数组长度大于集合的size,就不会新创建数组,而是使用传递进来的数组。
所以创建一个刚刚好的数组最优。(创建短的也可以,但是内存中会多一个数组)
2,为什么要将集合变数组?
为了限定对元素的操作,不需要进行增删了。
18-集合(增强for循环)
Iterator方法虽然在以前的版本上已经做了改进和更新,方便了很多,可是这个单词也不好写,容易写错。
接下来,Java在JDK1.5的时候,考虑到了我们的痛苦,帮我们解决了这个问题。
1.5版本以后,Java给Collection找了个爹,它继承了Iterable接口:
我们点进这个Iterable接口看看:
它是1.5开始的。
这个接口的出现,就是将迭代方法抽取出来了。
这个接口的出现有什么好处呢?
它不光是把这个迭代方法抽取出来提高了集合框架的扩展性(以后我们再出一个Collection2接口,只要继承这个Iterable方法,也可以具有迭代方法),另外它还给这个集合框架提供了一个新功能,就是高级for循环:
这个foreach循环是什么东东呢?
我们来讲一下高级for循环。
格式:
for(数据类型 变量名:被遍历的集合(Collection)或者数组)
示例:
编译运行,也出来了:
这个写法要比迭代器的写法简单太多。
但是它底层的原理其实还是迭代器,只是将复杂代码编程简单代码了,这个升级是简化书写。
s先指向“abc1”,再指向“abc2”,......,它指向的对象一直在变化。
我们加一个小动作,看看会怎么样:
打印结果还是原先的样子:
所以这个循环具有局限性,它只能对集合中的元素进行取出,而不能进行修改。
迭代器不会这样,迭代器至少还有一个remove删除,如果用列表迭代器(ListIterator),增删改查都行,所以是有区别的。
所以简化书写虽然提供了很大方便,但是也有局限的地方。
那么这个for循环和我们传统的for循环有区别吗?
有的。
传统for循环和高级for循环的区别:
高级for有一个局限性,必须有被遍历的目标。
举个例子,我们想将"hello world"打印100次,传统的for循环方法完全OK,但是高级for循环就无法简单粗暴的完成这个任务。
所以,建议在遍历数组的时候,还是希望用传统for,因为传统for可以定义角标。
新的例子:
将HashMap中的键和值拿了出来:
接下来我们用entrySet和高级for循环来完成:
OK:
另外要注意,ArrayList后面写了泛型,底下才能明确写String,如果没有写泛型,底下只能写Object。高级for循环是1.5版本出现的,泛型也是1.5版本出现的,所以上面带着泛型,下面就可以用泛型的类型。
19-集合(可变参数)
接下来讲另外一个JDK1.5版本出现的新特性。
用例子来说:
虽然写好show方法调用它已经方便很多了,但是我们还能更方便。我们写了一个show方法,每次打印数组的时候直接调用这个方法就OK,但是,每次打印不同的数组,我们都得定义一个新的数组出来,作为实际参数,就有点麻烦了。
1.5版本后,出现了一个新特性:
这是一个数组我们还可以打印数组的长度:
介绍一下这个特性:可变参数。
其实就是上一种数组参数的简写形式,不用每一次都手动的建立数组对象,只要将要操作的元素作为参数传递即可,隐式将这些参数封装成了数组。
写成了...,数组不用定义了,爱传几个传几个,不传都可以:
这个更新简化了开发代码,提高了开发速度。
注意:方法的可变参数在使用时,可变参数一定要定义在参数列表最后面。
例,这里String定义在前面,“haha”匹配给了它,其他的int型数据封装给了可变参数:
这样运行是OK的。
而String定义在后面,int...定义在前面,会默认将所有参数都封装到可变参数中,这样就会出问题了:
20-集合(静态导入)
接下来讲最后一个1.5版本的新特性:静态导入。
这里面有一个小插曲,我们先演示一下:
接下来我们用二分法查找一下1并打印:
我们发现,每次使用工具类的时候,都要工具类名.。。。,稍微有点麻烦。
再回想起之前,我们不导入工具类的包,就要这样写:
而我们导入工具类包之后,这个包名前缀就可以省略了。
而像Arrays和Collections等等这样的类中都是静态方法,我们把这些类导入的话,方法前面的工具类名是不是也可以省略了呢?
我们试着这样做:
编译运行都是OK的。(图略)
但是忘记省略Arrays.toString(arr)了呀。
我们也将它省略掉:
但是编译运行就报错了:
因为StaticImport类是继承了Object类的:
Object类中也有一个toString方法,但是这个toString方法不可以传参数,于是就报错了。
两个包中有同样的方法,一定要加包名来指定命名空间。
当类名重名时,需要指定具体的包名。 当方法重名时,需要指定所属的对象或者类。
再举个更简单的例子来说明这个问题,packa包和packb包中都有Demo类,那么将它们都导入后,new一个Demo,到底new的是哪一个包中的Demo呢:
这个时候必须要用指定的所属包名来区分:
而我们现在导入的包里面有多个同名方法,而且是属于不同的类,Object中有toString,Arrays中也有toString,所以这个时候一定要标明到底是谁的toString。这样就没毛病啦:
跟你讲喔,还有更爽的呢。
天天自己写输出语句很麻烦,在java.lang包中有一个System类,这个类中有一个out属性:
这个属性是静态的。
System中全都是静态方法。
我们也将System类中所有静态成员都导入:
(最后两句前面的System都可以删掉了)
总结一下,导入的时候,如果import后面没有跟静态static,导入的都是类:
导入的时候,如果import后面跟了静态static,导入的都是类中的静态成员:
网友评论