Java·集合
目录
集合
集合的概述
数组的特点
集合的特点
总结
集合的体系特点
Collection集合体系
Collection集合特点
集合对于泛型的支持
Collection的常用API
Collection集合
Collection API:
集合的遍历方式
方式一:迭代器
方式二:foreach/增强for循环
方式三:lambda表达式
集合存储自定义类型的对象
常见数据结构
List系列集合
List集合特点、特有API
List集合的遍历方式小结
ArrayList集合的底层原理
LinkedList集合的底层原理
补充知识:集合的并发修改异常问题
补充知识:泛型深入
泛型的概述和优势
自定义泛型类
自定义泛型方法
自定义泛型接口
泛型通配符、上下限
set系列集合
Set系列集合
Set系列集系概述
HashSet元素无序的底层原理:哈希表
HashSet元素去重复的底层原理
#
实现类:LinkedHashSet
实现类:TreeSet
Collection体系的特点、使用场景总结
补充知识:可变参数
补充知识:集合工具类Collections
Collection体系的综合案例
Map集合体系
Map集合的概述
Map集合体系特点
Map集合常用API
Map集合的遍历方式一:键找值
Map集合的遍历方式二:键值对
Map集合的遍历方式三:lambda表达式
Map集合案例-统计投票人数
Map集合的实现类HashMap
Map集合的实现类LinkedHashMap
Map集合的实现类TreeMap
补充知识:集合的嵌套
集合
集合的概述
集合和数组都是容器。
数组的特点
-
数组定义完成并启动后,类型确定、长度固定。
-
适合元素的个数和类型确定的业务场景,不适合做需要增删数据操作。
集合的特点
-
集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球。
-
集合非常适合做元素的增删操作。
-
注意:集合中只能存储引用类型数据,如果要存储基本类型数据可以选用包装类。
总结
-
数组和集合的元素存储的个数问题?
-
数组定义后类型确定,长度固定
-
集合类型可以不固定,大小是可变的。
-
-
数组和集合存储元素的类型问题?
-
数组可以存储基本类型和引用类型的数据。
-
集合只能存储引用数据类型的数据。
-
-
数组和集合适合的场景?
-
数组适合做数据个数和类型确定的场景。
-
集合适合做数据个数不确定,且要做增删元素的场景。
-
集合的体系特点
Collection集合体系
Collection集合特点
-
List系列集合:添加的元素是有序、可重复、有索引。 ArrayList、LinekdList :有序、可重复、有索引。
-
Set系列集合:添加的元素是无序、不重复、无索引。
-
HashSet: 无序、不重复、无索引;
-
LinkedHashSet: 有序、不重复、无索引。
-
TreeSet:按照大小默认升序排序、不重复、无索引。
(创新)
-
集合对于泛型的支持
集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型
Collection lists = new ArrayList();Collection lists = new ArrayList(); // JDK 1.7开始后面的泛型类型申明可以省略不写
注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象。
-
集合的代表是?
-
Collection接口。
-
-
Collection集合分了哪2大常用的集合体系?
-
List系列集合:添加的元素是有序、可重复、有索引。
-
Set系列集合:添加的元素是无序、不重复、无索引。
-
-
如何约定集合存储数据的类型,需要注意什么?
-
集合支持泛型。
-
集合和泛型不支持基本类型,只支持引用数据类型。
-
package com.itheima.d1_collection;import java.util.ArrayList;import java.util.Collection;import java.util.HashSet;//collection集合体系特点public class CollectionDemo1 { public static void main(String[] args) { //有序 可重复 有索引 //ArrayList List=new ArrayList(); Collection list=new ArrayList(); list.add("java"); list.add("java"); list.add("Mybatis"); list.add(23); list.add(23); list.add(false); System.out.println(list); //无序 不重复 无索引 Collection list1=new HashSet(); list1.add("java"); list1.add("java"); list1.add("Mybatis"); list1.add(23); list1.add(23); list1.add(false); System.out.println(list1); System.out.println("............................"); //Collection list2=new ArrayList();//JDK7开始之后后面类型可以省略不写 Collection list2=new ArrayList(); list2.add("java"); list2.add("黑马"); //list2.add(23); //集合和泛型不支持基本数据类型,只能支持引用数据类型 //Collection list3=new ArrayList(); Collection list3=new ArrayList(); list3.add(23); list3.add(23); list3.add(233);//自动装箱 已经不再是一个基本数据,而是当作一个对象。 Collection list4=new ArrayList(); list4.add(23.4); list4.add(23.0); list4.add(233.9); }}
Collection的常用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.itheima.d2_collection_api;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;public class CollectionDemo { public static void main(String[] args) { //HashSet:添加的元素是无序、不重复、无索引。 Collection c=new ArrayList();// public boolean add(E e)把给定的对象添加到当前集合中.添加成功返回true c.add("java"); c.add("HTML"); c.add("HTML"); c.add("MySQL"); c.add("java"); c.add("黑马"); System.out.println(c);// public void clear() 清空集合中所有的元素 //list.clear(); //System.out.println(list);// public boolean remove(E e)把给定的对象在当前集合中删除.如果有多个元素重复默认删除前面一个。 System.out.println(c.remove("java"));//不能通过索引删,只能通过内容删。// public boolean contains(Object obj)判断当前集合中是否包含给定的对象 c.contains("java");//true c.contains("Java");//false// public boolean isEmpty()判断当前集合是否为空.是空返回true System.out.println(c.isEmpty());// public int size()返回集合中元素的个数。 System.out.println(c.size());// public Object[] toArray()把集合中的元素,存储到数组中。集合转换为数组 Object[] arrs=c.toArray(); System.out.println("数组内容:"+ Arrays.toString(arrs)); System.out.println("..............拓展..............."); Collection c1=new ArrayList(); c1.add("java1"); c1.add("java2"); Collection c2=new ArrayList(); c2.add("赵敏"); c2.add("朱亚文"); //addAll把c2集合的元素全部倒入c1中去。 c1.addAll(c2); System.out.println(c1); System.out.println(c2); }}
集合的遍历方式
方式一:迭代器
迭代器遍历概述
-
遍历就是一个一个的把容器中的元素访问一遍。
-
迭代器在Java中的代表是Iterator,迭代器是集合的专用遍历方式。
Collection集合获取迭代器
方法名称 | 说明 |
---|---|
Iterator iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引 |
Iterator中的常用方法
方法名称 | 说明 |
---|---|
boolean hasNext() | 询问当前位置是否有元素存在,存在返回true ,不存在返回false |
E next() | 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界。 |
package com.itheima.d3_collection_traversal;import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;/ 目标:Collection集合的遍历方式。 什么是遍历? 为什么开发中要遍历? 遍历就是一个一个的把容器中的元素访问一遍。 开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。 Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。 Collection集合的遍历方式有三种: (1)迭代器。 (2)foreach(增强for循环)。 (3)JDK 1.8开始之后的新技术Lambda表达式(了解) a.迭代器遍历集合。 -- 方法: public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的 boolean hasNext():判断是否有下一个元素,有返回true ,反之。 E next():获取下一个元素值! --流程: 1.先获取当前集合的迭代器 Iterator it = lists.iterator(); 2.定义一个while循环,问一次取一次。通过it.hasNext()询问是否有下一个元素,有就通过it.next()取出下一个元素。 小结: 记住代码。 */public class CollectionDemo01 { public static void main(String[] args) { ArrayList lists = new ArrayList(); lists.add("赵敏"); lists.add("小昭"); lists.add("素素"); lists.add("灭绝"); System.out.println(lists); // [赵敏, 小昭, 素素, 灭绝] // it // 1、得到当前集合的迭代器对象。 Iterator it = lists.iterator();// String ele = it.next();// System.out.println(ele);//赵敏// System.out.println(it.next());//小昭// System.out.println(it.next());//素素// System.out.println(it.next());//灭绝 // System.out.println(it.next()); // NoSuchElementException 出现无此元素异常的错误 // 2、定义while循环 while (it.hasNext()){ String ele = it.next(); System.out.println(ele); } System.out.println("-----------------------------"); }}
总结
-
迭代器的默认位置在哪里。
-
Iterator iterator():得到迭代器对象,默认指向当前集合的索引0
-
迭代器如果取元素越界会出现什么问题。
-
会出现NoSuchElementException异常。
-
方式二:foreach/增强for循环
增强for循环
-
增强for循环:既可以遍历集合也可以遍历数组。
-
它是JDK5之后出现的,其内部原理是一个Iterator迭代器,遍历集合相当于是迭代器的简化写法。
-
实现Iterable接口的类才可以使用迭代器和增强for,Collection接口已经实现了Iterable接口。
格式
for(元素数据类型 变量名 : 数组或者Collection集合) { //在此处使用变量即可,该变量就是元素
package com.itheima.d3_collection_traversal;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;/ 目标:Collection集合的遍历方式。 什么是遍历? 为什么开发中要遍历? 遍历就是一个一个的把容器中的元素访问一遍。 开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。 Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。 Collection集合的遍历方式有三种: (1)迭代器。 (2)foreach(增强for循环)。 (3)JDK 1.8开始之后的新技术Lambda表达式。 b.foreach(增强for循环)遍历集合。 foreach是一种遍历形式,可以遍历集合或者数组。 foreach遍历集合实际上是迭代器遍历集合的简化写法。 foreach遍历的关键是记住格式: for(被遍历集合或者数组中元素的类型 变量名称 : 被遍历集合或者数组){ } */public class CollectionDemo02 { public static void main(String[] args) { Collection lists = new ArrayList(); lists.add("赵敏"); lists.add("小昭"); lists.add("殷素素"); lists.add("周芷若"); System.out.println(lists); // [赵敏, 小昭, 殷素素, 周芷若] // elefor(String ele:lists){ System.out.println(ele);} System.out.println("................................................");double[] scores={100,99,59.5}; for (double score : scores) { System.out.println(score);// if(score==59.5){// score=100;// }修改无意义,不会影响数组的元素值 } System.out.println(Arrays.toString(scores)); }}
方式三:lambda表达式
Lambda表达式遍历集合
-
得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
Collection结合Lambda遍历的API
方法名称 | 说明 |
---|---|
default void== forEach==(Consumer action): | 结合lambda遍历集合 |
package com.itheima.d3_collection_traversal;import java.util.ArrayList;import java.util.Collection;import java.util.function.Consumer;/ 目标:Collection集合的遍历方式。 什么是遍历? 为什么开发中要遍历? 遍历就是一个一个的把容器中的元素访问一遍。 开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。 Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。 Collection集合的遍历方式有三种: (1)迭代器。 (2)foreach(增强for循环)。 (3)JDK 1.8开始之后的新技术Lambda表达式。 c.JDK 1.8开始之后的新技术Lambda表达式。 */public class CollectionDemo03 { public static void main(String[] args) { Collection lists = new ArrayList(); lists.add("赵敏"); lists.add("小昭"); lists.add("殷素素"); lists.add("周芷若"); System.out.println(lists); // [赵敏, 小昭, 殷素素, 周芷若] // s lists.forEach(new Consumer() { @Override public void accept(String s) { System.out.println(s); } });// lists.forEach(s -> {// System.out.println(s);// });// lists.forEach(s -> System.out.println(s) ); lists.forEach(System.out::println ); }}
集合存储自定义类型的对象
案例
需求 某影院系统需要在后台存储上述三部电影,然后依次展示出来。 分析 :定义一个电影类,定义一个集合存储电影对象。 :创建3个电影对象,封装相关数据,把3个对象存入到集合中去。 :遍历集合中的3个对象,输出相关信息。
package com.itheima.d4_collection_object;public class Movie { private String name; private double scores; private String actor; public Movie() { } public Movie(String name, double scores, String actor) { this.name = name; this.scores = scores; this.actor = actor; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getScores() { return scores; } public void setScores(double scores) { this.scores = scores; } public String getActor() { return actor; } public void setActor(String actor) { this.actor = actor; } @Override public String toString() { return "Movie{" + "name='" + name + '\'' + ", scores=" + scores + ", actor='" + actor + '\'' + '}'; }}
package com.itheima.d4_collection_object;import java.util.ArrayList;import java.util.Collection;public class TestDemo { public static void main(String[] args) { // 1、定义一个电影类 // 2、定义一个集合对象存储3部电影对象 Collection movies=new ArrayList(); movies.add(new Movie("《你好,李焕英》", 9.5, "张小斐,贾玲,沈腾,陈赫")); movies.add(new Movie("《唐人街探案》", 8.5, "王宝强,刘昊然,美女")); movies.add(new Movie("《刺杀小说家》",8.6, "雷佳音,杨幂")); System.out.println(movies);// 打地址 System.out.println(movies);// 重写toString之后,打内容。 //3.遍历集合容器中的每个电影对象 for (Movie movie : movies) { //快捷键movies.for System.out.println("片名:"+movie.getName()); System.out.println("得分:"+movie.getScores()); System.out.println("演员:"+movie.getActor()); } }}
集合中存储的是元素的什么信息? 集合中存储的是元素对象的地址。
常见数据结构
见PPT
List系列集合
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) | 返回指定索引处的元素 |
package com.itheima.d5_collection_list;import java.util.ArrayList;import java.util.Collection;import java.util.LinkedList;import java.util.List;/ 目标:ArrayList集合。 Collection集合的体系 Collection(接口)/ \ Set(接口) List(接口) / \ /\ \ HashSet(实现类) TreeSet(实现类) LinkedList(实现类) Vector(线程安全) ArrayList(实现类) / LinkedHashSet(实现类) Collection集合体系的特点: Set系列集合: 添加的元素,是无序,不重复,无索引的。 -- HashSet:添加的元素,是无序,不重复,无索引的。 -- LinkedHashSet:添加的元素,是有序,不重复,无索引的。 List系列集合:添加的元素,是有序,可重复,有索引的。 -- LinkedList: 添加的元素,是有序,可重复,有索引的。 -- ArrayList: 添加的元素,是有序,可重复,有索引的。 -- Vector 是线程安全的,速度慢,工作中很少使用。 1、List集合继承了Collection集合的全部功能,"同时因为List系列集合有索引", 2、因为List集合多了索引,所以多了很多按照索引操作元素的功能: 3、ArrayList实现类集合底层基于数组存储数据的,查询快,增删慢! - public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。 - public E get(int index):返回集合中指定位置的元素。 - public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。 - public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回更新前的元素值。 小结: ArrayList集合的底层是基于数组存储数据。查询快,增删慢!(相对的) */public class ListDemo01 { public static void main(String[] args) { // 1.创建一个ArrayList集合对象: // List:有序,可重复,有索引的。 List list = new ArrayList(); // 一行经典代码!(多态) list.add("Java"); list.add("Java"); list.add("HTML"); list.add("HTML"); list.add("MySQL"); list.add("MySQL"); // 2.在某个索引位置插入元素。 list.add(2, "黑马"); System.out.println(list); // 3.根据索引删除元素,返回被删除元素 System.out.println(list.remove(1)); System.out.println(list); // 4.根据索引获取元素:public E get(int index):返回集合中指定位置的元素。 System.out.println(list.get(1)); // 5.修改索引位置处的元素: public E set(int index, E element) System.out.println(list.set(1, "传智教育"));//返回修改前的数据 System.out.println(list); list.clear(); }}
总结
1、List系列集合特点 ArrayList、LinekdList :有序,可重复,有索引。
2、List的实现类的底层原理 ArrayList底层是基于数组实现的,根据查询元素快,增删相对慢。 LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的
List集合的遍历方式小结
-
List集合的遍历方式有几种?
-
迭代器
-
增强for循环
-
Lambda表达式
-
for循环(因为List集合存在索引)
-
package com.itheima.d5_collection_list;import java.util.ArrayList;import java.util.Iterator;import java.util.List;/ 拓展:List系列集合的遍历方式有:4种。 List系列集合多了索引,所以多了一种按照索引遍历集合的for循环。 List遍历方式: (1)for循环。(独有的,因为List有索引)。 (2)迭代器。 (3)foreach。 (4)JDK 1.8新技术。 */public class ListDemo02 { public static void main(String[] args) { List lists = new ArrayList(); lists.add("java1"); lists.add("java2"); lists.add("java3"); / (1)for循环。 */ System.out.println("-----------------------"); for (int i = 0; i < lists.size(); i++) { String ele = lists.get(i); System.out.println(ele); } / (2)迭代器。 */ System.out.println("-----------------------"); Iterator it = lists.iterator(); while (it.hasNext()){ String ele = it.next(); System.out.println(ele); } / (3)foreach */ System.out.println("-----------------------"); for (String ele : lists) { System.out.println(ele); } / (4)JDK 1.8开始之后的Lambda表达式 */ System.out.println("-----------------------"); lists.forEach(s -> { System.out.println(s); }); }}
ArrayList集合的底层原理
ArrayList集合底层原理
-
ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
-
第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。
LinkedList集合的底层原理
LinkedList的特点
-
底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API
package com.itheima.d5_collection_list;import java.util.LinkedList;import java.util.List;/ 目标:LinkedList集合。 Collection集合的体系: Collection(接口) / \ Set(接口) List(接口) // \ \ HashSet(实现类) LinkedList(实现类) Vector(实现类) ArrayList(实现类) / LinkedHashSet(实现类) Collection集合体系的特点: Set系列集合: 添加的元素,是无序,不重复,无索引的。 -- HashSet:添加的元素,是无序,不重复,无索引的。 -- LinkedHashSet:添加的元素,是有序,不重复,无索引的。 List系列集合:添加的元素,是有序,可重复,有索引的。 -- LinkedList: 添加的元素,是有序,可重复,有索引的。 -- Vector: 添加的元素,是有序,可重复,有索引的。线程安全(淘汰了) -- ArrayList: 添加的元素,是有序,可重复,有索引的。 LinkedList也是List的实现类:底层是基于双链表的,增删比较快,查询慢!! LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的 所以LinkedList除了拥有List集合的全部功能还多了很多操作首尾元素的特殊功能: - public void addFirst(E e):将指定元素插入此列表的开头。 - public void addLast(E e):将指定元素添加到此列表的结尾。 - public E getFirst():返回此列表的第一个元素。 - public E getLast():返回此列表的最后一个元素。 - public E removeFirst():移除并返回此列表的第一个元素。 - public E removeLast():移除并返回此列表的最后一个元素。 - public E pop():从此列表所表示的堆栈处弹出一个元素。 - public void push(E e):将元素推入此列表所表示的堆栈。 小结: LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的。 所以提供了很多操作首尾元素的特殊API可的实以做栈和队列现。 如果查询多而增删少用ArrayList集合。(用的最多的) 如果查询少而增删首尾较多用LinkedList集合。 */public class ListDemo03 { public static void main(String[] args) { // LinkedList可以完成队列结构,和栈结构 (双链表) // 1、做一个队列: LinkedList queue = new LinkedList(); // 入队 queue.addLast("1号"); queue.addLast("2号"); queue.addLast("3号"); System.out.println(queue); // 出队 // System.out.println(queue.getFirst()); System.out.println(queue.removeFirst()); System.out.println(queue.removeFirst()); System.out.println(queue); // 2、做一个栈 LinkedList stack = new LinkedList(); // 入栈 压栈 (push) //stack.addFirst("第一颗子弹"); stack.push("第1颗子弹"); stack.push("第2颗子弹"); stack.push("第3颗子弹"); stack.push("第4颗子弹"); System.out.println(stack); // 出栈 弹栈 pop //System.out.println(stack.removeFirst()); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack); }}
补充知识:集合的并发修改异常问题
从集合中的一批元素中找出某些数据并删除,如何操作?是否存在问题呢 ?
问题引出
当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题。
那些遍历存在问题
-
迭代器遍历集合且直接用集合删除元素的时候可能出现。
-
增强for循环遍历集合且直接用集合删除元素的时候可能出现。
哪种遍历且删除元素不出问题
-
迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。
-
使用for循环遍历并删除元素不会存在这个问题。
package com.itheima.d6_collection_update_delete;import java.util.ArrayList;import java.util.Iterator;import java.util.List;/ 目标:研究集合遍历并删除元素可能出现的:并发修改异常问题。 */public class Test { public static void main(String[] args) { // 1、准备数据 ArrayList list = new ArrayList(); list.add("黑马"); list.add("Java"); list.add("Java"); list.add("赵敏"); list.add("赵敏"); list.add("素素"); System.out.println(list); // [黑马, Java, Java, 赵敏, 赵敏, 素素] // it // 需求:删除全部的Java信息。 // a、迭代器遍历删除 Iterator it = list.iterator();// while (it.hasNext()){// String ele = it.next();// if("Java".equals(ele)){// // 删除Java// // list.remove(ele); // 集合删除会出毛病// it.remove(); // 删除迭代器所在位置的元素值(没毛病),并且不会后移。// }// }// System.out.println(list); // b、foreach遍历删除 (会出现问题,这种无法解决的,foreach不能边遍历边删除,会出bug)// for (String s : list) {// if("Java".equals(s)){// list.remove(s);// }// } // c、lambda表达式(会出现问题,这种无法解决的,Lambda遍历不能边遍历边删除,会出bug(lambda内部也是通过foreach来遍历的))// list.forEach(s -> {// if("Java".equals(s)){// list.remove(s);// }// }); // d、for循环(从第0个开始会漏删)(边遍历边删除集合没毛病,但是必须从后面开始遍历删除才不会出现漏掉应该删除的元素) //解决方案1 for (int i = list.size() - 1; i >= 0 ; i--) { String ele = list.get(i); if("Java".equals(ele)){ list.remove(ele); } } System.out.println(list); //解决方案2 for (int i = 0; i < list.size(); i++) { String ele = list.get(i); if("Java".equals(ele)){ list.remove(ele); i--; } } System.out.println(list); }}
补充知识:泛型深入
泛型的概述和优势
泛型概述
-
泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
-
泛型的格式:;
-
注意:泛型只能支持引用数据类型。 集合体系的全部接口和实现类都是支持泛型的使用的。
泛型的好处
-
统一数据类型。
-
把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。
package com.itheima.d7_genericity;import java.util.ArrayList;import java.util.List;/ 目标:泛型的概述。 什么是泛型? 泛型就是一个标签: 泛型可以在编译阶段约束只能操作某种数据类型。 注意: JDK 1.7开始之后后面的泛型申明可以省略不写 小结: 泛型就是一个标签。 泛型可以在编译阶段约束只能操作某种数据类型。 泛型只能支持引用数据类型。 */public class GenericityDemo { public static void main(String[] args) { List list = new ArrayList(); list.add("Java"); list.add("Java2"); // list.add(23); List list1 = new ArrayList(); list1.add("Java");// list1.add(23.3);// list1.add(false); list1.add("Spring");// for (Object o : list1) {// String ele = (String) o;// System.out.println(ele);// } for (String s : list1) { System.out.println(s); } System.out.println("---------------------"); // 存储任意类型的元素 List list2 = new ArrayList(); list2.add(23); list2.add(23.3); list2.add("Java"); // List list3 = new ArrayList(); List list3 = new ArrayList(); }}
自定义泛型类
泛型类概述
-
定义类时同时定义了泛型的类就是泛型类。
-
泛型类的格式:修饰符 class 类名{ }
-
范例:public class MyArrayList { }
-
此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等。
-
作用:编译阶段可以指定数据类型,类似于集合的作用。
课程案例导学
模拟ArrayList集合自定义一个集合MyArrayList集合,完成添加和删除功能的泛型设计即可。
package com.itheima.d8_genercity_class;import java.util.ArrayList;public class MyArrayList { 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(); }}
package com.itheima.d8_genercity_class;public class Test { public static void main(String[] args) { //模拟ArrayList集合自定义一个集合MyArrayList集合,完成添加和删除功能的泛型设计即可。 MyArrayList list=new MyArrayList(); list.add("Java"); list.add("Java"); list.add("Java"); list.add("MySQL"); list.remove("MySQL"); System.out.println(list); MyArrayList list2=new MyArrayList(); list2.add(23); list2.add(24); list2.add(25); list2.remove(25); System.out.println(list2); }}
泛型类的原理
把出现泛型变量的地方全部替换成传输的真实数据类型。
自定义泛型方法
泛型方法概述
-
定义方法时同时定义了泛型的方法就是泛型方法。
-
泛型方法的格式:修饰符 方法返回值 方法名称(形参列表){}
-
范例: public void show(T t) { }
-
作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
课程案例导学
给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能!
package com.itheima.d9_genericity_method;/ 目标:自定义泛型方法。 什么是泛型方法? 定义了泛型的方法就是泛型方法。 泛型方法的定义格式: 修饰符 返回值类型 方法名称(形参列表){ } 注意:方法定义了是什么泛型变量,后面就只能用什么泛型变量。 泛型类的核心思想:是把出现泛型变量的地方全部替换成传输的真实数据类型。 需求:给你任何一个类型的数组,都能返回它的内容。Arrays.toString(数组)的功能! 小结: 泛型方法可以让方法更灵活的接收数据,可以做通用技术! */public class GenericDemo { public static void main(String[] args) { String[] names = {"小璐", "蓉容", "小何"}; printArray(names); Integer[] ages = {10, 20, 30}; printArray(ages); Integer[] ages2 = getArr(ages); String[] names2 = getArr(names); } public static T[] getArr(T[] arr){ return arr; } public static 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{}
-
作用:泛型接口可以让实现类选择当前功能需要操作的数据类型
课程案例导学
教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作
package com.itheima.d10_genericity_interface;public interface Data { void add(E e); void delete(int id); void update(E e); E queryById(int id);}
package com.itheima.d10_genericity_interface;/ 目标:泛型接口。 什么是泛型接口? 使用了泛型定义的接口就是泛型接口。 泛型接口的格式: 修饰符 interface 接口名称{ } 需求: 教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作 小结: 泛型接口可以约束实现类,实现类可以在实现接口的时候传入自己操作的数据类型 这样重写的方法都将是针对于该类型的操作。 */public class GenericDemo { public static void main(String[] args) { }}
package com.itheima.d10_genericity_interface;public class Student {}
package com.itheima.d10_genericity_interface;public class StudentData implements Data{ @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; }}
package com.itheima.d10_genericity_interface;public class Teacher {}
package com.itheima.d10_genericity_interface;public class TeacherData implements Data{ @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; }}
泛型接口原理
实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
泛型通配符、上下限
通配符:?
-
? 可以在“使用泛型”的时候代表一切类型。
-
E T K V 是在定义泛型的时候使用的。
泛型通配符案例导学
开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。
package com.itheima.d11_genericity_limit;import java.util.ArrayList;/ 目标:泛型通配符。? 需求:开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。 注意: 虽然BMW和BENZ都继承了Car 但是ArrayList和ArrayList与ArrayList没有关系的!! 通配符:? ?可以在“使用泛型”的时候代表一切类型。 E T K V 是在定义泛型的时候使用的。 泛型的上下限: ? extends Car : ?必须是Car或者其子类 泛型上限 ? super Car :?必须是Car或者其父类 泛型下限 小结: 通配符:? ?可以在“使用泛型”的时候代表一切类型。 */public class GenericDemo { public static void main(String[] args) { ArrayList bmws = new ArrayList(); bmws.add(new BMW()); bmws.add(new BMW()); bmws.add(new BMW()); go(bmws); ArrayList benzs = new ArrayList(); benzs.add(new BENZ()); benzs.add(new BENZ()); benzs.add(new BENZ()); go(benzs); ArrayList dogs = new ArrayList(); dogs.add(new Dog()); dogs.add(new Dog()); dogs.add(new Dog()); // go(dogs); } /所有车比赛 */ public static void go(ArrayList 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或者其父类 泛型下限
set系列集合
Set系列集合
Set系列集系概述
set系列集合特点
-
无序:存取顺序不一致
-
不重复:可以去除重复
-
无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
set集合实现类特点
-
HashSet : 无序、不重复、无索引。
-
LinkedHashSet:有序、不重复、无索引。
-
TreeSet:排序、不重复、无索引。
Set集合的功能上基本上与Collection的API一致。
package com.itheima.d1_set;import java.util.HashSet;import java.util.LinkedHashSet;import java.util.Set;public class SetDemo1 { public static void main(String[] args) { //看看set系列集合的特点:HashSet LinkHashSet TreeSet //无序(只是第一次会无序一次)、不重复、无索引 Set sets=new HashSet();//一行经典代码 sets.add("MySQL"); sets.add("MySQL"); sets.add("Java"); sets.add("Java"); sets.add("HTML"); sets.add("HTML"); sets.add("SpringBoot"); sets.add("SpringBoot"); System.out.println(sets); Set set=new LinkedHashSet();//一行经典代码 //有序、不重复、无索引 set.add("MySQL"); set.add("MySQL"); set.add("Java"); set.add("Java"); set.add("HTML"); set.add("HTML"); set.add("SpringBoot"); set.add("SpringBoot"); System.out.println(set); }}
HashSet元素无序的底层原理:哈希表
HashSet底层原理
-
HashSet集合底层采取哈希表存储的数据。
-
哈希表是一种对于增删改查数据性能都较好的结构。
哈希表的组成
-
JDK8之前的,底层使用数组+链表组成
-
JDK8开始后,底层采用数组+链表+红黑树组成。
哈希值的概念
哈希值
是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
Object类的API
public int hashCode():返回对象的哈希值
对象的哈希值特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的 默认情况下,不同对象的哈希值是不同的。
package com.itheima.d1_set;public class SetDemo2 { public static void main(String[] args) { //目标:学会获取对象的哈希值,并确认 String name="itheima"; System.out.println(name.hashCode()); System.out.println(name.hashCode()); String name1="itheima1"; System.out.println(name1.hashCode()); System.out.println(name1.hashCode()); }}
HashSet1.7版本原理解析:数组 + 链表 +(结合哈希算法)
-
创建一个默认长度16的数组,数组名table
-
根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
-
判断当前位置是否为null,如果是null直接存入
-
如果位置不为null,表示有元素,则调用equals方法比较
-
如果一样,则不存,如果不一样,则存入数组,
-
JDK 7新元素占老元素位置,指向老元素
-
JDK 8中新元素挂在老元素下面
-
结论:哈希表是一种对于增删改查数据性能都较好的结构。
JDK1.8版本开始HashSet原理解析
-
底层结构:哈希表(数组、链表、红黑树的结合体)
-
当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。
总结
-
Set集合的底层原理是什么样的?
-
JDK8之前的,哈希表:底层使用数组+链表组成
-
JDK8开始后,哈希表:底层采用数组+链表+红黑树组成。
-
-
哈希表的详细流程
-
创建一个默认长度16,默认加载因为0.75的数组,数组名table
-
根据元素的哈希值跟数组的长度计算出应存入的位置
-
判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素, 则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
-
当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
HashSet元素去重复的底层原理
需求: 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是同一个对象 分析 定义学生类,创建HashSet集合对象, 创建学生对象 把学生添加到集合 在学生类中重写两个方法,hashCode()和equals(),自动生成即可 遍历集合(增强for)
-
package com.itheima.d1_set;import java.util.Objects;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; } 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; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } / * 只要两个对象内容一样 就返回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); } / * 去重复了 * @return */ @Override public int hashCode() { return Objects.hash(name, age, sex); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", sex=" + sex + '}'; }}
package com.itheima.d1_set;import java.util.HashSet;import java.util.Set;public class SetDemo3 { public static void main(String[] args) { //让set集合把重复内容的对象去掉一个(去重复) //set集合去重复原因:先判断哈希值,在判断equals Set sets=new HashSet(); //s1 s2哈希值不同,是两个不同的对象,不会去重复。 Student s1=new Student("吴磊",16,'男'); Student s2=new Student("吴磊",16,'男'); Student s3=new Student("王凯",28,'男'); sets.add(s1); sets.add(s2); sets.add(s3); System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); System.out.println(s3.hashCode()); System.out.println(sets); }}//重写后[Student{name='吴磊', age=16, sex=男}, Student{name='吴磊', age=16, sex=男}, Student{name='王凯', age=28, sex=男}]//不重写[Student{name='吴磊', age=16, sex=男}, Student{name='王凯', age=28, sex=男}]
如果希望Set集合认为2个内容相同的对象是重复的应该怎么办? 重写对象的hashCode和equals方法。
#
实现类:LinkedHashSet
LinkedHashSet集合概述和特点
-
有序、不重复、无索引。
-
这里的有序指的是保证存储和取出的元素顺序一致
-
原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
-
package com.itheima.d1_set;import java.util.HashSet;import java.util.LinkedHashSet;import java.util.Set;public class SetDemo4 { public static void main(String[] args) { Set set=new LinkedHashSet();//一行经典代码 //有序、不重复、无索引 set.add("MySQL"); set.add("MySQL"); set.add("Java"); set.add("Java"); set.add("HTML"); set.add("HTML"); set.add("SpringBoot"); set.add("SpringBoot"); System.out.println(set); }}//[MySQL, Java, HTML, SpringBoot]
实现类:TreeSet
TreeSet集合概述和特点
-
不重复、无索引、可排序
-
可排序:按照元素的大小默认升序(有小到大)排序。
-
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
-
注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
TreeSet集合默认的规则
-
对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
-
对于字符串类型:默认按照首字符的编号升序排序。
-
对于自定义类型如Student对象,TreeSet无法直接排序。
结论:想要使用TreeSet存储自定义类型,需要制定排序规则
自定义排序规则
-
TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则
方式一
-
让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
方式二
-
TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。
两种方式中,关于返回值的规则:
-
如果认为第一个元素大于第二个元素返回正整数即可。
-
如果认为第一个元素小于第二个元素返回负整数即可。
-
如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注意:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。(离得近)
package com.itheima.d1_set;public class Apple implements Comparable { private String name; private String color; private int weight; private double price; public Apple() { } public Apple(String name, String color, int weight, double price) { this.name = name; this.color = color; this.weight = weight; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } @Override public String toString() { return "Apple{" + "name='" + name + '\'' + ", color='" + color + '\'' + ", weight=" + weight + ", price=" + price + '}'; } / * 方式一:类自定义比较规则 * @param o * @return */ @Override public int compareTo(Apple o) { //按照重量比较// return this.weight-o.weight;//去除重量重复的元素 return this.weight-o.weight>=0?1:-1;//保留重量重复的元素 }}
package com.itheima.d1_set;import java.util.Comparator;import java.util.Set;import java.util.TreeSet;//目标:观察TreeSet对于有值特性的数据如何排序。//学会对自定义类型的对象进行指定规则排序public class SetDemo5 { public static void main(String[] args) { Set sets=new TreeSet();//不重复 无索引 可排序 sets.add(23); sets.add(24); sets.add(12); sets.add(8); System.out.println(sets);//[8, 12, 23, 24] Set sets1=new TreeSet();//不重复 无索引 可排序 sets1.add("Java"); sets1.add("angela"); sets1.add("About"); sets1.add("java"); sets1.add("黑马"); sets1.add("UI"); sets1.add("Python"); System.out.println(sets1);//[About, Java, Python, UI, angela, java, 黑马] System.out.println("........................................"); //Set apples=new TreeSet();// 方式二:集合自带// Set apples=new TreeSet(new Comparator() {// @Override// public int compare(Apple o1, Apple o2) {// //return o1.getWeight()- o2.getWeight();//升序// //return o2.getWeight()- o1.getWeight();//降序// //浮点型建议直接使用Double.compare// return Double.compare(o2.getPrice(),o1.getPrice());//降序// }// }); Set apples=new TreeSet(( o1, o2) ->Double.compare(o2.getPrice(),o1.getPrice())); apples.add(new Apple("红富士","红色",500,9.9)); apples.add(new Apple("青苹果","绿色",300,19.9)); apples.add(new Apple("绿苹果","青色",400,29.9)); apples.add(new Apple("黄苹果","黄色",500,9.8)); System.out.println(apples); }}//[8, 12, 23, 24]//[About, Java, Python, UI, angela, java, 黑马]........................................//[Apple{name='绿苹果', color='青色', weight=400, price=29.9}, Apple{name='青苹果', color='绿色', weight=300, price=19.9}, Apple{name='红富士',
Collection体系的特点、使用场景总结
-
如果希望元素可以重复,又有索引,索引查询要快? 用ArrayList集合,基于数组的。(用的最多)
-
如果希望元素可以重复,又有索引,增删首尾操作快? 用LinkedList集合,基于链表的.
-
如果希望增删改查都快,但是元素不重复、无序、无索引。 用HashSet集合,基于哈希表的。
-
如果希望增删改查都快,但是元素不重复、有序、无索引。 用LinkedHashSet集合,基于哈希表和双链表。
-
如果要对对象进行排序。 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
补充知识:可变参数
假如需要定义一个方法求和,该方法可以灵活的完成如下需求: 计算1个数据的和。 计算2个数据的和。 计算3个数据的和。 计算n个数据的和,甚至可以支持不接收参数进行调用。
可变参数
-
可变参数用在形参中可以接收多个数据。
-
可变参数的格式:数据类型...参数名称
可变参数的作用
-
传输参数非常灵活,方便。可以不传输参数,可以传输1个或者多个,也可以传输一个数组。
-
可变参数在方法内部本质上就是一个数组。
可变参数的注意事项
1.一个形参列表中可变参数只能有一个 2.可变参数必须放在形参列表的最后面
package com.itheima.d2_params;import java.util.Arrays;//可变参数public class MethodDemo { public static void main(String[] args) { //1.不传参数 sum(); //2.传一个参数 sum(10); //3.传多个参数 sum(10,20); //3.传一个数组 sum(new int[]{10,20,30,40,50}); } //一个形参列表中可变参数只能有一个 //可变参数必须放在形参列表的最后面 public static void sum(int...nums){ //注意:可变参数在方法内部其实就是一个数组。nums System.out.println("元素个数:"+nums.length); System.out.println("元素内容:"+ Arrays.toString(nums)); }}元素个数:0元素内容:[]元素个数:1元素内容:[10]元素个数:2元素内容:[10, 20]元素个数:5元素内容:[10, 20, 30, 40, 50]
补充知识:集合工具类Collections
Collections集合工具类
-
java.utils.Collections:是集合工具类
-
作用:Collections并不属于集合,是用来操作集合的工具类。
Collections常用API
方法名称 | 说明 |
---|---|
public static boolean addAll(Collection c, T... elements) | 给集合对象批量添加元素 |
public static void shuffle(List list) | 打乱List集合元素的顺序 |
Collections排序相关API
使用范围:只能对于List集合的排序。
排序方法一
方法名称 | 说明 |
---|---|
public static void sort(List list) | 将集合中元素按照默认规则排序 |
注意:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规则Comparable接口。
排序方法二
方法名称 | 说明 |
---|---|
public static void sort(List list,Comparator c) | 将集合中元素按照指定规则排序 |
package com.itheima.d3_collections;/ * @author zhao */public class Apple implements Comparable{ private String name; private String color; private double price; private int weight; public Apple() { } public Apple(String name, String color, double price, int weight) { this.name = name; this.color = color; this.price = price; this.weight = weight; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } 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 "Apple{" + "name='" + name + '\'' + ", color='" + color + '\'' + ", price=" + price + ", weight=" + weight + '}'; } / 方式一:类自定义比较规则 o1.compareTo(o2) * @param o * @return */ @Override public int compareTo(Apple o) { // 按照重量进行比较的 return this.weight - o.weight ; // List集存储相同大小的元素 会保留! }}
package com.itheima.d3_collections;import java.util.*;/ 目标:Collections工具类的使用。 java.utils.Collections:是集合工具类 Collections并不属于集合,是用来操作集合的工具类。 Collections有几个常用的API: - public static boolean addAll(Collection c, T... elements) 给集合对象批量添加元素! - public static void shuffle(List list) :打乱集合顺序。 - public static void sort(List list):将集合中元素按照默认规则排序。 - public static void sort(List list,Comparator c):将集合中元素按照指定规则排序。 * @author zhao */public class CollectionsDemo01 { public static void main(String[] args) { List names = new ArrayList(); //names.add("楚留香"); //names.add("胡铁花"); //names.add("张无忌"); //names.add("陆小凤"); Collections.addAll(names, "楚留香","胡铁花", "张无忌","陆小凤"); System.out.println(names); // 2、public static void shuffle(List list) :打乱集合顺序。 Collections.shuffle(names); System.out.println(names); // 3、 public static void sort(List list):将集合中元素按照默认规则排序。 (排值特性的元素) List list = new ArrayList(); Collections.addAll(list, 12, 23, 2, 4); System.out.println(list); Collections.sort(list); System.out.println(list); }}[楚留香, 胡铁花, 张无忌, 陆小凤][张无忌, 胡铁花, 陆小凤, 楚留香][12, 23, 2, 4][2, 4, 12, 23]
package com.itheima.d3_collections;import java.util.*;/ 目标:引用数据类型的排序。 字符串按照首字符的编号升序排序! 自定义类型的比较方法API:Collections - public static void sort(List list): 将集合中元素按照默认规则排序。 对于自定义的引用类型的排序人家根本不知道怎么排,直接报错! - public static void sort(List list,Comparator c): 将集合中元素按照指定规则排序,自带比较器 */public class CollectionsDemo02 { public static void main(String[] args) { List apples = new ArrayList(); // 可以重复! apples.add(new Apple("红富士", "红色", 9.9, 500)); apples.add(new Apple("青苹果", "绿色", 15.9, 300)); apples.add(new Apple("绿苹果", "青色", 29.9, 400)); apples.add(new Apple("黄苹果", "黄色", 9.8, 500));// Collections.sort(apples); // 方法一:可以的,Apple类已经重写了比较规则// System.out.println(apples); // 方式二:sort方法自带比较器对象// Collections.sort(apples, new Comparator() {// @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); }}[Apple{name='黄苹果', color='黄色', price=9.8, weight=500}, Apple{name='红富士', color='红色', price=9.9, weight=500}, Apple{name='青苹果', color='绿色', price=15.9, weight=300}, Apple{name='绿苹果', color='青色', price=29.9, weight=400}]
Collection体系的综合案例
斗地主
需求:在启动游戏房间的时候,应该提前准备好54张牌,完成洗牌、发牌、牌排序、逻辑。 分析: :当系统启动的同时需要准备好数据的时候,就可以用静态代码块了。 :洗牌就是打乱牌的顺序。 :定义三个玩家、依次发出51张牌 :给玩家的牌进行排序(拓展) :输出每个玩家的牌数据。
package com.itheima.d4_test;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; }}
package com.itheima.d4_test;///需求:在启动游戏房间的时候,应该提前准备好54张牌,完成洗牌、发牌、牌排序、逻辑。// 分析:// :当系统启动的同时需要准备好数据的时候,就可以用静态代码块了。// :洗牌就是打乱牌的顺序。// :定义三个玩家、依次发出51张牌// :给玩家的牌进行排序(拓展)// :输出每个玩家的牌数据。import java.util.ArrayList;import java.util.Collections;import java.util.Comparator;import java.util.List;public class GameDemo { / * 1.定义一个静态集合存储54张牌对象 */ public static List allCards = new ArrayList(); / * 2.做牌:定义静态代码块初始化牌数据 * @param args */ static { //3.定义点数:个数确定,类型确定,使用数组 String[] sizes = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"}; //4.定义花色:个数确定,类型确定,使用数组 String[] colors = {"♠", "♥", "♣", "♦"}; //5.组合点数和花色 int index=0;//记录牌的大小 for (String size : sizes) { index++; for (String color : colors) { //6.封装成一个牌对象 Card c = new Card(size, color,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 linhuchong = new ArrayList(); List jiumozhi = new ArrayList(); List renyingying = new ArrayList(); //11.开始发牌(从牌集合中发出51张牌给三个玩家,剩余三张作为底牌) for (int i = 0; i < allCards.size() - 3; i++) { //先拿到当前牌对象 Card c = allCards.get(i); if (i % 3 == 0) { //请啊冲接牌 linhuchong.add(c); } else if (i % 3 == 1) { //啊鸠接牌 jiumozhi.add(c); } else if (i % 3 == 2) { //盈盈接牌 renyingying.add(c); } } //12.拿到最后三张牌(把最后三张牌截取成一个子集合) List lastThreeCards=allCards.subList(allCards.size()-3,allCards.size()); //13.给玩家的牌排序(从大到小)sortCards(linhuchong);sortCards(jiumozhi);sortCards(renyingying); //14.输出玩家的牌: System.out.println("啊冲"+linhuchong); System.out.println("啊鸠"+jiumozhi); System.out.println("盈盈"+renyingying); System.out.println("三张底牌"+lastThreeCards); } / * 给牌排序 * @param cards */ private static void sortCards(List cards) { Collections.sort(cards, new Comparator() { @Override public int compare(Card o1, Card o2) { return o1.getIndex()- o2.getIndex();//降序 } }); }}
Map集合体系
Map集合的概述
Map集合的概述
-
Map集合是一种双列集合,每个元素包含两个数据。
-
Map集合的每个元素的格式:key=value(键值对元素)。
-
Map集合也被称为“键值对集合”。
Map集合整体格式
-
Collection集合的格式: [元素1,元素2,元素3..]
-
Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , ...}
Map集合体系特点
Map集合体系特点
-
Map集合的特点都是由键决定的。
-
Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
-
Map集合后面重复的键对应的值会覆盖前面重复键的值。
-
Map集合的键值对都可以为null。
Map集合实现类特点
-
HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map
体系一致)
-
LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
-
TreeMap:元素按照建是排序,不重复,无索引的,值不做要求。
Map集合常用API
Map集合
-
Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。
方法名称 | 说明 |
---|---|
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
package com.itheima.d6_map_api;import java.util.Collection;import java.util.HashMap;import java.util.Map;import java.util.Set;/ 目标:Map集合的常用API(重点中的重点) - public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。 - public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。 - public V get(Object key) 根据指定的键,在Map集合中获取对应的值。 - public Set keySet(): 获取Map集合中所有的键,存储到Set集合中。 - public Set<Map.Entry> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。 - public boolean containKey(Object key):判断该集合中是否有此键。 - public boolean containValue(Object value):判断该集合中是否有此值。/* @author zhao */public class MapDemo { public static void main(String[] args) { // 1.添加元素: 无序,不重复,无索引。 Map 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); System.out.println(maps.get("生活用品")); // 10 System.out.println(maps.get("生活用品2")); // null // 5.根据键删除整个元素。(删除键会返回键的值) System.out.println(maps.remove("iphoneX")); System.out.println(maps); // 6.判断是否包含某个键 ,包含返回true ,反之 System.out.println(maps.containsKey("娃娃")); // true System.out.println(maps.containsKey("娃娃2")); // false System.out.println(maps.containsKey("iphoneX")); // false // 7.判断是否包含某个值。 System.out.println(maps.containsValue(100)); // System.out.println(maps.containsValue(10)); // System.out.println(maps.containsValue(22)); // // {huawei=100, 手表=10, 生活用品=10, 娃娃=20} // 8.获取全部键的集合:public Set keySet() Set keys = maps.keySet(); System.out.println(keys); System.out.println("------------------------------"); // 9.获取全部值的集合:Collection values(); Collection values = maps.values(); System.out.println(values); // 10.集合的大小 System.out.println(maps.size()); // 4 // 11.合并其他Map集合。(拓展) Map map1 = new HashMap(); map1.put("java1", 1); map1.put("java2", 100); Map map2 = new HashMap(); map2.put("java2", 1); map2.put("java3", 100); map1.putAll(map2); // 把集合map2的元素拷贝一份到map1中去 System.out.println(map1); System.out.println(map2); }}
Map集合的遍历方式一:键找值
Map集合的遍历方式有:3种。
-
方式一:键找值的方式遍历:先获取Map集合全部的键,再根据遍历键找
值。
-
方式二:键值对的方式遍历,把“键值对“看成一个整体,难度较大。
-
方式三:JDK 1.8开始之后的新技术:Lambda表达式。
Map集合的遍历方式一:键找值
-
先获取Map集合的全部键的Set集合。
-
遍历键的Set集合,然后通过键提取对应值。
键找值涉及到的API:
package com.itheima.d7_map_traversal;import java.util.HashMap;import java.util.Map;import java.util.Set;/ * @author zhao */public class MapDemo01 { public static void main(String[] args) { // 1.添加元素: 无序,不重复,无索引。 Map 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); //遍历方法一:键找值 //1.键找值:第一步,先拿到集合的全部键 Set keys=maps.keySet(); //2.第二步,遍历每个键,根据键提取值 for (String key : keys) { int value=maps.get(key); System.out.println(key+"==>"+value); } }}
Map集合的遍历方式二:键值对
Map集合的遍历方式二:键值对
-
先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。
-
遍历Set集合,然后提取键以及提取值。
Map集合的遍历方式三:lambda表达式
键值对涉及到的API:
package com.itheima.d7_map_traversal;import java.util.HashMap;import java.util.Map;import java.util.Set;/ 目标:Map集合的遍历方式。 Map集合的遍历方式有:3种。 (1)“键找值”的方式遍历:先获取Map集合全部的键,再根据键找值。 (2)“键值对”的方式遍历:难度较大。 (3)JDK 1.8开始之后的新技术:Lambda表达式。 b.“键值对”的方式遍历: 1.把Map集合转换成一个Set集合:Set<Map.Entry> entrySet(); 2.此时键值对元素的类型就确定了,类型是键值对实体类型:Map.Entry 3.接下来就可以用foreach遍历这个Set集合,类型用Map.Entry */public class MapDemo02 { public static void main(String[] args) { Map maps = new HashMap(); // 1.添加元素: 无序,不重复,无索引。 maps.put("娃娃",30); maps.put("iphoneX",100); maps.put("huawei",1000); maps.put("生活用品",10); maps.put("手表",10); System.out.println(maps); // maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30} / maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30} 👇 使用foreach遍历map集合.发现Map集合的键值对元素直接是没有类型的。所以不可以直接foreach遍历集合。 👇 可以通过调用Map的方法 entrySet把Map集合转换成Set集合形式 maps.entrySet(); 👇 Set<Map.Entry> entries = maps.entrySet(); [(huawei=1000), (手表=10), (生活用品=10), (iphoneX=100), (娃娃=30)] entry 👇 此时可以使用foreach遍历*/// 1、把Map集合转换成Set集合 Set<Map.Entry> entries = maps.entrySet(); // 2、开始遍历 for(Map.Entry entry : entries){ String key = entry.getKey(); int value = entry.getValue(); System.out.println(key + "====>" + value); } }}
Map集合的遍历方式三:Lambda
得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
Map结合Lambda遍历的API
方法名称 | 说明 |
---|---|
default void forEach(BiConsumer action) | 结合lambda遍历Map集合 |
package com.itheima.d7_map_traversal;import java.util.HashMap;import java.util.Map;import java.util.function.BiConsumer;/ 目标:Map集合的遍历方式。 Map集合的遍历方式有:3种。 (1)“键找值”的方式遍历:先获取Map集合全部的键,再根据键找值。 (2)“键值对”的方式遍历:难度较大。 (3)JDK 1.8开始之后的新技术:Lambda表达式。 c.JDK 1.8开始之后的新技术:Lambda表达式。(暂时了解) */public class MapDemo03 { public static void main(String[] args) { Map maps = new HashMap(); // 1.添加元素: 无序,不重复,无索引。 maps.put("娃娃",30); maps.put("iphoneX",100);// Map集合后面重复的键对应的元素会覆盖前面重复的整个元素! maps.put("huawei",1000); maps.put("生活用品",10); maps.put("手表",10); System.out.println(maps); // maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}// maps.forEach(new BiConsumer() {// @Override// public void accept(String key, Integer value) {// System.out.println(key + "--->" + value);// }// }); maps.forEach((k, v) -> { System.out.println(k + "--->" + v); }); }}
Map集合案例-统计投票人数
需求 某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。 分析 将80个学生选择的数据拿到程序中去。 定义Map集合用于存储最终统计的结果。 遍历80个学生选择的数据,看Map集合中是否存在,不存在存入“数据=1“,存在则其对应值+1,
package com.itheima.d8_map_test;import java.util.HashMap;import java.util.Map;import java.util.Random;//统计投票人数public class MapTest1 { public static void main(String[] args) { //把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 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)){ //让其键加一 infos.put(ch, infos.get(ch)+1); }else{ infos.put(ch,1); } } //4.输出集合 System.out.println(infos); }}
Map集合的实现类HashMap
HashMap的特点
-
HashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引
-
没有额外需要学习的特有方法,直接使用Map里面的方法就可以了。
-
HashMap跟HashSet底层原理是一模一样的,都是哈希表结构,只是HashMap的每个元素包含两个值而已。
实际上:Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
-
HashMap的特点和底层原理
-
由键决定:无序、不重复、无索引。HashMap底层是哈希表结构的。
-
依赖hashCode方法和equals方法保证键的唯一。
-
如果键要存储的是自定义对象,需要重写hashCode和equals方法。 基于哈希表。增删改查的性能都较好。
Map集合的实现类LinkedHashMap
LinkedHashMap集合概述和特点
-
由键决定:有序、不重复、无索引。
-
这里的有序指的是保证存储和取出的元素顺序一致
-
原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
Map集合的实现类TreeMap
TreeMap集合概述和特点
-
由键决定特性:不重复、无索引、可排序
-
可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
-
注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
-
TreeMap跟TreeSet一样底层原理是一样的。
TreeMap集合自定义排序规则有2种
-
类实现Comparable接口,重写比较规则。
-
集合自定义Comparator比较器对象,重写比较规则。
package com.itheima.d9_map_impl;import com.itheima.d1_set.Apple;import java.util.Comparator;import java.util.Map;import java.util.TreeMap;public class TreeMapDemo3 { public static void main(String[] args) { //TreeMap集合自带排序 Map maps1=new TreeMap(); maps1.put(1,"张三"); maps1.put(2,"王麻子"); maps1.put(3,"县长"); System.out.println(maps1);//自带排序,可排序,不重复(只要大小规则一样) 无索引 Map maps2=new TreeMap(new Comparator() { @Override public int compare(Apple o1, Apple o2) { return Double.compare(o2.getPrice(), o1.getPrice());//按照价格降序 } }); maps2.put(new Apple("红富士","红色",500,9.9),"山东"); maps2.put(new Apple("青苹果","绿色",300,19.9),"广州"); maps2.put(new Apple("绿苹果","青色",400,29.9),"江西"); maps2.put(new Apple("黄苹果","黄色",500,9.8),"湖北"); System.out.println(maps2); }}
补充知识:集合的嵌套
需求 某个班级多名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生可以选择多个景点,请统计出最终哪个景点想去的人数最多。 分析 将80个学生选择的数据拿到程序中去,需要记住每个学生选择的情况。 定义Map集合用于存储最终统计的结果。
package com.itheima.d9_map_impl;import java.util.*;//统计投票人数public class MapTest4 { public static void main(String[] args) { //1.要求程序记录每个学生选择的情况 //使用一个Map集合存储 Map<String , List> data=new HashMap(); //2.吧学生选择的数据存入进去。 List selects=new ArrayList(); Collections.addAll(selects,"A","C"); data.put("啊勇", selects); List selects1=new ArrayList(); Collections.addAll(selects1,"B","C","D"); data.put("胡桃", selects1); List selects2=new ArrayList(); Collections.addAll(selects2,"A","C","B","D"); data.put("刘军", selects2); System.out.println(data); //3.统计每个景点选择的人数 Map infos=new HashMap(); //4.提取所有人选择的景点信息 Collection<List> values = data.values(); //values=[ [B, C, D],[A, C],[A, C, B, D]] for (List 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); }}{胡桃=[B, C, D], 啊勇=[A, C], 刘军=[A, C, B, D]}{A=2, B=2, C=3, D=2}