Java集合
集合
集合概述
- 集合和数组都是容器。
- 集合是Java中存储对象数据的一种容器。
- 集合的特点:
- 集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球。
- 集合非常适合做元素的增删操作。
- 注意:集合中只能存储引用类型数据,如果要存储基本类型数据可以选用包装类。
总结:
- 数组和集合的元素存储的个数问题。
- 数组定义后类型确定,长度固定
- 集合类型可以不固定,大小是可变的。
- 数组和集合存储元素的类型问题。
- 数组可以存储基本类型和引用类型的数据。
- 集合只能存储引用数据类型的数据。
- 数组和集合适合的场景
- 数组适合做数据个数和类型确定的场景。
- 集合适合做数据个数不确定,且要做增删元素的场景。
集合类体系结构
- Collection单列集合,每个元素(数据)只包含一个值。
- Map双列集合,每个元素包含两个值(键值对)。
Collection集合的体系
Collection集合的体系特点
Collection集合特点:
- List系列集合:添加的元素是有序、可重复、有索引。
- ArrayList、LinekdList:有序、可重复、有索引。
- Set系列集合:添加的元素是无序、不重复、无索引。
- HashSet:无序、不重复、无索引; LinkedHashSet:有序、不重复、无索引。
- TreeSet:按照大小默认升序排序、不重复、无索引。
//有序 可重复 有索引
Collection list=new ArrayList();
list.add("Java");
list.add("Java");
list.add("Collection");
list.add(18);
list.add(false);
System.out.println(list);//[Java, Java, Collection, 18, false]
//无序 不重复 无索引
Collection list1=new HashSet();
list1.add("Java");
list1.add("Java");
list1.add("Collection");
list1.add(18);
list1.add(false);
System.out.println(list1);//[Java, 18, false, Collection]
- 集合对于泛型的支持
- 集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型
Collection<String> list=new ArrayList<String>();
Collection<String> list=new ArrayList<>();//JDK 1.7开始后面的泛型类型申明可以省略不写
注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象。
- 如果集合中要存储基本类型的数据要使用包装类
//存储基本类型使用包装类
Collection<Integer> list=new ArrayList<>();
Collection<Double> list=new ArrayList<>();
Collection集合常用API
- Collection API如下:
//HashSet:添加的元素是无序,不重复,无索引。
Collection<String> list = new ArrayList<>();//替换所有变量名快捷键(先选中):Shift+F6
//1.添加元素,添加成功返回true。
list.add("Java");
list.add("HTML");
list.add("MySQL");
list.add("Java");
System.out.println(list);//[Java, HTML, MySQL, Java]
//2.清空集合的元素。
//list.clear();
//System.out.println(list);
//3.判断集合是否为空是空返回true,反之false。
System.out.println(list.isEmpty());
//4.获取集合的大小。
System.out.println(list.size());
//5.判断集合中是否包含某个元素。
System.out.println(list.contains("Java"));//true
System.out.println(list.contains("java"));//false
//6.删除某个元素:如果有多个重复元素默认删除前面的第一个!
System.out.println(list.remove("Java"));//true
System.out.println(list);//[HTML, MySQL, Java]
//7.把集合转换成数组 [HTML, MySQL, Java]
Object[] arrs=list.toArray();
System.out.println("数组:"+ Arrays.toString(arrs));
System.out.println("-------拓展----------");
Collection<String> list1 = new ArrayList<>();
list1.add("java1");
list1.add("java2");
Collection<String> list2 = new ArrayList<>();
list1.add("小明");
list1.add("小红");
//addAll把list2集合的元素全部倒入到list1中去。
list1.addAll(list2);
System.out.println(list1);//[java1, java2, 小明, 小红]
System.out.println(list2);//[]
Collection集合的遍历方式
-
方式一:迭代器
- 遍历就是一个一个的把容器中的元素访问一遍。
- 迭代器在Java中的代表是Iterator,迭代器是集合的专用遍历方式。
- Collection集合获取迭代器:
- Iterator中的常用方法:
总结:
- 迭代器的默认位置在哪里。
- lterator iterator():得到迭代器对象,默认指向当前集合的索引0
- 迭代器如果取元素越界会出现什么问题。
- 会出现NoSuchElementException异常。
Collection<String> list = new ArrayList<>();
list.add("赵敏");
list.add("苏苏");
list.add("素素");
list.add("道士");
System.out.println(list);
//1、得到当前集合的迭代器对象。
Iterator<String> it = list.iterator();
//System.out.println(it.next());
//System.out.println(it.next());
//System.out.println(it.next());
//System.out.println(it.next());
//2、定义while循环
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
-
方式二: foreach/增强for循环
- 增强for循环:既可以遍历集合也可以遍历数组。
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器,遍历集合相当于是迭代器的简化写法。
- 实现Iterable接口的类才可以使用迭代器和增强for,Collection接口已经实现了Iterable接口。
- 格式
for (元素数据类型 变量名 : 数组或者Collection集合){ //在此处使用变量即可,该变量就是元素 } System.out.println("-------foreach/增强for循环--------"); //方式二: foreach/增强for循环 Collection<String> lists = new ArrayList<>(); lists.add("赵敏"); lists.add("苏苏"); lists.add("素素"); lists.add("道士"); for (String ele:lists){ System.out.println(ele); } System.out.println("-------------------"); double[] scores={100,99.5,59.5}; for (double score : scores) { System.out.println(score); }
- 增强for修改变量:
double[] scores={100,99.5,59.5}; for (double score : scores) { System.out.println(score); //if (score==59.5){ //score=100.0;//修改无意义,不会影响数组的元素值。 //} } System.out.println(Arrays.toString(scores));//[100.0, 99.5, 59.5]
-
方式三:lambda表达式
- 得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
- Collection结合Lambda遍历的API
System.out.println("--------lambda表达式-----------");
Collection<String> list = new ArrayList<>();
list.add("赵敏");
list.add("苏苏");
list.add("素素");
list.add("道士");
//方式三:lambda表达式
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//简化
list.forEach((String s) ->System.out.println(s));
//方法引用:继续简化
list.forEach(System.out::println);
Collection集合存储自定义类型的对象
案例:
需求:
- 某影院系统需要在后台存储上述三部电影,然后依次展示出来。
分析:
- 定义一个电影类,定义一个集合存储电影对象。
- 创建3个电影对象,封装相关数据,把3个对象存入到集合中去。
- 遍历集合中的3个对象,输出相关信息。
//1、定义一个电影类
//2、定义一个集合对象存储3部电影对象
Collection<Movie> movies = new ArrayList<>();
movies.add(new Movie("《特战荣耀》",9.5,"杨洋、李一桐"));
movies.add(new Movie("《且试天下》",7.9,"杨洋、赵露思"));
movies.add(new Movie("《猎罪图鉴》",7.8,"檀健次、张柏嘉"));
//3、遍历集合容器中的每个电影对象
for (Movie movie : movies) {
System.out.println("片名:"+movie.getName());
System.out.println("得分:"+movie.getScore());
System.out.println("主演:"+movie.getActor());
System.out.println("--------------------");
}
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;
}
常见数据结构
数据结构概述:
- 数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的
- 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率
常见的数据结构:
- 栈
- 队列
- 数组
- 链表
- 二叉树
- 二叉查找树
- 平衡二叉树
- 红黑树
- ……
栈、队列
栈数据结构的执行特点:
- 后进先出,先进后出
- 数据进入栈模型的过程称为:压/进栈
- 数据离开栈模型的过程称为:弹/出栈
常见数据结构之队列:
- 先进先出,后进后出
- 数据从后端进入队列模型的过程称为:入队列
- 数据从前端离开队列模型的过程称为:出队列
数组
常见数据结构之数组:(数组是一种通过索引查询快,增删慢的模型)
- 查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的)
- 删除效率低:要将原始数据删除,同时后面每个数据前移。
- 添加效率极低:添加位置后的每个数据后移,再添加元素。
链表
链表的特点:
-
链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址。
-
链表查询慢。无论查询哪个数据都要从头开始找
-
链表增删相对快:
在数据AC之间添加一个数据B:
- 数据B对应的下一个数据地址指向数据C
- 数据A对应的下一个数据地址指向数据B
删除数据D之间的数据C:
- 数据B对应的下一个数据地址指向数据D
- 数据C删除
-
链表的种类:
- 单向链表
- 双向链表
二叉树、二叉查找树
二叉树的特点:
- 只能有一个根节点,每个节点最多支持2个直接子节点。
- 节点的度:节点拥有的子树的个数,二叉树的度不大于2叶子节点度为o的节点,也称之为终端结点。
- 高度:叶子结点的高度为1,叶子结点的父节点高度为2,以此类推,根节点的高度最高。
- 层:根节点在第一层,以此类推
- 兄弟节点:拥有共同父节点的节点互称为兄弟节点
二叉查找树又称二叉排序树或者二叉搜索树。
特点:(目的:提高检索数据的性能)
- 每一个节点上最多有两个子节点
- 左子树上所有节点的值都小于根节点的值
- 右子树上所有节点的值都大于根节点的值
二叉树查找树添节点:
规则:
- 小的存左边
- 大的存右边
- 一样的不存
平衡二叉树
- 平衡二叉树是在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能。
平衡二叉树的要求:
- 任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树
平衡二叉树在添加元素后可能导致不平衡
- 基本策略是进行左旋,或者右旋保证平衡。
平衡二叉树-旋转的四种情况:
- 左左:当根节点左子树的左子树有节点插入,导致二叉树不平衡
- 左右:当根节点左子树的右子树有节点插入,导致二叉树不平衡
- 右右:当根节点右子树的右子树有节点插入,导致二叉树不平衡
- 右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡
红黑树
- 红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。
- 1972年出现,当时被称之为平衡二叉B树。1978年被修改为如今的”红黑树”。
- 每一个节点可以是红或者黑;红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的。
红黑规则:
- 每一个节点或是红色的,或者是黑色的,根节点必须是黑色。
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,叶节点是黑色的。
- 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)。
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
添加节点:
- 添加的节点的颜色,可以是红色的,也可以是黑色的。默认用红色效率高。
红黑树小结:
- 红黑树不是高度平衡的,它的平衡是通过”红黑规则”进行实现的
规则如下:
- 每一个节点或是红色的,或者是黑色的,根节点必须是黑色
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的;
- 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
- 红黑树增删改查的性能都很好
总结:(各种数据结构的特点和作用是什么样的)
- 队列:先进先出,后进后出。
- 栈:后进先出,先进后出。
- 数组:内存连续区域,通过索引查询快,增删慢。
- 链表:元素是游离的,查询慢,首尾操作极快。
- 二叉树:永远只有一个根节点,每个结点不超过2个子节点的树。
- 查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差。
- 平衡查找二叉树:让树的高度差不大于1,增删改查都提高了。
- 红黑树(就是基于红黑规则实现了自平衡的排序二叉树)
List系列集合
List集合特点、特有API
List系列集合特点:
- ArrayList、LinekdList :有序,可重复,有索引。
- 有序∶存储和取出的元素顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
List集合特有方法:
- List集合因为支持索引,所以多了很多索引操作的独特api,其他Collection的功能List也都继承了。
//1.创建一个ArrayList集合对象:
//List:有序,可重复,有索引的。
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Java");
list.add("MySQL");
list.add("MySQL");
//2.在某个索引位置插入元素。
list.add(2,"HTML");
System.out.println(list);//[Java, Java, HTML, MySQL, MySQL]
//3.根据索引删除元素,返回被删除元素
list.remove(2);
System.out.println(list);//[Java, Java, MySQL, MySQL]
//4.根据索引获取元素: public E get(int index):返回集合中指定位置的元素。
System.out.println(list.get(2));//MySQL
//5.修改索引位置处的元素: public E set(int index,E element)
//返回修改前的数据
System.out.println(list.set(1, "HTML"));//Java
System.out.println(list);//[Java, HTML, MySQL, MySQL]
总结:
- List系列集合特点:
- ArrayList、LinekdList :有序,可重复,有索引。
- List的实现类的底层原理:
- ArrayList底层是基于数组实现的,根据查询元素快,增删相对慢。
- LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的。
List集合的遍历方式小结
List<String> lists = new ArrayList<>();
lists.add("Java1");
lists.add("Java2");
lists.add("Java3");
- for循环(因为List集合存在索引)
System.out.println("--------for循环----------");
for (int i = 0; i < lists.size(); i++) {
String ele = lists.get(i);
System.out.println(ele);
}
- 迭代器
System.out.println("---------迭代器-----------");
Iterator<String> it = lists.iterator();
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
- 增强for循环(foreach)
System.out.println("---增强for循环(foreach)----");
for (String ele : lists) {
System.out.println(ele);
}
- Lambda表达式
System.out.println("-------Lambda表达式--------");
lists.forEach(System.out::println);
ArrayList集合的底层原理
- ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
- 第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。
LinkedList集合的底层原理
LinkedList的特点:
- 底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API
LinkedList集合的特有功能:
//LinkedList可以完成队列结构,和栈结构(双链表)
//栈
LinkedList<String> stack = new LinkedList<>();
//压栈,入栈
//addFirst == push
stack.push("第一颗子弹");
stack.push("第二颗子弹");
stack.addFirst("第三颗子弹");
stack.addFirst("第四颗子弹");
//出栈,弹栈
//removeFirst == pop
System.out.println(stack.pop());//第四颗子弹
System.out.println(stack.removeFirst());//第三颗子弹
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());//1号
System.out.println(queue.removeFirst());//2号
System.out.println(queue);//[3号, 4号]
补充知识:集合的并发修改异常问题
-
问题:
- 当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题。
-
哪些遍历存在问题?
- 迭代器遍历集合且直接用集合删除元素的时候可能出现。
- 增强for循环遍历集合且直接用集合删除元素的时候可能出现。
-
哪种遍历且删除元素不出问题
- 迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。
- 使用for循环遍历并删除元素不会存在这个问题。
//1、准备数据
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Java");
list.add("HTML");
list.add("HTML");
list.add("MySQL");
System.out.println(list);//[Java, Java, HTML, HTML, MySQL]
//需求:删除全部的Java信息。
//a、迭代器遍历删除
System.out.println("-------迭代器遍历删除--------");
Iterator<String> it = list.iterator();
while (it.hasNext()){
String ele = it.next();
if ("Java".equals(ele)){
//list.remove("Java");//报错
//使用迭代器删除当期位置的元素,保证不后移,能够成功遍历到全部元素!
it.remove();//删除当前所在元素,并且不会后移
}
}
//b、foreach遍历删除(会出现bug)
System.out.println("-------foreach遍历删除--------");
//for (String ele : list) {
//if ("Java".equals(ele)){
//list.remove("Java");
//}
//}
//c、lambda表达式(会出现bug)
System.out.println("-------lambda表达式--------");
//list.forEach(s ->{
//if ("Java".equals(s)){
//list.remove("Java");
//}
//});
//d、for循环(不会出现异常错误,但是数据删除出现了问题:会漏掉元素)
System.out.println("-------for循环--------");
//for (int i = 0; i < list.size(); i++) {
//String ele = list.get(i);
//if ("Java".equals(ele)){
//list.remove("Java");
//}
//}
//System.out.println(list);
//for循环 解决方案1
System.out.println("-------for循环 解决方案1--------");
for (int i = list.size()-1; i >= 0; i--) {
String ele = list.get(i);
if ("Java".equals(ele)){
list.remove("Java");
}
}
System.out.println(list);
//for循环 解决方案2
System.out.println("-------for循环 解决方案2--------");
for (int i = 0; i < list.size(); i++) {
String ele = list.get(i);
if ("Java".equals(ele)){
list.remove("Java");
i--;
}
}
System.out.println(list);
补充知识:泛型深入
- 泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
- 泛型的格式:<数据类型>;注意:泛型只能支持引用数据类型。
- 集合体系的全部接口和实现类都是支持泛型的使用的。
- 泛型可以在很多地方进行定义:
- 类后面———> 泛型类
- 方法申明上———> 泛型方法
- 接口后面———> 泛型接口
自定义泛型类
- 定义类时同时定义了泛型的类就是泛型类。
- 泛型类的格式:修饰符 class 类名<泛型变量>{}
public class MyArrayList<T>{}
- 此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等。
- 作用:编译阶段可以指定数据类型,类似于集合的作用。
自定义泛型方法
- 定义方法时同时定义了泛型的方法就是泛型方法。
- 泛型方法的格式:修饰符<泛型变量> 方法返回值 方法名称(形参列表){}
public<T> void show(T t){}
- 作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
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);
}
}
自定义泛型接口
- 使用了泛型定义的接口就是泛型接口。
- 泛型接口的格式:修饰符 interface 接口名称<泛型变量>{}
public interface Data<E>{}
- 作用:泛型接口可以让实现类选择当前功能需要操作的数据类型
public interface Data<E> {
void add(E e);
void delete(int id);
void update(E e);
E queryById(int id);
}
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;}
}
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;}
}
泛型通配符、上下限
通配符:?
- ? 可以在“使用泛型”的时候代表一切类型。
- E T K V 是在定义泛型的时候使用的。
泛型的上下限:
- ? extends Car: ?必须是Car或者其子类 泛型上限
- ? super car : ?必须是Car或者其父类 泛型下限
public class GenericityDmeo {
public static void main(String[] args) {
ArrayList<BMW> bmws = new ArrayList<>();
bmws.add(new BMW());
bmws.add(new BMW());
bmws.add(new BMW());
go(bmws);
ArrayList<BENZ> benzs = new ArrayList<>();
benzs.add(new BENZ());
benzs.add(new BENZ());
benzs.add(new BENZ());
go(benzs);
}
/*
所有车比赛
*/
public static void go(ArrayList<? extends Car> cars){
}
}
//宝马汽车类
class BENZ extends Car{
}
//奔驰汽车类
class BMW extends Car{
}
//父类
class Car{}
set系列集合
Set系列集系概述
set系列集合特点:
- 无序:存取顺序不一致
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
Set集合实现类特点:
- HashSet:无序、不重复、无索引。
- LinkedHashSet:有序、不重复、无索引。
- TreeSet:排序、不重复、无索引。
Set集合的功能上基本上与Collection的API一致
//看看Set系列集合的特点: HashSet LinkedHashSet TreeSet
Set<String> set = new HashSet<>();//无序不重复,无索引
//Set<String> set = new LinkedHashSet<>();//有序 不重复 无索引
set.add("MySQL");
set.add("MySQL");
set.add("Java");
set.add("Java");
set.add("HTML");
set.add("HTML");
System.out.println(set);//[Java, MySQL, HTML]
Hashset元素无序的底层原理:哈希表
HashSet底层原理:
- HashSet集合底层采取哈希表存储的数据。
- 哈希表是一种对于增删改查数据性能都较好的结构。
哈希值:
- 是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
object类的API:
- public int hashCode():返回对象的哈希值
对象的哈希值特点:
- 同一个对象多次调用hashcode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。
HashSet1.7版本原理解析:数组+链表+(结合哈希算法):
- 创建一个默认长度16的数组,数组名table
- 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
- 判断当前位置是否为null,如果是null直接存入
- 如果位置不为null,表示有元素,则调用equals方法比较
- 如果一样,则不存,如果不一样,则存入数组
- JDK7新元素占老元素位置,指向老元素
- JDK8中新元素挂在老元素下面
- 结论:哈希表是一种对于增删改查数据性能都较好的结构
JDK1.8版本开始HashSet原理解析:
- 底层结构:哈希表(数组、链表、红黑树的结合体)
- 当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。
Hashset元素去重复的底层原理
案例
需求:
- 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是同一个对象
分析:
- 定义学生类,创建HashSet集合对象,创建学生对象
- 把学生添加到集合
- 在学生类中重写两个方法,hashCode()和equals(),自动生成即可
- 遍历集合(增强for)
//Set集合去重复原因:先判断哈希值再判断equals
Set<Student> set = new HashSet<>();
Student s1 = new Student("小明",20,'男');
Student s2 = new Student("小明",20,'男');
Student s3 = new Student("小红",20,'女');
set.add(s1);
set.add(s2);
set.add(s3);
System.out.println(set);
//[Student{name='小红', age=20, sex=女}, Student{name='小明', age=20, sex=男}]
public class Student {
private String name;
private int age;
private char sex;
public Student() {}
public Student(String name, int age, char sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
/**
*只要2个对象内容一样,结果一定是true
* @param o
* @return
*/
@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 && sex == student.sex && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, sex);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
实现类:LinkedHashSet
- 有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理∶底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
//看看Set系列集合的特点: HashSet LinkedHashSet TreeSet
Set<String> set = new LinkedHashSet<>();//有序 不重复 无索引
set.add("MySQL");
set.add("MySQL");
set.add("Java");
set.add("Java");
set.add("HTML");
set.add("HTML");
System.out.println(set);//[MySQL, Java, HTML]
实现类: TreeSet
- 不重复、无索引、可排序
- 可排序:按照元素的大小默认升序(有小到大)排序。
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
- 注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序
TreeSet集合默认的规则:
- 对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
- 对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型如Student对象,TreeSet无法直接排序。
- 结论:想要使用TreeSet存储自定义类型,需要制定排序规则
自定义排序规则:
- TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则
方式一:
- 让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
public class Apple implements Comparable<Apple>{
private String name;
private String color;
private double price;
private int weigh;
/**
* 方式一:类自定义比较规则
* o1.compareTo(o2)
* @param o
* @return
*/
@Override
public int compareTo(Apple o) {
//按照重量进行比较的
return this.weigh-o.weigh;
}
}
方式二:
- TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。
//方式二:集合自带比较器对象进行规则定制
Set<Apple> apples = new TreeSet<>(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeigh()-o2.getWeigh();//升序
//注意:浮点型建议直接使用Double.compare进行比较
return Double.compare(o1.getPrice(),o2.getPrice());//升序
}
});
//简化
Set<Apple> apples = new TreeSet<>((o1, o2) ->Double.compare(o1.getPrice(), o2.getPrice()));//升序
collection体系的特点、使用场景总结
总结:
- 如果希望元素可以重复,又有索引,索引查询要快?
- 用ArrayList集合,基于数组的。(用的最多)
- 如果希望元素可以重复,又有索引,增删首尾操作快?
- 用LinkedList集合,基于链表的。
- 如果希望增删改查都快,但是元素不重复、无序、无索引。
- 用HashSet集合,基于哈希表的。
- 如果希望增删改查都快,但是元素不重复、有序、无索引。
- 用LinkedHashSet集合,基于哈希表和双链表。
- 如果要对对象进行排序。
- 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
补充知识:可变参数
假如需要定义一个方法求和,该方法可以灵活的完成如下需求:
- 计算1个数据的和。
- 计算2个数据的和。
- 计算3个数据的和。
- 计算n个数据的和,甚至可以支持不接收参数进行调用。
可变参数:
- 可变参数用在形参中可以接收多个数据。
- 可变参数的格式:数据类型…参数名称
可变参数的作用:
- 传输参数非常灵活,方便。可以不传输参数,可以传输1个或者多个,也可以传输一个数组
- 可变参数在方法内部本质上就是一个数组。
public static void main(String[] args) {
sum();//1、不传参数
sum(10);//2、可以传输一个参数
sum(10,20,30);//3、可以传输多个参数
sum(new int[]{10,20,30,40});//4、可以传输一个数组
}
public static void sum(int... nums){
//注意:可变参数在方法内部其实就是一个数组。nums
System.out.println("元素个数:"+nums.length);
System.out.println("元素内容:"+ Arrays.toString(nums));
}
可变参数的注意事项:
- 1.一个形参列表中可变参数只能有一个
- 2.可变参数必须放在形参列表的最后面
public static void main(String[] args) {
sum(10);//1、可以传输一个参数
sum(10,20,30);//2、可以传输多个参数
}
/*
注意:一个形参列表中只能有一个可变参数,可变参数必须放在形参列表的最后面
*/
public static void sum(int age,int... nums){
//注意:可变参数在方法内部其实就是一个数组。nums
System.out.println("元素个数:"+nums.length);
System.out.println("元素内容:"+ Arrays.toString(nums));
}
补充知识:集合工具类Collections
- java.utils.Collections:是集合工具类
- 作用:Collections并不属于集合,是用来操作集合的工具类。
Collections常用的API:
List<String> names = new ArrayList<>();
//names.add("小明");
//names.add("小红");
//names.add("小黑");
//1、给集合对象批量添加元素!
Collections.addAll(names,"小明","小红","小黑");
System.out.println(names);//[小明, 小红, 小黑]
//2、public static void shuffle(List<?> list):打乱集合顺序
Collections.shuffle(names);
System.out.println(names);//[小红, 小黑, 小明]
//3、public static <T> void sort(List<T> list):将集合中元素按照默认规则排序
List<Integer> list = new ArrayList<>();
Collections.addAll(list,23,24,12,8);
Collections.sort(list);
System.out.println(list);//[8, 12, 23, 24]
Collections排序相关API:
排序方式1:
注意:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规则Comparable接口。
public class Apple implements Comparable<Apple>{
private String name;
private String color;
private double price;
private int weigh;
/**
* 方式一:类自定义比较规则
* o1.compareTo(o2)
* @param o
* @return
*/
@Override
public int compareTo(Apple o) {
//按照重量进行比较的
return this.weigh-o.weigh;//List集存储相同大小的元素会保留!
}
}
List<Apple> apples = new ArrayList<>();//可以重复
apples.add(new Apple("红富士","红色",9.9,500));
apples.add(new Apple("青苹果","绿色",19.9,300));
apples.add(new Apple("绿苹果","青色",29.9,400));
apples.add(new Apple("黄苹果","黄色",9.8,500));
Collections.sort(apples);
System.out.println(apples);
排序方式2:
List<Apple> apples = new ArrayList<>();//可以重复
apples.add(new Apple("红富士","红色",9.9,500));
apples.add(new Apple("青苹果","绿色",19.9,300));
apples.add(new Apple("绿苹果","青色",29.9,400));
apples.add(new Apple("黄苹果","黄色",9.8,500));
//方式二
Collections.sort(apples, new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return Double.compare(o1.getPrice(), o2.getPrice());
}
});
//简化
Collections.sort(apples,( o1, o2) ->Double.compare(o1.getPrice(), o2.getPrice()));
System.out.println(apples);
Collection体系的综合案例
斗地主游戏
需求:
- 在启动游戏房间的时候,应该提前准备好54张牌,完成洗牌、发牌、牌排序、逻辑。
分析:
- 当系统启动的同时需要准备好数据的时候,就可以用静态代码块了。
- 洗牌就是打乱牌的顺序。
- 定义三个玩家、依次发出51张牌
- 给玩家的牌进行排序(拓展)
public class Card {
private String size;
private String color;
private int index;
public Card() {
}
public Card(String size, String color,int index) {
this.size = size;
this.color = color;
this.index = index;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
@Override
public String toString() {
return size+color;
}
}
/**
*
* 功能:
* 1.做牌
* 2.洗牌
* 3.定义3个玩家
* 4.发牌
* 5.排序(拓展)
* 6.看牌
*/
public class GameDemo {
/**
* 1、定义一个静态的集合存储54张牌对象
*/
public static List<Card> allCards=new ArrayList<>();
/**
* 2、做牌:定义静态代码块初始化牌数据
*/
static {
//3、定义点数:个数确定,类型确定,使用数组
String[] sizes={"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
//4、定义花色:个数确定,类型硫定,使用数组
String[] colors={"♠","♥","♣","♦"};
//排序索引
int index=0;
//判断索引
boolean isIndex=false;
//5、组合点数和花色
for (String color : colors) {
for (String size : sizes) {
//判断排序索引
if (index==13){
isIndex=true;
}
if (!isIndex){
index++;
}else {
isIndex=false;
index=0;
index++;
}
//6、封装成一个牌对象。
Card c = new Card(color, size,index);
//7、存入到集合容器中去
allCards.add(c);
}
}
//8、大小王存入到集合对象中去 "小?" , "大?"
Card c1 = new Card("", "小?",++index);
Card c2 = new Card("", "大?",++index);
Collections.addAll(allCards,c1,c2);
System.out.println("新牌:"+allCards);
}
public static void main(String[] args) {
//9、洗牌
Collections.shuffle(allCards);
System.out.println("洗牌后:"+allCards);
//10、发牌(定义三个玩家,每个玩家的牌也是一个集合容器)
List<Card> xiaohong = new ArrayList<>();
List<Card> xiaoming = new ArrayList<>();
List<Card> xiaohei = new ArrayList<>();
//11、开始发牌(从牌集合中发出51张牌给三个玩家,剩余3张作为底牌)
for (int i = 0; i < allCards.size()-3; i++) {
//先拿到当前牌对象
Card c = allCards.get(i);
if (i % 3==0){
//小红拿牌
xiaohong.add(c);
}else if (i % 3==1){
//小明拿牌
xiaoming.add(c);
}else if (i % 3==2){
//小黑拿牌
xiaohei.add(c);
}
}
//12、拿到最后三张底牌(把最后三张牌截取成一个子集合)
List<Card> lastThreeCards=allCards.subList(allCards.size()-3,allCards.size());
//13、给玩家的牌排序(从小到大)
sortMethod(xiaohong);
sortMethod(xiaoming);
sortMethod(xiaohei);
//14、输出玩家的牌:
System.out.println("小红:"+xiaohong);
System.out.println("小明:"+xiaoming);
System.out.println("小黑:"+xiaohei);
System.out.println("底牌:"+lastThreeCards);
}
/*
给玩家的牌排序 从小到大
*/
private static void sortMethod(List<Card> sortCard) {
Collections.sort(sortCard, new Comparator<Card>() {
@Override
public int compare(Card o1, Card o2) {
return o1.getIndex()- o2.getIndex();
}
});
}
}
Map集合体系
- Map集合是一种双列集合,每个元素包含两个数据。
- Map集合的每个元素的格式: key=value(键值对元素)。
- Map集合也被称为“键值对集合”。
Map集合体系特点
说明:
- 使用最多的Map集合是HashMap。
- 重点掌握HashMap , LinkedHashMap , TreeMap。其他的后续理解。
Map集合体系特点:
- Map集合的特点都是由键决定的。
- Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)
- Map集合后面重复的键对应的值会覆盖前面重复键的值。
- Map集合的键值对都可以为null。
Map集合实现类特点:
- HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
- LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
- TreeMap:元素按照键是排序,不重复,无索引的,值不做要求。
//1、创建一个Map集合对象
Map<String,Integer> maps=new HashMap<>();
maps.put("电脑",1);
maps.put("Java",1);
maps.put("桔子",5);
maps.put("Java",100);//覆盖前面的数据
maps.put(null,null);
System.out.println(maps);//{桔子=5, null=null, Java=100, 电脑=1}
Map集合常用API
Map集合
- Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。
Map API如下:
//1、创建一个Map集合对象
Map<String,Integer> maps=new HashMap<>();
maps.put("iphoneX",10);
maps.put("娃娃",20);
maps.put("iphoneX",100);//Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
maps.put("huawei",100);
maps.put("生活用品",10);
maps.put("手表",10);
//{huawei=100, 手表=10, 生活用品=10, iphoneX=100, 娃娃=20}
System.out.println(maps);
//2.清空集合
//maps.clear();
//System.out.println(maps);//{}
//3.判断集合是否为空,为空返回true ,反之!
System.out.println(maps.isEmpty());
//4.根据健获取对应值: public V get(Object key)
Integer key = maps.get("huawei");
System.out.println(key);//100
System.out.println(maps.get("生活用品"));//10
System.out.println(maps.get("生活用品2"));//null
//5.根据键删除整个元素。〔刷除键会返回键的值)
System.out.println(maps.remove("iphoneX"));//100
System.out.println(maps);//{huawei=100, 手表=10, 生活用品=10, 娃娃=20}
//6.判断是否包含某个键,包含返回true ,反之
System.out.println(maps.containsKey("娃娃"));//true
System.out.println(maps.containsKey("娃娃2"));//false
//7.判断是否包含某个值。
System.out.println(maps.containsValue(100));//true
System.out.println(maps.containsValue(22));//false
//{huawei=100, 手表=10, 生活用品=10, 娃娃=20}
//8.获取全部键的集合: public Set<K> keySet()
Set<String> keys = maps.keySet();
System.out.println(keys);//[huawei, 手表, 生活用品, 娃娃]
System.out.println("-------------------------------");
//9.获取全部值的集合: Collection<V> values();
Collection<Integer> values=maps.values();
System.out.println(values);//[100, 10, 10, 20]
//10.集合的大小
System.out.println(maps.size());//4
//11.合并其他Map集合。(拓展)
Map<String, Integer> map1 = new HashMap<>();
map1.put("Java1",1);
map1.put("Java2",100);
Map<String, Integer> map2 = new HashMap<>();
map2.put("Java2",1);
map2.put("Java3",100);
map1.putAll(map2);//把集合map2的元素拷贝一份到map1中去
System.out.println(map1);//{Java2=1, Java3=100, Java1=1}
System.out.println(map2);//{Java2=1, Java3=100}
Map集合的遍历方式一:键找值
- 先获取Map集合的全部键的Set集合。
- 遍历键的Set集合,然后通过键提取对应值。
键找值涉及到的API:
Map<String,Integer> maps=new HashMap<>();
//1.添加元素:无序,不重复,无索引。
maps.put("娃娃",20);
maps.put("iphoneX",10);
maps.put("huawei",100);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
//{huawei=100, 手表=10, 生活用品=10, iphoneX=10, 娃娃=20}
//1、键找值:第一步:先拿到集合的全部键。
Set<String> keys = maps.keySet();
//{huawei=100, 手表=10, 生活用品=10, iphoneX=10, 娃娃=20}
//2、第二步:遍历每个键,根据键提取值
for (String key : keys) {
int value=maps.get(key);
System.out.println(key+"==>"+value);
//huawei==>100
//手表==>10
//生活用品==>10
//iphoneX==>10
//娃娃==>20
}
Map集合的遍历方式二:键值对
- 先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。
- 遍历Set集合,然后提取键以及提取值。
- 快捷键:maps.entrySet() + Ctrl + Alt + V
键值对涉及到的API:
Map<String,Integer> maps=new HashMap<>();
//1.添加元素:无序,不重复,无索引。
maps.put("娃娃",20);
maps.put("iphoneX",10);
maps.put("huawei",100);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
//{huawei=100, 手表=10, 生活用品=10, iphoneX=10, 娃娃=20}
//1、把Map集合转换成Set集合
Set<Map.Entry<String, Integer>> entries = maps.entrySet();
//2、开始遍历
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
int value = entry.getValue();
System.out.println(key+"==>"+value);
//huawei==>100
//手表==>10
//生活用品==>10
//iphoneX==>10
//娃娃==>20
}
Map集合的遍历方式三: lambda表达式
- 得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
Map结合Lambda遍历的API:
Map<String,Integer> maps=new HashMap<>();
//1.添加元素:无序,不重复,无索引。
maps.put("娃娃",20);
maps.put("iphoneX",10);
maps.put("huawei",100);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
//{huawei=100, 手表=10, 生活用品=10, iphoneX=10, 娃娃=20}
maps.forEach(new BiConsumer<String, Integer>() {
@Override
public void accept(String key, Integer value) {
System.out.println(key+"==>"+value);
//huawei==>100
//手表==>10
//生活用品==>10
//iphoneX==>10
//娃娃==>20
}
});
//简化1
maps.forEach((key,value) ->{
System.out.println(key+"==>"+value);
});
//简化2
maps.forEach((key,value) ->System.out.println(key+"==>"+value));
Map集合案例
Map集合案例—-统计投票人数
需求:
- 某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。
分析:
- 将80个学生选择的数据拿到程序中去。
- 定义Map集合用于存储最终统计的结果。
- 遍历80个学生选择的数据,看Map集合中是否存在,不存在存入“数据=1“,存在则其对应值+1
//1、把80个学生选择的数据拿进来。
String[] selects={"A","B","C","D"};
StringBuilder sb = new StringBuilder();
Random r = new Random();
for (int i = 0; i < 80; i++) {
sb.append(selects[r.nextInt(selects.length)]);
}
System.out.println(sb);
//2、定义一个Map集合记录最终统计的结果: A=30 B=20 C=20 D=10 键是景点 值是选择的数量
Map<Character, Integer> infos = new HashMap<>();
//3、遍历80个学生选择的数据
for (int i = 0; i < sb.length(); i++) {
//4、提取当前选择景点字符
char ch= sb.charAt(i);
//5、判断Map集合中是否存在这个键
if (infos.containsKey(ch)){
//让其值 +1
infos.put(ch,infos.get(ch)+1);
}else {
//说明此景点是第一次被选
infos.put(ch,1);
}
}
System.out.println(infos);
Map集合的实现类HashMap
- 由键决定:无序、不重复、无索引。HashMap底层是哈希表结构的。
- 依赖hashcode方法和equals方法保证键的唯一。
- 如果键要存储的是自定义对象,需要重写hashCode和equals方法。
- 基于哈希表。增删改查的性能都较好。
Map集合的实现类LinkedHashMap
- 由键决定:有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
Map集合的实现类TreeMap
- 由键决定特性:不重复、无索引、可排序
- 可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
- 注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
- TreeMap跟TreeSet一样底层原理是一样的。
TreeMap集合自定义排序规则有2种:(与 实现类: TreeSet 一样)
- 类实现Comparable接口,重写比较规则。
- 集合自定义Comparator比较器对象,重写比较规则。
补充知识:集合的嵌套
Map集合案例—-统计投票人数
需求:
- 某个班级多名学生,现在需要组成秋游活动,班长提供了四个景点依次是((A、B、C、D),每个学生可以选择多个景点,请统计出最终哪个景点想去的人数最多。
分析:
- 将80个学生选择的数据拿到程序中去,需要记住每个学生选择的情况。
- 定义Map集合用于存储最终统计的结果。
//1、要求程序记录每个学生选择的情况。
// 使用一个Map集合存储。
Map<String, List<String>> data = new HashMap<>();
//2、把学生选择的数据存入进去。
List<String> selects=new ArrayList<>();
Collections.addAll(selects,"A","B");
data.put("小明",selects);
List<String> selects1=new ArrayList<>();
Collections.addAll(selects1,"B","C","D");
data.put("小红",selects1);
List<String> selects2=new ArrayList<>();
Collections.addAll(selects2,"A","B","C","D");
data.put("小黑",selects2);
System.out.println(data);
//3、统计每个景点选择的人数。
Map<String, Integer> infos = new HashMap<>();
//4、提取所有人选择的景点的信息。
Collection<List<String>> values = data.values();
for (List<String> value : values) {
for (String s : value) {
//有没有包含这个景点
if (infos.containsKey(s)){
infos.put(s,infos.get(s)+1);
}else {
infos.put(s,1);
}
}
}
System.out.println(infos);
创建不可变集合
- JDK9特性
- 不可变集合,就是不可被修改的集合。
- 集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。
- 在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合。
//1、不可变的List集合
List<Double> lists=List.of(569.5,700.5,650.5);
System.out.println(lists);
//2、不可变的Set集合,不能重复
Set<String> names= Set.of("张三","李四","王五");
System.out.println(names);
//3、不可变的Map集合
Map<String,Integer> maps= Map.of("Java",1,"MySQL",2,"HTML",5);
System.out.println(maps);
Stream流
- 在Java8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念。
- 目的:用于简化集合和数组操作的API。
Stream流的三类方法:
- 获取Stream流
- 创建一条流水线,并把数据放到流水线上准备进行操作
- 中间方法
- 流水线上的操作。一次操作完毕之后,还可以继续进行其他操作
- 终结方法
- 一个Stream流只能有一个终结方法,是流水线上的最后一个操作
需求:按照下面的要求完成集合的创建和遍历
- 创建一个集合,存储多个字符串元素
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张三丰");
list.add("张三");
- 把集合中所有以”张”开头的元素存储到一个新的集合
- 把”张”开头的集合中的长度为3的元素存储到一个新的集合
- 遍历上一步得到的集合中的元素输出。
List<String> names = new ArrayList<>();
Collections.addAll(names,"张无忌","周芷若","赵敏","张三丰","张三");
System.out.println(names);//[张无忌, 周芷若, 赵敏, 张三丰, 张三]
//1、从集合中找出姓张的放到新集合
List<String> zhanglist = new ArrayList<>();
for (String name : names) {
if (name.startsWith("张")){
zhanglist.add(name);
}
}
System.out.println(zhanglist);//[张无忌, 张三丰, 张三]
//2、找名称长度是3的姓名
List<String> zhangThreeList = new ArrayList<>();
for (String name : zhanglist) {
if (name.length()==3){
zhangThreeList.add(name);
}
}
System.out.println(zhangThreeList);//[张无忌, 张三丰]
//3、使用Stream实现
names.stream().filter(s -> s.startsWith("张")).filter(s -> s.length()==3).forEach(s -> System.out.println(s));
//张无忌
//张三丰
Stream流的获取
集合获取Stream流的方式:
- 可以使用Collection接口中的默认方法stream()生成流
数组获取Stream流的方式:
/**-------------Collection集合获取流-------------------*/
Collection<String> list=new ArrayList<>();
Stream<String> s = list.stream();
/**-------------------Map集合获取流--------------------*/
Map<String, Integer> maps = new HashMap<>();
//键流
Stream<String> keyStream = maps.keySet().stream();
//值流
Stream<Integer> valuesStream = maps.values().stream();
//键值对流(拿整体)
Stream<Map.Entry<String, Integer>> keyAndValuesStream = maps.entrySet().stream();
/**---------------------数组获取流---------------------*/
//方法一
String[] names={"赵敏","周芷若","张无忌","灭绝"};
Stream<String> nameStream = Arrays.stream(names);
//方法二
Stream<String> nameStream2 = Stream.of(names);
Stream流的常用API(中间操作方法)
- Stream流的常用API(中间操作方法)
- 注意:
- 中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
- 在Stream流中无法直接修改集合、数组中的数据。
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张三");
list.add("张三丰");
list.add("张三丰");
/**
* forEach : 逐一处理(遍历)
* filter : 过滤元素
* count : 统计个数
* limit : 取前几个元素
* skip : 跳过前几个
* map : 加工方法
* concat : 合并流。
*/
list.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
//count : 统计个数
long count = list.stream().filter(s -> s.length() == 3).count();
System.out.println(count);//4
//limit : 取前几个元素
list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(s -> System.out.println(s));
//简化:条件s与(s)一样时使用
list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(System.out::println);
//skip : 跳过前几个
list.stream().filter(s -> s.startsWith("张")).skip(2).forEach(s -> System.out.println(s));
//map : 加工方法 :第一个参数原材料->第二个参数是加工后的结果。
//给集合元素的前面都加上一个: 无敌的:
list.stream().map(s -> "无敌的: "+s).forEach(s -> System.out.println(s));
//需求:把所有的名称都加工成一个学生对象。
list.stream().map(s -> new Student(s)).forEach(s -> System.out.println(s));
//简化 构造器引用 方法引用
list.stream().map(Student::new).forEach(System.out::println);
//concat : 合并流
System.out.println("----------------------");
Stream<String> s1 = list.stream().filter(s -> s.startsWith("张"));
Stream<String> s2 = Stream.of("Java1","Java2");
Stream<String> s3 = Stream.concat(s1,s2);
//distinct : 去重复
s3.distinct().forEach(s -> System.out.println(s));
Stream流的常见终结操作方法
注意:
- 终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream了
Stream流的综合应用
案例:
需求:
- 某个公司的开发部门,分为开发一部和二部,现在需要进行年中数据结算。
分析:
- 员工信息至少包含了(名称、性别、工资、奖金、处罚记录)
- 开发一部有4个员工、开发二部有5名员工
- 分别筛选出2个部门的最高工资的员工信息,封装成优秀员工对象Topperformer
- 分别统计出2个部门的平均月收入,要求去掉最高和最低工资。
- 统计2个开发部门整体的平均工资,去掉最低和最高工资的平均值。
public class Employee {
private String name;
private char sex;
private double salary;//薪水
private double bonus;//奖金
private String punish;//处罚信息
public Employee() {
}
public Employee(String name, char sex, double salary, double bonus, String punish) {
this.name = name;
this.sex = sex;
this.salary = salary;
this.bonus = bonus;
this.punish = punish;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public String getPunish() {
return punish;
}
public void setPunish(String punish) {
this.punish = punish;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sex=" + sex +
", salary=" + salary +
", bonus=" + bonus +
", punish='" + punish + '\'' +
'}';
}
}
public class Topperformr {
private String name;
private double money;//月薪
public Topperformr() {
}
public Topperformr(String name, double money) {
this.name = name;
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Topperformr{" +
"name='" + name + '\'' +
", money=" + money +
'}';
}
}
public static double allMoney;//1个部门去掉最高工资,最低工资的总和
public static double allMoney2;//2个部门去掉最高工资,最低工资的总和
public static void main(String[] args) {
List<Employee> one = new ArrayList<>();
one.add(new Employee("猪八戒",'男',30000,25000,null));
one.add(new Employee("孙悟空",'男',25000,1000,"顶撞上司"));
one.add(new Employee("沙僧",'男',20000,20000,null));
one.add(new Employee("小白龙",'男',20000,25000,null));
List<Employee> two = new ArrayList<>();
two.add(new Employee("武松",'男',15000,9000,null));
two.add(new Employee("李逵",'男',20000,10000,null));
two.add(new Employee("西门庆",'男',50000,10000,"被打"));
two.add(new Employee("潘金莲",'女',3500,1000,"被打"));
two.add(new Employee("武大郎",'男',20000,0,"下毒"));
//1、开发一部的最高工资的员工
// 指定大小规则了
Topperformr t = one.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus())).map(e -> new Topperformr(e.getName(), e.getSalary() + e.getBonus())).get();
System.out.println(t);//Topperformr{name='猪八戒', money=55000.0}
//开发二部的最高工资的员工
Topperformr t2 = two.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus())).map(e -> new Topperformr(e.getName(), e.getSalary() + e.getBonus())).get();
System.out.println(t2);//Topperformr{name='西门庆', money=60000.0}
//2、统计平均工资,去掉最高工资和最低工资
//一部
one.stream().sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus())).skip(1).limit(one.size()-2).forEach(e->{
//求出总和:剩余员工的工资总和
allMoney+=e.getSalary()+e.getBonus();
});
System.out.println("开发一部的平均工资为:"+allMoney/(one.size()-2));//开发一部的平均工资为:42500.0
//二部
two.stream().sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus())).skip(1).limit(two.size()-2).forEach(e->{
//求出总和:剩余员工的工资总和
allMoney+=e.getSalary()+e.getBonus();
});
System.out.println("开发二部的平均工资为:"+allMoney/(one.size()-2));//开发二部的平均工资为:79500.0
//3、合并2个集合流,再统计
Stream<Employee> s1 = one.stream();
Stream<Employee> s2 = two.stream();
Stream<Employee> s3 = Stream.concat(s1,s2);
s3.sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus())).skip(1).limit(one.size()+ two.size()-2).forEach(e->{
//求出总和:剩余员工的工资总和
allMoney2+=e.getSalary()+e.getBonus();
});
//BigDecimal
BigDecimal a=BigDecimal.valueOf(allMoney2);
BigDecimal b=BigDecimal.valueOf(one.size()+two.size()-2);
System.out.println("开发部的平均工资为:"+a.divide(b,2, RoundingMode.HALF_UP));//保留两位,四舍五入
//开发部的平均工资为:34285.71
}
收集Stream流
- 收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去。
- Stream流:方便操作集合/数组的手段。
- 集合/数组:才是开发中的目的。
Stream流的收集方法:
Collectors工具类提供了具体的收集方式:
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张三");
list.add("张三丰");
list.add("张三丰");
Stream<String> s1 = list.stream().filter(s -> s.startsWith("张"));
List<String> zhangList = s1.collect(Collectors.toList());//可变集合
System.out.println(zhangList);//[张无忌, 张三, 张三丰, 张三丰]
//注意注意注意:“流只能使用一次”,
// 除非重新写一次 : Stream<String> s2 = list.stream().filter(s -> s.startsWith("张"));
Stream<String> s2 = list.stream().filter(s -> s.startsWith("张"));
Set<String> zhangSet = s2.collect(Collectors.toSet());
System.out.println(zhangSet);//报错//只能使用一次
//[张三, 张三丰, 张无忌]
Stream<String> s3 = list.stream().filter(s -> s.startsWith("张"));
Object[] arrs = s3.toArray();
//拓展:转换为String[]
String[] arrs1 = s3.toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
});
//简化1
String[] arrs2 = s3.toArray(s ->new String[s]);
//简化2
String[] arrs3 = s3.toArray(String[]::new);
System.out.println("Arrays数组内容:"+Arrays.toString(arrs));//[张无忌, 张三, 张三丰, 张三丰]