Java SE进阶之集合

本文最后更新于:1 年前

集合-1

1.集合的概念

​ 集合就是用于存储多个数据的容器。相对于具有相同功能的数组来说,集合的长度可变会更加灵活方便。Java中提供了使用不同数据结构存储数据的不同集合类,他们有各自不同的特点,并且在类中提供了很多常用了方法,便于我们使用。

2.集合体系结构

​ 集合主要有两个顶层接口,Collection和Map。

3. 常用list集合

3.1 list集合的特点

​ List接口下的集合都会有以下特点:

  • 有索引
  • 可以存储重复元素
  • 元素存入的顺序和实际存储的顺序相同

3.2 ArrayList

3.2.1 创建对象

1
2
ArrayList list = new ArrayList<>();//不限定集合中存放元素的数据类型
ArrayList<集合元素的数据类型> list2 = new ArrayList<>();//限定集合中存放元素的数据类型
JAVA

3.2.2 常用方法

1
2
3
4
5
6
7
8
boolean add(E e)  //添加元素,直接添加到集合的末尾 返回值代表是否添加成功
void add(int index, E element) //往指定索引位置添加元素
boolean remove(Object o)// 删除元素
E remove(int index) //删除指定索引位置的元素,返回值是被删除的元素
E set(int index, E element) //修改指定索引位置的元素 返回值为修改之前的元素值
E get(int index) //获取指定索引位置的元素 返回值为对应的元素
int size() //获取集合中元素的个数
boolean contains(Object o) //判断集合中是否存在某个元素 ,返回值代表是否存在
JAVA

我们平时对集合用的最多的是add,remove,get,set,size这几个方法。

3.2.3 遍历

1.使用索引遍历

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("三");
list.add("更");
list.add("草");
list.add("堂");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
JAVA

2.使用迭代器遍历

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("三");
list.add("更");
list.add("草");
list.add("堂");
Iterator<String> it = list.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
}
JAVA

注意:为了避免并发修改异常(ConcurrentModificationException)的出现。避免在使用迭代器遍历的过程中对集合进行操作。

3.使用foreach遍历

​ foreach是java提供的一个语法糖。可以让我们更方便的遍历集合或数组。

格式如下:

1
2
3
for(元素数据类型 变量名 : 遍历的集合或者数组){
//遍历的时候会把遍历到的元素赋值给我们上面定义的变量
}
JAVA

例如:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("三");
list.add("更");
list.add("草");
list.add("堂");
for (String s : list) {
System.out.println(s);
}
}
JAVA
1
2
3
4
String[] arr = {"三","更","草","堂"};
for(String s : arr){
System.out.println(s);
}
JAVA

4.转换为数组遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("三");
list.add("更");
list.add("草");
list.add("堂");

//把list集合转换为数组 参数列表 返回值类型 []
// Object[] objects = list.toArray();


//遍历数组
// for (int i = 0; i < objects.length; i++) {
// System.out.println(objects[i]);
// }

// public <T> T[] toArray(T[] a)
String[] strings = list.toArray(new String[0]);
for (String string : strings) {
System.out.println(string);
}

}
JAVA

3.3 LinkedList

3.3.1 创建对象

1
2
LinkedList list = new LinkedList<>();//不限定集合中存放元素的数据类型
LinkedList<集合元素的数据类型> list2 = new LinkedList<>();//限定集合中存放元素的数据类型
JAVA

3.3.2 常用方法

1
2
3
4
5
6
7
8
boolean add(E e)  //添加元素,直接添加到集合的末尾 返回值代表是否添加成功
void add(int index, E element) //往指定索引位置添加元素
boolean remove(Object o)// 删除元素
E remove(int index) //删除指定索引位置的元素,返回值是被删除的元素
E set(int index, E element) //修改指定索引位置的元素 返回值为修改之前的元素值
E get(int index) //获取指定索引位置的元素 返回值为对应的元素
int size() //获取集合中元素的个数
boolean contains(Object o) //判断集合中是否存在某个元素 ,返回值代表是否存在
JAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
//添加元素
list.add("更");
list.add(0,"三");
//删除元素
list.remove("三");
//修改元素
list.set(0,"三更草堂");
//获取元素
String s = list.get(0);
//获取集合大小
int size = list.size();
//判断元素是否存在
boolean flag = list.contains("三更草堂");
}
JAVA

我们发现上面这些方法其实和ArrayList中的常用方法都是相同的。因为LinkedList和ArrayList都是List接口的实现类,上面的很多方法都是他们共同的接口中定义的方法,所以都会有。

下面是LinkedList的一些特有方法:

1
2
3
4
>void addFirst(E e)  //把元素添加到集合的最前面
>void addLast(E e) //把元素添加到集合的最后面
>E removeFirst() //删除集合最前面的一个元素,返回值代表被删除的元素
>E removeLast() //删除集合最后面的一个元素,返回值代表被删除的元素
JAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("三");
list.add("更");
list.add("草");
list.add("堂");

list.addFirst("[");
list.addLast("]");


String s = list.removeFirst();
System.out.println(s);
String s1 = list.removeLast();
System.out.println(s1);
}
JAVA

3.3.3 遍历

​ 同ArrayList。

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
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("三");
list.add("更");
list.add("草");
list.add("堂");

//遍历集合
//for循环遍历
// for (int i = 0; i < list.size(); i++) {
// System.out.println(list.get(i));
// }

//迭代器
// Iterator<String> it = list.iterator();
// while (it.hasNext()){
// String s = it.next();
// System.out.println(s);
// }

//foreach
// for(String s : list){
// System.out.println(s);
// }

//转换为数组遍历
String[] strings = list.toArray(new String[0]);
for (int i = 0; i < strings.length; i++) {
System.out.println(strings[i]);
}
}
JAVA

3.4 ArrayList和LinkedList的区别

​ 都是实现了List接口,不同点是底层存储数据的数据结构不同。ArrayList底层是用数组来存储,而LinkedList是链表。所以各自的特点也和数据结构的特点一样。

ArrayList : 查找快,增删慢

LinkedList: 增删快,查找慢

集合-2

1. 常用Set集合

1.1 Set集合的特点

​ Set接口下的集合都会有以下特点

  • 不能存储重复元素
  • 没有索引

1.2 HashSet

HashSet集合的特点

  • 底层数据结构是哈希表
  • 存储元素的顺序和遍历获取出来的顺序可能不一致
  • 没有索引
  • 集合中不能存储重复元素

1.2.1 创建对象

1
HashSet<元素数据类型> set = new HashSet<>();
JAVA
1
2
3
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
}
JAVA

1.2.2 常用方法

1
2
3
4
>boolean add(E e)  //添加元素,如果元素添加不成功    返回值代表是否添加成功,
>boolean remove(Object o) //删除元素 ,返回值代表删除元素是否成功
>boolean contains(Object o) //判断元素是否存在
>int size() //获取集合的大小
JAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
//添加元素
boolean f = set.add("三");
set.add("更");
set.add("草");
set.add("堂");

boolean f2 = set.add("三");

//删除元素
boolean f3 = set.remove("三");
boolean f4 = set.remove("三");

//判断元素是否存在
boolean f5 = set.contains("更");

//获取集合的大小
int size = set.size();

}
JAVA

1.2.3 遍历

1.转换为数组遍历

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("三");
set.add("更");
set.add("草");
set.add("堂");

String[] strings = set.toArray(new String[0]);
for (int i = 0; i < strings.length; i++) {
System.out.println(strings[i]);
}
}
JAVA

2.使用迭代器遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("三");
set.add("更");
set.add("草");
set.add("堂");

Iterator<String> it = set.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
}
JAVA

3.foreach遍历

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("三");
set.add("更");
set.add("草");
set.add("堂");

for (String s : set) {
System.out.println(s);
}
}
JAVA

2. 泛型

2.1 概述

​ 泛型可以把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型 。

​ 相当于把数据类型作为参数来进行传递。

注意:泛型只能是引用数据类型。

2.2 使用

2.2.1 泛型类&泛型接口

​ 泛型类和泛型接口的用都相同,下面我们以泛型类为例进行讲解。

​ 泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来 。

2.2.1.1 定义泛型

​ 在类名后加<>,在<>中定义泛型,<>中的内容相当于泛型的名字,可以随便写。在泛型类中我们可以把这个泛型的名字当做一个数据类型来使用。

1
2
3
public class TestClass<T> {
//...
}
JAVA
2.2.1.2 使用泛型

​ 在泛型类中可以使用在类名后面定义的泛型。

1
2
3
4
5
public class TestClass<T> {
public void test(T t){

}
}
JAVA
2.2.1.3 泛型的确定

①创建对象时确定

​ 在创建泛型类对象的时候确定之前定义的泛型代表什么数据类型。在定义泛型类对象的时候,在类名的后加<>,在其中写一个具体的数据类型。

1
2
3
4
public static void main(String[] args) {
TestClass<String> t = new TestClass();//指定了该对象的泛型T是String类型
t.test("三更草堂");//所以test方法的参数类型应该也是String类型
}
JAVA

②定义子类时确定

​ 在定义子类的时候可以确定泛型。具体用法如下:

1
2
3
4
5
6
public class SubClass extends TestClass<String> {
@Override
public void test(String s) {

}
}
JAVA

​ 这样在子类SubClass中泛型就确定为String类型了。

注意:我们在定义子类时也可以选择不确定泛型,让其在创建对象的时候在确定。写法如下

1
2
3
4
5
6
public class SubClass<T> extends TestClass<T> {
@Override
public void test(T t) {
super.test(t);
}
}
JAVA

2.2.2泛型方法

2.2.2.1 定义泛型

​ 在方法返回值类型的前面加<>,在<>中定义泛型,<>中的内容相当于泛型的名字,可以随便写。在该泛型方法中我们可以把这个泛型的名字当做一个数据类型来使用。

1
2
3
public static  <T> T test(T t){
return t;
}
JAVA
2.2.2.2 使用泛型

​ 在泛型方法中可以使用定义的泛型。并且我们一般是在参数列表中或者是返回值类型上使用到这个泛型。

1
2
3
public static  <T> T test(T t){
return t;
}
JAVA
2.2.2.3 泛型的确定

​ 在调用泛型方法的时候才真正确定之前定义的泛型代表什么数据类型。在调用泛型方法的时候,程序会根据你的调用自动推导泛型的具体类型。

1
2
3
4
public static void main(String[] args) {
Integer test = test(1);
String s = test("三更草堂");
}
JAVA

2.3 泛型上限&泛型下限

2.3.1 泛型限定的概念

​ 我们在使用确定泛型的时候可以使用任意的引用数据类型去确定。但是在某些场景下我们要求这个泛型必须是某个类的子类或者是某个类的父类。这种情况下我们就需要用到泛型上限和泛型上限来限制泛型的范围。

2.3.1 泛型上限

​ 限制泛型必须是某个类或者是其子类。

格式:

1
<? extends 具体的类型>
JAVA

例如:

1
2
3
public static void test(List<? extends Person> t){

}
JAVA

这样我们再调用test方法的时候只能存入泛型为Person或者是Person子类的List集合对象。

2.3.2 泛型下限

​ 限制泛型必须是某个类或者是其父类。

格式:

1
<? super 具体的类型> 
JAVA

例如:

1
2
3
public static  void test(List<? super Student> t){

}
JAVA

这样我们再调用test方法的时候只能存入泛型为Student或者是Student父类的List集合对象。

2.3.3 注意事项

​ 1.泛型上限可以在定义泛型类和方法参数上使用

1
2
3
4
public class Box<E extends Person> {
E e;
}

JAVA

​ 2.泛型下限主要在方法参数上使用。

集合-3

1. 常用Map集合

1.1 Map集合的概述

​ Map接口是双列集合的顶层接口,下面是Map接口的定义

1
interface Map<K,V>  K:键的类型;V:值的类型
JAVA

​ 存储的数据必须包含key和value。

​ key和value在Map集合中是一一对应的关系。一个key对应一个value。

​ key在map集合中是不会重复的。

1.2 HashMap

HashMap集合的特点

  • 底层数据结构是哈希表
  • 存储元素的顺序和遍历获取出来的顺序可能不一致
  • key不会重复

1.2.1 创建对象

1
HashMap<key的数据类型,value的数据类型> map = new HashMap<>();
JAVA

例如:

1
2
3
4
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
HashMap<String,Integer> map = new HashMap<>();
}
JAVA

1.2.2 常用方法

1
2
3
4
5
6
>V put(K key, V value)   //添加元素,如果key不存在就添加,如果key已经存在则是修改对应的value,并且返回修改前的value
>V get(Object key) //根据key获取对应的value值返回。如果key不存在就返回null
>V remove(Object key) //根据key删除map中对应的键值对。并且把删除的value返回
>boolean containsKey(Object key) //判断key是否存在
>int size() //集合中键值对的对数
>void clear() //清空集合中的所有键值对
JAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
// map.put()
//添加元素
map.put("name", "三更");
map.put("age", "15");
String v = map.put("name", "三更草堂");
String name = map.get("name");
String age = map.get("age");
//删除元素
String delV = map.remove("age");
//判断key是否存在
if(map.containsKey("name")){
String age111 = map.get("name");//null
System.out.println(age111.length());
}
//size
int size = map.size();
map.clear();
}
JAVA

1.2.3 遍历

1.使用entrySet遍历

​ map集合的entrySet方法可以获取一个Set集合,集合中存放的是Entry对象,一个Entry对象相当于一个键值对。我们可以遍历set集合拿到Entry对象,然后获取出里面的键和值。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("name","三更");
map.put("age","15");

Set<Map.Entry<String, String>> entries = map.entrySet();
//使用迭代器遍历entrySet
Iterator<Map.Entry<String, String>> it = entries.iterator();
while (it.hasNext()){
Map.Entry<String, String> entry = it.next();
System.out.println(entry.getKey()+"===="+entry.getValue());
}
}
JAVA
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("name","三更");
map.put("age","15");

Set<Map.Entry<String, String>> entries = map.entrySet();
//使用foreach遍历entrySet
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey()+"===="+entry.getValue());
}
}
JAVA

2.使用keySet遍历

​ map集合的keySet方法可以获取一个Set集合,集合中存放的是所有的key。我们可以遍历set集合拿到key对象,然后通过key获取对应的value。

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("name","三更");
map.put("age","15");

Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key+"===="+map.get(key));
}
}
JAVA

1.3 HashMap的key去重原理

​ HashMap在添加元素的时候会判断集合中是否有key和本次存入的key相同。判断的时候主要是通过hashCode方法和equals方法来进行判断的。hashCode相同,并且equals判断也相同就会认为是同一个key。

​ 所以如果我们要存储到HashMap中的key是一个自定义的类型。就需要根据情况判断下是否需要重写下hashCode方法和equals方法。重写的时候使用IDEA的提示即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Student {
private int age;
private String name;
// ....此次省略了构造方法和set/get方法

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}

@Override
public int hashCode() {
return Objects.hash(age, name);
}
}
JAVA

注意:HashSet存储数据其实也是使用了HashMap。所以如果往HashSet中存储自定义对象也要看情况是否需要重写hashCode方法和equals方法。


Java SE进阶之集合
http://example.com/2020/05/09/Java-SE进阶之集合/
作者
Crush
发布于
2020年5月9日
更新于
2023年7月9日
许可协议