容器-Collection子类接口List和Set

容器:是用来装数据的

1.数组
优点:可以根据索引快速的定位到某个元素,访问速度特别快
缺点:长度是固定的,如果数组满了,需要考虑扩容,并且删除和插入元素时,需要移动元素.
需要另一个变量,例如:total,来辅助记录实际有效元素的个数.
2.集合
集合是新设计的一组容器,具有各种特点.
数据结构
栈,队列,链表,堆,图,树,哈希表

无论多复杂,都是从一下两种物理结构的基础上构建出来的:
(1)数组
在内存中需要开辟连续的存储空间,有可能有很多空余的空间
元素类型:就是数据的类型
(2)链式
在内存中不需要连续的空间,不会有空闲空间.
元素类型:结点类型
结点类型:
单向链表
class Node{
Object data;
Node next;
}
双向链表
class Node{
Node previous;
Object data;
Node next;
}

class Node{
Node parent;
Object data;
Node left;
Node right;
}

数组:
缺点:(1)长度固定,如果要扩容等需要程序自己维护,如果要删除和插入,程序员要移动元素等.
(2)数组只支持可重复,顺序存储的特点,比较单一.

实际开发中,数据的存储特点:(1)有序的(2)无序的(3)可以重复的(4)不能重复的(5)一对一的(6)一对多的....
JDK在数组和链式结构的基础上,重新设计了很多的容器类型.

主要是两大类:
1.Collection:一组对象,比喻"单身party"
2.Map:键值对,(key,value),比喻"情侣party","家庭party"

容器有共同的行为特征,操作方式:增删改查

把对容器的操作的行为标准化,用接口来声明.

一 java.util.Collection
(一)Collection的概述
Collection层次结构中的根接口.Collection表示一组对象.
一些Collection允许有重复的元素,二另一些不允许,一些Collection时有序的,而另一些时无序的.
JDK不提供此接口的任何直接实现:它提供更具体的子接口(如Set和List)实现
1.List
列表:可重复的,有序的和添加顺序有关系(按顺序存储)
实现类:例如ArrayList(动态数组)
2.Set
集:不可重复的,无序的(和添加顺序无关)
(二)Collection的常用方法
1.添加
(1)add(Object obj):一次添加一个
(2)addAll(Collection other):一次添加多个,把other中的元素都添加到当前集合中,this = this 并 other
2.删除
(1)remove(Object obj):一次删除一个
(2)removeAll(Collection other):一次删除多个,删除other与this交的交集的元素.
(3)clean()清空
3.修改:Collection中没有提供修改的方法
4.查找
(1)Boolean contains(Object obj):判断obj是否在当前集合中
(2)Boolean containsAll(Collection c):判断c是否为this的子集
(3)Boolean isEmpty()
Collection接口中没有提供获取一个元素的方法

5.获取有效元素的个数 int size()
6.遍历
(1)老方法
Object[] toArray():如果该集合的底层是数组,那么可以用这种方法,如果其底层不是数组,用数组遍历会比较浪费时间浪费空间,无论底层怎么样,都会返回一个尺寸为size的数组.
(2)foreach:称为增强版for循环
语法结构:for(元素的类型 元素名:可迭代的容器名)
把元素名理解为形参,每循环一次,就把数组或集合的元素以此作为实参赋值给形参,
即:如果元素类型是基本数据类型,那么把数组或者集合的元素"数据值"赋值给它,那么对他怎么修改和实参无关.
如果元素是引用数据类型,那么把数组或者集合的元素"地址值"赋值给他,那么对他的属性修改和实参有关.对他的地址修改和实参无关.

结论:如果你只是查看数组或者集合的元素,用foreach比较简单,如果是修改,就考虑其他的循环方式.

(3)Iterator迭代器遍历
java.util.Iterator迭代器:用于遍历(迭代)Collection系列集合用的.

步骤:
(1)先通过Collection系列集合,对象拿到迭代器对象
(2)再通过Iterator的方法进行迭代.
boolean hasNext():判断集合中是否有下一个元素
Object next():取出下一个元素
void remove():删除刚刚取出的元素,用于根据条件删除

在集合中,涉及到对象的比较,用的都是equals方法进行比较,分为空和不空两种讨论方式,如果在原来的元素所在类中没有重写equals方法,就会比较两个元素的地址值。


下图为利用foreach方法遍历并且想删除元素的操作,报错了,针对引用数据类型.

7.其他
retainAll(Collection c):保留this与c的交集

java.util.List:接口
(1)有序:可以对元素的索引index,进行控制
(2)可重复。

常用方法:
继承了Collection,因此Collection的所有的方法和操作它都有。
List还增加了很多方法,这些方法都和index有关系。

1.添加
add(int index,E element):在index位置插入一个元素
addAll(int index,Collection<? extends E> c):在index位置插入多个元素
2.删除
remove(int index)
删除指定索引位置的元素
3.改
Collection中没有提供关于修改的操作方法
set(int index,E element)
修改指定索引位置的元素为element
4.查询
int indexOf(E element):从左往右找
int lastIndexOf(Eelement):从右往左找
因为List是可以重复的集合,所以设置了两种查找顺序。
get(int index):返回指定位置的元素
subList(int fromIndex,int toIndex):截取[fromIndex,toIndex)
5.遍历
(1)toArray()
(2)foreach
(3)Iterator
(4)listIterator
listIterator是Iterator的子接口,Iterator的功能方法,listIterator
也有,还增加了一些功能
A:Iterator只能从前往后遍历
listIterator可以从任意位置开始,从前往后,或者从后往前遍历。
listIterator的使用步骤:
(1)先获取listIterator的对象,获取方式为:集合对象.listIterator()
(2)通过遍历方法
hasNext()+next()
hasPrevious()+previous()
B:不仅可以在遍历中删除了,还增加了set和add方法。

//从前往后遍历
ListIterator listIterator = c.listIterator();//默认迭代器指向最前面
while(listIterator.hasNext()){
Object next = listIterator.next();
System.out.println(next);
}
//从后往前遍历
ListIterator listIterator = c.listIterator(list.size());
while(listIterator.hasPrevious()){
Object previous = listIterator.previous();
System.out.println(previous);
}
//从第三个元素位置开始遍历
ListIterator listIterator = c.listIterator(3);
while(listIterator.hasPrevious()){
Object previous = listIterator.previous();
System.out.println(previous);
}

List常见的实现类
1.Vector:动态数组
内部实现:数组,初始大小为10
2.ArrayList:动态数组
内部实现:数组,初始大小为10
3.LinkedList:双向链表,双端队列:又是Queue和Deque的实现子类
内部实现:链表
4.Stack:栈,又是Vector的子类
Stack:后进先出(LIFO)(Last in first out)、先进后出(FILO)(First in last out)
压栈:push;弹栈:pop(移除栈顶元素);peek(返回栈顶元素,但不移除)

(接口)Queue(队列)(extends Collection):先进先出(FIFO)
添加到队列offer(e),移出队列poll(),返回队头但不移除peek()

(接口)Deque(extends Queue):(Double ended queue)双端队列:队头和队尾都可以添加元素和移除元素。不支持通过索引来访问元素
offerFirst(e)\offerLast(e)
pollFirst()\pollLast()
peekFirst()\peekLast()

面试题:Vector和ArrayList有什么区别?
Vector:旧版,线程安全的,当空间不够时,扩容为原来的两倍。支持的迭代方式更多,支持旧版Enumeration迭代器
ArrayList:新版,线程不安全的,当空间不够时,扩容为原来的1.5倍。不支持旧版的Enumeration迭代器。

面试题:动态数组和LinkedList有什么区别?
(1)内部实现不同
动态数组底层是数组
LinkedList底层是链表,元素的类型是Node
(2)动态数组:对索引的相关操作,效率很高
链表:对索引的相关操作,效率比较低
(3)动态数组:插入和删除操作涉及到元素的移动,时间复杂度高
链表:插入和删除操作只涉及到前后元素的关系。

结论:如果后面的操作针对索引更多,那么选择动态数组,如果是添加和删除的操作更多,那么选择链表。

java.util.Set(接口)
(1)不支持重复
(2)无序的(和添加顺序无关)
set中没有新添加方法,都是Collection的方法

Set:实现子类
(1)HashSet:完全无序
说明:
判断元素是否重复,是参考equals方法,如果equals方法在元素的类中没有被重写,则默认比较地址值。
(2)TreeSet:大小顺序,和添加顺序无关,如果添加元素为引用数据类型,比如Student等,这个类需要实现Comparable,然后复写compareTo的方法。
说明:
判断元素是否重复,依据是元素复写的compareTo方法。
(3)LinkedHashSet:遍历时可以保证添加顺序,存储和添加顺序无关。
说明:
判断元素是否重复,是参考equals方法,如果equals方法在元素的类中没有被重写,则默认比较地址值。

LinkedHashSet是HashSet的子类,但是它的元素比HashSet的元素要多维护一个添加的顺序。
LinkedHashSet的效率就比HashSet低,每次添加,删除要同时考虑顺序。但是前者迭代中受到容量的影响较小。

结论:
如果既要元素不重复,又要按大小,选TreeSet
如果既要元素不重复,又要保证添加顺序,选LinkedHashSet
如果只要元素不重复,选HashSet

使用HashSet为容器承载数据元素的话,如果要求某个属性一样就认为是同一种元素,这种时候需要重写hashCode()和equals()方法。
重写要求:
(1)必须一起重写。
(2)hashCode值相同,不一定相同
hashCode值不同,equals一定不相同
equals相同,hashCode值一定相同。

要求:
参与hashCode值计算的属性,就要做equals的比较。
(3)equals的方法重写遵循几个原则
对称性、自反性、传递性、一致性、非空与null比较返回false。
参考:Object的equals的API。

实际情况分析:在实际中,我们可能遇到这样的情况,一次可能需要一个对象的一个元素属性进行排序,那么此时我们会使此对象类实现Comparable接口,进而重写compareTo方法,制定好排序的依据,然后利用treeSet容器进行承载,遍历之后的结果就依据compareTo定义排好了顺序。紧接着有要求依据元素对象的另一个属性进行排序,比如价钱,那么此时再回去重写compareTo方法就很麻烦,而且丧失了之前的排序依据,此时就可以在定义treeSet容器时,传入一个比较器,那么系统会优先考虑比较器中的排序依据,传入的比较器需要重写compare方法,此方法的返回值是int类型,而比较的属性可能是double类型,不能强制转换成int,因为这样会失去精度,并且影响比较结果。此时有三种处理方法:
(1)用compare方法中传入的两个对象.属性进行比较,如果前大后小,返回值为1
如果前小后大,返回值为-1;否则返回值为0.此方法精度有限
(2)利用Double的静态compare方法,将被比较的两个对象.属性传入Double的compare方法,返回值类型为int。
(3)将比较器中compare方法中传入的两个对象.属性利用Double.doubleToLongBits方法转成long类型数据,然后利用Long.compare(long x,long y)进行比较,返回值为int,此方法是1.8之后加进来的。

猜你喜欢

转载自www.cnblogs.com/1185937986-jili/p/12887945.html