Java集合,Collection,常见数据结构,list集合,泛型
一.集合的体系特点
数组的特点
-
数组定义完成并启动后,类型确定、长度固定。
-
不适合元素的个数和类型不确定的业务场景,更不适合做需要增删数据操作。
-
数组的功能也比较的单一,处理数据的能力并不是很强大。
集合的特点
-
集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球。
-
集合非常适合元素个数不能确定,且需要做元素的增删操作的场景。
-
同时,集合提供的种类特别的丰富,功能也是非常强大的,开发中集合用的更多。
集合类体系结构
-
Collection单列集合,每个元素(数据)只包含一个值。
-
Map双列集合,每个元素包含两个值(键值对)。
Collection集合特点
List系列集合:添加的元素是有序、可重复、有索引。
- ArrayList、LinekdList :有序、可重复、有索引。
Set系列集合:添加的元素是无序、不重复、无索引。
-
HashSet: 无序、不重复、无索引;LinkedHashSet: 有序、不重复、无索引。
-
TreeSet:**按照大小默认升序排序、**不重复、无索引。
泛型
- 集合都是泛型的形式,可以在编译阶段约束集合只能操作某种数据类型
Collection lists = new ArrayList();
Collection lists = new ArrayList<>(); // JDK 1.7开始后面的泛型类型申明可以省略不写
注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象。
// 存储基本类型使用包装类
Collection lists = new ArrayList<>();
Collection lists = new ArrayList<>();
二.Collection集合的常用API
- Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
1.Collection 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() | 把集合中的元素,存储到数组中 |
2.实际运用:
public class CollectionAPIDemo {
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
//1.添加元素,添加成功返回true
c.add("Java");
c.add("HTML");
c.add("HTML");
c.add("MySQL");
c.add("Java");
c.add("张飞");
c.add("Java");
System.out.println(c);
//2.清空集合的元素
// c.clear();
// System.out.println(c);
//3.判断集合是否为空,是空返回true,反之
System.out.println(c.isEmpty());
//4.获取集合的大小
System.out.println(c.size());
//5.判断集合中是否包含某个元素
System.out.println(c.contains("Java")); // true
System.out.println(c.contains("诸葛亮"));// false
System.out.println(c.contains("MySQL"));// true
//6.删除某个元素:如果有更多个重复元素默认删除前面的第一个!
System.out.println(c.remove("java"));//false
System.out.println(c);
System.out.println(c.remove("Java"));//true
System.out.println(c);
//7.把集合转换成数组 [HTML, HTML, MySQL, Java, 张飞, Java]
Object[] arrs = c.toArray();
System.out.println("数组:" + Arrays.toString(arrs));
System.out.println("=================================");
Collection<String> c1 = new ArrayList<>();
c1.add("java1");
c1.add("java2");
Collection<String> c2 = new ArrayList<>();
c2.add("关羽");
c2.add("张飞");
//addAll把c2集合的元素全部放入c1中去
c1.addAll(c2);
System.out.println(c1);
System.out.println(c2);
}
}
三.Collection集合的遍历方式
1.方式一:迭代器,Iterator
迭代器遍历概述
-
遍历就是一个一个的把容器中的元素访问一遍。
-
迭代器在Java中的代表是Iterator,迭代器是集合的专用的遍历方式。
Collection集合获取迭代器
方法名称 | 说明 |
---|---|
Iterator iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引 |
Iterator中的常用方法
方法名称 | 说明 |
---|---|
boolean hasNext() | 询问当前位置是否有元素存在,存在返回true ,不存在返回false |
E next() | 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界。 |
public class CollectionDemo1 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("刘备");
lists.add("关羽");
lists.add("张飞");
lists.add("卧龙");
System.out.println(lists);
//1.得到当前集合的迭代器对象
Iterator<String> it = lists.iterator();
//2.定义while循环
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
}
}
迭代器如果取元素越界会出现NoSuchElementException异常。
2.方式二:foreach/增强for循环
增强for循环
for(元素数据类型 变量名 : 数组或者Collection集合) {
//在此处使用变量即可,该变量就是元素}
- 增强for循环:既可以遍历集合也可以遍历数组
例子:
Collection list = new ArrayList<>();
…
for(String ele : list) {
System.out.println(ele);
}
public class CollectionDemo2 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("刘备");
lists.add("关羽");
lists.add("张飞");
lists.add("卧龙");
System.out.println(lists);
for (String list : lists) {
System.out.println(list);
}
System.out.println("======================");
double[] scores = {100, 99, 45, 56, 56.8};
System.out.println(Arrays.toString(scores));
for (double score : scores) {
System.out.println(score);
}
}
}
3.方式三:Lambda表达式
Lambda表达式遍历集合
- 得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单,更直接的遍历集合的方式
Collection结合Lambda遍历的API
方法名称 | 说明 |
---|---|
default void forEach(Consumer<? super T> action): | 结合lambda遍历集合 |
public class CollectionDemo3 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("刘备");
lists.add("关羽");
lists.add("张飞");
lists.add("卧龙");
System.out.println(lists);
// lists.forEach(new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// });
lists.forEach( s -> {
System.out.println(s);
});
// lists.forEach( s -> System.out.println(s));
//Lambda简化形式
}
}
四.Collection集合存储自定义类型的对象
1.案例:影片信息在程序中的表示
需求
某影院系统需要在后台存储上述三部电影,然后依次展示出来。
分析
①:定义一个电影类,定义一个集合存储电影对象。
②:创建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;
}
}
public class Test {
public static void main(String[] args) {
//1.定义一个电影类
//2.定义一个集合对象存储3部电影对象
Collection<Movie> movies = new ArrayList<>();
movies.add(new Movie("《你好,李焕英》",9.5,"张小雯,贾玲,沈腾,陈赫"));
movies.add(new Movie("《唐人街探案》",8.5,"王宝强,刘昊然,机车大汉"));
movies.add(new Movie("《刺杀小说家》",8.6,"雷佳音,杨幂"));
//3.遍历集合容器中的每一个电影对象
for (Movie movie : movies) {
System.out.println("电影名:"+movie.getName());
System.out.println("评分:"+movie.getScore());
System.out.println("演员:"+movie.getActor());
}
}
}
- 集合中存储的是元素对象的地址
五.常见数据结构
1.数据结构概述,栈,队列
-
栈:先进后出,后进先出
-
队列:先进先出,后进后出
2.数组
- 查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的)
- **删除效率低:**要将原始数据删除,同时后面每个数据前移。
- **添加效率极低:**添加位置后的每个数据后移,再添加元素。
3.链表
链表的特点:
- 链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址。
- 链表查询慢。无论查询哪个数据都要从头开始找
- 链表增删相对快
- 单向,双向链表
4.二叉树,二叉查找树
二叉树的特点
-
只能有一个根节点,每个节点最多支持2个直接子节点。
-
节点的度: 节点拥有的子树的个数,二叉树的度不大于2 叶子节点 度为0的节点,也称之为终端结点。
-
**高度:**叶子结点的高度为1,叶子结点的父节点高度为2,以此类推,根节点的高度最高。
-
**层:**根节点在第一层,以此类推
-
**兄弟节点 :**拥有共同父节点的节点互称为兄弟节点
二叉查找树又称二叉排序树或者二叉搜索树。
特点:
1.每一个节点上最多有两个子节点
2.左子树上所有节点的值都小于根节点的值
3.右子树上所有节点的值都大于根节点的值
目的:提高检索数据的性能。
**规则:**小的存左边,大的存右边,一样的不存.
5.平衡二叉树
- 平衡二叉树是在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能。
平衡二叉树的要求
- 任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树
平衡二叉树在添加元素后可能导致不平衡
- 基本策略是进行左旋,或者右旋保证平衡。
6.红黑树
红黑树概述
-
红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。
-
1972年出现,当时被称之为平衡二叉B树。1978年被修改为如今的"红黑树"。
-
每一个节点可以是红或者黑;红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的。
红黑规则
-
每一个节点或是红色的,或者是黑色的,根节点必须是黑色。
-
如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)。
-
对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
添加节点
-
添加的节点的颜色,可以是红色的,也可以是黑色的。
-
默认用红色效率高。
红黑树小结
- 红黑树不是高度平衡的,它的平衡是通过"红黑规则"进行实现的
规则如下:
-
每一个节点或是红色的,或者是黑色的,根节点必须是黑色
-
如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的;
-
如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
-
对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
红黑树增删改查的性能都很好
小结:
各种数据结构的特点和作用:
- 队列:先进先出,后进后出。
- 栈:后进先出,先进后出。
- 数组:内存连续区域,查询快,增删慢。
- 链表:元素是游离的,查询慢,首尾操作极快。
- 二叉树:永远只有一个根节点, 每个结点不超过2个子节点的树。
- 查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差。
- 平衡查找二叉树:让树的高度差不大于1,增删改查都提高了。
- 红黑树(就是基于红黑规则实现了自平衡的排序二叉树)
六.List系列集合
1.List集合特点、特有API
List系列集合特点
- ArrayList、LinekdList :有序,可重复,有索引。
- 有序:存储和取出的元素顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
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) | 返回指定索引处的元素 |
public class ListDemo1 {
public static void main(String[] args) {
//1.创建一个Arratlist集合对象
//List:有序,可重复,有索引
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Java");
list.add("MySQL");
list.add("MySQL");
System.out.println(list);
//2.在某个索引位置插入元素
list.add(2,"HTML");
System.out.println(list);
//3.根据索引删除元素,返回被删除的元素
System.out.println(list.remove(2));
System.out.println(list);
//4.根据索引获取元素:public E get(int index):返回集合中指定位置的元素
System.out.println(list.get(2));
//5.返回索引位置出的元素:public E set(int index, E element)
//返回修改的数据
System.out.println(list.set(1,"张飞"));
System.out.println(list);
list.clear();
System.out.println(list);
}
}
List的实现类的底层原理:
-
ArrayList底层是基于数组实现的,根据查询元素快,增删相对慢。
-
LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的。
2.List集合的遍历方式小结
①迭代器
②增强for循环
③Lambda表达式
④for循环(因为List集合存在索引)
public class ListDemo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("java1");
list.add("java2");
list.add("java3");
/* 1.for循环 */
System.out.println("--------------");
for (int i = 0; i < list.size(); i++) {
String ele = list.get(i);
System.out.println(ele);
}
/* 2.迭代器。*/
System.out.println("-----------------");
Iterator<String> it = list.iterator();
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
/* 3.增强for循环*/
System.out.println("---------------");
for (String s : list) {
System.out.println(s);
}
/* 4.JDK 1.8开始之后的Lambda表达式*/
System.out.println("--------------------");
list.forEach(s -> {
System.out.println(s);
});
}
}
3.ArrayList集合的底层原理
ArrayList集合底层原理:
-
ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
-
第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。
List list = new ArrayList<>();
list.add(“a”);
4.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 ListDemo3 {
public static void main(String[] args) {
//LinkedList可以完成队列结构,和栈结构 (双链表)
//栈
LinkedList<String> stack = new LinkedList<>();
//压栈,入栈
stack.push("第一颗子弹");
stack.push("第二颗子弹");
stack.push("第三颗子弹");
stack.push("第四颗子弹");
System.out.println(stack);
//出栈,弹栈
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack);
//队列
LinkedList<String> queue = new LinkedList<>();
//入队
queue.addLast("一号");
queue.addLast("二号");
queue.addLast("三号");
queue.addLast("四号");
System.out.println(queue);
//出队
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue);
}
}
七.集合的并发修改异常问题
- 当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题。
哪些遍历存在问题?
-
迭代器遍历集合且直接用集合删除元素的时候可能出现。
-
增强for循环遍历集合且直接用集合删除元素的时候可能出现。
哪种遍历且删除元素不出问题
-
迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。
-
使用for循环遍历并删除元素不会存在这个问题。
public class Test {
public static void main(String[] args) {
//1.准备数据
List<String> list = new ArrayList<>();
list.add("三国志");
list.add("刘备");
list.add("刘备");
list.add("张飞");
list.add("关羽");
System.out.println(list);
//需求:删除全部的 刘备 信息。
// //a. 迭代器遍历删除
// Iterator<String> it = list.iterator();
// while (it.hasNext()){
// String ele = it.next();
// if ("刘备".equals(ele)){
// it.remove();//使用迭代器删除当前未知的元素,保证不后移,能够成功遍历到全部元素
// }
// }
// System.out.println(list);
//b. foreach遍历删除(会有BUG,不能使用)
// for (String s : list) {
// if ("刘备".equals(s)){
// list.remove("刘备");
// }
// }
//c. Lambda表达式(有BUG)
// list.forEach(s -> {
// if ("刘备".equals(s)){
// list.remove("刘备");
// }
// });
//d. for循环(不会出现异常错误,但是数据删除不完整)
for (int i = 0; i < list.size(); i++) {
String ele = list.get(i);
if ("刘备".equals(ele)){
list.remove("刘备");
i--;
}
}
System.out.println(list);
}
}
八.泛型深入
1.泛型的概述和优势
泛型概述:
-
泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
-
泛型的格式:<数据类型>; 注意:泛型只能支持引用数据类型。
-
集合体系的全部接口和实现类都是支持泛型的使用的。
泛型的好处:
-
统一数据类型。
-
把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。
泛型可以在很多地方进行定义:
类后面 ----> 泛型类
方法申明上 -----> 泛型方法
接口后面 -------> 泛型接口
2.自定义泛型类
泛型类的概述
-
定义类时同时定义了泛型的类就是泛型类。
-
泛型类的格式:修饰符 class 类名<泛型变量>{ }
-
范例:public class MyArrayList { }
此处泛型变量T可以随便写为任意标识,常见的如**E、T、**K、V等。
作用:编译阶段可以指定数据类型,类似于集合的作用。
案例:
- 模拟ArrayList集合自定义一个集合MyArrayList集合,完成添加和删除功能的泛型设计即可。
public class MyArrayList<E> {
private ArrayList lists = new ArrayList();
public void add(E e){
lists.add(e);
}
public void remove(E e){
lists.remove(e);
}
@Override
public String toString() {
return lists.toString();
}
}
public class Test {
public static void main(String[] args) {
//需求:模拟ArrayList定义一个MyArrayList,关注泛型设计
MyArrayList<String> list = new MyArrayList<>();
list.add("Java");
list.add("Java");
list.add("MySQL");
list.remove("MySQL");
System.out.println(list);
MyArrayList<Integer> list1 = new MyArrayList<>();
list1.add(23);
list1.add(24);
list1.add(25);
list1.remove(25);
System.out.println(list1);
}
}
泛型类的原理:
- 把出现泛型变量的地方全部替换成传输的真实数据类型。
3.自定义泛型方法
泛型方法的概述:
-
定义方法时同时定义了泛型的方法就是泛型方法。
-
泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
-
范例: public void show(T t) { }
作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
案例:
- 给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能!
public class GenericDemo {
public static void main(String[] args) {
String[] names = {"刘备","关羽","张飞"};
printArray(names);
Integer[] ages = {10, 20, 30};
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);
}
}
}
泛型方法的原理:
- 把出现泛型变量的地方全部替换成传输的真实数据类型。
4.自定义泛型接口
泛型接口的概述
-
使用了泛型定义的接口就是泛型接口。
-
泛型接口的格式:修饰符 interface 接口名称<泛型变量>{}
-
范例: public interface Data{}
作用:泛型接口可以让实现类选择当前功能需要操作的数据类型
案例:
- 教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作
public interface Data <E>{
void add(E e);
void delete(int id);
void update(E e);
E queryById(int id);
}
public class Student {
}
public class Teacher {
}
public class StudentData implements Data<Student> {
@Override
public void add(Student student) {
}
@Override
public void delete(int id) {
}
@Override
public void update(Student student) {
}
@Override
public Student queryById(int id) {
return null;
}
}
public class TeacherData implements Data<Teacher>{
@Override
public void add(Teacher teacher) {
}
@Override
public void delete(int id) {
}
@Override
public void update(Teacher teacher) {
}
@Override
public Teacher queryById(int id) {
return null;
}
}
泛型接口的原理:
- 实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
5.泛型通配符、上下限
泛型通配符:案例导学
- 开发一个赛车的游戏,所有的汽车都能一起参与比赛。
public class Demo {
public static void main(String[] args) {
ArrayList<BNEZ> bnezs = new ArrayList<>();
bnezs.add(new BNEZ());
bnezs.add(new BNEZ());
bnezs.add(new BNEZ());
go(bnezs);
ArrayList<BWM> bwms = new ArrayList<>();
bwms.add(new BWM());
bwms.add(new BWM());
bwms.add(new BWM());
go(bwms);
}
/**
所有车比赛
*/
public static void go(ArrayList<? extends Car> cars){
}
}
class BNEZ extends Car{
}
class BWM extends Car{
}
//父类
class Car{
}
通配符:?
-
? 可以在“使用泛型”的时候代表一切类型。
-
E T K V 是在定义泛型的时候使用的。
注意:
- 虽然BMW和BENZ都继承了Car但是ArrayList和ArrayList与ArrayList没有关系的!!
泛型的上下限:
- ? extends Car: ?必须是Car或者其子类 泛型上限
- ? super Car : ?必须是Car或者其父类 泛型下限