目录
1 集合概述
集合是Java中存储对象数据的一种容器。
集合的特点
- 集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球。
- 集合非常适合做元素的增删操作。
- 注意:集合中只能存储引用类型数据,如果要存储基本类型数据可以选用包装类。
集合适合的场景
- 数据的个数不确定,需要进行增删元素的时候。
有两大结合
- Collection单列集合, 每个元素(数据)只包含一个值。
- Map双列集合, 每个元素包含两个值( 键值对)
- 注意:前期先掌握Collection集合体系的使用。
2 Collection
2.1 Collection集合体系
Collection为接口,不能直接使用
Collection集合特点
List系列集合: 添加的元素是有序、可重复、有索引。(List也是接口)
ArrayList、 LinekdList :有序、可重复、有索引。(实现类)
Set系列集合: 添加的元素是无序、不重复、无索引。
HashSet: 无序、不重复、无索引; LinkedHashSet: 有序、不重复、无索引。
TreeSet: 按照大小默认升序排序、不重复、无索引。
集合对于泛型的支持
- 集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型
注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象。
如果集合中要存储基本类型的数据怎么办?
- 用 Integer、Double等引用数据类型代替。
package com.itzw.d1_collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
public class Test01 {
public static void main(String[] args) {
Collection list = new ArrayList();
list.add(1);
list.add('a');
list.add("嗨嗨嗨");
list.add(3.14);
list.add(1);
//有序、可重复、有索引
System.out.println(list);
Collection set = new HashSet();
set.add(1);
set.add("嗨嗨嗨");
set.add(3.14);
set.add(1);
set.add('a');
//无序、不可重复、无索引
System.out.println(set);
//使用泛型只能用引用类型
Collection<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
}
}
2.2 Connection常用API
Collection集合
- Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
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() 把集合中的元素,存储到数组中
package com.itzw.d1_collection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class Test02 {
public static void main(String[] args) {
Collection<String> list = new ArrayList<>();
list.add("张三");
list.add("Java");
list.add("嗨嗨嗨");
list.add("123456");
list.add("java");
System.out.println(list);
//list.clear();
//System.out.println(list);
list.remove("123456");
System.out.println(list);
System.out.println(list.contains("java"));//true
System.out.println(list.contains("123"));//false
System.out.println(list.isEmpty());//false
System.out.println(list.size());//4
//将集合中的元素存储到数组
Object[] lists = list.toArray();
System.out.println(Arrays.toString(lists));
System.out.println("==============================");
//将集合的内容存储到另一个集合
Collection<String> c1 = new ArrayList<>();
c1.add("123");
c1.add("456");
Collection<String> c2 = new ArrayList<>();
c2.add("张麻子");
c2.add("马邦德");
System.out.println("===================添加前============");
System.out.println(c1);
System.out.println(c2);
c1.addAll(c2);
System.out.println("===================添加后============");
System.out.println(c1);
}
}
2.3 Collection集合的遍历
2.3.1 迭代器
迭代器遍历概述
- 遍历就是一个一个的把容器中的元素访问一遍。
- 迭代器在Java中的代表是Iterator, 迭代器是集合的专用遍历方式。
Collection集合获取迭代器
Iterator<E> iterator( )返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引
Iterator中的常用方法
boolean hasNext() 询问当前位置是否有元素存在,存在返回true ,不存在返回false
E next()获 取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界。
Iterator<String> it = c1.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
2.3.2 增强for循环
增强for循环
- 增强for循环: 既可以遍历集合也可以遍历数组。
- 它是JDK5之 后出现的,其内部原理是一个Iterator迭代器,遍历集合相当于是迭代器的简化写法。
- 实现Iterable接口 的类才可以使用迭代器和增强for, Collection接口已经实现了lterable接口。
格式:
for(元素数据类型变量名:数组或者Collection集合) {
//在此处使用变量即可,该变量就是元素}
System.out.println("===========增强for循环==============");
for (String ele : c1){
System.out.println(ele);
}
System.out.println("=======数组上使用增强for循环==========");
double[] doubles = {31.4,66.4,77,9.0};
for (double d : doubles) {
System.out.println(d);
}
2.3.3 lambda表达式
System.out.println("===========lambda遍历==============");
c1.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//简化
c1.forEach((s) -> System.out.println(s));
2.4 Collection集合存储自定义类型的对象
需求
- 某影院系统需要在后台存储上述三部电影,然后依次展示出来。
分析
- 定义一个电影类,定义一个集合存储电影对象。
- 创建3个电影对象,封装相关数据,把3个对象存入到集合中去。
- 遍历集合中的3个对象,输出相关信息。
package com.itzw.d2_collection;
import java.util.ArrayList;
import java.util.Collection;
public class Test {
public static void main(String[] args) {
Collection<Movie> movies = new ArrayList<>();
movies.add(new Movie("让子弹飞",8.9,"姜文,葛优"));
movies.add(new Movie("追凶者也",8.5,"张译"));
movies.add(new Movie("疯狂的石头",8.8,"黄渤"));
//遍历
for (Movie movie : movies) {
System.out.println("名称:" + movie.getName());
System.out.println("得分:" + movie.getScore());
System.out.println("主演:" + movie.getActor());
System.out.println("========================");
}
}
}
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;
}
}
3 常见数据结构
数据结构概述
- 数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。
- 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。
常见的数据结构
栈、队列、数组、链表、二叉树、二叉查找树、平衡二叉树、红黑树等等
3.1 栈
栈数据结构的执行特点
- 后进先出,先进后出
3.2 队列
常见数据结构之队列
- 先进先出,后进后出
数据从后端进入队列模型的过程称为:入队列
数据从前端离开队列模型的过程称为:出队列
3.3 数组
根据索引查找
- 查询速度快: 查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中 是连续存储的)
- 删除效率低:要将原始数据删除,同时后面每个数据前移。
- 添加效率极低:添加位置后的每个数据后移,再添加元素。
3.4 链表
- 链表中的元素是在内存中不连续存储的, 每个元素节点包含数据值和下一个元素的地址。
- 链表查询慢。无论查询哪个数据都要从头开始找
- 链表增删相对快
在数据AC之间添加一个数据B
- 数据B对应的下一个数据地址指向数据C
- 数据A对应的下一个数据地址指向数据B
删除数据BD之间的数据C
- 数据B对应的下一个数据地址指向数据D
- 数据C删除
链表是一种增删快的模型(对比数组)
3.5 二叉树
- 只能有一个根节点,每个节点最多支持2个直接子节点。
- 节点的度:节点拥有的子树的个数,二叉树的度不大于2叶子节点度为0的节点,也称之为终端结点。
- 高度:叶子结点的高度为1,叶子结点的父节点高度为2,以此类推,根节点的高度最高。
- 层:根节点在第一-层,以此类推
- 兄弟节点:拥有共同父节点的节点互称为兄弟节点
二叉查找树又称二叉排序树或者二叉搜索树。
特点:
- 每一个节点上最多有两个子节点
- 左子树上所有节点的值都小于根节点的值
- 右子树上所有节点的值都大于根节点的值
目的:提高检索数据的性能。
3.6 平衡二叉树
平衡二叉树的要求
- 任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡=叉树
平衡二叉树在添加元素后可能导致不平衡
- 基本策略是进行左旋,或者右旋保证平衡。
平衡二叉树-旋转的四种情况
左左、左右、右右、右左
3.7 红黑树
红黑树概述
- 红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。
- 1972年出现, 当时被称之为平衡二叉B树。1978年被修改为如今的"红黑树"。
- 每一个节点可以是红或者黑;红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的。
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。.
总结:
各种数据结构的特点和作用是什么样的
- 队列:先进先出,后进后出。
- 栈:后进先出,先进后出。
- 数组:内存连续区域,查询快,增删慢。
- 链表:元素是游离的,查询慢,首尾操作极快。
- 二叉树:永远只有一个根节点,每个结点不超过2个子节点的树。
- 查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差。
- 平衡查找二叉树:让树的高度差不大于1,增删改查都提高了。
- 红黑树(就是基于红黑规则实现了自平衡的排序二叉树)
4 List集合
4.1 List集合概述
List集合属于Collection集合
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) 返回指定索弓|处的元素
List<String> list = new ArrayList<>();
list.add("java");
list.add("Java");
list.add("mysql");
list.add("mysql");
System.out.println(list);
list.add(2,"Html");
System.out.println(list);
list.remove(2);
System.out.println(list);
list.set(1,"嗨嗨嗨");
System.out.println(list);
System.out.println(list.get(1));
4.2 List集合的遍历
Collection集合可以用的List集合都可以,但因为List集合有索引下标,所以可以用for循环,也就是共四种方式
演示:
List<String> list = new ArrayList<>();
list.add("JAVA");
list.add("嗨嗨嗨");
list.add("C++");
list.add("MYSQL");
System.out.println("===========for循环============");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("===========增强for循环============");
for (String s : list) {
System.out.println(s);
}
System.out.println("===========迭代器============");
Iterator<String> it = list.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
System.out.println("===========lambda============");
list.forEach((s) -> System.out.println(s));
4.3 List底层原理
4.3.1 ArrayList集合底层原理
- ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
- 第一次创建集合并添加第一 个元素的时候, 在底层创建一个默认长度为10的数组。
- 长度超过10时按照1.5倍扩容
4.3.2 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( ) 从此列表中删除并返回最后一个元素
//栈
LinkedList<String> stack = new LinkedList<>();
//压栈 先进后出
stack.addFirst("第一颗子弹");
stack.addFirst("第二颗子弹");
stack.addFirst("第三颗子弹");
stack.addFirst("第四颗子弹");
System.out.println(stack);
System.out.println(stack.getFirst());
System.out.println(stack.getLast());
System.out.println(stack.removeFirst());
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.getFirst());
System.out.println(queue.removeFirst());
System.out.println(queue);
5 集合的并发修改异常问题
问题引出
- 当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题。
哪些遍历存在问题?
- 迭代器遍历集合且直接用集合删除元素的时候可能出现。
- 增强for循环遍历集合且直接用集合删除元素的时候可能出现。
哪种遍历且删除元素不出问题
- 迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。
- 使用for循环遍 历并删除元素不会存在这个问题。
package com.itzw.d3_list;
import javax.jnlp.ClipboardService;
import java.util.Iterator;
import java.util.LinkedList;
//遍历删除JAVA
public class ErrorTest {
public static void main(String[] args) {
//栈
LinkedList<String> stack = new LinkedList<>();
//压栈 先进后出
stack.addFirst("嗨嗨嗨");
stack.addFirst("JAVA");
stack.addFirst("JAVA");
stack.addFirst("私密马赛");
/*System.out.println("===========迭代器===========");
Iterator<String> it = stack.iterator();
while (it.hasNext()){
String s = it.next();
if (s.equals("JAVA")){
//stack.remove("JAVA");有问题
//每次删掉一个都要后移,而删掉后数据又会前移
it.remove();
}
}
System.out.println(stack);*/
/*System.out.println("==============增强for循环=============");
for (String s : stack) {
if (s.equals("JAVA")){
//stack.remove("JAVA");和迭代器一样不能,而且没有解决办法,无法使用
}
}*/
//这样虽然不会报错,但会少删
/*System.out.println("===========for循环===========");
for (int i = 0; i < stack.size(); i++) {
if (stack.get(i).equals("JAVA")){
stack.remove(i);
}
}
System.out.println(stack);*/
/* System.out.println("===========for循环===========");
for (int i = 0; i < stack.size(); i++) {
if (stack.get(i).equals("JAVA")){
stack.remove(i);
i--;//加一个i--
}
}
System.out.println(stack);*/
System.out.println("===========for循环===========");
//或者从后往前删
for (int i = stack.size() - 1; i >= 0; i--) {
if (stack.get(i).equals("JAVA")){
stack.remove(i);
}
}
System.out.println(stack);
}
}
6 泛型深入
6.1 泛型概述
泛型概述
- 泛型:是DK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
- 泛型的格式: <数据类型>;注意:泛型只能支持引用数据类型。
- 集合体系的全部接口和实现类都是支持泛型的使用的。
泛型的好处:
- 统一数据类型。
- 把运行时期 的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。
6.2 自定义泛型
6.2.1 自定义泛型类
泛型类的概述
- 定义类时同时定义了泛型的类就是泛型类。
- 泛型类的格式:修饰符class 类名<泛型变量>{ }
范例: public class MyArrayList<T>{ }
- 此处泛型变量T可以随便写为任意标识, 常见的如E、T. K、V等。
- 作用:编译阶段可以指定数据类型,类似于集合的作用。
课程案例导学
- 模拟ArrayList集合自定义一个集合MyArrayList集合 ,完成添加和删除功能的泛型设计即可。
泛型类的原理:
- 把出现泛型变量的地方全部替换成传输的真实数据类型
package com.itzw.d4_genericity;
import java.util.ArrayList;
public class Test01 {
public static void main(String[] args) {
MyArrayList<String> list = new MyArrayList<>();
list.add("嗨嗨嗨");
list.add("吆西");
list.add("刚拔地");
System.out.println(list);
list.remove("嗨嗨嗨");
System.out.println(list);
}
}
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();
}
}
6.2.2 自定义泛型方法
泛型方法的概述
- 定义方法时同时定义了泛型的方法就是泛型方法。
- 泛型方法的格式:修饰符<泛型变量>方法返回值方法名称(形参列表){}
范例: public <T> void show(T t){ }
- 作用:方法中可以使用泛型接收一切实际类 型的参数,方法更具备通用性。
课程案例导学
- 给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能!
package com.itzw.d4_genericity;
public class Test02 {
public static void main(String[] args) {
Integer[] a1 = {1,2,3,4,5};
String[] a2 = {"张麻子","马邦德","王德发"};
getArr(a1);
getArr(a2);
}
public static <T> void getArr(T[] arr){
StringBuilder sb = new StringBuilder("[");
if (arr == null){
System.out.println(arr);
}else {
for (int i = 0; i < arr.length; i++) {
sb.append(i == arr.length - 1 ? arr[i] + "]" : arr[i] + ",");
}
System.out.println(sb.toString());
}
}
}
6.2.3 自定义泛型接口
泛型接口的概述
- 使用了泛型定义的接口就是泛型接口。
- 泛型接口的格式:修饰符interface接口名称<泛型变量>{}
范例: public interface Data<E>{}
- 作用:泛型接口可以让实现类选择当前功能需要操作的数据类型
课程案例导学
- 教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作
泛型接口的原理:
- 实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
package com.itzw.d4_genericity;
public class Test03 {
public static void main(String[] args) {
}
}
class TeacherData implements Data<Teacher>{
@Override
public void add(Teacher teacher) {
}
@Override
public void delete(Teacher teacher) {
}
@Override
public void update(Teacher teacher) {
}
@Override
public Teacher query(int i) {
return null;
}
}
class Student{
}
class Teacher{
}
interface Data<E>{
void add(E e);
void delete(E e);
void update(E e);
E query(int i);
}
6.2.4 通配符和上下限
通配符:?
- ? 可以在“使用泛型”的时候代表一切类型。
- ETKV 是在定义泛型的时候使用的。
泛型通配符:案例导学
- 开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。
注意:
- 虽然BMW和BENZ都继承 了Car但是ArrayList<BMW>和ArrayList<BENZ>与ArrayList<Car>没有关系的!
泛型的上下限:
- ? extends Car: ?必须是Car或者其子类泛型. 上限
- ? super Car : ?必须是Car或者其父类 泛型下限
package com.itzw.d4_genericity;
import java.util.ArrayList;
public class Test04 {
public static void main(String[] args) {
ArrayList<BWM> bwms = new ArrayList<>();
bwms.add(new BWM());
bwms.add(new BWM());
bwms.add(new BWM());
go(bwms);
ArrayList<BENZ> benzs = new ArrayList<>();
benzs.add(new BENZ());
benzs.add(new BENZ());
benzs.add(new BENZ());
go(benzs);
ArrayList<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
dogs.add(new Dog());
//go(dogs);
}
//?是通配符,什么类都能传,而继承Car后就只能传Car子类
public static void go(ArrayList<? extends Car> cars){
}
}
class Dog{
}
class BENZ extends Car{
}
class BWM extends Car{
}
class Car{
}
7 Set系列集合
7.1 Set集合概述
Set集合也属于Collection集合
Set系列集合特点
- 无序: 存取顺序不一致
- 不重复: 可以去除重复
- 无索引: 没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
Set集合实现类特点
- HashSet: 无序、不重复、无索引。
- LinkedHashSet: 有序、不重复、无索引。
- TreeSet: 排序、不重复、无索引。
Set集合的功能上基本上与Collection的API一致。
//无序、不重复、无索引
//Set<String> set = new HashSet<>();
//有序、不重复、无索引
Set<String> set = new LinkedHashSet<>();
set.add("嗨嗨嗨");
set.add("嗨嗨嗨");
set.add("java");
set.add("java");
set.add("mysql");
System.out.println(set);
7.2 HashSet底层原理
HashSet1.7版本原理解析:数组+链表+ (结合哈希算法)
- 创建一个默认长度16的数组,数组名table
- 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
- 判断当前位置是否为null,如果是null直接存入
- 如果位置不为null, 表示有元素,则调用equals方法比较
- 如果一样,则不存,如果不一样,则存入数组,
JDK 7新元素占老元素位置,指向老元素
JDK 8中新元素挂在老元素下面
6.当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
结论:哈希表是一种对于增删改查数据性能都较好的结构。
JDK1.8版本开始HashSet原理解析
- 底层结构:哈希表(数组、链表、红黑树的结合体)
- 当挂在元素下面的数据过多时,查询性能降低,从DK8开始后,当链表长度超过8的时候,自动转换为红黑树。
7.3 Set集合去重复
需求:
- 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是同一个对象
分析
- 定义学生类,创建HashSet集合对象,创建学生对象
- 把学生添加到集合
- 在学生类中重写两个方法, hashCode()和equals(), 自动生成即可
- 遍历集合(增强for)
package com.itzw.d5_set;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* 目标:让Set集合去掉重复的数据
*/
public class Test02 {
public static void main(String[] args) {
Set<Student> students = new HashSet<>();
students.add(new Student("张麻子",32));
students.add(new Student("张麻子",32));
students.add(new Student("马邦德",33));
System.out.println(students);
}
}
class Student{
private String name;
private int 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;
}
@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);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
重点就是重新写hashcode和equals
7.4 LinkedHashSet
LinkedHashSet属于HashSet,属于Set集合的孙子
LinkedHashSet集合概述和特点
有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
7.5 TreeSet
TreeSet集合概述和特点
- 不重复、无索引、可排序
- 可排序: 按照元素的大小默认升序(有小到大)排序。
- TreeSet集合 底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
注意: TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
TreeSet集合默认的规则
- 对于数值类型: Integer , Double,官方默认按照大小进行升序排序。
- 对于字符串类型: 默认按照首字符的编号升序排序。
- 对于自定义 类型如Student对象, TreeSet无法直接排序。
结论:想要使用TreeSet存储自定义类型,需要制定排序规则
自定义排序规则
- TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则
方式一
- 让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
方式二
- TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。
两种方式中,关于返回值的规则:
- 如果认为第一 个元素大于第二个元素返回正整数即可。
- 如果认为第一 个元素小于第二个元素返回负整数即可。
- 如果认为第一 个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
- 注意:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。
演示:
package com.itzw.d5_set;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class Test03 {
public static void main(String[] args) {
//Integer类型就根据大小排序
Set<Integer> set = new TreeSet<>();
set.add(16);
set.add(16);
set.add(3);
set.add(77);
set.add(2);
System.out.println(set);
//字符串根据首字母排序
Set<String> set1 = new TreeSet<>();
set1.add("Java");
set1.add("Java");
set1.add("嗨嗨嗨");
set1.add("Mysql");
System.out.println(set1);
System.out.println("==============");
//自定义类的排序
//集合自定义Comparator比较器对象
/*Set<Phone> phones = new TreeSet<>(new Comparator<Phone>() {
@Override
public int compare(Phone o1, Phone o2) {
return Double.compare(o2.getPrice(),o1.getPrice());//降序
}
});*/
//化简
Set<Phone> phones = new TreeSet<>(( o1, o2) -> Double.compare(o2.getPrice(),o1.getPrice()));//降序
phones.add(new Phone("菠萝手机",9999.9,250));
phones.add(new Phone("大米手机",3999.9,245));
phones.add(new Phone("AIAI手机",999.9,232));
phones.add(new Phone("哇为手机",7999.9,250));
//没排序前运行会报错
System.out.println(phones);
}
}
class Phone implements Comparable<Phone>{
private String name;
private double price;
private int weight;
public Phone(){
}
public Phone(String name, double price, int weight) {
this.name = name;
this.price = price;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Phone{" +
"name='" + name + '\'' +
", price=" + price +
", weight=" + weight +
'}';
}
/**
* 方法一:实现Comparable接口,重写比较规则
* @param o
* @return
*/
@Override
public int compareTo(Phone o) {
//return this.weight - o.weight;//若重量一样就会删去一样的元素
return this.weight - o.weight >= 0 ? 1 : -1;//解决相等会删掉元素方法
}
}
7.6 Collection集合总结
1.如果希望元素可以重复,又有索引,索引查询要快?
- 用ArrayList集合, 基于数组的。(用的最多)
2.如果希望元素可以重复,又有索引,增删首尾操作快?
- 用LinkedList集合, 基于链表的。
3.如果希望增删改查都快,但是元素不重复、无序、无索引。
- 用HashSet集合, 基于哈希表的。
4.如果希望增删改查都快,但是元素不重复、有序、无索引。
- 用LinkedHashSet集合, 基于哈希表和双链表。
5.如果要对对象进行排序。
- 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
8 可变参数
8.1 可变参数概述
可变参数
- 可变参数用在形参中可以接收多个数据。
- 可变参数的格式: 数据类型...参数名称
可变参数的作用
- 传输参数非常灵活, 方便。可以不传输参数,,可以传输1个或者多个,也可以传输一-个数组
- 可变参数在方法内部本质上就是一个数组。
可变参数的注意事项:
- 一个形 参列表中可变参数只能有一个
- 可变参数必须放在形参列表的最后面
public static void main(String[] args) {
hai();
hai(1);
hai(1,2,3);
}
public static void hai(int...num){
System.out.println(num.length);
System.out.println(Arrays.toString(num));
}
8.2 Collections集合工类
Collections集合工具类
- java.utils.Collections:是集合工具类
- 作用: Collections并不属于集合,是用来操作集合的工具类。
Collections常用的API
方法名称 说明
public static <T> boolean addAll(Collection<? super T> c, T... elements) 给集合对象批量添加元素
public static void shuffle(List<?> list) 打乱List集合元素的顺序
Collections排序相关API
- 使用范围:只能对于List集合的排序。
排序方式1:
public static <T> void sort(List<T> list) 将集合中元素按照默认规则排序
注意:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规Comparable接口。
排序方式2:
public static <T> void sort(List<T> list, Comparator<? super T> c)将集合中元素按照指定规则排序
package com.itzw.params;
import java.util.*;
public class Test02 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
/*//这样一个一个加很麻烦
list.add("嗨嗨嗨");
list.add("mysql");
list.add("java");*/
//用Collections集合工具类,非常的方便
Collections.addAll(list,"嗨嗨嗨","mysql","java");
System.out.println(list);
//打乱集合顺序
Collections.shuffle(list);
System.out.println(list);
//排序
Collections.sort(list);
System.out.println(list);
//对自定义的类排序,和之前学的TreeSet排序类似
List<Phone> phones = new ArrayList<>();
phones.add(new Phone("菠萝手机",9999.9,250));
phones.add(new Phone("大米手机",3999.9,245));
phones.add(new Phone("AIAI手机",999.9,232));
phones.add(new Phone("哇为手机",7999.9,250));
System.out.println(phones);
Collections.sort(phones, new Comparator<Phone>() {
@Override
public int compare(Phone o1, Phone o2) {
return Double.compare(o2.getPrice(),o1.getPrice());//降序
}
});
System.out.println(phones);
}
}
class Phone implements Comparable<Phone>{
private String name;
private double price;
private int weight;
public Phone(){
}
public Phone(String name, double price, int weight) {
this.name = name;
this.price = price;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Phone{" +
"name='" + name + '\'' +
", price=" + price +
", weight=" + weight +
'}';
}
/**
* 方法一:实现Comparable接口,重写比较规则
* @param o
* @return
*/
@Override
public int compareTo(Phone o) {
//ArrayList不需要考虑相等会删掉元素的问题,因为ArrayList允许元素相等
return this.weight - o.weight;
}
}
9 模拟斗地主游戏
需求:
在启动游戏房间的时候,应该提前准备好54张牌,完成洗牌、发牌、牌排序、逻辑。
分析:
- 当系统启动的同时需要准备好数据的时候,就可以用静态代码块了。
- 洗牌就是打乱牌的顺序。
- 定义三个玩家、依次发出51张牌
- 给玩家的牌进行排序(拓展)
- 输出每个玩家的牌数据。
package com.itzw.test;
public class Card {
private String color;
private String size;
private int index;//牌的真正大小
public Card(){
}
public Card(String color, String size, int index) {
this.color = color;
this.size = size;
this.index = index;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
@Override
public String toString() {
return size + color;
}
}
10 Map集合体系
10.1 概述
Map集合概述和使用
- Map集合是-种双列集合,每个元素包含两个数据。
- Map集 合的每个元素的格式: key=value(键 值对元素)。
- Map集合也被称为“键值对集合”
Map集合整体格式:
- Collection集合的格式: [元素1 ,元素2,元素3..]
- Map集合的完整格式: {key1=value1 , key2=value2 , key3=value3 ... }
Map集合体系特点
- Map集合的特点都是由键决定的。
- Map集合的键是无序,不重复的, 无索引的,值不做要求(可以重复),
- Map集合后面重复的键对应的值会覆盖前面重复键的值。
- Map集 合的键值对都可以为null.
Map集合实现类特点
- HashMap:元素按照键是无序, 不重复,无索引,值不做要求。( 与Map体系一致)
- LinkedHashMap:元素按照键是有序, 不重复,无索引,值不做要求。
- TreeMap: 元素按照建是排序,不重复,无索引的,值不做要求。
//HashMap 无序不重复 无索引
Map<String,Integer> maps = new HashMap<>();
maps.put("蜜汁小汉堡",10);
maps.put("蜜汁小汉堡",100);//key的值不能相同
maps.put("手机",1);
maps.put("枸杞",1);//value的值可以相同
System.out.println(maps);
//LinkedHashMap 有序 不重复 无索引
Map<String,Integer> maps2 = new LinkedHashMap<>();
maps2.put("蜜汁小汉堡",10);
maps2.put("蜜汁小汉堡",100);//key的值不能相同
maps2.put("手机",1);
maps2.put("枸杞",1);//value的值可以相同
System.out.println(maps2);
10.2 Map集合常用API
Map集合
- Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。
Map API如下:
方法名称 说明
V put(K key,V value) 添加元素
V remove(0bject key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Objeqt key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty( ) 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
Map<String,Integer> maps = new HashMap<>();
maps.put("蜜汁小汉堡",10);
maps.put("矿泉水",100);//key的值不能相同
maps.put("手机",1);
maps.put("枸杞",99);//value的值可以相同
System.out.println(maps);
maps.remove("手机");
System.out.println(maps);
//maps.clear();
//System.out.println(maps);
System.out.println(maps.isEmpty());
System.out.println(maps.containsKey("矿泉水"));
System.out.println(maps.containsKey("手机"));
System.out.println(maps.containsValue(100));
System.out.println(maps.containsValue(12));
System.out.println(maps.size());
10.3 Map集合的遍历
10.3.1 方式一:键找值
Map集合的遍历方式:键找值
- 先获取Map集 合的全部键的Set集合。
- 遍历键的Set集合,然后通过键提取对应值。
键找值涉及到的API:
Set<K> keySet() 获取所有键的集合
V get(Object key) 根据键获取值
Map<String,Integer> maps = new HashMap<>();
maps.put("蜜汁小汉堡",10);
maps.put("矿泉水",100);//key的值不能相同
maps.put("手机",1);
maps.put("枸杞",99);//value的值可以相同
//先获取集合的所有键
Set<String> key = maps.keySet();
for (String s : key) {
//获取键对应的值
Integer value = maps.get(s);
System.out.println(s + "--->" + value);
}
10.3.1 方式二:键值对
Map集合的遍历方式二:键值对
- 先把Map集合转换成Set集合, Set集合中每个元素都是键值对实体类型了。
- 遍历Set集合,然后提取键以及提取值。
键值对涉及到的API:
方法名称 说明
Set <Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合
K getKey() A获得键
V getValue() 获取值
10.3.3 方式三:lambda
- 得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
Map结合Lambda遍历的API
default void for'Each( BiConsumer<? super K,? super V> action) 结合lambda遍历Map集合
流程
maps = {huawei=1000,手表=10,生活用品=10, iphoneX=100}
maps.forEach((k, v)-> {
System. out.println(k +----->"+v);
});
System.out.println("=======================");
maps.forEach(new BiConsumer<String, Integer>() {
@Override
public void accept(String s, Integer integer) {
System.out.println(s + "---->" + integer);
}
});
//化简
maps.forEach(( s, integer) -> System.out.println(s + "---->" + integer));
10.4 Map集合案例
统计投票人数
需求
某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D) ,每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。
分析:
- 将80个学生选择的数据拿到程序中去。
- 定义Map集合 用于存储最终统计的结果。
- 遍历80个学生选择的数据,看Map集合中是否存在,不存在存入“数据=1“,存在则其对应值+1
package com.itzw.d9_maptest;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class Test {
public static void main(String[] args) {
//1.把80个学生的选择拿出来
String[] select = {"A","B","C","D"};
Random r = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 80; i++) {
sb.append(select[r.nextInt(select.length)]);
}
System.out.println(sb);
//2.定义一个map集合,记录最终结果
Map<Character,Integer> infos = new HashMap<>();
//3.遍历80个选择
for (int i = 0; i < sb.length(); i++) {
//4.判断当前选择的景点
char c = sb.charAt(i);
//5.判断infos集合中是否存在这个景点,存在则值加一
if (infos.containsKey(c)){
infos.put(c,infos.get(c) + 1);
}else {
infos.put(c,1);
}
}
System.out.println(infos);
}
}
10.5 HashMap
HashMap的特点
- HashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引
- 没有额外需要学习的特有方法,直接使用Map里面的方法就可以了。
- HashMap跟HashSet底层原理是一 模一样的,都是哈希表结构,只是HashMap的每个元素包含两个值而已。
实际上: Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
HashMap的自定义类的去重,和Set基本上一样
package com.itzw.d8_map;
import java.util.*;
public class Test04 {
public static void main(String[] args) {
Map<Student,String> students = new HashMap<>();
students.put(new Student("张麻子",32),"鹅城");
students.put(new Student("张麻子",32),"鹅城");
students.put(new Student("马邦德",33),"鸡城");
System.out.println(students);
}
}
class Student{
private String name;
private int 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;
}
@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);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
10.6 LinkedHashMap
LinkedHashMap集合概述和特点
- 由键决定:有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序-致
- 原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
10.7 TreehMap
TreeMap集合概述和特点
- 由键决定特性:不重复、无索引、可排序
- 可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
- 注意: TreeMap集合是-定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
- TreeMap跟TreeSet- 样底层原理是一样的 。
TreeMap集合自定义排序规则有2种
- 类实现Comparable接口, 重写比较规则。
- 集合自定义Comparator比较器对象,重写比较规则。
这部分内容都和之前学的set集合差不多
11 集合嵌套
案例
需求
- 某个班级多名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D) ,每个学生
可以选择多个景点,请统计出最终哪个景点想去的人数最多。
分析
- 将80个学生选择的数据拿到程序中去,需要记住每个学生选择的情况。
- 定义Map集合用于存储最终统计的结果。
package com.itzw.map_imp;
import java.util.*;
public class Test {
public static void main(String[] args) {
//添加元素
Map<String, Set<String>> data = new HashMap<>();
Set<String> select = new HashSet<>();
Collections.addAll(select,"B","C");
data.put("雷军",select);
Set<String> select1 = new HashSet<>();
Collections.addAll(select1,"A","B","C","D");
data.put("宋大腿",select1);
Set<String> select2 = new HashSet<>();
Collections.addAll(select2,"C");
data.put("大嘴",select2);
System.out.println(data);
//统计选择各景点人数
Map<String,Integer> infos = new HashMap<>();
//将所有景点取出来
Collection<Set<String>> selects = data.values();
System.out.println(selects);
//计算每个景点选择次数
for (Set<String> sel : selects) {
for (String s : sel) {
if (infos.containsKey(s)){
infos.put(s,infos.get(s) + 1);
}else {
infos.put(s,1);
}
}
}
System.out.println(infos);
}
}
12 不可变集合
什么是不可变集合?
- 不可变集合,就是不可被修改的集合。
- 集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。
为什么要创建不可变集合?
- 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
- 或者当集合对象被不可信的库调用时,不可变形式是安全的。
如何创建不可变集合?
- 在List、 Set、 Map接口中,都存在of方法,可以创建一一个不可变的集合。
static <E> List<E> fE..lements) 创建一个具有指定元素的List集合对象
static <E> Set<E> f(..elements) 创建一个具有指定元素的Set集合对象
static <K,V> Map<K, V> of(E..elements) 创建一个具有指定元素的Map集合对象
演示:
Set<Double> set = Set.of(400.5,305.5,333.0);
System.out.println(set);
//set.add(6666.0);
//System.out.println(set);
List<String> list = List.of("张麻子","马邦德","县长夫人");
System.out.println(list);
//list.add("黄四郎");
//System.out.println(list);
//list.set(0,"黄四郎");
Map<String,Integer> map = Map.of("手表",1,"大米手机",1,"八宝粥",10);
System.out.println(map);