10.集合、泛型
AI-摘要
Tianli GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
10.集合、泛型
Pupper一、集合
- 单列集合:其中的元素都是单个的;
- Collection 接口有两个重要的子接口 List、Set,他们实现子类都是单列集合
- 双列集合:其中的元素是以键值对的形式出现的;
- Map 接口实现的子类 是双列集合
1. Collection 接口
Collection 接口实现类的特点:
- Collection 实现子类可以存放多个元素,每个元素可以是 object;
- 有些实现类可以存放重复的元素,有些不可以;
- 有些实现类是有序的(List),有些是无序的(Set);
- Collection 接口没收直接实现子类,它是通过子接口 List 和 set 来实现的;
Collection 接口常用方法
add | 添加单个元素 |
---|---|
remove | 删除指定元素 |
contains | 查找元素是否存在 |
size | 获取元素个数 |
isEmpty | 判断是否为空 |
clear | 清空 |
addAll | 添加多个元素 |
contains | 查找多个元素是否都存在 |
removeAll | 删除多个元素 |
1 | // Collection 常用方法 |
Collection 遍历元素的方式
1. 使用 Iterator (迭代器)
- Iterator 对象成为迭代器,主要用于遍历 Collection 集合中的元素;
- 所有实现 Collection 接口的集合类都有一个 iteration 方法;
- 用以返回一个实现了 iteration 接口的对象,即迭代器
- Iterator 仅用于遍历集合,其本身并不存放对象;
- 使用快捷方式 itit, 快速创建遍历循环
得到集合的迭代器 :Iterator iterator = 集合.iterator();
hasNext() : 判断是否还有下一个元素
nest() :下移,将移动后集合位置上的元素返回
如果需要再次遍历,需要 重置迭代器(即,重新生成迭代器并赋值给之前的变量)
1 | // 迭代器 的使用方法 |
2. 使用增强 for 循环迭代
增强 for 循环是简化版的 iterator,只能用于遍历集合或数组;
其本质底层还是使用的迭代器;
1 | for(元素类型 元素名:集合名/数组名){ |
1 | // 增强 for 循环 |
1. List 接口
List 接口是 Collection 接口的子接口
- List 集合类中的元素是有序的,且可以重复;
- 元素顺序就是添加时的顺序;
- 如: [tom, jack, menar, jack]
- List 集合类中的每个元素都有其对应的 索引;
- 索引从 0 开始;
- List 容器中的元素可以根据序号存取
list 接口的常用方法
void add(int index, Object ele): | 在 index 位置插入 ele 元素 |
---|---|
boolean addAll(int index, Collection eles): | 从 index 位置开始将 eles 中的所有元素添加进来 |
Object get(int index): | 获取 index 位置的元素 |
int indexOf(Object obj): | 返回 obj 在集合中首次出现的位置 |
int lastIndexOf(Object obj): | 返回 obj 在集合中 末次出现的位置 |
Object remove(int index): | 移除 index 位置的元素,并返回此元素 |
Object set(int index,Object ele): | 对 index 位置的元素 重新赋值 |
List subList(int formIndex, int toIndex): | 返回 从 fromIndex 到 toIndex 位置的子集合 |
1 | // List 接口常用方法 - 案例 |
List [ArrayList, LinkedList, Vector]的三种遍历方式:
- 使用 iterator
- 加强 for 循环
- 使用普通的 for 循环
1 | // 书本排序 案例 |
ArrayList
ArrayList 注意事项:
- ArrayList 可以存放任何元素,包括 null ,不限制个数;
- ArrayList 是由数组来实现数据存储的;
- ArrayList 基本等同于 Vector,执行效率高;
- ArrayList 是线程不安全的(没有 synchronized 修饰),多线程时,不建议使用;
- synchronized 表示线程互斥,起到线程安全的作用
ArrayList 底层源码分析:
- ArrayList 中维护了一个 Object 类型的数组 elementData
- transient Object[] elementData;
- 当创建 ArrayList 对象时,如果使用的是无参构造器,则初始 elementData 容量为 0;
- 第一次添加,则扩容 elementData 为 10;
- 如需再次扩容,则扩容 elementData 为 1.5 倍;
- 如果使用的是指定大小的构造器,则初始 elementData 容量为指定大小;
- 如果需要扩容,则直接扩容 elementData 为 1.5 倍
Vector
Vector 和 ArrayList 比较:
底层结构 | 版本 | 线程同步 | 扩容倍数 | |
---|---|---|---|---|
ArrayList | 可变数组 | jdk1.2 | 不安全,效率高 | 有参构造默认为 15,以后以 1.5 倍扩容;无参构造,第一次为 10,以后以 1.5 倍扩 |
Vector | 可变数组 | jdk1.0 | 安全,效率不高 | 无参构造,默认为 10,以后以 2 倍扩容;如果指定大小,以后以 2 倍扩容; |
Vector 底层源码分析:
- Vector 底层也是一个对象数组;
- protected Object[] elementD
- Vector 是线程同步的,即线程安全,操作方法带有 synchronized
- 在开发中,需要使用线程同步安全时,使用 Vector
- 当创建 Vector 对象时,如果使用的是无参构造器,则初始 elementData 容量为 0;
- 第一次添加,则扩容 elementData 为 10;
- 如需再次扩容,则扩容 elementData 为 2 倍;
- 如果使用的是指定大小的构造器,则初始 elementData 容量为指定大小;
- 如果需要扩容,则直接扩容 elementData 为 2 倍
LinkedList
LinkedList 和 ArrayList 比较:
底层结构 | 增删效率 | 改查效率 | 线程安全 | |
---|---|---|---|---|
ArrayList | 可变数组 | 较低,数组扩容 | 较高 | 不安全 |
LinkedList | 双向链表 | 较高,通过链表追加 | 较低 | 不安全 |
- 如果我们改查的操作多,选择 ArrayList;
- 如果我们增删的操作多,选择 LinkedList;
- 大部分情况下都是查询操作,所以一般会选择 ArrayList;
- 也可以一个模块使用 ArrayList,一个模块使用 LinkedList;
LinkedList 底层机制:
- LinkedList 底层维护了一个双向链表;
- LinkedList 中维护了两个属性:
- first 和 last 分别指向 首节点 和 尾节点;
- 每个节点(Node 对象)里面又维护了 prev、next、item 三个属性,最终实现双向链表;
- 通过 prev 指向前一个;
- 通过 next 指向后一个;
- LinkedList 的元素增删 不是用过数组完成的,所以效率较高;
- 可以添加任意元素(元素可以重复),包括 null;
- 线程不安全,没有实现同步;
2. Set 接口
- 无序(添加和去除的顺序不一致),没有索引;
- 不能使用 索引 的方式来获取元素;
- 不允许重复元素,所有最多包含一个 null;
- 可以使用迭代器 和 增强 for 循环进行遍历;
1 | // Set - 案例 |
HashSet
- HashSet 实现了 Set 接口,实际上是 HashMap,底层维护的是一个 数组 + 单向链表;
- 只能存放一个 null;
- HashSet 是无序且不重复的;
HashSet 底层原理:
- HashSet 底层是 HashMap;
- 添加一个元素时,先得到该元素的 hash 值,然后转换为 索引值;
- 找到存储数据表 table,查看索引位置是否存在元素;
- 如果不存在,则直接放入;
- 如果存在,则调用 equals 方法进行比较,如果相同,就放弃添加,如果不同,则添加到最后;
- 如果一条链表的元素个数超过 TREEIFY_THRESHOLD(默认为 8),并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认为 64),就会进行树化(红黑树);
1 | // HashSet - 案例 |
1 | // HashSet - 案例(多个自定义类) |
LinkedHashSet
- LinkedHashSet 是 HashSet 的子类;
- LinkedHashSet 底层是一个 LinkedHashMap,维护了一个 数组 + 双向链表;
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置;
- 使用链表维护元素的次序,这使的元素看起来是以插入顺序保存的;
- LinkedHashSet 不允许添加重复元素;
1 | // LinkedHashSet - 案例 |
TreeSet
- 使用 TreeSet 的无参构造器创建的对象是无序的;
- 使用 TreeSet 提供的有参构造器,传入一个比较器(匿名内部类)并必定规则;
1 | // TreeSet 有参构造器 - 有序 |
1 | // 从大到小排序 - 案例 |
2. Map 接口
Map 接口的特点:
- Map 和 Collection 并列存在,用于保存具有映射关系的数据: Key - Value;
- Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中;
- Map 中的 key 不允许重复,value 可以重复;
- Map 的 key 可以为 null,value 也可以为 null;
- 注意: key 为 null 的只能有一个,value 为 null 可以有多个;
- 常用 String 类型为 Map 的 key;
- key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value;
- 一堆 key - value 是放在一个 HashMap$Node 中的,因为 Node 实现了 Entry 接口,所以也说 一对 k-v 就是一个 Entry;
- 通过 keySet 和 valuesSet 方法,可以获取所有 key 或 value 的集合;
Map 接口的常用方法
put | 添加 | 如果 key 存在,则更新值 |
---|---|---|
remove | 删除 | 如果 key 不存在,则返回 null;如果 key 存在,则返回 value |
get | 根据键获取值 | 返回 value |
size | 获取元素个数 | 返回元素个数 |
isEmpty | 判断个数是否为 0 | 返回 布尔值 |
clear | 清空 | |
containsKey | 查找键是否存在 | 返回布尔值 |
keySet | 获取所有的键 | |
values | 获取所有的值 | |
entrySet | 获取所有关系 k-v |
1 | // Map 常用方法 |
1. HashMap
HashMap :
- Map 接口的常用实现类: HashMap、Hashtable 和 Properties;
- HashMap 是 Map 接口使用频率最高的实现类;
- HashMap 是以 key-value 对的方式来存储数据;
- key 不能重复,但是值可以重复,允许有一个 key 为 null 的元素;
- 如果添加相同的 key,则会覆盖原来的 key-value,等同于修改;
- HashMap 没有实现同步,因此是线程不安全的;
HashMap 底层机制:
- HashMap 与 HashSet 的扩容机制相同
- HashMap 底层维护了 Node 类型的数组 table,默认为 null;
- 当创建对象时,将加载因子初始化为 0.75;
- 当添加 key-value 时,通过 key 的哈希值得到在 table 的索引,然后判断该索引是否有元素;
- 如果该索引处有元素,继续判断该元素的 key 是否和准备加入的可以相等;
- 如果相等,则直接替换 value
- 如果不相等,则需要判断是树结构还是链表结构,做出相应的处理;
- 如果添加时发现容量不够,则需要扩容;
- 如果该索引处有元素,继续判断该元素的 key 是否和准备加入的可以相等;
- 第一次添加,则需要扩容 table 容量为 16,临界值为 12;
- 以后扩容,则需要扩容 table 容量为原来的 2 倍,临界值为原来的 2 倍;
- 如果一条链表元素个数超过 8,并且 table 大小 大于等于 64,则会进行树化;
1 | // HashMap - 案例 |
2.HashTable
- HashTable 存放元素是键值对:即 key-value;
- HashTable 的键和值不能为 null,否则会抛出 空指针异常(NullPointerException)
- HashTable 的使用方法和 HashMap 基本一致;
- HashTable 的线程是安全的,HashMap 的线程是不安全的;
1 | // HashTable - 案例 |
3. Properties
- Properties 类继承自 HashTable 类,并实现了 Map 接口;
- Properties 是以键值对的形式存储,不能使用 null;
- Properties 与 HashTable 类似;
- Properties 可用于从 xxx.properties 文件中,加载数据到 Properties 类对象,并进行修改和读取;
- xxx.properties 通常作为配置文件;
1 | // Properties - 案例 |
4. TreeMap
TreeSet 和 TreeMap 的区别:
- 底层数据不同
- TreeSet 底层的 key 值是传入的值,value 是一个固定值;
- TreeMap 底层的 key 和 value 都是可变的;
1 | // TreeMap - 案例 |
3. 如何选择集合实现类
- 判断存储的类型(一组对象或一组键值对);
- 一组对象:Collection 接口
- 允许重复:List
- 增删多:LinkedLiist(底层维护了一个双向链表);
- 改查多:ArrayList(底层维护 Object 类型的可变数组);
- 不允许重复:Set
- 无序:HashSet(底层是 HashMap,维护了一个哈希表,即(数组+链表+));
- 有序:TreeSet
- 插入和取出的顺序一致:LinkedHashSet(底层维护了一个数组+双向链表);
- 允许重复:List
- 一组键值对:Map 接口 1. 键无序:HashMap(底层是:哈希表,数组+链表+红黑树); 2. 键有序:TreeMap 3. 键插入和取出顺序一致:LinkedHashMap 4. 读取文件:Properties
4. Collections 工具类
Collections 工具类介绍:
- Collections 是一个操作 Set、List 和 Map 等集合的工具类;
- Collections 中提供了一系列静态的方法,对集合元素进行排序、查询、修改等操作;
- 排序操作均为 static 方法;
- reverse(List):反转 List 中元素的顺序;
- shuffle(List): 对 List 集合元素进行随机排序;
- sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
- sort(List,Comparator):根据指定的 Comparator 产生顺序排序;
- swap(List,int,int):将指定 list 集合中的 i 处元素和 j 处元素进行交换;
- 查找、替换
- Object max(Collection):根据自然排序, 返回给定集合中的最大元素;
- Object max(Collection, Comparator): 根据 Comparator 指定条件顺序,返回最大值;
- Object min(Collection):返回最小值;
- Object min(Collection,Comparator): 根据规则返回最小值;
- int frequency(Collection, Object): 返回指定集合中指定元素的出现次数;
- void copy(List dest,List src): 将 src 中的内容复制到 dest 中,新的集合元素个数需要和旧的集合元素个数一致,否则报错;
- boolean replaceAll(List list,Object oldVal, Object newVal): 使用新值替换 List 对象的所有旧值;
1 | // Collection 工具类 - 排序 案例 |
1 | // Collections - 查找、替换 案例 |
案例 1
1 | // 创建两个新闻,处理标题,倒序输出 |
案例 2
1 | // 创建两个新闻,处理标题,倒序输出 |
二、泛型
1 | interface 接口<T> |
说明:
- 其中,T、K、V 不代表值,而是表示类型;
- 任何字母都可以,常用 T 或 E 表示;
- 泛型又称参数化类型,解决数据类型安全性问题;
- 在类声明或实例化时,只要指定号序号的具体类型即可;
- 如:
ArrayList<Dog> dog = new ArraryList<Dog>
;
- 如:
- 泛型可以保证编译时没有警告,运行时不会抛异常;
- 泛型的作用:
- 可以在类声明时通过一个标识(如:E)表示类中某个属性的类型,或是某个返回值的类型,或者是参数类型
1 | public class Generic01 { |
注意事项:
- 泛型 只能是 引用数据类型,不能是基本数据类型;
- 如:
List<Integer>
- 引用数据是类型; - 如:
List<int>
会报错 - 基本数据类型;
- 如:
- 在给泛型指定具体类型后,可以传入该类型或其子类型;
- 如果不指定泛型的类型,默认给他的泛型为 Object;
案例
1 | // 对员工进行排序,(先按名称排序,再按生日排序) |
1. 自定义泛型 - 类
1 | // 泛型标识可以有多个 |
注意事项:
- 普通成员可以使用泛型(属性,方法);
- 使用泛型的数组,不能初始化;
- 静态方法中不能使用类的泛型;
- 泛型类的类型,是在创建对象时确定的 ( 创建对象时,需要指定确定的类型 );
- 如果在创建对象时, 没有指定类型, 默认为 Object;
1 | // 自定义泛型 |
2. 自定义泛型 - 接口
1 | // 泛型标识可以有多个 |
注意事项:
- 接口中, 静态成员也不能使用泛型;
- 泛型接口的类型, 在继承接口 或者实现接口时确定;
- 没有指定类型, 默认为 Object;
1 | // 接口泛型 |
3. 自定义泛型 - 方法
1 | 修饰符<T,R...> 返回类型 方法名(参数列表){} |
注意事项:
- 泛型方法, 可以定义在普通方法中, 也可以定义在泛型类中;
- 当泛型方法被调用时, 类型需要确定;
- public void eat(E e){}; , 修饰符后没有
表示不是泛型方法,而是使用了泛型; - 泛型方法, 既可以使用类 声明的泛型, 也可以使用 自己申明的泛型;
1 | // 泛型方法 |
4. 泛型的继承和通配符
- 泛型不具备继承性;
- 如
List<Object> list = new ArrayList<String>()
; // 报错<?>
: 表示支持任意泛型类型;<? extends A>
: 表示支持 A 类 以及 A 类的子类, 规定了 泛型的上限;<? super A>
: 表示支持 A 类 以及 A 类的父类, 规定了泛型的 下限;
1 | // 通配符的使用 |
5. JUnit (单元测试框架)
介绍:
- JUnit 是一个 java 语言的单元测试框架;
- 多数 Java 开发环境都已经集成了 JUnit 作为单元测试的工具;
- 在需要测试的方法前加上 @Test , 引入相应的库即可
1 | // JUnit 使用 |
案例
1 | public class HomeWork01 { |
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果