java集合

❤️概述

Java 集合可分为 CollectionMap 两种体系

Collection接口:单列数据,定义了存取一组对象的方法的集合

  • List元素有序、可重复的集合
  • Set元素无序、不可重复的集合

Map接口:双列数据,保存具有映射关系“key-value对”的集合

Collection接口继承树

image-20220724180218568

Map接口继承树

image-20220724180225716

java集合框架图

img

从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayListLinkedListHashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。

Collection 接口方法

1、添加
add(Object obj)
addAll(Collection coll)
2、获取有效元素的个数
int size()
3、清空集合
void clear()
4、是否是空集合
boolean isEmpty()
5、是否包含某个元素
boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象 (单个对象)
boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。(一个集合)
6、删除
boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
boolean removeAll(Collection coll):取当前集合的差集
7、取两个集合的交集
boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
8、集合是否相等
boolean equals(Object obj)
9、转成对象数组
Object[] toArray()
10、获取集合对象的哈希值
hashCode()
11、遍历
iterator():返回迭代器对象,用于集合遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Test
void MethodTest() {
Collection coll = new ArrayList();
//add()
coll.add(21342);
coll.add("6666");

//size()
System.out.println(coll.size());
//addAll()
Collection coll1 = coll;
coll.addAll(coll1);
System.out.println(coll);
coll.add(new Person(16, "Dick"));
//isEmpty() //判断集合中是否有元素
if (coll.isEmpty())
System.out.println("集合为空");
if (coll.contains("6666"))
System.out.println("包含");
//contains 的判断依据是调用equals() 方法
if (coll.contains(new Person(16, "Dick")))
System.out.println("包含" + "new Person(16, \"Dick\")");
//remove all
coll.addAll(coll1);
System.out.println("addAll" + coll);
coll.removeAll(coll1);
System.out.println("removeAll" + coll);
coll.addAll(coll1);

coll.retainAll(coll1);
System.out.println("retainAll" + coll);

//hashcode()
System.out.println(coll.hashCode());
//toArray 集合转换成数组
Object arr[] = coll.toArray();
for (int i = 0; i < arr.length; i++)
System.out.println(arr[i]);
//Arrays.asList() 数组-->集合
Collection coll2 = Arrays.asList(arr);
System.out.println(coll2);
List<int[]> list1 = Arrays.asList(new int[]{123, 1245});
System.out.println(list1);
List<Integer> list2 = Arrays.asList(new Integer[]{123, 1245});
System.out.println(list2.size());

//iterator 返回Iterator接口的实例,用于遍历集合元素,放在IteratorTest.java中测试
}

Set和List的区别

  1. Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。
  2. Set 检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>
  3. List 和数组类似,可以动态增长,根据实际存储的数据的长度自动增长 List 的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector>

✋Iterator迭代器接口

1.Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。

2.GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。**迭代器模式,就是为容器而生。**类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。

3.Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。

4.Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。

5.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合
的第一个元素之前。

1
2
3
4
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}

🅰️注意的问题and 方法:

  • iterator.next():①指针下移 ②将下移以后集合位置上的元素返回

    注意只要调用了next() ,指针就会下移一次

  • hasNext():判断是否还有下一个元素

  • Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方 法,不是集合对象的remove方法。

  • 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。

for each遍历集合

​ 1.Java 5.0 提供了 foreach 循环迭代访问 Collection和数组。

​ 2.遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。

​ 3.遍历集合的底层调用Iterator完成操作。

​ 4.foreach还可以用来遍历数组。

⭐注意:temp的改变不会影响集合本身

1
2
3
4
5
6
7
Collection<Integer> arr = Arrays.asList(123, 54, 76987, 765);  //简洁版

//Collection<Integer> arr = Arrays.asList(new Integer[]{123, 54, 76987, 765});

for (Integer temp : arr) {
System.out.println(temp.intValue());
}

⭐⭐⭐List接口

1.鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组

2.List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。

3.List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。

4.JDK API中List接口的实现类常用的有:ArrayList、LinkedList、Vector

ArrayList LinkedList Vector 三类异同

:都是实现了List接口, 类内元素是 有序可重复的
不同

  • ArrayList: 作为List接口的主要实现类, 线程不安全,效率高 ,底层使用Object[] elementData存储

  • LinkedList :底层使用双向链表存储, 对于频繁的插入以及删除操作,使用此类的效率高于ArrayList

  • Vector: 作为List接口的古老实现类(模仿cpp) 线程安全,效率低 ,底层使用Object[] elementData存储

ArrayList的源码分析:

jdk 7情况下

1
2
3
4
5
6
7
8
9
ArrayList list = new ArrayList();
//底层创建了长度是10的object[]数组eLementDatalist.add(123);
//eLementData[e] = new Integer(123);

list.add(11);
//如果此次的添加导致底层eLementData数组容量不够,则扩容。
//默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
//结论:建议开发中使用带参的构造器:
ArrayList list = new ArrayList(int capacity)

jdk8中ArrayList的变化

1
2
3
4
5
ArrayList list = new ArrayList();
//底层object[] elementData初始化为{ }.并没有创建容量为10的数组
list.add(123);
//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到eLememntData[]后续的添加和扩容操作与jdk 7无异。

JDK7 的类似于单例模式的饿汉式,JDK8中ArrayList对象的创建类似于懒汉式,节省内存

LinkedList的源码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
LinkedList list = new LinkedList();//内部声明了Node类型的first和Last属性,默认值为null
list.add(123);
//将123封装到Node中,创建了Node对象。
其中,Node定义为:体现了LinkedList的双向链表的说法
private static cLass Node<E>
{
E item;
Node<E> next;
Node<E>prev;

Node(Node<E>prev, E eLement,Node<E> next)
{
this.item= eLement;
this.next = next;
this.prev = prev;
}
}

Vector的源码分析:

Vector的源码分析: jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来的数组长度的2倍。|

List接口方法

List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来

操作集合元素的方法。

  • **void add(int index, Object ele)😗***在index位置插入ele元素

  • boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来

  • Object get(int index):获取指定index位置的元素

  • int indexOf(Object obj):返回obj在集合中首次出现的位置

  • int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置

  • **Object remove(int index)😗*移除指定index位置的元素,并返回此元素

  • Object set(int index, Object ele):设置指定index位置的元素为*ele

  • List subList(int fromIndex, int toIndex):返回从*fromIndex到toIndex位置的子集合

List常用方法

  • 增:add(Object obj)

  • 删:remove(int idx)

  • 改:set(int idx,Obj ele)

  • 查:get(int idx)

  • 插:add(int idx,Object obj)

  • 长度:size()

  • 遍历

    1. Iterator iterator =list.iterator;
      while(iterator.hasnext())
      {
      	Systen.out.println(iterator.next());
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37

      2. foreach

      3. 普通的循环 (利用 get()方法)

      ---



      ## **注意Collection 中的remove() 与 List的remove()的区别**

      - List()重载了Collection 的remove()

      ```java
      public class ListExer {
      public static void main(String[] args) {
      List list = new ArrayList();
      list.add(1);
      list.add(2);
      list.add(3);
      List l2 = new ArrayList(list);
      System.out.println(list);
      f(list);
      System.out.println(list);
      list = l2;
      f2(list);
      System.out.println(list);

      }

      static void f(List list) {
      list.remove(2);
      }
      static void f2(Collection coll) {
      coll.remove(2);
      }
      }

以及int ,Integer,Object传参优先级的问题

测试类设计

  • 测试类一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Text {

public void remove(int index){
System.out.println("调用传参为int的remove方法");
}

public void remove(Integer object){
System.out.println("调用传参为Integer的remove方法");
}

public void remove(Object object){
System.out.println("调用传参为Object的remove方法");
}
}
  • 测试类二
1
2
3
4
5
6
7
8
9
10
public class Text {

public void remove(Integer object){
System.out.println("调用传参为Integer的remove方法");
}

public void remove(Object object){
System.out.println("调用传参为Object的remove方法");
}
}
  • 测试类三
1
2
3
4
5
6
public class Text {

public void remove(Object object){
System.out.println("调用传参为Object的remove方法");
}
}12345

结果

三个测试类分别传入int,Integer,Object型变量,观察效果。

  • 测试类一

传入类型为int:调用传参为int的remove方法

传入类型为Integer:调用传参为Integer的remove方法

传入类型为Object:调用传参为Object的remove方法

  • 测试类二

传入类型为int:调用传参为Integer的remove方法

传入类型为Integer:调用传参为Integer的remove方法

传入类型为Object:调用传参为Object的remove方法

  • 测试类三

传入类型为int:调用传参为Object的remove方法

传入类型为Integer:调用传参为Object的remove方法

传入类型为Object:调用传参为Object的remove方法

从输出结果可以看出,当方法的传参的类层级逐渐变高时,层级较低的传参会进行向上转型适应传参的需要。

🐆Set接口

  • 存储无序的,不可重复的数据,Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法.

✋特点

要求:

  1. 像Set中添加的数据,其所在的类一定要重写equals()以及hashCode()

  2. 重写的hashcode()以及equals()尽可能保持一致性

  3. 以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

    💡对于equals用到的属性,hashCode()也要用到

存储数据

  • 存储无序的,不可重复的数据

以HashSet为例
1.无序性:无序性,不等于随机性,存储的数据在数组中并非按照数组索引的顺序添加二是根据数据的哈希值添加的

​ 2.不重复性,保证添加的元素按照equals方法判断时,不能返回true,即相同的元素无法添加进来,

添加元素的过程:以HashSet为例

​ 1.我们向HashSet中添加元素a,首先调用a所在类的hashcode()方法,计算a的哈希值,此哈希值接着通过某种算法
计算出在HashSet底层数组存放的位置(index),判断 数组位置上是否已经有元素,
如果没有元素,则a添加成功 ------------1
如果有其他元素(b或者已经存在以链表形式存在的多个元素),则比较a与b 的哈希值,
​ 如果哈希值不相同,则添加成功 ,------------2
如果哈希值相同,那么进而调用a所在类的equals()方法,
​ 如果equals()返回true,则a添加失败
​ 如果返回false, 则a添加成功------------3
对于添加成功的情况2 &&情况3而言,a 元素与已经存在在指定索引位置上的数据以链表的方式存储
JDK7 中, 元素a 放到数组中,指向原来的元素
JDK8 中, 原来的元素放到数组中,指向元素a

image-20220724180238457

1.HashSet

1
HashSet:作为Set的主要实现类,线程不安全,可以存储null

1.HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。

2.HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除

性能。

3.HashSet 具有以下特点:

  • 不能保证元素的排列顺序

  • HashSet 不是线程安全的

  • 集合元素可以是 null

4.HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。

5.对于存放在Set容器中的对象,对应的类一定要重写

equals()和hashCode(Object obj)

方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

2.LinkedSet

1
LinkedSet:作HaseSet的子类遍历器内部数据时,可以按照添加的顺序遍历,

1.LinkedHashSet 是 HashSet 的子类

  1. LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。

3.LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。

4.LinkedHashSet 不允许集合元素重复。

3.TreeSet

1
2
TresSet:底层使用二叉树实现,放入其中的数据,可以按照添加的对象的指定属性进行排序,

💘重写compareTo记得要实现Comparable接口💘

1.TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。

2.TreeSet底层使用红黑树结构存储数据

3.自然排序:TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列

TreeSet中的类的对象必须要重写compareTo()方法,也就是必须要实现Comparable接口否则会ClassCastException

4.新增的方法如下: (了解)

  • Comparator comparator()

  • Object first()

  • Object last()

  • Object lower(Object e)

  • Object higher(Object e)

  • SortedSet subSet(fromElement, toElement)

  • SortedSet headSet(toElement)

  • SortedSet tailSet(fromElement)

TreeSet 两种排序方法:自然排序定制排序。默认情况下,TreeSet 采用自然排序

image-20220724180243681

向TreeSet中添加的数据,必须是相同类,不能添加不同类的对象,

红黑树小的在左边,大的在右边

排序—自然排序

1.向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。

2.**因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象。

3.对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值。

4.当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals() 方法时,应保证该方法与 compareTo(Object obj) 方法有一致的结果:如果两个对象通过equals() 方法比较返回 true,则通过 compareTo(Object obj) 方法比较应返回 0。否则,让人难以理解

Person.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package pers.dhx_.java0608;

import java.util.Objects;

public class
Person implements Comparable {
private int age;
private String name;

public Person(int age, String name) {
this.age = age;
this.name = name;
}

Person() {
}

@Override
public boolean equals(Object o) {
// System.out.println("调用equals方法");
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
return getAge() == person.getAge() && Objects.equals(getName(), person.getName());
}

@Override
public int hashCode() {
return Objects.hash(getAge(), getName());
}

public void setAge(int age) {
this.age = age;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}

@Override
public int compareTo(Object o) {
if (o instanceof Person) {
Person temp = (Person) o;
// return -this.name.compareTo(((Person) o).getName());
/*keypoint
直接return String 的compareTo 的话,如果name相同,则会直接返回0, 造成两个Person
相同的 "逻辑错误"
*/
int compare = this.name.compareTo(((Person) o).getName());
if (compare == 0) {
return Integer.compare(this.getAge(), ((Person) o).getAge());
} else return compare;
} else {
throw new RuntimeException("输入的类型不是Person");
}
}

public String getName() {
return name;
}

public static void walk() {
System.out.println("Person Walking");
}
}

自然排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
TreeSet set = new TreeSet();
System.out.println(set);
/* 自然排序
keypoint
比较两个对象是否相同的标准是:compareTo() 返回0, 不再是equals
*/
set.add(new Person(15, "Tom"));
set.add(new Person(25, "Dick"));
set.add(new Person(35, "Dan"));
set.add(new Person(23, "Amy"));
set.add(new Person(43, "Amy"));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}

**排 序—**定制排序

1.TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。

2.利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。

3.要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。

4.此时,仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异常。

5.使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package pers.dhx_.java0608.Set1;

import pers.dhx_.java0608.Person;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

/**
* @author Dhx_
* @className TreeSetTest
* @description TODO
* @date 2022/6/9 6:46
*/
public class TreeSetTest {
public static void main(String[] args) {
Comparator cmp = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Person & o2 instanceof Person) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
return p1.getName().compareTo(p2.getName());
} else throw new RuntimeException("输入的类型不匹配");
}
};
TreeSet set = new TreeSet(cmp);
//将设置好的Comparator作为 参数 放入TreeSet的构造方法
System.out.println(set);
set.add(new Person(15, "Tom"));
set.add(new Person(25, "Dick"));
set.add(new Person(35, "Dan"));
set.add(new Person(23, "Amy"));
set.add(new Person(43, "Amy"));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}