1.集合的简介
集合Collection,也是一个数据容器,类似于数组,但是和数组是不一样的。集合是一个可变的容器,可以随时向集合中添加元素,也可以随时从集合中删除元素。另外,集合还提供了若干个用来操作集合中数据的方法。
集合里的数据,我们称之为元素(elements);集合只能用来存储引用类型的数据,不能存储八大基本数据类型的数据。
2.泛型的引入
为了避免从集合中获取对象后的强转,Java SE 5.0 以后,使用新特性”泛型”,用来指定要存放在集合中的对象类型。
3.集合与数组区别
1.数组是定长的容器,一但实例化完成,长度不能改变。集合是变长的,可以随时事物进行增删操作。
2.数组中可以存储基本数据类型和引用数据类型的元素,集合中只能存储引用数据类型的元素。
3.数组的操作比较单一,只能通过下标进行访问。集合中提供了若干个方便对元素进行操作的方法。
注意点:在存储引用类型时,集合与数组,存储的其实都是对象的地址
即通过集合的地址,去找元素的地址,从而找到对应的元素对象。如下图:
4.集合框架体系图
5.Collection接口
Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义了他们三个子接口的共同方法。既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。作为父接口,其子类集合的对象,存储元素的特点,可能是无序的,也可能是有序的,因此在父接口中并没有定义通过下标获取元素的方法功能。
(1)常用方法
1.E add(E e) 向集合中添加元素
Collection<Integer> c1 = new ArrayList<>();
c1.add(1);
c1.add(2);
c1.add(3);
c1.add(4);
c1.add(5);
2. boolean isEmpty() 判断集合是否为空
boolean empty = c1.isEmpty();
3.int size(): 返回的是集合元素的个数
int size = c1.size();
System.out.println(size);//5
4.String toString():把集合转成字符串输出
· String string = c1.toString();
System.out.println(string);//[1, 2, 3, 4, 5]
5.addAll(Collection c) :把指定的c集合放入集合中,往后追加,只能加入相同类型的元素。
Collection<Integer> c2 = new ArrayList<>();
c2.add(4);
c2.add(5);
c2.add(6);
c2.add(7);
c2.add(8);
c1.addAll(c2);
System.out.println(c1);//[1, 2, 3, 4, 5, 4, 5, 6, 7, 8]
6.boolean contains(Object o):查看集合中是否包含某个元素o
boolean b = c1.contains(8);
System.out.println(b);//true
boolean b1 = c1.contains(9);
System.out.println(b1);//false
7.boolean containsAll(Collection c):查看集合中是否包含c集合
Collection<Integer> c3 = new ArrayList<>();
c3.add(5);
c3.add(8);
c3.add(7);
c3.add(6);
boolean b2 = c1.containsAll(c3);
System.out.println(b2);//true
8.boolean equals(Object o) :判断两个集合是否相同
Collection<Integer> c4 = new ArrayList<>();
c4.add(5);
c4.add(8);
c4.add(7);
c4.add(6);
boolean equals = c3.equals(c4);
System.out.println(equals);//true
9.boolean remove(Object o):移除集合里的元素o
boolean b3 = c4.remove(5);
System.out.println("是否移除成功:"+b3);
System.out.println(c4);//[8, 7, 6]
10. boolean removeAll(Collection<?> c) : 从集合中删除另一个集合c中所具有的元素。即删除和c集合相交的所有元素。
遍历集合,将每一个元素带入到参数集合中判断是否存在,如果存在,就删除这个元素。
System.out.println(c1);//[1, 2, 3, 4, 5, 4, 5, 6, 7, 8]
Collection<Integer> c6 = new ArrayList<>();
c6.add(4);
c6.add(5);
c6.add(6);
c6.add(10);
c6.add(15);
System.out.println(c6);//[4, 5, 6, 10, 15]
boolean b4 = c1.removeAll(c6);
System.out.println(b4);//true
System.out.println(c1);//[1, 2, 3, 7, 8]
11. boolean retainAll(Collection c) :保留和集合c相交的所有元素,只要和集合c相交都要保留,即使集合中有重复的,如下:
System.out.println(c1);//[1, 2, 3, 4, 5, 4, 5, 6, 7, 8]
Collection<Integer> c6 = new ArrayList<>();
c6.add(4);
c6.add(5);
c6.add(6);
c6.add(10);
c6.add(15);
System.out.println(c6);//[4, 5, 6, 10, 15]
boolean b4 = c1.retainAll(c6);
System.out.println(b4);//true
System.out.println(c1);//[4, 5, 4, 5, 6]
12.void clear():清空c1
System.out.println(c6);//[4, 5, 6, 10, 15]
c6.clear();
System.out.println(c6);//[]
13.toArray():转成Object 数组
System.out.println(c1);
Object[] array = c1.toArray();//可以用下标输出元素
System.out.println(Arrays.toString(array));
6.集合的迭代
1.增强for循环遍历集合
public static void main(String[] args) {
Collection<String> num = new ArrayList<>();
num.add("A");
num.add("B");
num.add("C");
num.add("D");
for (String s : num){
System.out.print(s); //ABCD
}
}
2.迭代器
迭代器Iterator,是一个接口, Collection集合中有一个方法 iterator() 可以获取这个接口的实现类 对象。在这个迭代器中,维护了一个引用,指向集合中的某一个元素。默认指向一个集合前不存在的元 素,可以认为是下标为-1的元素。
迭代器的工作原理:循环调用 next() 方法进行向后的元素指向,并返回新的指向的元素。同时,在向 后进行遍历的过程中,使用 hasNext() 判断是否还有下一个元素可以迭代。
在迭代器使用的过程中,需要注意:
-
不应该对集合中的元素进行修改 ,要修改也可以,但是要用迭代器的方法进行修改,不能使用集合自己的方法
-
不应该对集合的长度进行修改,同上
使用迭代器遍历集合:
//第一步:获取要遍历的集合对象的迭代器对象
Iterator<Integer> it = num.iterator();
//第二步:问是否有下一个元素
while (it.hasNext()){
//第三步:获取元素
Integer a = it.next();//
System.out.println(a);
}
迭代接口提供了多个方法,用于迭代
-boolean hasNext():用于判断集合中是否有下一个元素。 可以形象的认为右指针,指针的最初位置在第一个元素的前面。
-E next():用于返回指针指向的那个元素,然后指针向后移动,为下一次的判断做准备。
-E remove():用于删除指针指向的那个元素。
System.out.println(num);//[A, B, C, D]
Iterator<String> it = num.iterator();
while (it.hasNext()){
if (it.next()=="C"){
//使用集合自己的删除方法,如果要删除的元素存在重复的话,会报异常
//num.remove(element);
//可以使用迭代器自己的方法remove方法
it.remove();
}
}
System.out.println(num);//[A, B, D]
迭代器的源码解析:
三个成员变量:
cursor:记录下一次要返回的元素的下标
lastRet:记录上一次刚刚返回的元素的下标
expectedModCount:预计的修改次数。默认为集合的长度,与modCount的值息息相关 注意:集合在调用add(E e)方法添加元素时,modCount++。
hasNext():return cursor!=size:当光标的值为集合长度时,没有下一个元素了
next(): 1.检查expectedModCount和 modCount的值是否相等,不相等报异常
2.光标的值先赋值给lastRet。相当于指针后移。光标的值+1,为下一次的hasNext()做准备工作 。
新的lastRet的值就是指针指向的元素,也就是刚刚取出来的元素
remove(): 1.检查expectedModCount和 modCount的值是否相等,不相等报异常
2.调用ArrayList的 E remove(int index)做真正的删除元素操作。 而 E remove(int remove)里面调用了System.Arraycopy()方法, 从指定的index+1处,向前移动一位,以此形式做删除操作。
1 2 3 4 5 6 要删除4 ,4的下标就是inedx ,从后一位开始拷贝后面的元素,即
1 2 3 5 6 就把4删除了。
index的值是lastRet赋值的。
因为后续的元素向前移动了,因此cursor也要向前移动一次,即cursor = lastRet, 如果不向前移动,会漏掉元素,即index+1这个位置上的元素,就会被漏掉
最重要的是,remove(int index)里面进行了modCount++操作。 但是,迭代器的remove里,进行重新赋值expectedModCount=modCount
7.List接口
(1)简介
-
List 是一个元素有序、且可重复的集合,集合中的每个元素都有其对应的顺序索引,从0开始
-
List 允许使用重复元素,可以通过索引来访问指定位置的集合元素。
-
List 默认按元素的添加顺序设置元素的索引。
-
List 集合里添加了一些根据索引来操作集合元素的方法
(2)ArrayList和LinkedList
这两个类都是List接口的实现类(子类)
ArrayList是实现了基于动态数组的数据结构,对象存储在连续的位置上。
LinkedList基于双链表的数据结构,链表中的每个节点都包含了前一个和后一个元素的引用
即有前指针和后指针
对元素的获取和修改,ArrayList优于LinkedList,后者需要移动指针。
删除和插入元素时,LinkedList 优于ArrayList,后者需要移动数据。
(3)常用方法
ArrayList和LinkedList两个实现类的常用方法基本相同
1)添加方法
1.boolean add(E e) 作用:向列表末尾添加指定的元素
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("a");
System.out.println(list);//[a, b, c, a]
2.void add(int index, E element)作用:在列表的指定位置添加元素
System.out.println(list);//[a, b, c, a]
//根据元素的添加顺序设置索引
list.add(1,"d");
System.out.println(list);//[a, d, b, c, a]
3.boolean addAll(Collection c )作用:将集合c中的所有元素添加到列表的结尾
System.out.println(list);//[a, b, c, a]
List<String> list1 = new ArrayList<>();
list1.add("1");
list1.add("2");
list1.add("3");
list1.add("4");
list.addAll(list1);//往集合的末尾添加另一个集合的元素
System.out.println(list);//[a, b, c, a, 1, 2, 3, 4]
4.boolean addAll(int index, Collection c)将集合c中的所有元素添加到列表的指定位置
System.out.println(list);//[a, b, c, a]
List<String> list1 = new ArrayList<>();
list1.add("1");
list1.add("2");
list1.add("3");
list1.add("4");
list.addAll(2,list1);//往集合的指定索引处添加另一个集合的元素
System.out.println(list);//[a, b, 1, 2, 3, 4, c, a]
2)获取元素
1.E get(int index)作用:返回列表指定位置的元素
System.out.println(list1);//[1, 2, 3, 4]
System.out.println(string);//4
3)查找元素
1.int indexOf(Object obj)作用:返回列表中指定元素第一次出现的位置,如果没有该元素,返回-1,也可做元素查询来用,即查看该集合中是否有这个元素。
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("a");
System.out.println(list);
int i = list.indexOf("c");//返回元素第一次出现的下标
System.out.println(i);//2
//可以用来查看集合是否含有某个元素
if (list.indexOf("c")!=-1){
System.out.println("元素存在");
}else {
System.out.println("元素不存在");
}
2.int lastIndexOf(Object obj)作用:返回列表中指定元素最后一次出现的位置,如果没有该元素,返回-1
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("c");
list.add("a");
System.out.println(list);//[a, b, c, c, a]
int i = list.lastIndexOf("c");//返回元素最后一次出现的下标
System.out.println(i);//3
4)移除元素
1.E remove(int index)作用:移除集合中指定位置的元素,返回被移除掉的元素对象
System.out.println(list);//[a, b, c, c, a]
String i = list.remove(2);
System.out.println(i);//c
System.out.println(list);//[a, b, c, a]
5)修改元素
1.E set(int index, E element)作用:用指定元素替换指定位置上的元素,返回被替换出来的元素对象
//用d替换掉索引2的元素
String s = list.set(2, "d");//返回替换出来的元素,即指定索引处的元素
System.out.println(s);//c
System.out.println(list);//[a, b, d, c, a]
//可以通过这个方法实现元素的交换,如索引1和索引3处的元素进行交换
String s1 = list.set(1, list.set(3, list.get(1)));//返回的是索引1处的元素
System.out.println(s1);//b
System.out.println(list);//[a, c, d, b, a]
全体元素扩大十倍:
List<Integer> nums = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
nums.add(i);
}
System.out.println(nums);//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for (int i = 0; i < nums.size(); i++) {
nums.set(i,nums.get(i)*10);
}
System.out.println(nums);//[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
6)截取子集
1.ist subList(int fromIndex, int toIndex)作用:截取子集,返回集合中指定的fromIndex 到 toIndex之间部分视图,包前不包后
System.out.println(list);//[a, b, c, c, a]
List<String> list2 = list.subList(1, 3);//返回指索引之间的元素集合,包前不包后
System.out.println(list2);//[b, c]
//在list2集合中用“h"替换掉索引1处的元素
String s = list2.set(1, "h");//[b, h]
System.out.println(s);//c
System.out.println(list2);//
//查看对截取的元素集合进行修改,是否影响原集合
System.out.println(list);//[a, b, h, c, a]结果上可以看出,原集合被修改了
(4)ListIterator
ListIterator是Iterator接口的子接口,继承到了Iterator中所有的方法,同时自己也添加了若干个方法。 允许使用ListIterator在进行元素迭代的时候,对集合中的数据进行修改,或者对集合的长度进行修改。 同时,使用ListIterator还可以进行倒序的迭代。
注意:在进行迭代的过程中,允许修改集合。但是要注意,这个修改集合,只能通过接口中的方法进行,并不能够通过集合中的方法进行修改。
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");
System.out.println(list);
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()){
String next = iterator.next();
if (next=="c"){
// //在迭代过程中删除这个元素
// iterator.remove();
// // 在迭代的过程中,添加一个元素(加在当前迭代的这个元素的后面)
// iterator.add("f"); //两个连用可以达到修改的效果
// // 在迭代的过程中,对当前迭代的元素进行修改,不能和上述两个方法连用
iterator.set("g");
}
}
System.out.println(list);
}
Iterator接口中没有添加add方法和set方法。但是可以用它遍历和删除集合
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");
System.out.println(list);
Iterator<String> it = list.iterator();
while (it.hasNext()){
String next = it.next();
System.out.println(next);
}
}
也可以使用增强for循环进行遍历。
for(String lists:list){
System.out.println(lists);
}
(5)集合转数组 T[] toArray(T[] a)
Object[] toArray() 将集合转成Object[] 一旦想要使用数组中元素的自己的方法和属性。还需要强转
List<String> names = new ArrayList<>();
names.add("michael");
names.add("peter");
names.add("mark");
Object[] array = names.toArray();
//获取第二个元素的第二个字符
Object obj = array[1];
if (obj instanceof String){//判断是不是同一类型的
String str = (String) obj;//进行强转
char ch = str.charAt(1);
System.out.println("ch:"+ch);
}
T[] toArray(T[] a)将集合转成数组。只需要传入一个元素类型的数组对象,就可以返回该类型的数组
String[] arr = new String[0];
String[] arr1 = names.toArray(arr);
System.out.println(Arrays.toString(arr1));
//判读第一个元素是不是以el结尾
boolean b = arr1[0].endsWith("el");
System.out.println(b);
(6)数组转集合Arrays.asList(T... a)
1.数组转成的集合对象,不能修改长度
String[] names = new String[5];
names[0] = "michael";
names[1] = "tom";
names[2] = "lucy";
names[3] = "lily";
names[4] = "john";
List<String> list = Arrays.asList(names);
System.out.println(list);
//将集合第二个元替换成张三
String s = list.set(1, "张三");//没有更改长度
System.out.println(s);//tom
System.out.println(list);//[michael, 张三, lucy, lily, john]
//下面两个操作都是修改长度,运行时会报错
// list.remove(2);//移除是修改集合的长度
// System.out.println(list);
// list.add("aaaa");//也是修改长度
// System.out.println(list);
2.如果想要进行增删,可以将集合拷贝到另一个集合中,进行操作
/**
* 向集合中添加新元素,张三和李四。然后将张三和第一个元素进行交换
*/
System.out.println(list);//[michael, tom, lucy, lily, john]
List<String> list1 = new ArrayList<>();
list1.addAll(list);//把list集合拷贝到list1集合中
list1.add("张三");//可以修改长度
list1.add("李四");
int i = list1.indexOf("张三");
Collections.swap(list1,0,i);
System.out.println(list1);//[张三, tom, lucy, lily, john, michael, 李四]
8.Queue子接口
(1)简介
-
队列Queue也是Collection的一个子接口,它也是常用的数据存储结构,可以将队列看成特殊的线性表,队列限制对线性表的访问方式:只能从一端添加(offer)元素,从另一端取出(poll)元素。
-
队列遵循先进先出(FIFO first Input First Output)的原则
-
实现类LinkedList也实现了该接口,选择此类实现Queue的原因在于Queue经常要进行添加和删除操作,而LinkedList在这方面效率比较高。
(2)常用方法
1.boolean offer(E e)作用:将一个对象添加到队尾,如果添加成功返回true
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);//将一个对象添加到队尾
queue.offer(2);
queue.offer(3);
queue.offer(4);
queue.offer(5);
System.out.println(queue);//[1, 2, 3, 4, 5]
2.E poll()作用:从队首删除并返回这个元素
Integer poll = queue.poll();//删除队首元素,并返回
System.out.println(poll);
System.out.println(queue);
//全部删除
while (queue.size()>0){
Integer poll1 = queue.poll();
System.out.println(poll1);
}
//也可这样写
//while (names.peek()!=null){
// //查看头部元素是否为空
// String old = names.poll();
// System.out.println(old);
// System.out.println(names);
// }
System.out.println(queue);//[]
3.E peek() 作用:查看队首的元素
Integer peek = queue.peek(); //查看队首元素
System.out.println(peek);
3.使用迭代器遍历和删除
Queue<Integer> qu = new LinkedList<>();
qu.add(1);
qu.add(2);
qu.add(3);
qu.add(4);
qu.add(5);
System.out.println(qu);
Iterator<Integer> it = qu.iterator();
while (it.hasNext()){
//遍历元素
Integer next = it.next();
System.out.println(next);
//删除元素
// if (next.equals(3)){
// it.remove();
// }
}
(3)Deque接口:双端队列
1.介绍
双端队列:即从队列的两头都可以入队和出队
Deque接口,实现了双端队列的存储结构,它是Queue的子接口。
对应的实现类,还是LinkedList();
2.提供的方法:
//创建一个双端队列
Deque<String> names = new LinkedList<>();
//添加第一个元素 offer()
names.offer("小明");
1.offerFirst:从队列的头部进入
//第二个元素从队列的头部进入 offerFirst()
names.offerFirst("小红");
2.offerLast:从队列的末尾进入
//第二个元素从队列的尾部进入 offerLast()
names.offerLast("小芳");
System.out.println(names);//[小红, 小明, 小芳]
3.pollFirst:移除队列的头部(poll()方法也可)
//移除队列的头部 ,移除并返回 poll() pollFirst()
String p1 = names.pollFirst();
System.out.println(p1);
System.out.println(names);//[小明, 小芳]
4.pollLast:从队列的尾部移除元素
//从栈的尾部移除
String p2 = names.pollLast();
System.out.println(p2);
System.out.println(names);//[小明]
(4)使用Deque模拟栈的存储结构:
栈: First Input Lsat Output 先进后出
1.对应的方法:push():从栈的出口进入,将元素推进去
//创建一个存储结构
Deque<String> stack = new LinkedList<>();
//从栈的出口进入,对应方法push,表示将元素推进去
stack.push("micheal");//推入到栈的底部
stack.push("lucy");//在最底层的上一层
stack.push("lily");
stack.push("tom");
System.out.println(stack);//[tom, lily, lucy, micheal]
2.出栈:pop()方法,将元素弹出栈结构
//出栈(弹出):对应的方法 pop(),将元素弹出栈结构
while (stack.size()>0){
String el = stack.pop();//弹出并返回元素
System.out.print(el);//tom lily lucy micheal 弹出顺序(先进后出)
}
9.Set接口,是collection的子接口
1.简介
1.Set集合中的元素是无序的(取出的顺序与存入的顺序无关)
(一旦存入,在存储结构里的顺序就固定了。和存入的先后顺序无关)
2.Set集合中的元素不能重复(即不能把同一个东西或者相似的东西两次添加到同一个Set容器中,每次放入时都会进行判断是否存在,如果存在,就不添加。)
3.set接口里的方法都是Collection接口的方法,没有新增的方法。
2.实现类
1)HashSet是Set接口的典型实现
大多时候都是用这个实现类。
HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。
特点:
-
1) 不能保证元素的排列顺序
-
2) HashSet 不是线程安全的
-
3) 集合元素可以是 null
当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值决定该对象在 HashSet 中的存储位置。
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
2)LinkedHashSet 是 HashSet 的子类
-
LinkedHashSet 是 HashSet 的子类
-
LinkedHashSet 集合根据元素的 hashCode 值来决定元素的存储位置,但它同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
-
LinkedHashSet 插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
-
LinkedHashSet 不允许集合元素重复。
3.TreeSet 是 SortedSet 接口的实现类
TreeSet集合是用来对元素进行排序的,同样他也可以保证元素的唯一。TreeSet 可以确保集合元素处于排序状态。
TreeSet集合的元素是有序的,即内部维护一个二叉树算法的排序方式
TreeSet 支持两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。(即升序排序),如果想要改变排序方式。可以自定义一个比较器。
public static void main(String[] args) {
Set<String> set = new TreeSet<>();
set.add("a");
set.add("h");
set.add("b");
set.add("f");
set.add("d");
set.add("bob");
set.add("bab");
System.out.println(set);//字母按字典顺序排序的
Comparator c1 = new Comparator<Integer>() {
@Override
//形参类型要一致
public int compare(Integer o1, Integer o2) {
return o2-o1;//降序排序
}
};
Set<Integer> nums = new TreeSet<>(c1);
nums.add(1);
nums.add(10);
nums.add(20);
nums.add(2);
System.out.println(nums);
}
4.Set的去重原理
1)HashSet 、LinkedHashSet去重原理
hashCode相同时,调用equals方法,相同就是相同元素,反之则不是。
注意:每个hash值对应的位置都维护了一个单向链表。可以进入存储 ,hash值相同 * equals值不同的元素。
对于把对象存储在Set集合中的情况。
public class Person {
private String name;
private int age;
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// public boolean equals(Object obj) {
// if(obj == null){
// return false;
// }
// if(!(obj instanceof Person)){
// return false;
// }
// if(this == obj){
// return true;
// }
// Person p = (Person)obj;
// return name.equals(p.getName()) && age == p.getAge();
//
// }
//
// /**
// * 重写hashCode方法的原则: 尽量让所有的成员变量都参与运算。
// * @return
// */
// public int hashCode() {
// return name.hashCode() + age;
// }
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试1: 不重写equals和hashCode方法
public static void main(String[] args) {
Set<Person> ps = new HashSet<Person>();
//存入集合四个Person对象
ps.add(new Person("michael",18));
ps.add(new Person("john",19));
ps.add(new Person("tom",18));
ps.add(new Person("michael",18));
System.out.println(" ps :" + ps);
//需求:查看有没有一个叫michael并且是18的人
Person p1 = new Person("michael",18);
Person p2 = new Person("michael",18);
//查看hashCode是否相等
System.out.println(p1.hashCode());//1735600054
System.out.println(p2.hashCode());// 21685669
/**
* 调用contains方法,判断是否包含p1
* 1. 通过输出语句发现,返回的是false。
* 先调用hashCode,如果值不相同。就认为不存在
* 如果值相同,还是调用equals方法进行标记, equals返回true,表示存在,否则表示不存在
*
* 2. 结论:
* --。在自定义类的时候,如果这个类的对象要存入到Set集合中,需要重写hashCode和equals方法。
* -- 基于上述需求,我们自定义的类就应该必须重写这两个方法。因为我们也不知道未来如何使用。
*/
boolean b = ps.contains(p1);
System.out.println("是否包含:"+b);//false
}
上述测试:如果不重写equals和hashCode方法,则调用的是父类里的方法,不能满足现在我们想要的效果。
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
测试2:重写了equals和hashCode方法。
将元素存储到集合后,修改元素的hash值。
1.修改了元素的属性,会造成如下结果:
重复的元素就可以添加到集合中了
修改属性的那个元素,删除不掉了。
2.综上所述: 当对象存入集合后就不要修改对象的属性
public static void main(String[] args) {
Set<Person> set = new HashSet<>();
Person p1 = new Person("小明",18);
Person p2 = new Person("小张",19);
Person p3 = new Person("小王",18);
set.add(p1);
set.add(p2);
set.add(p3);
//因为我们重新了equasl和hashCode方法,所以p4,添加失败
Person p4 = new Person("小王",18);
set.add(p4);
System.out.println(set);
//[Person{name='小王', age=18}, Person{name='小明', age=18}, Person{name='小张', age=19}]
//修改p3的年龄为20岁
p3.setAge(20);
//添加与p3相同的对象
Person p5 = new Person("小王",20);
set.add(p5);
System.out.println(set); //添加成功
//[Person{name='小王', age=20}, Person{name='小明', age=18}, Person{name='小王', age=20}, Person{name='小张', age=19}]
//删除p3
set.remove(p3);
//从结果上看,Person("小王",20)并没有删除干净。
System.out.println(set);
//[Person{name='小王', age=20}, Person{name='小明', age=18}, Person{name='小张', age=19}]
}
5.Set集合的遍历:使用增强for循环和迭代器。
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
set.add(10);
set.add(20);
set.add(30);
set.add(40);
set.add(50);
System.out.println(set);
/**
* 1.set是无序的,因此不能使用fori遍历
* 2.使用增强for循环遍历
*/
for (Integer i : set){
System.out.println(i);
}
/**
* 3.使用迭代器进行遍历
*/
Iterator<Integer> it = set.iterator();
while (it.hasNext()){
Integer ele = it.next();
System.out.println(ele);
}
2)TreeSet去重原理
如果元素的比较规则中,两个对象的比较结果是0,则视为是同一个元素,去重。
10.List排序
1.Comparable接口
1.自定义的类,如果过想要排序,不管是在数组中,还是在集合中。 要求元素必须是Comparable的子类型,因为底层需要调用Comparable的compareTo 所以,自定义的类,如果想要排序,那么必须实现Comparable接口,以及重写compareTo方法
比较规则:(默认升序)
-
如当前对象大于给定对象,那么返回值应为>0的整数
-
若小于给定对象,那么返回值应为<0的整数
-
若两个对象相等,则应返回0
2.在上述的基础之上,如果想要重新指定排序方式,不应该修改compareTo的方法,而是应该 使用Comparator比较器接口,来定义一个新的比较规则。调用集合或者数组的相关重载方法,可以传入一个比较器这样的方式,进行比较。
此时我们可以采用Comparator接口回调的方式。它提供了一个抽象方法:
-
int compare(T o1 , T o2)
比较规则如下
-
若o1>02, 则返回值应该>0
-
若o1<o2,则返回值应该<0
-
若o1==o2, 则返回值应该为0
工具类中提供了sort方法的重载方法
-
static void sort(List<T> list , Comparator c)
作用:使用比较器c,指定临时排序规则
案例演示:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add((int)(Math.random()*100));
}
System.out.println(list);
/**
* 利用工具类里的排序方法
*/
Collections.sort(list);//升序排序
System.out.println(list);
/**
* 由于集合工具类的sort(List list)是默认的升序排序,要重写一个降序排序
*/
Comparator c = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;//(自动拆箱)降序排序
}
};
Collections.sort(list,c);
System.out.println(list);
}
2.collections工具类
1)排序操作
1.reverse(List<?> list):反转 List 中元素的顺序
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
System.out.println(list);//[1, 2, 3, 4, 5]
Collections.reverse(list);
System.out.println(list);//[5, 4, 3, 2, 1]
2.shuffle(List):对 List 集合元素进行随机排序,即打乱集合
System.out.println(list);//[1, 2, 3, 4, 5]
Collections.shuffle(list);
System.out.println(list);//[3, 2, 4, 5, 1]
3.sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(5);
list.add(4);
list.add(3);
list.add(2);
System.out.println(list);//[1, 5, 4, 3, 2]
Collections.sort(list);
System.out.println(list);//[1, 2, 3, 4, 5]
4.sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(5);
list.add(4);
list.add(3);
list.add(2);
System.out.println(list);//[1, 5, 4, 3, 2]
//降序排序
Comparator<Integer> c = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
};
Collections.sort(list,c);
System.out.println(list);//[5, 4, 3, 2, 1]
}
5.swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(5);
list.add(4);
list.add(3);
list.add(2);
System.out.println(list);//[1, 5, 4, 3, 2]
Collections.swap(list,1,3);
System.out.println(list);//[1, 3, 4, 5, 2]
}
2)查找、替换
1.Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
System.out.println(list);//[1, 5, 4, 3, 2]
Integer max = Collections.max(list);
System.out.println(max);//5
2.Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
System.out.println(list);//[1, 5, 4, 3, 2]
//降序排序
Comparator<Integer> c = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
};
Integer max = Collections.max(list, c);
System.out.println(max);//1
3.Object min(Collection)
System.out.println(list);//[1, 5, 4, 3, 2]
Collections.sort(list);
System.out.println(list);//[1, 2, 3, 4, 5]
Integer min = Collections.min(list);
System.out.println(min);//1
4.Object min(Collection,Comparator)
System.out.println(list);//[1, 5, 4, 3, 2]
//降序排序
Comparator<Integer> c = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
};
Collections.sort(list,c);
System.out.println(list);//[5, 4, 3, 2, 1]
Integer min = Collections.min(list,c);
System.out.println(min);//5
5.int frequency(Collection,Object):返回指定集合中指定元素的出现次数
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(5);
list.add(5);
list.add(4);
list.add(3);
list.add(2);
System.out.println(list);//[1, 5, 5, 4, 3, 2]
int frequency = Collections.frequency(list, 5);
System.out.println(frequency);//2
}
6.boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
System.out.println(list);//[1, 5, 5, 4, 3, 2]
Collections.replaceAll(list,5,6);
System.out.println(list);//[1, 6, 6, 4, 3, 2]
11.Map接口
1.简介
Map是集合框架中的另一个父接口,它用来保存具有映射(一对一)关系的数据,这样的数据称之为键值对(Key-Value-Pair)。key可以看成是value的索引。特点如下:
-
key和value必须是引用类型的数据
-
作为key,在Map集合中不允许重复,但是可以为null。Value可以重复
-
key和value之间存在单向一对一关系,通过指定的key总能找到唯一,确定的value
根据内部数据结构的不同,Map接口有多种实现类,
(1)HashMap :底层使用了Hash表和红黑树的数据结构
(2)TreeMap :底层使用的二叉树。
Map里的元素也是无序的(在结构中的顺序和存入顺序无关)
2.常用方法
1.put():存入元素 (如果key相同,value不同,则会覆盖掉原有的value)
Map<String,Integer> map = new HashMap<>();
map.put("a",1);
map.put("b",2);
map.put("c",3);
map.put("d",4);
map.put("d",5);
//key值相同,会覆盖前一个的value值,即可以不可以重复
System.out.println(map);//{a=1, b=2, c=3, d=5}
2.get (K k) :返回的是value,通过键找值
System.out.println(map);//{a=1, b=2, c=3, d=5}
Integer integer = map.get("a");
System.out.println(integer);//1
3.key可以为空,但是只能有一个。
System.out.println(map);//{a=1, b=2, c=3, d=5}
map.put(null,9);
map.put(null,10);//会覆盖掉上一个
System.out.println(map);//{null=10, a=1, b=2, c=3, d=5}
4.boolean isEmpty() 查看是否为空
System.out.println(map);//{a=1, b=2, c=3, d=5}
boolean empty = map.isEmpty();
System.out.println("是否为空:"+empty);//false
5.containsKey(Object o) 查看是否包含这个key值
System.out.println(map);//{a=1, b=2, c=3, d=5}
boolean b = map.containsKey("d");
System.out.println("是否包含:"+b);//true
6.containsValue(Object o) 查看是否包含这个value值
System.out.println(map);//{a=1, b=2, c=3, d=5}
boolean b = map.containsValue(4);
System.out.println("是否包含:"+b);//false
7.V remove(K k) 移除一个键值树 ,返回的是移除的键值树的value值
System.out.println(map);//{a=1, b=2, c=3, d=5}
Integer b = map.remove("d");
System.out.println(b);//5
System.out.println(map);//{a=1, b=2, c=3}
8.clear()清空所有键值树
System.out.println(map);//{a=1, b=2, c=3, d=5}
map.clear();
System.out.println(map);//{}
9.Map集合的遍历:keySet()和entrySet()
(1). keySet() 返回所有的key的set集合形式
System.out.println(map);//{a=1, b=2, c=3, d=5}
Set<String> strings = map.keySet();
for (String s :strings){
System.out.print(s+" ");//a b c d
}
(2)entrySet()返回entry对象(每一个entry都是一个键值对)的Set集合
Entry类型提供了getKey()和getValue()方法
System.out.println(map);//{a=1, b=2, c=3, d=5}
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry:entries){
System.out.print(entry.getKey()+"="+entry.getValue()+" ");//a=1 b=2 c=3 d=5
}
3.HashMap的实现原理
1)原理
HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突
图中,紫色部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中
2)装载因子及其HashMap优化
capacity:容量,hash表里bucket(桶)的数量,也就是散列数组大小
initial capacity:初始容量,创建hash表时,初始bucket的数量,默认构建容量为16,也可以使用特定容量。
size:大小,当前散列表中存储数据的数量
load factor:加载因子,默认值0.75也就是75%,当向散列表增加数据时,如果size/capacity的值大于loadfactor,则发生扩容并且重新散列(rebash)
性能优化:加载因子较小时,散列查找性能会提高,但是也浪费了散列桶空间容量。0.75是性能和空间相对平衡结果。在创建散列表时指定合理容量,减少rehash能提供性能
4.HashMap与HashTable
HashMap 和 Hashtable 是 Map 接口的两个典型实现类
区别:
-
Hashtable 是一个古老的 Map 实现类,不建议使用
-
Hashtable 是一个线程安全的 Map 实现,但 HashMap 是线程不安全的,所以查找效率高
-
Hashtable 不允许使用 null 作为 key 和 value,而 HashMap 可以
与 HashSet 集合不能保证元素的顺序一样,Hashtable 、HashMap 也不能保证其中 key-value 对的顺序
5.LinkedHashMap
LinkedHashMap 是 HashMap 的子类
LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致
public static void main(String[] args) {
Map<String,String> map = new LinkedHashMap<>();
map.put("张三","北京");
map.put("李四","上海");
map.put("王五","北京");
map.put("赵六","长春");
System.out.println(map);//有序的//{张三=北京, 李四=上海, 王五=北京, 赵六=长春}
}
6.TreeMap
TreeMap 存储 Key-Value对时,需要根据 Key 对 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
TreeMap 的 Key 的排序:
-
自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
Map<String,Integer> map = new TreeMap<>();
map.put("lisi",80);
map.put("wangwu",68);
map.put("zhaoliu",68);
map.put("michael",68);
System.out.println(map);//{lisi=80, michael=68, wangwu=68, zhaoliu=68}
-
定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
public static void main(String[] args) {
Comparator<String> c = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);//调用String类自己的compareTo方法
}
};
Map<String,Integer> map = new TreeMap<>(c);
map.put("lisi",80);
map.put("wangwu",68);
map.put("zhaoliu",68);
map.put("michael",68);
System.out.println(map);//{zhaoliu=68, wangwu=68, michael=68, lisi=80}
}
12.Properties
1.是HashTable的子类型,比较常用,一般用于加载配置文件里的KEY和VALUE
2.因为配置文件里都是字符串,因此Properties里的KREY和VALURE也都要是String类型。
3.该对象的key和value都不可以为null
(1)常用方法
1.put()方法:添加键值树
Properties p = new Properties();
p.put("a","3");
p.put("h","5");
p.put("g","7");
p.put("f","9");
p.put("c","5");
System.out.println(p);//{a=3, h=5, g=7, f=9, c=5}
2.getProperty( String key) 通过指定的key,获取对应的value,如果不存在,返回null
System.out.println(p);//{a=3, h=5, g=7, f=9, c=5}
String f = p.getProperty("f");
System.out.println(f);//9
3.getProperty(String key,String defaultValue)
如果存在,获取到对应的value,覆盖掉默认的defaultValue,
如果不存在,返回默认的defaultValue
System.out.println(p);//{a=3, h=5, g=7, f=9, c=5}
String f = p.getProperty("f","0");
System.out.println(f);//9
String w = p.getProperty("w", "6");
System.out.println(w);//6
4.遍历
System.out.println(p);//{a=3, h=5, g=7, f=9, c=5}
Set<Map.Entry<Object, Object>> entries = p.entrySet();
for (Map.Entry<Object, Object> entry:entries){
System.out.print(entry.getKey()+"="+entry.getValue()+" ");//a=3 h=5 g=7 f=9 c=5
}