1 集合概述
集合是Java中存储对象数据的一种容器,大小、类型可以不固定,适合做元素的增删操作
🌈 集合和数组的区别 :
- 相同点
- 都是可以存储多个数据的容器
- 不同点
- 数组的长度是不可变的,集合的长度是可变的
- 数组只能存放相同数据类型的数据,集合可以存放相同也可以存放不同数据类型的数据
- 数组可以存基本数据类型和引用数据类型;集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类,所以集合中的元素都认为是对象
⛪️ 集合的体系结构 :
Collection
单列集合:每个元素只包含一个值List
系列集合:添加的元素有序、可重复、有索引ArrayList
、LinkedList
:添加的元素有序、可重复、有索引
Set
系列集合:添加的元素无序、不可重复、无索引HashSet
:添加的元素无序、不可重复、无索引;LinkedHashSet
: 添加的元素 有序、不可重复、无索引
TreeSet
: 按照大小默认升序排序、不可重复、无索引
Map
双列集合:每个元素包含两个值(键值对)
❗️注意:
- 接口不能创建对象,其实现类可以
- 集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型
- 集合重写了
toString
(自定义类型对象除外),可以直接打印集合元素
2 Collection 集合
2.1 Collection概述
Collection集合
- 是单例集合的顶层接口,它表示一组对象,这些对象也称为 Collection 的元素
- JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现
创建Collection集合的对象
- 多态的方式
- 具体的实现类 ArrayList
2.2 Collection的常用方法
Collection
是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的
还有一个将集合转为数组
的方法:Object[] toArray()
🙋举个栗子:
public class Test {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("aaa");
collection.add("bbb");
collection.add("ccc");
collection.add("dddd");
System.out.println(collection); // [aaa, bbb, ccc, dddd]
// 将集合转为数组,toArray()是空参,那么返回的数组类型为Object类型的(返回Object类型数组是为了避免非法类型数据闯入)
Object[] res = collection.toArray();
// System.out.println(res);
System.out.println(Arrays.toString(res)); // [aaa, bbb, ccc, dddd]
// 将集合转为数组,toArray()不是空参,返回的数组类型为String类型的
String[] res2 = collection.toArray(new String[collection.size()]);
System.out.println(Arrays.toString(res2));
System.out.println("---------拓展-----------------");
Collection<String> c1 = new ArrayList<>();
Collection<String> c2 = new ArrayList<>();
c1.add("a");
c1.add("b");
c1.add("c");
c2.add("d");
c2.add("e");
// addAll把c2集合的元素全部加入到c1集合中
c1.addAll(c2);
System.out.println(c1); // [a, b, c, d, e]
System.out.println(c2); // [d, e]
// method1(collection);
// method2(collection);
// method3(collection);
// method4(collection);
// method5(collection);
// method6(collection);
}
private static void method6(Collection<String> collection) {
int size = collection.size();
System.out.println(size); // 4
}
private static void method5(Collection<String> collection) {
collection.clear();
boolean result = collection.isEmpty();
System.out.println(result); // true
}
private static void method4(Collection<String> collection) {
boolean result = collection.contains("a");
System.out.println(result); // false
boolean result2 = collection.contains("aaa");
System.out.println(result2); // true
}
private static void method3(Collection<String> collection) {
//就是将集合中所有的元素全部删除.
collection.clear();
System.out.println(collection); // []
}
private static void method2(Collection<String> collection) {
/*removeif底层会遍历集合,得到集合中的每一个元素
s依次表示集合中的每一个元素
就会把这每一个元素都到lambda表达式中去判断一下
如果返回的是true,则删除
如果返回的是false,则保留不删除.*/
collection.removeIf(
(String s)->{
return s.length() == 3;
}
);
System.out.println(collection); // [dddd]
}
private static void method1(Collection<String> collection) {
// remove删除某个元素,如果有多个重复元素,默认删除前面的第一个
//如果删除成功了,则返回true
//如果删除失败了,则返回false
boolean result1 = collection.remove("aaa");
boolean result2 = collection.remove("ddd");
System.out.println(result1); // true
System.out.println(result2); // false
System.out.println(collection); // [bbb, ccc, dddd]
}
}
❗️注意:
- 对于添加元素的操作,
List
一般不会返回false
,因为集合元素是允许重复的,而对于Set
集合添加元素的操作,可能会出现重复元素,返回false
2.3 Collection的遍历
2.3.1 迭代器
迭代器介绍
- 迭代器,集合的专用遍历方式
Iterator<E> iterator()
: 返回此集合中元素的迭代器,通过集合对象的iterator()
方法得到
Iterator
中的常用方法
boolean hasNext()
: 判断当前位置是否有元素可以被取出E next()
: 获取当前位置的元素,将迭代器对象移向下一个索引位置void remove()
: 删除迭代器对象当前指向的元素
🙋举个栗子:遍历集合元素
public static void main(String[] args) {
Collection<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
//获得迭代器的对象
//迭代器对象一旦被创建出来,默认指向集合的0索引处
Iterator<String> it = list.iterator();
//利用迭代器里面的方法进行遍历
//当前位置是否有元素可以被取出
// System.out.println(it.hasNext());
// //取出当前位置的元素 + 将迭代器往后移动一个索引的位置
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next()); 元素取完了会报错
while(it.hasNext()){
System.out.println(it.next());
}
}
❗️注意:
- 迭代器的元素取完了会报错
🙋🙋再举个栗子:删除集合中的 b 元素
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");
list.add("d");
// 1. 之前的删除方法,for循环,通过下面的指针回退方法或者倒序遍历元素的方法解决
/* for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if("b".equals(s)){
list.remove(i);
i--;
}
}*/
// 2. 增强for循环内部原理是也一个 `Iterator` 迭代器,使用集合删除,同样会报并发修改异常错误,这种错误无法解决(不能边遍历边删除)
// for (String s : list) {
// if("b".equals(s)){
// list.remove(s);
// }
// }
// 3. lambda表达式,使用集合删除,同样会报并发修改异常错误,不能边遍历边删除
// list.forEach(s -> {
// if ("b".equals(s)) {
// list.remove(s);
// }
// });
// 4. 迭代器删除方法【错误】(会报错,并发修改异常,可以解决)
// Iterator<String> it = list.iterator();
// while(it.hasNext()){
// String s = it.next();
// if("b".equals(s)){
// list.remove(s); // 使用集合删除(由于删除过程中,集合长度改变,指针继续后移,可能会导致漏删,Java抛出异常)
// }
// }
// 迭代器删除方法 【正确】
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
if("b".equals(s)){
//指向谁,那么此时就删除谁.
it.remove(); // 使用迭代器删除
}
}
System.out.println(list); // [a, c, d]
}
❗️注意:
- 当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题(边遍历元素边删除元素时出错)
- 哪些遍历存在这个问题
- 迭代器遍历集合且直接使用集合删除元素
- 增强for循环遍历集合且直接使用集合删除元素
- 哪种边遍历边删除元素不会出现这个问题
- 使用 for 循环不会存在这个问题
- 迭代器遍历集合且使用迭代器自己的删除方法操作可以解决这个问题
2.3.2 foreach/增强for循环
增强 for 循环
- 它是JDK5之后出现的,其内部原理是一个
Iterator
迭代器 - 单列集合Collection(实现了
Iterable
接口的类)才可以使用 迭代器 和 增强for - 简化数组和Collection集合的遍历(既可以遍历数组也可以遍历集合)
格式:
for(集合/数组中元素的数据类型 变量名 : Collection集合/数组名) {
// 已经将当前遍历到的元素封装到变量中了,直接使用变量即可
}
🙋举个栗子:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
//1,数据类型一定是集合或者数组中元素的类型
//2,str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素
//3,list就是要遍历的集合或者数组
for(String str : list){
// System.out.print(str+" "); // a b c d e f
str = "q";
System.out.println(str);
/*
* q
q
q
q
q
q
* */
}
System.out.println(list); // [a, b, c, d, e, f]
// 为什么会出现这种情况呢?
}
❗️ 注意:
- 增强for循环中的变量其实是一个第三方变量,第三方变量会记录集合/数组中的每一个元素的值,遍历的过程中修改的是第三方变量,并没有改变集合或数组。即修改第三方变量的值不会影响到集合中的元素。
- idea中增强for循环的快捷方式:
集合/数组名.for
+回车- 三种for循环的使用场景
- 如果需要操作索引,使用普通for循环
- 如果在遍历的过程中需要删除元素,使用迭代器
- 如果仅仅是想遍历,那么使用增强for循环
2.3.3 lambda表达式
Lambda 表达式遍历集合
- 得益于 JDK 8 开始的新技术 Lambda 表达式,提供了一种更简单、更直接的遍历集合的方式
🙋举个栗子:
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
public class Demo {
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("aaa");
c.add("bbb");
c.add("ccc");
c.add("dddd");
System.out.println(c); // [aaa, bbb, ccc, dddd]
// 方式1
c.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
// 方式2
c.forEach(
(String s)->{
System.out.println(s);
}
);
// 方式3
c.forEach(
s-> System.out.println(s)
);
// 方式4
c.forEach(System.out::println);
}
}
2.4 Collection存储自定义类型对象
需求
- 某影院系统需要在后台存储几部电影,然后依次展示出来。
分析
- 定义一个电影类,定义一个集合存储电影对象。
- 创建 3 个电影对象,封装相关数据,把 3 个对象存入到集合中去。
- 遍历集合中的 3 个对象,输出相关信息。
示例代码:
电影类
public class Movie {
private String name;
private double score;
private String actor;
public Movie() {
}
public Movie(String name, double score, String actor) {
this.name = name;
this.score = score;
this.actor = actor;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public String getActor() {
return actor;
}
public void setActor(String actor) {
this.actor = actor;
}
@Override
public String toString() {
return "Movie{" +
"name='" + name + '\'' +
", score=" + score +
", actor='" + actor + '\'' +
'}';
}
}
测试类
import java.util.ArrayList;
import java.util.Collection;
public class Demo {
public static void main(String[] args) {
Collection<Movie> moives = new ArrayList<>();
moives.add(new Movie("《你好,李焕英》",9.5,"贾玲、张小斐"));
moives.add(new Movie("《唐人街探案》",9.1,"王宝强"));
moives.add(new Movie("《刺杀小说家》",8.6,"雷佳音"));
System.out.println(moives);
for (Movie moive : moives) {
System.out.println("片名:"+moive.getName());
System.out.println("得分:"+moive.getScore());
System.out.println("主演:"+moive.getActor());
}
}
}
❗️注意:
- 集合中存储的是元素的内存地址,集合重写了
toString
(自定义类型对象除外,需要手动重写toString
方法),可以直接打印集合元素
3 List集合
3.1 List集合概述
- List集合
- 有序集合,这里的有序指的是存取顺序
- 用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
- 与Set集合不同,列表通常允许重复的元素
- List集合的特点
- 存储和取出的元素 顺序 一致
- 存储的 元素可以重复
- 可以通过 索引 操作元素
3.2 List的特有方法
除了继承 Collection 的方法外,List 因为支持索引,有一些自己特有方法
🙋举个栗子:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
System.out.println(list); // [aaa, bbb, ccc]
list.add(0,"qqq");
System.out.println(list); // [qqq, aaa, bbb, ccc]
String remove = list.remove(1);
System.out.println(remove); // aaa
boolean result = list.remove("bbb"); // 删的是第一个指定的元素
System.out.println(result); // true
System.out.println(list); // [qqq, ccc]
String set = list.set(0, "fff");
System.out.println(set); // qqq
String s = list.get(0);
System.out.println(s); /// fff
}
另外,由于List集合支持索引,其遍历方式就比Collection集合多了一种
- for 循环
- 迭代器
- foreach /增强 for 循环
- lambda 表达式 / forEach
4 ArrayList 类
4.1 ArrayList 概述
数组的长度是固定的,无法适应数据变化的需求。为了解决这个问题,Java 提供了另一个容器 java.util.ArrayList
集合类,让我们可以更便捷的存储和操作对象数据。
ArrayList
对象是 大小可变的数组 的实现,存储在内的数据称为元素。此类提供一些方法来操作内部存储的元素。不能存储基本类型,只能存储引用类型的数据。类似 <int>
不能写,但是存储基本数据类型对应的包装类型是可以的。
ArrayList
底层数据结构是 数组,查询(根据索引定位元素)快,增删慢
4.2 ArrayList的构造方法
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("微微");
list.add(123);
list.add(true);
System.out.println(list); // [微微, 123, true]
}
此时,集合内添加的元素可以是任意的数据类型。在特定场景下,需要限定相同的数据类型进行操作,那该如何处理呢?
对于ArrayList
来说,有一个尖括号<E>
代表泛型(也就是装在集合当中的所有元素全都是统一的什么类型),只需要在类名的后面加上<>
,并在<>
里面写入指定的类型即可。
ArrayList<String> list = new ArrayList<String>();
// 从JDK 1.7+开始,右侧的尖括号内部可以不写内容,但是 <> 本身还是要写的。
ArrayList<String> list = new ArrayList<>();
❗️注意:
- 泛型只能是引用类型(类、接口、数组、字符串),不能是基本类型
- 集合容器如果没有加入
<>
就可以存储任意数据类型, 加入<>
泛型是对集合容器存储的数据类型进行限制public ArrayList()
创建了一个空的集合对象- 直接打印对象 / 数组名,输出的是对象 / 数组的地址值,而对于
ArrayList
集合来说,直接打印得到的不是地址值而是内容(此外还有String
、StringBuilder
类),但集合存储的元素并不是对象本身,而是其地址 。如果创建集合的内容是空,得到的是空的中括号:[]
4.3 ArrayList的常用方法
- 添加
public boolean add(E e)
:将指定的元素添加到此集合的尾部,返回值代表添加的动作是否成功void add(int index,E e)
: 将指定的元素插入此列表中的指定位置。
- 删除
public boolean remove(Object o)
:删除指定的元素,返回值代表删除的动作是否成功(删除的是第一个匹配元素)public E remove(int index)
:移除此集合中指定位置上的元素,返回被删除的元素。
- 修改
public E set(int index,E e)
:修改指定索引处的元素,返回被修改的元素
- 查询
public E get(int index)
:返回此集合中指定位置上的元素,返回获取的元素。public int size()
:返回此集合中的元素数。遍历集合时,可以控制索引范围,防止越界。
🙋举个栗子:
public class Demo {
public static void main(String[] args) {
getAndSize();
testSet();
testRemove();
}
private static void getAndSize() {
ArrayList<String> list = new ArrayList<>();
list.add("abc");
list.add("111");
list.add("222");
// public E get(int index) 返回指定索引处的元素
String s1 = list.get(0);
String s2 = list.get(1);
String s3 = list.get(2);
System.out.println(s1); // abc
System.out.println(s2); // 111
System.out.println(s3); // 222
// public int size() 返回集合中的元素的个数
int size = list.size();
System.out.println(size); // 3
}
public static void testSet() {
ArrayList<String> list = new ArrayList<>();
list.add("abc");
list.add("111");
list.add("222");
// public E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
String s = list.set(0, "666");
System.out.println(s); // 666
System.out.println(list); //[666,111,222]
}
public static void testRemove() {
ArrayList<String> list = new ArrayList<>();
list.add("abc");
list.add("111");
list.add("222");
// public boolean remove(Object o) 删除指定的元素,返回删除是否成功
boolean b1 = list.remove("abc");
boolean b2 = list.remove("zzz");
System.out.println(b1); // true
System.out.println(b2); // false
System.out.println(list); // [111, 222]
// public E remove(int index) 删除指定索引处的元素,返回被删除的元素
String s = list.remove(0);
System.out.println(s); // 111
System.out.println(list); // [222]
}
}
❗️注意:
- 对于
ArrayList
集合来说,add
添加动作一定是成功的,所以返回值可用可不用。但是对于其他集合来说,add
添加动作不一定成功- 对于长度的获取,
字符串.length()
,数组名.length
,ArrayList集合名.size()
4.4 ArrayList 的应用
1️⃣ 数值添加到集合
生成6个1~33之间的随机整数,添加到集合,并遍历集合。
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 6; i++) {
int num = r.nextInt(33) + 1;
list.add(num);
}
// 遍历集合
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
2️⃣ 对象添加到集合
自定义4个学生对象,添加到集合,并遍历。
此处省略标准类(student类)
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
Student one = new Student("洪七公", 20);
Student two = new Student("欧阳锋", 21);
Student three = new Student("黄药师", 22);
Student four = new Student("段智兴", 23);
list.add(one);
list.add(two);
list.add(three);
list.add(four);
// 遍历集合
for (int i = 0; i < list.size(); i++) {
Student stu = list.get(i);
System.out.println(stu);//得到的是每个对象的内存地址
System.out.println("姓名:" + stu.getName() + ",年龄" + stu.getAge());
}
}
3️⃣ 筛选集合数据
🙋举个栗子:
用一个大集合存入20个随机数字,然后筛选其中的偶数元素,放到小集合当中。要求使用自定义的方法来实现筛选。
public static void main(String[] args) {
ArrayList<Integer> bigList = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 20; i++) {
int num = r.nextInt(100) + 1; // 1~100
bigList.add(num);
}
ArrayList<Integer> smallList = getSmallList(bigList);
System.out.println("偶数总共有多少个:" + smallList.size());
for (int i = 0; i < smallList.size(); i++) {
System.out.println(smallList.get(i));
}
}
// 这个方法,接收大集合参数,返回小集合结果
public static ArrayList<Integer> getSmallList(ArrayList<Integer> bigList) {
// 创建一个小集合,用来装偶数结果
ArrayList<Integer> smallList = new ArrayList<>();
for (int i = 0; i < bigList.size(); i++) {
int num = bigList.get(i);
if (num % 2 == 0) {
smallList.add(num);
}
}
return smallList;
}
🙋🙋 再举个栗子:存储自定义类型
定义一个方法,方法接收一个集合对象(泛型为Student),方法内部将年龄低于18的学生对象找出,并存入新集合对象,方法返回新集合。
此处省略标准类(student类)
public class DemoStudent {
public static void main(String[] args) {
// 7. main方法中测试该方法
ArrayList<Student> list = new ArrayList<>();
Student stu1 = new Student("张三1",10);
Student stu2 = new Student("张三2",10);
Student stu3 = new Student("张三3",20);
list.add(stu1);
list.add(stu2);
list.add(stu3);
ArrayList<Student> newList = getList(list);
for (int i = 0; i < newList.size(); i++) {
Student stu = newList.get(i);
System.out.println(stu.getName() + "..." + stu.getAge());
}
}
// 1. 定义方法,方法的形参定义为ArrayList<Student> list
public static ArrayList<Student> getList(ArrayList<Student> list) {
// 2. 方法内部定义新集合,准备存储筛选出的学生对象 ArrayList<Student> newList
ArrayList<Student> newList = new ArrayList<>();
// 3. 遍历原集合,获取每一个学生对象
for (int i = 0; i < list.size(); i++) {
Student stu = list.get(i);
// 4. 通过学生对象调用getAge方法获取年龄,并判断年龄是否低于18
int age = stu.getAge();
if (age < 18) {
// 5. 将年龄低于18的学生对象存入新集合
newList.add(stu);
}
}
// 6. 返回新集合
return newList;
}
}
结果输出:
张三1...10
张三2...10
4️⃣ 删除集合元素
创建一个存储 String
的集合,内部存储(test,张三,李四,test,test)字符串,删除所有的test字符串,删除后,将集合剩余元素打印在控制台。
public static void main(String[] args) {
// 1. 创建集合对象
ArrayList<String> list = new ArrayList<>();
// 2. 调用add方法,添加字符串
list.add("test");
list.add("张三");
list.add("李四");
list.add("test");
list.add("test");
// 3. 遍历集合,取出每一个字符串元素
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
// 4. 加入if判断,如果是test字符串,调用remove方法删除
//if(s.equals("test")){} 使用常量调用方法可以避免出现空指针异常
if("test".equals(s)){
list.remove(i);
i--; //若省略这一句,最后打印结果为 [张三, 李四, test]
}
}
System.out.println(list); // [张三, 李四]
}
❗️注意:
- 由于每删除一次元素,其余元素的索引值就会发生改变。本例中,在删除第四个元素
test
的同时,最后一个元素test
的索引值-1
,而指针经过i++
会+1
,正好错过删除最后一个元素test
。- 也可以不加
i--;
这一句,倒序遍历数组即可,删除一个元素后,元素会和指针i
一起向前进
5 LinkList 类
5.1 LinkList初体验
LinkList
底层数据结构是双链表,查询慢,增删快
遍历 LinkList
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 普通的for循环遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("--------分割线---------");
// 迭代器遍历
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("--------分割线---------");
// 增强for循环
for (String s : list) {
System.out.println(s);
}
}
5.2 LinkList 的特有方法
LinkList
可以完成队列结构和栈结构
🙋举个栗子:
import java.util.LinkedList;
public class Demo {
public static void main(String[] args) {
// 实现一个队列
LinkedList<String> queue = new LinkedList<>();
// 入队
queue.addLast("1号");
queue.addLast("2号");
queue.addLast("3号");
System.out.println(queue); // [1号, 2号, 3号]
// 出队
System.out.println(queue.removeFirst()); // 1号
System.out.println(queue.removeFirst()); // 2号
System.out.println(queue); // [3号]
// 实现一个栈
LinkedList<String> stack = new LinkedList<>();
// 入栈,也可以使用 push
// stack.push("第1颗子弹");
stack.addFirst("第1颗子弹");
stack.addFirst("第2颗子弹");
stack.addFirst("第3颗子弹");
System.out.println(stack);
//出栈,也可以使用 pop
// System.out.println(stack.pop());
System.out.println(stack.removeFirst()); // 第3颗子弹
System.out.println(stack.removeFirst()); // 第2颗子弹
System.out.println(stack); // [第1颗子弹]
}
}