集合(一)
目录
集合
集合概述
集合和数组都是容器
数组的特点
- 数组在定义完成并启动后,类型确定,长度固定。
- 在进行增删数据的时候,数组是不太合适的,增删数据都需要放弃原有数组或移位
数组适合的场景
- 当业务数据的个数是固定的,且都是同一批数据类型的时候,可以采取定义数组存储
集合是Java中存储对象的一种容器
集合的特点
- 集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球
- 集合非常适合做元素的增删操作。
- 注意:集合中只能存储引用类型的数据,如果要存储基本数据类型可以选用包装类。
集合适合的场景
- 数据的个数不确定,且需要进行增删元素的时候。
Collection集合的体系特点
集合的体系结构
- Collection单列集合,每个元素(数据) 只包含一个值
- Map双列集合,每个元素包含两个值 (键值对)
Collection集合特点
- List系列集合:添加的元素是有序的,可重复的、有索引
- ArrayList、LinkedList:有序、可重复、有索引
- Set系列集合:添加的元素是无序的、不重复、无索引
- HashSet:无序、不重复、无索引;LinkedHashSet:有序、不重复、无索引
- TreeSet:按照大小默认升序排序、不重复、无索引。
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
/**
明确Collection集合体系的特点
*/
public class CollectionDemo1 {
public static void main(String[] args) {
//有序,可重复、有索引
Collection list = new ArrayList();
list.add("java");
list.add("java");
list.add("Mybatis");
list.add(23);
list.add(false);
list.add(false);
list.add("中");
System.out.println(list);
//无序,不可重复,无索引
Collection list1 = new HashSet();
list1.add("java");
list1.add("java");
list1.add("Mybatis");
list1.add(23);
list1.add(false);
list1.add(false);
list1.add("中");
System.out.println(list1);
}
}
output:
[java, java, Mybatis, 23, false, false, 中]
[java, false, 23, 中, Mybatis]
集合对于泛型的支持
- 集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型。
Collection<String> lists = new ArrayList<String>();
Collection<String> lists = new ArrayList<>();//从jdk1.7开始后面的泛型类型申明可以省略不写
注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象
Collection集合常用API
Collection集合
- Collection是单例集合的祖宗接口,它是功能是全部单列集合都可以继承使用的。
Collection API如下:
方法名 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中的所有元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object o) | 判断当前集合中是否含有给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数 |
public Object[] toArray() | 把集合中的元素存储到数组中 |
public boolean addAll(Collection<? extends E> c) | 将指定集合中的所有元素添加到此集合(可选操作) |
Collection集合的遍历方式
方式一:迭代器
迭代器遍历概述
- 遍历就是一个一个的把容器中的元素访问一遍。
- 迭代器在Java中的代表是Iterator,迭代器是集合专用的遍历方式
Collection集合获取迭代器
方法名 | 说明 |
---|---|
Iterator |
返回此集合中元素的迭代器对象,该迭代器对象默认指向当前集合的0索引 |
Iterator中的常用方法
方法名称 | 说明 |
---|---|
boolean hasNext() | 询问当前位置是否有元素存在,存在则返回true,否则返回false |
E next() | 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界 |
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo1 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("小昭");
lists.add("赵敏");
lists.add("素素");
lists.add("灭绝");
System.out.println(lists);
//获取迭代器对象
Iterator<String> it = lists.iterator();
//循环遍历集合中的元素
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
}
}
output:
[小昭, 赵敏, 素素, 灭绝]
小昭
赵敏
素素
灭绝
小结
- 迭代器的默认位置在哪里?
- Iterator
iterator():得到迭代器对象,默认指向当前集合的索引0位置。
- 迭代器如果元素越界会出现什么问题。
- 会出现NoSuchElementException异常。
方式二:foreach/增强for循环
增强for循环
- 增强for循环:既可以遍历集合,也可以遍历数组。
- 他是JDK5之后出现的,其内部原理是一个Iterator迭代器,遍历集合相当于是迭代器的简化写法。
- 实现Iterable接口的接口才可以使用迭代器和增强for,Collection接口已经实现了Iterable接口
格式
for(元素数据类型 变量名 : 数组或者Collection集合){
//在此处使用变量即可,该变量就是元素
}
举例说明
Collection<String> list = new ArrayList<>();
...
for(String ele : list){
System.out.println(ele);
}
方式三:lambda表达式
Lambda表达式遍历集合
- 得益于JDK 8 开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
Collection结合Lambda遍历的API
方法名 | 说明 |
---|---|
default void forEach(Consumer<? super T> action): | 结合lambda遍历集合 |
public class IteratorDemo1 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("小昭");
lists.add("赵敏");
lists.add("素素");
lists.add("灭绝");
System.out.println(lists);
//lambda表达式循环遍历集合中的元素
/* lists.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});*/
/* //简化 Lambda
lists.forEach((String s) ->{
System.out.println(s); });*/
/* //在简化
lists.forEach( s -> System.out.println(s));*/
//新形式
lists.forEach(System.out::println);
}
}
output:
[小昭, 赵敏, 素素, 灭绝]
小昭
赵敏
素素
灭绝
记住这个
lists.forEach((String s) ->{
System.out.println(s);
});
Collection集合存储自定义 类型的对象
案例:影片信息在程序中的表示:
需求:
- 某影院系统需要在后台存储三部电影,然后依次展示出来。
分析:
- 定义一个电影类,定义一个集合存储电影对象
- 创建3个电影对象,封装相关数据,把3个对象存入到集合中去
- 遍历集合中的3个对象,输出相关信息
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class TestDemo {
public static void main(String[] args) {
//定义一个电影类,定义一个集合存储电影对象
Collection<Movie> movies = new ArrayList<>();
movies.add(new Movie("《你好,李焕英》",9.5,"张小斐,贾玲,沈腾,陈赫"));
movies.add(new Movie("《唐人街探案》",8.5,"王宝强,刘昊然,美女"));
movies.add(new Movie("《刺杀小说家》",8.6,"刘德华,肖央"));
//遍历集合中的元素
movies.forEach(movie -> System.out.println("片名:"+movie.getName()
+"评分:"+movie.getScore()+"演员:"+movie.getActor()));
System.out.println("----------------------");
for (Movie movie : movies) {
System.out.println("片名:"+movie.getName()+
"评分:"+movie.getScore()+
"主演:"+movie.getActor());
}
System.out.println("----------------------");
Iterator<Movie> it = movies.iterator();
while (it.hasNext()){
Movie ele = it.next();
System.out.println("片名:"+ele.getName()+"评分:"+ele.getScore()+"主演:"+ele.getActor());
}
}
}
output:
片名:《你好,李焕英》评分:9.5演员:张小斐,贾玲,沈腾,陈赫
片名:《唐人街探案》评分:8.5演员:王宝强,刘昊然,美女
片名:《刺杀小说家》评分:8.6演员:刘德华,肖央
----------------------
片名:《你好,李焕英》评分:9.5主演:张小斐,贾玲,沈腾,陈赫
片名:《唐人街探案》评分:8.5主演:王宝强,刘昊然,美女
片名:《刺杀小说家》评分:8.6主演:刘德华,肖央
----------------------
片名:《你好,李焕英》评分:9.5主演:张小斐,贾玲,沈腾,陈赫
片名:《唐人街探案》评分:8.5主演:王宝强,刘昊然,美女
片名:《刺杀小说家》评分:8.6主演:刘德华,肖央
集合中存储的是元素对象的地址。
常见数据结构
数据结构概述、栈、队列
- 数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。
- 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。
栈数据结构的执行特点
- 后进先出,先进后出
- 数据进入栈模型的过程称为:压/进栈
- 数据离开栈模型的过程称为:弹/出栈
- 栈顶元素
- 栈低元素
常见数据结构之队列
- 先进先出,后进后出
- 数据从后端进入队列模型的过程称为:入队列
- 数据从前端进入队列模型的过程称为:出队列
数组
常见数据结构之数组——是一种查询快,增删慢的模型
- 查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的).
- 删除效率低:要将原始数据删除,同时后面每个数据前移
- 添加效率极低:添加位置后的每个数据后移,再添加元素
链表的特点
- 链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址。
链表的特点
- 链表中的元素是游离存储的,每个元素节点包含数据值和下一个元素的地址。
- 链表查询慢。无论查询哪个数据都要从头开始找
- 链表增删相对快
二叉树、二叉查找树
二叉树
- 只能有一个根节点 ,每个节点最多支持2个直接子节点
- 节点的度:节点拥有的子树的个数,二叉树的度不大于2叶子节点 度为0的节点,也称为终端节点
- 高度:叶子节点的高度为1,叶子节点的父节点高度为2,以此类推,根节点的高度最高。
- 层:根节点在第一层,以此类推
- 兄弟节点:拥有共同父节点的节点互称为兄弟节点
二叉查找树:又称二叉排序树或者二叉搜索树
特点:
- 每一个节点上最多有两个子节点
- 左子树上所有节点的值都小于根节点的值
- 右子树上所有节点的值都大于根节点的值
平衡二叉树
- 平衡二叉树是在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能
平衡二叉树的要求
- 任意节点的左右两个子树的高度查不超过1,任意节点的左右两个子树都是一颗平衡二叉树
红黑树
红黑数概述
- 红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构
- 1972年出现,当时被称之为平衡二叉B树,1978年被修改为如今的"红黑树"
- 每一个节点可以使红或者黑,红黑树不是通过高度平衡的,它的平衡是通过”红黑规则“进行实现的
红黑节点
- 每一个节点或是红色的,或者是黑色的,根节点必须是黑色的
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,叶节点是黑色的
- 如果某一个节点是红色的,那么它的子节点必须是黑色的(不能出现两个红色节点相连的情况)。
- 对于每一个节点,从该节点到其所有后代节叶节点的简单路径上,均包含相同数目的黑色节点。
总结
- 队列:先进先出,后进后出
- 栈:后进先出,先进后出
- 数组:内存连续区域,查询快,增删慢
- 链表:元素是游离的,查询慢,首尾操作极快。
- 二叉树:永远只有一个根节点,每个节点不超过2个子节点的树
- 查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差。
- 平衡二叉树:让数的高度差不大于1,增删改查都提高了。
- 红黑叔:就是基于红黑规则实现了自平衡的排序二叉树。
List系列集合
List系列集合特点、特有API
- ArrayList、LinkedList:有序,可重复,有索引。
- 有序:存储和取出的元素顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
List集合特有方法
- List集合因为支持索引,所以多了很多索引操作的独特API,其他Collection的功能List也都继承了
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中指定的位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被修改的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
List的实现类的底层原理
- ArrayList底层是基于数组实现的,根据查询元素快,增删相对慢。
- LinkedList底层是基于双链表的,查询元素慢,增删首尾元素是非常快的。
List集合的遍历方式小结
- 迭代器
- 增强for循环
- Lambda表达式
- for循环(因为List集合存在索引)
public class ListDemo {
public static void main(String[] args) {
List<String> lists = new ArrayList<>();
lists.add("java1");
lists.add("java2");
lists.add("java3");
/**(1) for循环*/
for (int i = 0; i < lists.size(); i++) {
String ele = lists.get(i);
System.out.println(ele);
}
/** (2)迭代器*/
Iterator<String> it = lists.iterator();
while (it.hasNext()){
String rs = it.next();
System.out.println(rs);
}
/**(3)增强for*/
for (String list : lists) {
System.out.println(list);
}
/**(4) Lambda表达式*/
lists.forEach(ele -> System.out.println(ele));
}
}
ArrayList集合的底层原理
- ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
- 第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10 的数组。
LinkedList集合的底层原理
LinkedList的特点
- 底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API
LinkedList集合的特有功能
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在该列表的开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
public class LinkedListDemo1 {
public static void main(String[] args) {
//栈
LinkedList<String> stack = new LinkedList<>();
//压栈,入栈
stack.push("第1颗子弹!");//push就是包装了addFirst()
stack.push("第2颗子弹!");
stack.push("第3颗子弹!");
stack.push("第4颗子弹!");
//出栈,弹栈
System.out.println(stack.pop());//pop就是包装了removeFirst()
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack);
//队列
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);
}
}
output:
第4颗子弹!
第3颗子弹!
第2颗子弹!
[第1颗子弹!]
[1号, 2号, 3号, 4号]
1号
2号
3号
[4号]
补充知识:集合的并发修改异常问题
问题引出
- 当我们从集合中找出某个元素并删除的时候可能会出现一种并发修改异常问题
哪些遍历存在问题?
- 迭代器遍历集合且直接用集合删除元素的时候可能出现
- 增强for循环遍历集合且直接用集合删除元素的时候可能出现
哪种遍历且删除元素不出问题
- 迭代器遍历集合但是用迭代器自己的删除方法操作可以解决
- 使用for循环遍历并删除元素不出出现这个问题(倒着遍历删,或者正着删i--);
补充知识:泛型深入
泛型的概述和优势
泛型概述
- 泛型:是jdk5中引入的新特性,可以在编译阶段约束操作的数据类型,并进行检查。
- 泛型格式:<数据类型>;注意 :泛型只能支持引用数据类型
- 集合体系的全部接口和是实现类都是支持泛型的使用的。
泛型的好处:
- 统一数据类型
- 把运行时间的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。
泛型可以在很多地方进行定义:
- 类后面——》 泛型类
- 方法申明上——》 泛型方法
- 接口后面——》 泛型接口
自定义泛型类
泛型类的概述
-
定义类的同时订定义了泛型的类就是泛型类
-
泛型类的格式:修饰符 class 类名<泛型变量>{}
-
范例: public class MyArrayList<T>{}
-
-
此处的泛型变量T 可以随便写为任意标识,常见如E、T、K、V等。
-
作用:编译阶段可以指定数据类型,类似于集合的作用。
-
案例
- 模拟ArrayList集合自定义一个集合MyArrayList集合,完成添加和删除功能的泛型设计即可。
public class MyArrayList<E> { private ArrayList list = new ArrayList(); public void add(E e){ list.add(e); } public void remove(E e){ list.remove(e); } @Override public String toString() { return list.toString(); } } public class Test { public static void main(String[] args) { MyArrayList<String> list = new MyArrayList<>(); list.add("java"); list.add("323"); list.add("中国"); list.remove("java"); System.out.println(list); } } output: [323, 中国]
泛型类的原理:
- 把出现泛型变量的地方全部替换成传输的真是数据类型。
自定义泛型方法
泛型方法的概述
- 定义方法的同时定义了泛型的方法就是泛型方法
- 泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
- 范例:public
void show(T t)
- 范例:public
- 作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性
案例
- 给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能!
public class GenericDemo {
public static void main(String[] args) {
String[] names = {"紫霞", "至尊宝", "白晶晶"};
printArray(names);
Integer[] ages = {22, 33, 44};
printArray(ages);
}
public static <T> void printArray(T[] arr) {
if (arr != null) {
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < arr.length; i++) {
sb.append(arr[i]).append(i == arr.length - 1 ? "" : ",");
}
sb.append("]");
System.out.println(sb);
} else {
System.out.println(arr);
}
}
}
output:
[紫霞,至尊宝,白晶晶]
[22,33,44]
泛型方法的原理
- 把出现泛型变量的地方全部替换成传输的真实数据类型。
自定义泛型接口
泛型接口的概述
- 使用了泛型定义的接口就是泛型接口
- 泛型接口的格式:修饰符 interface 接口名称<泛型变量>{}
范例: public interface Data
- 作用:泛型接口可以让实现类选择当前功能需要操作的数据类型
案例:
- 教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作
泛型接口的原理
- 实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
泛型通配符、上下限
通配符:?
- ?可以在“使用泛型” 的时候代表一切类型。
- E T K V 实在定义泛型的时候使用的。
泛型通配符:案例导学
- 开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。
public class GenericDemo {
public static void main(String[] args) {
ArrayList<BENZ> benzs = new ArrayList<>();
benzs.add(new BENZ());
benzs.add(new BENZ());
benzs.add(new BENZ());
go(benzs);
ArrayList<BMW> bmws = new ArrayList<>();
bmws.add(new BMW());
bmws.add(new BMW());
bmws.add(new BMW());
go(bmws);
ArrayList<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
//go(dogs);
}
public static void go(ArrayList<? extends Car> cars){
}
}
class Dog{}
class BENZ extends Car{
}
class BMW extends Car{
}
//父类
class Car{
}
注意:
- 虽然BMW和BENZ都继承了Car但是ArrayList
和|ArrayList 与ArrayList 没有关系的!
泛型的上下限
- ?extends Car : ?必须是Car或者其子类 泛型上限
- ?super Car : ?必须是Car或者其父类 泛型下限