容器
很多时候,我们写程序需要进行批量的操作,比如说,新增一批学生列表.那么就需要有容器来装下这10个对象。
Java提供了许多容器来装对象,在JDK的java.util
包下,编程中最常用的有:
-
List:
有序列表,有序地存储元素 -
Set:
集合,存储的元素不可重复 -
Map:
映射,建立key->value关系 -
Quene:
队列,先进先出 -
Stack:
栈,先进后出
泛型
使用Java的集合类最好配合泛型进行使用,以免发生以下的错误:
package com.tea.modules.java8.generic;
import java.util.ArrayList;
import java.util.List;
/**
* com.tea.modules.java8.generic <br>
* 不要使用原始类型
* @author jaymin
* @since 2021/6/4
*/
@SuppressWarnings("all")
public class DoNotUseRawType {
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add("2");
}
}
例子中,我们对list没做任何类型限制,添加了数字1和字符串2,那么在遍历取值使用的时候,我们可能会将字符相关的操作用在数字上,从而引发了异常。
equals 和 hashcode
如果存储的是对象,那么需要重写equals和hashcode,否则你的集合操作会产生意外的异常.
package com.tea.modules.java8.collection.prevent;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
/**
* com.xjm.collection.prevent<br>
* 对集合对象进行操作,最好重写equals和hashcode,避免带来不好的影响<br>
*
* @author xiejiemin
* @create 2020/12/25
*/
@Slf4j
public class EqualsAndHashCode {
public static class Student implements Comparable<Student>{
/**
* 姓名
*/
private String name;
/**
* 性别:0-man|1-women
*/
private Integer gender;
public Student(String name, Integer gender) {
this.name = name;
this.gender = gender;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Student) {
Student student = (Student) obj;
return Objects.equals(this.name, student.name);
}
return false;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + gender;
return result;
}
@Override
public int compareTo(Student student) {
return this.gender - student.gender;
}
}
/**
* <h2>实现/不实现equals和hashcode对于判等的影响</h2>
*/
private static void beforeOverrideEqualsAndHashCode() {
Student jackA = new Student("Jack", 20);
Student jackB = new Student("Jack", 20);
log.info("Before override equals and hashcode->{}", Objects.equals(jackA,jackB));
Set<Student> studentSet = new HashSet<>();
studentSet.add(jackA);
studentSet.add(jackB);
studentSet.forEach(System.out::println);
Map<Student,Integer> studentMap = new HashMap<>();
studentMap.put(jackA,20);
studentMap.put(jackB,20);
System.out.println(studentMap.size());
}
/**
* <h3>类实现了compareTo方法,就需要实现equals方法</h3>
* <h3>compareTo与equals的实现过程需要同步</h3>
*/
private static void compareToWithEquals(){
ArrayList<Student> students = new ArrayList<>();
students.add(new Student("Jack",10));
students.add(new Student("Jack",20));
Student jack = new Student("Jack", 20);
// equals
int studentIndex = students.indexOf(jack);
// compareTo
int index = Collections.binarySearch(students, jack);
System.out.println(studentIndex+" "+index);
}
public static void main(String[] args) {
beforeOverrideEqualsAndHashCode();
compareToWithEquals();
}
}
接口与实现
以列表为例子,我们通常这样使用:
List<String> strings = new ArrayList<>();
List是列表这个数据结构的顶层接口,ArrayList是具体的实现,ArrayList底层基于数组实现,可以动态扩容。
如果此时,我们需要更换队列的实现类,那么直接更换即可,依然返回List接口类型的结果.
List<String> strings = new LinkedList<>();
Collection
java中的绝大多数集合类都实现了Collection
接口:
来看看这些接口分别代表的含义:
method | description |
---|---|
size | 返回集合中元素的个数 |
isEmpty | 判断当前集合中是否存储了元素 |
contains | 集合中是否包含某个对象 |
iterator | 获取迭代器 |
toArray | 将集合转换成Object数组 |
toArray(T[]) | 将集合转换成对象数组 |
add | 往集合中添加元素 |
remove | 从集合中移除元素 |
containsAll | 集合中是否有子集 |
addAll | 将两个集合合并 |
removeAll | 求集合的差集 |
removeIf | 如果符合Predicate的条件,将元素从集合中删除 |
retainAll | 从该集合中移除所有未包含在指定集合中的元素 |
clear | 移除集合中所有元素 |
stream | 将集合转为流 |
parallerStream | 将集合转为并行流 |
Iterator-迭代器
关于迭代器,可以看看我之前的文章,这里不重复描述
点我前往
- 关于为什么要设计迭代器的解释
要使用集合,必须对集合的确切类型编程。这一开始可能看起来不是很糟糕,但是考虑下面的情况:如果原本是对 List 编码的,但是后来发现如果能够将相同的代码应用于 Set 会更方便,此时应该怎么做?或者假设想从一开始就编写一段通用代码,它不知道或不关心它正在使用什么类型的集合,因此它可以用于不同类型的集合,那么如何才能不重写代码就可以应用于不同类型的集合?
迭代器(也是一种设计模式)的概念实现了这种抽象。迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。
摘自《Java编程思想》
集合实现
-
队列与集合
-
映射
说到映射,很多朋友可能觉得很迷糊,其实就是key->value的关系.
package com.tea.modules.java8.collection.maps;
import java.util.HashMap;
import java.util.Map;
/**
* com.tea.modules.java8.collection.maps <br>
* 用于测试Map的API
* @author jaymin
* @since 2021/9/18
*/
public class WhatIsMap {
public static void main(String[] args) {
Map<String,Object> hashMap = new HashMap<>();
hashMap.put("男","man");
hashMap.put("女","woman");
System.out.println(hashMap.get("男"));
}
}
collection | description |
---|---|
ArrayList | 可以动态扩容和缩减的有序列表 |
LinkedList | 可以在任一位置进行插入和删除的有序列表,基于链表 |
HashSet | 无重复元素的无序集合 |
HashMap | 存储键值对的映射表,无序 |
EnumSet | 枚举集合 |
LinkedHashSet | 记录插入顺序的无重复元素集合 |
PriorityQueue | 一种允许高效删除最小元素的集合 |
TreeMap | Key根据自然排序的映射表 |
LinkedHashMap | 记录插入顺序的映射表 |
WeakHashMap | 对象引用为弱引用的映射表 |
IdentityHashMap | 用==来对比键值的映射表 |
快速失败机制
package com.tea.modules.java8.collection.prevent;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* com.tea.modules.java8.collection.prevent <br>
* 触发集合的ConcurrentModificationException <br>
* 可以想象, 如果在某个迭代器修改集合时, 另一个迭代器对其进行遍历,一定会出现
* 混乱的状况。例如,一个迭代器指向另一个迭代器刚刚删除的元素前面,现在这个迭代器
* 就是无效的,并且不应该再使用。链表迭代器的设计使它能够检测到这种修改。如果迭代
* 器发现它的集合被另一个迭代器修改了, 或是被该集合自身的方法修改了, 就会抛出一个
* ConcurrentModificationException 异常 <br>
* JDK文档: <br>
* 例如,通常不允许一个线程修改一个集合,而另一个线程正在对其进行迭代。 <br>
* 通常,在这些情况下迭代的结果是不确定的。 <br>
* 如果检测到此行为,某些迭代器实现(包括 JRE 提供的所有通用集合实现的实现)可能会选择抛出此异常。 <br>
* 这样做的迭代器被称为快速失败迭代器,因为它们快速而干净地失败, <br>
* 而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。<br>
* @author jaymin
* @since 2021/9/18
*/
public class ConcurrentModificationExceptionDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
ListIterator<String> stringListIteratorA = list.listIterator();
ListIterator<String> stringListIteratorB = list.listIterator();
stringListIteratorA.next();
stringListIteratorA.remove();
stringListIteratorB.next();
}
}