集合框架的一个小总结
简介
概述
集合就像一种容器,可以把多个对象放进容器内
特点:提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变
集合按照其存储结构可以分为两大类:
- 单列集合 Collection
- 双列集合 Map
集合体系结构
- Collection 单列
- List 可重复、有序(存储顺序)
- ArrayList(数组)
- LinkedList(双向链表)
- Set 不可重复、无序
- HashSet(哈希表)
- LinkedHashSet(哈希表、链表) 有序(存储顺序)、不可重复
- TreeSet(有序:大小顺序、不可重复)
- HashSet(哈希表)
- List 可重复、有序(存储顺序)
- Map 双列
- HashMap
- TreeMap
集合与数组的区别
-
数组的长度是固定的,集合的长度是可变的
-
数组长度在数组初始化时就已经确定,只能保存定长的数据;
而集合可以保存数量不确定的数据,同时可以保存具有映射关系的数据(key - value) -
数组元素既可以是基本类型的值,也可以是对象;
而集合只能保存对象(保存对象的引用变量),基本数据类型变量需要转换为对应的包装类才能放入集合类中
Collection集合
概述
Collection:单列集合类的 根接口,用于存储一系列符合某种规则的元素
两个子接口:
- List:有序、可重复
- Set:无序、不可重复
常用方法
方法名 | 说明 |
---|---|
boolean add(E e) | 添加元素 |
boolean remove(Object o) | 移除指定元素 |
void clear() | 清空集合 |
boolean contains(Object o) | 判断当前集合中是否包含给定的对象 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 返回集合元素个数 |
Collection<String> collection = new ArrayList<>();
collection.add("蓝天");
collection.add("白云");
System.out.println(collection);
System.out.println(collection.contains("蓝天"));
int len = collection.size();
// 集合的遍历
Object[] objects = collection.toArray();
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
for (String s : collection) {
System.out.println(s);
}
集合的遍历
Iterator:迭代器,集合的专用遍历方式,是一个接口,它是Collection接口的父接口
迭代器是通过集合的 iterator() 方法得到的,所以说它是依赖于集合而存在的
常用方法:
方法 | 说明 |
---|---|
E next() | 返回迭代的下一个元素 |
boolean hasNext() | 如果仍有元素可以迭代,则返回true |
//创建集合
Collection<Student> students = new ArrayList<Student>();
students.add(new Student("张三", 23));
// 获得集合的迭代器
Iterator<Student> iterator = students.iterator();
// 遍历
while (iterator.hasNext()) {
Student s = iterator.next();
System.out.println(s.toString());
}
使用迭代器进行元素的迭代,集合元素的值传递给了迭代变量,,仅仅传递了对象引用,保存的是指向对象内存空间的地址
实现原理:在遍历集合时,首先调用集合的 iterator() 方法获得迭代器对象,然后使用 hashNext() 方法判断集合中是否存在下一个元素,如果存在,则调用 next() 方法将元素取出,否则说明已到达了集合末尾,停止遍历元素
并发修改问题
使用Iterator迭代器遍历元素的时候,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致,如果使用add方法添加元素,会造成并发修改异常
解决:使用for、get方法遍历
List集合
概述
List接口继承Collection接口,是单列集合的一个重要分支
特点:
-
有序:存储和取出顺序一致
-
可重复
-
所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素
List接口的实现类:
- ArrayList
- LinkedList
特有方法
方法 | 说明 |
---|---|
void add(int index, E element) | 添加指定元素到指定索引位置 |
E remove(int index) | 删除指定索引处元素,并返回被删除的值 |
E set(int index, E element) | 修改指定索引处元素,并返回被修改的值 |
E get(int index) | 返回指定索引处元素 |
List<String> list = new ArrayList<>();
list.add("蓝");
list.add("蓝天");
list.add("蓝天空");
System.out.println(list);
System.out.println(list.remove(1));
list.set(0, "宇");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
for (String s : list) {
System.out.println(s);
}
ListIterator
ListIterator:列表迭代器
通过List集合的 listIterator() 方法得到
作用:运行沿任一方向遍历列表的列表迭代器
常用方法
方法 | 说明 |
---|---|
E next() | 返回下一个元素 |
boolean hasNext() | |
E previous() | 返回上一个元素 |
boolean hasPrevious() | |
void add(E e) | 插入 |
ArrayList
底层数据结构:数组
特点:查询快、增删慢
LinkedList
底层数据结构:链表
特点:查询慢、增删快
缺点:随机访问效率较低
LinkedList是一个双向链表,且LinkedList提供了大量首尾操作的方法:
方法 | 说明 |
---|---|
void addFirst(E e) | 在列表开头插入指定元素 |
void addLast(E e) | 在列表末尾插入指定元素 |
E getFirst() | 返回列表第一个元素 |
E getLast() | 返回列表最后一个元素 |
E removeFirst() | 删除并返回第一个元素 |
E removeLast() | 删除并返回最后一个元素 |
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("1");
linkedList.add("2");
linkedList.add("3");
System.out.println(linkedList.getFirst());
System.out.println(linkedList.getLast());
System.out.println(linkedList.removeFirst());
System.out.println(linkedList.removeLast());
while (!linkedList.isEmpty()) {
System.out.println(linkedList.pop());
}
System.out.println(linkedList);
Set集合
概述
特点:
- 不包含重复元素
- 元素无序
- 没有带索引的方法,所以不能使用普通for遍历
Set集合的实现类:
- HashSet
- TreeSet
遍历
Set<String> set = new HashSet<String>();
set.add("1");
set.add("2");
// 方式一
for (String s : set) {
System.out.println(s);
}
// 方式二:迭代遍历
Set<String> set = new HashSet<String>();
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String str = it.next();
System.out.println(str);
}
HashSet
底层数据结构:哈希表(实际上是一个HashMap实例)
特点:
- 无序
- 不可重复
- 没有带索引的方法,所以不能使用普通for循环遍历
- 根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能
HashSet<String> strings = new HashSet<>();
strings.add("123");
strings.add("1234");
strings.add("12345");
for (String name : strings) {
System.out.println(name);
}
元素唯一分析
哈希表,在JDK8之前底层采用 数组+链表 实现
要保证元素唯一性,需要重写hashCode()和equals()方法
HashSet集合添加一个元素的过程:
- 调用对象的hashCode()方法获取对象的哈希值
- 根据对象的哈希值计算对象的存储位置
- 判断该位置是否有元素存在
- 有:遍历该位置的所有元素,和新存入的元素比较哈希值是否相同
- 有相同的:调用equals()方法比较对象内容是否相等
- 相同:说明元素重复,不存储
- 不同:将元素存储到该位置
- 都不相同:将元素存储到该位置
- 有相同的:调用equals()方法比较对象内容是否相等
- 没有:将元素存储到该位置
- 有:遍历该位置的所有元素,和新存入的元素比较哈希值是否相同
存储自定义对象
- 自定义Student类(需要重写hashCode、equals方法)
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
@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(name, age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class HashSetTest {
public static void main(String[] args) {
HashSet<Student> students = new HashSet<>();
students.add(new Student("蓝天", 100));
students.add(new Student("星月", 1000));
students.add(new Student("镇天", 10000));
for (Student student : students) {
System.out.println(student);
}
}
}
LinkedHashSet
特点:
- 底层:哈希表和链表
- 有序
- 元素不重复
TreeSet
特点:
- 元素有序(排序),排序方式取决于构造方法
- 无带索引的方法,不能使用普通for遍历
- 不可重复
TreeSet() // 自然排序(从小到大)
TreeSet<Integer> t = new TreeSet<>();
TreeSet(Comparator comparator); // 根据指定的比较器进行排序
如果要对对象进行排序,对象需要实现Comparable接口,例如(使用自然排序方式)
public class Student implements Comparable<Student> {
private String name;
private int age;
@Override
public int compareTo(Student s) {
//return 0; 说明元素重复
//return 1; 按照存储顺序
//return -1; 按照倒序
// 按照年龄从小到大排序,年龄相同时,按照姓名
int num = this.age - s.age;
// 从大到小
// int num = s.age - this.age;
int t = num == 0 ? this.name.compareTo(s.name) : num;
return t;
}
}
除了使用自然排序方式实现排序,还可以使用比较器排序Comparator
这里需要有带参构造方法
TreeSet<Student> t = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge() - s2.getAge();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num2;
}
})
新增方法
方法 | 功能描述 |
---|---|
first() | 返回此Set中当前第一个(最低)元素 |
last() | 返回此Set中当前最后一个(最高)元素 |
comparator() | 返回比较器,如果使用的是自然顺序,返回null |
headSet(E toElement) | 返回一个新的Set集合,包含toElement之前所有对象 |
subSet(E fromElement, E fromElement) | 返回[)之间的对象 |
tailSet(E fromElement) | 返回fromElement(包含)之后的所有对象 |
泛型
泛型自JDK5引入
本质:参数化类型
参数化类型:将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型
这种参数类型可以用正在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口
好处:
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换
泛型类
格式:
修饰符 class 类名<类型> { }
public class Generic<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
使用
Generic<String> g = new Generic<String>();
泛型方法
使用泛型类改进
public class Generic<T> {
public void show(T t) {
System.out.println(t);
}
}
泛型方法改进
public class Generic {
public <T> void show(T t) {
System.out.println(t);
}
}
使用
Generic g = new Generic();
g.show("ni");
g.show(1);
泛型接口
定义格式
修饰符 interface 接口名<类型> { }
类型通配符
为了表示各种泛型List的父类,可以使用类型通配符 <?>
List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型
类型通配符上限:<? extends 类型>
例如:List<? extends Number>,表示的类型是Number或者其子类型
类型通配符下限:<? super 类型>
例如:List<? super Number>,表示的类型是Number或者其父类型
Map集合
概述
Map集合没有继承Collection接口,其提供的是key到value的映射
特点:
- 不能包含重复的键(对象重写equals和hashCode方法)
- 每个键可以映射最多一个值
Map接口的实现类:
- HashMap
- TreeMap
由HashMap类实现的Map集合添加和删除映射关系效率更高
常用方法
方法 | 说明 |
---|---|
V put(K kye, V value) | 添加元素 |
V get(Object key) | 获取指定键的键值 |
V remove(Object key) | 根据键删除键值对象 |
void clear() | 移除所有键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合长度 |
Set |
获取所有键的集合 |
Collection |
获取所有值的集合 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
Map集合的遍历
方式一:
- 获取所有 键的集合
- 遍历键的集合,获取键值
- 根据键找键值
Set<String> keySet = map.keySet();
for (String key : keySet) {
String value = map.get(key);
...
}
方式二:
- 获取所有 键值对对象的集合
- 遍历键值对对象的集合,得到每一个键值对对象
- 根据键值对对象获取键和值
Map<String, String> map = new HashMap<String, String>();
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String, String> me : entrySet) {
String key = me.getKey();
String value = me.getValue();
}
HashMap
底层:哈希表的Map接口,此实现提供所有可选的映射操作,并允许使用null值和null键,但必须保证键的唯一性
HashMap通过哈希码对其内部的映射关系进行快速查找
常用子类:
- HashMap<K,V>
- 存储数据采用的哈希表结构
- 无序
- 由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法
- LinkedHashMap<K,V>
- HashMap的子类
- 存储数据采用的哈希表结构+链表结构
- 有序
- 通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法
Map接口中的集合都有两个泛型变量<K,V>
在使用时,要为两个泛型变量赋予数据类型
两个泛型变量<K,V>的数据类型可以相同,也可以不同
Map集合不能直接使用迭代器或者foreach进行遍历,但是转成Set之后就可以使用了
Collections
Collections类是针对集合操作的工具类
常用方法:
- public static <T extends Comparable<? super T>> void sort(List
list) :将指定列表升序排序 - public static void reverse(List<?> list):反转列表元素顺序
- public static void shuffle(List<?> list):使用默认的随机源随机排序指定的列表
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(30);
list.add(20);
list.add(50);
list.add(40);
list.add(10);
System.out.println(list);
// 升序排序
Collections.sort(list);
System.out.println(list);
// 反转
Collections.reverse(list);
System.out.println(list);
// 洗牌
Collections.shuffle(list);
System.out.println(list);
}
如果要对对象进行排序,需要添加比较器排序Comparator
Collections.sort(array, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge() - s2.getAge();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num2;
}
})
见LinkedHashSet