一、集合的基本概述
集合是Java中存储对象数据的一种容器。
特点:①集合的大小不固定,启用后长度可以动态变化,集合中存储类型也可以选择不固定;②集合非常适合做元素的增删操作(有很多可以直接调用的API);③集合中只能存储引用类型数据,如果要存储基本类型数据可以选用包装类。
Q:集合中可以存储不同类型的元素,是创建一个集合之后,可以同时存储不同类型的元素吗? A:是的。参见如下代码,其输出为: [I'm String, 1, true]
import java.util.ArrayList;
import java.util.Collection;
public class DifferentElementTest {
public static void main(String[] args) {
//集合中可以存储不同类型的元素,是创建一个集合之后,可以同时存储不同类型的元素吗?
Collection set=new ArrayList();
set.add("I'm String");
set.add(1);
set.add(true);
//打印
System.out.println(set);
}
}
二、集合的体系结构
分为Collection单列集合和Map双列集合。
Collection单列集合:每个元素(数据)只包含一个值。 Map双列集合,每个元素包含两个值(键值对 (Key,Value))。
集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型。
三、Collection集合
Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
3.1 数据手册中罗列的所有接口及本文中主要概述的常用接口
数据手册中罗列的所有接口
我们主要概述的接口(图源黑马程序员)
3.2 Collection单列集合特点
3.2.1 List系列集合
添加的元素是有序、可重复、有索引。
3.2.2 Set系列集合
HashSet: 添加的元素是无序、不重复、无索引;
LinkedHashSet: 有序、不重复、无索引;
TreeSet:按照大小默认升序排序、不重复、无索引。
3.3Collection常用的API
public boolean add(E e);
public void clear();
public boolean remove(E e);
public boolean contains(Object obj);
public boolean isEmpty();
public int size();
public Object[] toArray(); //把集合中的元素,存储到数组中。
3.4 迭代方式&具体实现
3.4.1 迭代器及其常用方法
Collection集合获取迭代器:
Iterator<E> iterator(); //返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引
常用方法:
boolean hasNext(); //询问当前位置是否有元素存在,存在则返回true,反之false
E next(); //获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界
具体实现:
输出为:How are you ?
//数据准备
Collection arrayList=new ArrayList();
arrayList.add("How");
arrayList.add("are");
arrayList.add("you");
arrayList.add("?");
//使用迭代器方法遍历并打印当前元素
Iterator it=arrayList.iterator();
while(it.hasNext()){
System.out.print(it.next()+" ");
}
3.4.2 foreach/增强for循环
既可以遍历集合也可以遍历数组,它是JDK5之后出现的,其内部原理是一个iterator迭代器,遍历集合相当于是迭代器的简化写法。只有实现了Iterable接口的类才可以使用迭代器和增强for,Collection接口已经实现了Iterable接口。
其格式:
for(元素数据类型 变量名 : 数组或者Collection集合){
//在此处使用变量来完成自己所想的操作,变量对应的就是元素
}
注:如果使用的是idea编译器,可以通过输入 "集合名称.for" 来使用foreach框架
具体实现:
输出为:How~are~you~?~
//数据准备
Collection arrayList=new ArrayList();
arrayList.add("How");
arrayList.add("are");
arrayList.add("you");
arrayList.add("?");
//使用foreach/增强for循环遍历并打印当前元素
for (Object o : arrayList) {
System.out.print(o+"~");
}
3.4.3 lambda表达式
得益于JDK8开始的新技术Lambda表达式(匿名函数,即没有函数名的函数),提供了一种更简单、更直接的遍历集合的方式。
Collection结合Lambda遍历的API
default void forEach(Consumer<? superT>action): //结合Lambda遍历集合
具体实现:
输出为: How--are--you--?--
//数据准备
Collection arrayList=new ArrayList();
arrayList.add("How");
arrayList.add("are");
arrayList.add("you");
arrayList.add("?");
//使用Lambda方式遍历并打印当前元素
arrayList.forEach(new Consumer() {
@Override
public void accept(Object o) {
System.out.print(o+"--");
}
});
还有简化匿名内部类的写法
//数据准备
Collection arrayList=new ArrayList();
arrayList.add("How");
arrayList.add("are");
arrayList.add("you");
arrayList.add("?");
//使用Lambda方式遍历并打印当前元素
arrayList.forEach(o-> {
System.out.print(o+"--");
});
3.5 List系列集合
3.5.1 集合特点详细解释
ArrayList、LinkedList:有序、可重复、有索引。
有序:存储和取出的元素顺序一致。
有索引:可以通过索引操作元素。
可重复:存储的元素可以重复
3.5.2 List集合特有API
void add(int index,E element) 在集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素
E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素
3.5.3 List集合的遍历方式
比之前3.4中所述的方式多了一种:for循环(因为List集合是有索引的)
输出为:How are you ?
List<String> list=new LinkedList();
list.add("How");
list.add("are");
list.add("you");
list.add("?");
for (int i = 0; i < list.size(); i++) {
String s=list.get(i);
System.out.print(s+" ");
}
3.5.4 ArrayList集合的底层原理
ArrayList底层是基于数组实现的,它在内存中使用一个连续的数组来存储元素:根据索引定位元素快,增删需要做元素的移位操作。
第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。当容量满的时候,自动扩容。
当进行查询操作时,只需要通过下标直接访问相应的数组元素即可,这个过程的时间复杂度是 O(1),即常量时间。而增加或删除元素时,需要移动后面的元素以保持数组的连续性,这个过程的时间复杂度是 O(n),即线性时间,其中 n 是数组的长度。因此,当元素数量很大时,增加或删除元素的时间复杂度将会很高。
此外,由于 ArrayList 内部是基于数组实现的,所以在插入或删除元素时需要进行数组的复制或移动操作,这会带来额外的空间和时间成本。而且,当 ArrayList 中的元素数量达到一定程度时,可能需要扩容数组,这也会导致额外的时间和空间成本。
3.5.5 LinkedList集合的底层原理
底层数据结构是双链表,查询慢,但是首尾操作的速度是极快的。
3.5.5 LinkedList集合的特有功能
public void addFirst(E e) 在该列表开头插入指定的元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst( ) 返回此列表的第一个元素
public E getLast( ) 返回此列表的最后一个元素
public E removeFirst( ) 从此列表中删除并返回第一个元素
public E removeLast( ) 从此列表中删除并返回最后一个元素
3.6 Set系列集合
3.6.1 集合特点详细解释
HashSet:无序、不重复、无索引
LinkedHashSet:有序、不重复、无索引
TreeSet:排序、不重复、无索引
无序:存取顺序不一样
有序:存取顺序一样
排序:会对插入的元素进行排序
不重复:可以去除重复的元素
无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
3.6.2 集合特有API
3.6.3 List集合的遍历方式
3.6.4 HashSet集合的底层原理及去重原理分析
HashSet集合底层采取哈希表存储的数据。哈希表是一种对于增删改查数据性能都比较好的结构。
哈希表的组成(在JDK8之前,底层采用数组+链表;JDK8之后,底层采用数组+链表+红黑树)
哈希值:是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
public int hashCode():返回对象的哈希值
对象的哈希值特点:①同一个对象多次调用hashCode()方法返回的哈希值是相同的;
②默认情况下,不同对象的哈希值是不同的。
图源:黑马程序员
图源:黑马程序员
图源:黑马程序员
图源:与ChatGPT的对话
3.6.5 LinkedHashSet集合的底层原理
图源:黑马程序员
3.6.6 TreeSet集合的底层原理
底层基于TreeMap实现,基于红黑树实现排序,增删改查性能较好。
图源:黑马程序员
自定义排序规则:
第一种方式:让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则;
第二种方式:TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。
以上两种方式中,关于返回值的规则:
如果认为第一个元素比第二个元素大返回正整数即可。(升序)
如果认为第一个元素比第二个元素小返回负整数即可。(降序)
如果认为第一个元素等于第二个元素返回0即可,此时TreeSet集合只会保留一个元素,认为两者重复。
注:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。
四、Map集合
Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。
4.1 数据手册中罗列的所有接口及本文中主要概述的常用接口
我们主要概述的接口如下【图源黑马程序员】
4.2 集合体系特点
Map集合的特点都是由键决定的。
Map集合的键是无序,不重复,无索引的,值不做要求(可以重复)。
Map集合后面重复的键对应的值会覆盖前面重复键的值。
Map集合的键值对都可以为null。
Map集合实现类特点:
HashMap:键是无序的,不重复,无索引,值不做要求。(与Map体系一致)
LinkedHashMap:键有序,不重复,无索引,值不做要求。
TreeMap:键排序,不重复,无索引,值不做要求。
4.3 Map常用的API
方法名称 | 说明 |
V put( K Key, V Value ) | 添加元素 |
V remove( Object Key ) | 根据键删除键值对元素 |
void clear( ) | 移除所有的键值对元素 |
boolean containsKey(Object Key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty( ) | 判断集合是否为空 |
int size( ) | 集合的长度,也就是集合中键值对的个数 |
获取键的集合&获取值的集合
public static void main(String[] args) {
Map<String,Integer> map1=new HashMap<>();
map1.put("hello",1);
map1.put("hi",2);
map1.put("bye~",3);
//获取键的集合
Set<String> keys=map1.keySet();
System.out.println(keys);
//获取值的集合
//由于value可重复,所以我们不能使用Set的集合
Collection<Integer> values=map1.values();
System.out.println(values);
}
合并其他map集合:
public static void main(String[] args) {
//map1
Map<String,Integer> map1=new HashMap<>();
map1.put("hello1",1);
map1.put("hi1",2);
map1.put("bye1~",3);
//map2
Map<String,Integer> map2=new HashMap<>();
map2.put("hello2",1);
map2.put("hi2",2);
map2.put("bye2~",3);
//整合到一块
map1.putAll(map2);
//打印输出
System.out.println(map1);
//输出结果为:{hi2=2, hi1=2, hello1=1, bye1~=3, bye2~=3, hello2=1}
}
4.4 遍历方式&具体实现
4.4.1遍历方式一:键找值
public static void main(String[] args) {
//1.创建一个Map集合对象
Map<String,Integer> maps=new HashMap<>();
maps.put("娃娃",30);
maps.put("粑粑",20);
maps.put("蕾蕾",10);
maps.put("花花",40);
//使用键找值方式遍历
//1.首先将Map集合的键转换成Set集合
Set<String> keys = maps.keySet();
//2.开始遍历
for (String key:keys){
int value=maps.get(key);
System.out.println(key+"===>"+value);
}
}
4.4.2遍历方式二:键值对
public static void main(String[] args) {
//1.创建一个Map集合对象
Map<String,Integer> maps=new HashMap<>();
maps.put("娃娃",30);
maps.put("粑粑",20);
maps.put("蕾蕾",10);
maps.put("花花",40);
System.out.println(maps);
//使用键值对遍历
//1.首先将Map集合转换成Set集合
Set<Map.Entry<String, Integer>> entries = maps.entrySet();
System.out.println(entries);
//2.开始遍历
for (Map.Entry<String, Integer> entry:entries){
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
4.4.3遍历方式三:Lambda表达式
public static void main(String[] args) {
//1.创建一个Map集合对象
Map<String,Integer> maps=new HashMap<>();
maps.put("娃娃",30);
maps.put("粑粑",20);
maps.put("蕾蕾",10);
maps.put("花花",40);
System.out.println(maps);
//使用Lambda表达式遍历
//1.
maps.forEach(new BiConsumer<String, Integer>() {
@Override
public void accept(String key, Integer value) {
System.out.println(key+"===>"+value);
}
});
//2.
maps.forEach((k,v)-> {
System.out.println(k+"=====>"+v);
});
}
}
4.5 HashMap集合
特点:与Map体系结构一致,特点由键决定,无序,无重复,无索引。
HashMap跟HashSet底层原理是一模一样的,都是哈希表结构,只是HashMap的每个元素包含两个值而已。
实际上,Set系列结合的底层就是Map实现的,只是Set集合中的元素只要键对象,不要值对象。
4.6 LinkedHashMap集合
特点:特点由键决定,有序(存储和取出的元素顺序一致),无重复,无索引。
原理:底层数据结构是哈希表,只是每个键值对元素又额外多了一个双链表的机制记录存储的顺序。
4.7 TreeMap集合
特点:特点由键决定,不重复,无索引,可排序(按照键数据的大小默认升序排序,也可以将键按照指定的规则进行排序)。
TreeMap和TreeSet的底层原理是一样的。
五、可变参数和工具类Collections
5.1 可变参数
可变参数在形参中可以接收多个数据,其格式为
数据类型...参数名称
可变参数的作用:传输参数非常灵活,方便。可以不传输,或传输1个或者多个,也可以传输一个数组。其在方法内部本质上就是一个数组。
可变参数的注意事项:1.一个形参列表中可变参数只能有一个;可变参数必须放在形参列表的后面。
例如:打印n个数,
public class VariableParameter {
public static void main(String[] args) {
calculate(3,1,2,3);
}
public static void calculate(int n,int...var){
for(int i=0;i<n;i++){
System.out.print(var[i]+" ");
}
}
}
5.2工具类Collections
5.2.1 给集合对象批量添加元素
Collections.addAll(collectionName,data_1,...,data_n);
public static void main(String[] args) {
List<Integer> order=new ArrayList<>();
order.add(1);
order.add(2);
order.add(3);
order.add(4);
System.out.println(order);//[1, 2, 3, 4]
Collections.addAll(order,4,5,6,7);
System.out.println(order);//[1, 2, 3, 4, 4, 5, 6, 7]
}
5.2.2 打乱list集合顺序
Collections.shuffle( listName);
public static void main(String[] args) {
List<Integer> order=new ArrayList<>();
Collections.addAll(order,1,2,3,4);
System.out.println(order);//[1, 2, 3, 4]
Collections.shuffle(order);
System.out.println(order);//[2, 3, 4, 1]
}
5.2.3排序(使用范围:只能用于List集合的排序)
public static <T> void sort(List<T> list) //将集合中元素按照默认规则排序,本方式不可 //以直接对自定义类型的List集合排序,除非 //自定义类型实现了比较规则Comparable接口。
public static <T> void sort(List<T> list,Comparator<? super T> c)
//将集合中元素按照指定规则排序
//按照默认规则进行排序
public static void main(String[] args) {
List<Integer> order=new ArrayList<>();
Collections.addAll(order,1,2,3,4);
Collections.shuffle(order);
Collections.sort(order);
System.out.println(order); //[1, 2, 3, 4]
}
//创建学生类,实现Comparable接口
public class Student implements Comparable<Student>{
public String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student o) {
//升序:this.age-o.age
//降序:o.age-this.age
return o.getAge()-this.age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//main中调用sort函数,查看是否为降序输出
public static void main(String[] args) {
List<Student> ls=new ArrayList<>();
Student s1=new Student("a",18);
Student s2=new Student("b",19);
Student s3=new Student("c",20);
Collections.addAll(ls,s1,s2,s3);
Collections.sort(ls);
System.out.println(ls);//[Student{name='c', age=20}, Student{name='b', age=19}, Student{name='a', age=18}]
}
//by using Comparator
public static void main(String[] args) {
List<Integer> order=new ArrayList<>();
Collections.addAll(order,1,2,3,4);
Collections.shuffle(order);
Collections.sort(order, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
System.out.println(order); //[4, 3, 2, 1]
}