List 集合
特点与方法
List 系列集合:添加的元素是有序、可重复、有索引
java
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
// 1. 创建一个 ArrayList 集合对象
List<String> lst = new ArrayList<>();
lst.add("Jack");
lst.add("Peter");
lst.add("Tony");
System.out.println(lst); // [Jack, Peter, Tony]
// 2. 在某索引位置,插入元素
lst.add(1,"Tomato");
System.out.println(lst); // [Jack, Tomato, Peter, Tony]
// 3. 带走某个索引的元素
System.out.println(lst.remove(1)); // Tomato
System.out.println(lst); // [Jack, Peter, Tony]
// 4. 返回某索引的元素
System.out.println(lst.get(2)); // Tony
// 5. 修改索引位置处的元素,修改成功后,会返回原来的数据
System.out.println(lst.set(2, "Wang")); // Tony
System.out.println(lst); // [Jack, Peter, Wang]
}
}
遍历方式
- for 循环(因为 List 集合有索引)
- 迭代器
- 增强 for 循环
- Lambda 表达式
具体代码演示省略,请参照其父类
底层原理
ArrayList 集合的底层原理
-
基于数组实现
-
特点
查询速度快(通过索引)
删除效率低
添加效率低
LinkedList 集合的底层原理
-
基于双链表实现
-
特点
查询速度慢
增加和删除相对较快
-
应用场景:可以用来设计队列,也可以设计栈
java
// 模拟队列
import java.util.LinkedList;
public class Test {
public static void main(String[] args) {
// 1. 创建一个队列
LinkedList<String> queue = new LinkedList<>();
// 入队
queue.addLast("第1号人");
queue.addLast("第2号人");
queue.addLast("第3号人");
queue.addLast("第4号人");
System.out.println(queue);
// 出队
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue);
}
}
java
// 模拟栈
import java.util.LinkedList;
public class Test {
public static void main(String[] args) {
// 1. 创建一个栈
LinkedList<String> stack = new LinkedList<>();
// 入栈(push)
stack.addFirst("第1颗子弹"); // 等价于stack.push("第1颗子弹")
stack.addFirst("第2颗子弹"); // 注意,官方的push背后的本质就是stack.addFirst()
stack.addFirst("第3颗子弹");
stack.addFirst("第4颗子弹");
System.out.println(stack);
// 出栈(pop)
System.out.println(stack.removeFirst()); // 等价于stack.pop()
System.out.println(stack.removeFirst()); // 官方pop背后的本质就是stack.removeFirst()
System.out.println(stack.removeFirst());
System.out.println(stack);
}
}
Set 集合
特点
- 无序、不重复、无索引
- Set 要用到的常用方法,基本上就是 Collection 提供的!自己几乎没有额外新增的常用功能
HashSet 集合的底层原理
HashSet 集合:无序、不重复、无索引
哈希值
- 就是一个 int 类型的数值,Java 中每个对象都有一个哈希值
- Java中的所有对象,都可以调用 Object 类提供的 hashCode 方法,返回该对象自己的哈希值
对象哈希值的特点
- 同一个对象多次调用 hashCode() 方法返回的哈希值是相同的
- 不同的对象,它们的哈希值一般不相同,但也有可能相同(哈希碰撞)
底层原理
- 基于哈希表实现
- 哈希表是一种增删改查数据,性能都较好的数据结构
哈希表
- JDK 8 之前:哈希表 = 数值 + 链表
- JDK 8 之后:哈希表 = 数值 + 链表 + 红黑树
补充:如果希望 Set 集合认为2个内容一样的对象是重复的,必须重写对象的 hashCode() 和 equals() 方法
java
import java.util.Objects;
public class Test {
public static void main(String[] args) {
Student s1 = new Student("Jack", 20);
Student s2 = new Student("Tony", 25);
Student s3 = new Student("Tony", 25);
// 哈希值
System.out.println(s1.hashCode()); // 71329718
System.out.println(s2.hashCode()); // 80993012
System.out.println(s3.hashCode()); // 80993012
System.out.println(s2.hashCode() == s3.hashCode()); // true
}
}
class Student {
private String name;
private int age;
// 重写 equals 方法
@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);
}
// 重写 hashCode 方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public Student() {
}
public Student(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;
}
}
LinkedHashSet 集合
LinkedHashSet 集合:有序、不重复、无索引
底层原理
- 依然是基于哈希表(数值、链表、红黑数)实现的
- 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置
TreeSet 集合
TreeSet 集合:不重复、无索引、可排序(默认升序排序)
底层是基于红黑树实现的排序
java
import java.util.Set;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
Set<Integer> set1 = new TreeSet<>();
set1.add(6);
set1.add(5);
set1.add(5);
set1.add(7);
System.out.println(set1); // [5, 6, 7]
}
}
对于自定义类型,如 Student 对象,TreeSet 默认是无法直接排序的(会报错)
java
import java.util.Set;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
// 未简化的
// Set<Student> set1 = new TreeSet<>(new Comparator<Student>() {
// @Override
// public int compare(Student o1, Student o2) {
// return Double.compare(o1.getHeight(), o2.getHeight());
// }
// });
// lambda 简化后
Set<Student> set1 = new TreeSet<>((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()));
set1.add(new Student("Jack", 25, 51.2));
set1.add(new Student("Tony", 18, 77.9));
set1.add(new Student("Peter", 33, 65.8));
System.out.println(set1);
// [Student{name='Jack', age=25, height=51.2}, Student{name='Peter', age=33, height=65.8}, Student{name='Tony', age=18, height=77.9}]
}
}
class Student {
private String name;
private int age;
private double height;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
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 double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
注意事项:集合的并发修改异常问题
注意:使用迭代器遍历集合的时候,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
产生问题的原因:当我们正向的进行"迭代"或者 for 循环时,变量 i 指向了需要被删除的元素,我们成功删除对象后,其后面的所有元素会立刻前移,此时变量 i 的指向却还是停留原地,这就导致了指向的不匹配!这个问题,在 for 循环中不会报错,但是无法完成业务,而且较难发现问题所在,为了避免这个问题的难以发现,迭代器就贴心的为我们进行了报错,
解决办法
- 如果能使用 for 循环遍历:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做 i - - 操作
- 使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可(其方法的本质上是做了类似 i - - 的操作)
Collection 其他内容
可变参数
java
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
// 可变参数
run();
run(10);
run(10, 20, 30);
run(10, 20, 30, 40);
// 运行结果:
// []
// [10]
// [10, 20, 30]
// [10, 20, 30, 40]
}
// 注意事项:
// 1. 一个形参列表中,只能有一个可变参数
// 2. 可变参数必须放在形参列表的最后面
public static void run(int... nums) {
// 可变参数在方法内部,本质就是数组
System.out.println(Arrays.toString(nums));
}
}
Collections
- 注意!它不是 Collection 集合,它多了个"s"
- Collections 是一个用来操作集合的工具类
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Test {
public static void main(String[] args) {
// 1. 为集合批量添加数据
List<String> lst = new ArrayList<>();
Collections.addAll(lst, "Jack", "Peter", "Tony");
System.out.println(lst); // [Jack, Peter, Tony]
// 2. 打乱List集合里面的元素顺序
Collections.shuffle(lst);
System.out.println(lst); // [Peter, Tony, Jack]
// 3. 对List集合里面的元素进行升序排序
List<Integer> lst2 = new ArrayList<>();
lst2.add(3);
lst2.add(5);
lst2.add(1);
System.out.println(lst2); // [3, 5, 1]
Collections.sort(lst2);
System.out.println(lst2); // [1, 3, 5]
// 4. 自定义的对象如何排序
List<Student> lst3 = new ArrayList<>();
lst3.add(new Student("Jack", 25, 51.2));
lst3.add(new Student("Tony", 18, 77.9));
lst3.add(new Student("Peter", 33, 65.8));
Collections.sort(lst3, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getHeight(), o2.getHeight());
}
});
}
}
class Student {
private String name;
private int age;
private double height;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
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 double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}