Java 集合详解 | 一篇文章解决Java 三大集合
更好阅读体验:Java 集合详解 | 一篇文章搞定Java 三大集合
好看的皮囊像是一个个容器,有趣的灵魂像是容器里的数据。接下来讲解Java集合数据容器。
文章篇幅有点长,还请耐心阅读。如只是为了解决某个疑问,可以阅读目录来查找你所需的内容。
开门见山:「Java集合框架图」
1.Iterator(迭代器)
迭代器的基本功能就是遍历集合中的所有元素。
Iterable 接口组合了迭代器Iterator,通过方法:Iterator iterator(); 来获取迭代器。
Collection 和 Map是 Java集合框架的根接口,Iterable 接口提供了迭代的功能。
1.1.集合和迭代器的关系
Collection 接口通过继承Iterable 接口来获得迭代功能;
Map 接口的迭代功能是嫁接Collection 接口的迭代功能,看Map 接口的抽象方法就知道了:具体怎么用在Map 集合的常用方法中讲到。
// 通过键来遍历,返回值是Set集合,Map的key是Set集合,特性也和Set一样的
Set<K> keySet();
// 通过值来遍历,返回值是Collection
Collection<V> values();
// 通过键值对来遍历,返回值是Set集合
Set<Map.Entry<K, V>> entrySet();
2.集合和集合特点
List集合:元素按进入先后有序排序有序排序保存,可以存储重复元素
Set集合:不可以存储重复元素
Map集合:Key值不可以重复(key等于Set集合),Value值可以重复
数据结构:数组、双向链表、哈希表、二叉树;关于数据底层存储结构,决定了集合绝大部分的特性,如:查询和增删快慢,是否有序,是否可以重复。
线程安全:就是集合中的方法使用了同步锁关键字:synchronized;有锁的效率低。
3.怎么选择集合
根据需求和集合特点来选择所需的集合类型。
「集合的具体使用」
4.List 集合
4.1.单集合操作
List<String> list = new ArrayList<String>();
// 1.1 新增元素
list.add("测试1");
list.add("测试2");
System.out.println("集合大小="+list.size());
// 1.2 指定下标新增,index 范围:[0,list.size()]
list.add(1,"测试3");
for (int i=0;i<list.size();i++){
// 2.1 获取元素
String str = list.get(i);
System.out.println("新增="+str);
}
// 3.1 修改元素,index 范围:[0,list.size())
list.set(2,"测试?");
for (String str : list){
System.out.println("修改"+str);
}
// 4.1 删除第3个元素,index 范围:[0,list.size())
// list.remove(2);
// 4.2 删除指定对象值
list.remove(new String("测试?"));
System.out.println("删除="+list);//AbstractCollection重写了toString()
4.2.集合间操作
//集合间操作
LinkedList<String> linkedList = new LinkedList<String>();
// 1 新增集合所有元素(合集),如果集合间无相同元素,则为并集
linkedList.addAll(list);
linkedList.addAll(2,list);
Iterator<String> listIterator = linkedList.listIterator();
while (listIterator.hasNext()){
String next = listIterator.next();
System.out.println("++"+next);
}
// 2 获取两者都有的元素(交集)
list.add("存在和价值");
list.retainAll(linkedList);
list.forEach(str -> {
System.out.println("交集="+str);
}); // 3 移除指定集合元素(差集)
list.add("存在和价值");
list.removeAll(linkedList);
for (String str : list){
System.out.println("差集="+str);
} // 4 并集,如果集合见存在相同元素,则去除相同在合并就可以得到并集
list.removeAll(linkedList);
list.addAll(linkedList);
for (int i=0;i<list.size();i++){
// 2.1 获取元素
String str = list.get(i);
System.out.println("并集="+str);
}
list 集合的「四种遍历方式」都在代码里面了
还有很多其他方法,ArrayList 和 LinkedList 还有一些特有的方法,可自行学习。
5.Set 集合
Set 集合不能保存重复元素。通过equals() 和 hashCode() 都相等来决定元素是否相等(重复元素)。
5.1.实现equals() 和hashCode()方法
AbstractSet 抽象类默认实现equals() 和 hashCode(),实现类会调用存储对象的hashCode() 方法来得到hashCode值,然后根据该hashCode值决定该对象在的存储位置;通过equal() 方法来判断两个对象的地址是否相等、类型是否相等、是否包含在集合内。所以两个对象equals()相等,hashcode()一定相等;hashcode()不相等,则equals()肯定不相等就不用再判断了提高效率。
重写equals() 和hashCode()方法
hashCode 值的计算方式
一个实体类有多个成员变量,为了避免偶然出现哈希值相等问题,常常将多列的哈希值成语质数再相加:hashCode=(int)var*31+var.hashCode()*31;
「例子」
public class Person {
private String name;
private int age; public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
} @Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode())*prime;
result = prime * result + age*prime;
return result;
}
}
如果觉得这样生成hashCode麻烦,可以用Objects类型的hash方法生成
@Override
public int hashCode() {
return Objects.hash(name,age);
}
在使用HashSet 或 LinkedHashSet 集合时,实现hashCode和equals 方法即可。但在使用TreeSet 的时候,实体类还需要重写比较器来实现定制排序。
5.2.比较器的实现
「比较器实现方式汇总」
- 实体类实现Comparable接口,重写compareTo方法;
- 专门写一个实现类实现Comparator接口,重写compare方法;创建TreeSet的时候传入该实体类的对象;
- 匿名实现Comparator;
- lambda实现Comparator;
第1种方式实现Comparable接口,重写compareTo方法;例如Person类通过age 和 name 字段进行排序
// 1.实现Comparable接口,重写compareTo方法
@Override
public int compareTo(Object obj) {
if (!(obj instanceof Person))
throw new RuntimeException("不是正确对象"); Person p = (Person) obj;
if (p.age > this.age) return -1;
if (p.age == this.age) {
return this.name.compareTo(p.name);
}
return 1;
}
比较器有多种实现方式,这只是其中一种比较简单的。
第2、3、4方式核心代码都一样,只是写法不同
// 2.单独类实现
public class ComparatorImpl implements Comparator {
@Override
public int compare(Object o1, Object o2) {
if (o1==o2) return 0;
if (!(o1 instanceof Person) || !(o2 instanceof Person))
throw new RuntimeException("不是Person 类型"); Person p1=(Person)o1;
Person p2=(Person)o2;
int num=p1.getName().compareTo(p2.getName());
if (num==0){
return new Integer(p1.getAge()).compareTo(new Integer(p2.getAge()));
}
return num;
}
} // 怎么使用
Set<Person> treeSet = new TreeSet<Person>(new ComparatorImpl()); // 3.匿名方式
TreeSet ts = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {内容一样}
}); // 4.lambda 方式 (匿名方式的简写)
TreeSet ts = new TreeSet((o1, o2)->{内容一样}
});
1和2的实现方式的特点是写死了,所有使用的定制排序都是一样的,不能对不同的对象定制不同的排序规则,需要对每个对象定制不同排序则使用3或4的实现方式。
5.3.Set 集合通用方法
5.4.TreeSet 特有方法
- Comparator comparator():用于获取定制排序Comparator 对象。如果实现了自定义定制排序,则该方法返回Comparator对象;反正返回null。
- Object first():返回集合中的第一个元素。
- Object last():返回集合中的最后一个元素。
- Object lower(Object e):返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,参考元素不需要是TreeSet集合里的元素)。
- Object higher(Object e):返回集合中位于指定元素之后的元素(即大于指定元素的最小元素,参考元素不需要是TreeSet集合里的元素)。
- SortedSet subSet(Object fromElement, ObjecttoElement):返回此Set的子集合,范围从fromElement(包含)到toElement(不包含)。
- SortedSet headSet(Object toElement):返回此Set的子集,由小于toElement的元素组成。
- SortedSet tailSet(Object fromElement) :返回此Set 的子集,由大于或等于fromElement的元素组成。
6.1.Map 集合
「建议:要想掌握好Map集合得先学会Collection集合」
Map集合存储键值对数据,Key 拥有Set集合特点,Value 拥有着Collection的特点,Map 像是Set+Collection的集合。所以在使用map存储、获取对象时,用作Key的对象必须实现equals 和 hashCode 方法。不重写这两个方法就会出现,相同的Key的数据也可以保存到Map集合中,就会出现**「key不唯一」**,如:当你使用map.get() 获取到的是“null”。
6.2.为什么必须重写equals 和 hashCode 方法
「必须举个例子说明,因为常常有人在这掉入陷阱」
public class Student {
private String name;
private int age; public Student(){} public Student(String name,int age){
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
} @Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试是否会保存相同的key
// 测试
public class TestMap {
public static void main(String[] args) {
Student student1 = new Student("张三", 18);
Student student2 = new Student("我是唯一", 28);
Student student3 = new Student("张三", 18); HashMap<Student, Student> studentHashMap = new HashMap<>();
studentHashMap.put(student1,student1);
studentHashMap.put(student2,student2);
studentHashMap.put(student3,student3); Iterator<Map.Entry<Student, Student>> iterator = studentHashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Student, Student> next = iterator.next();
System.out.println("key="+next.getKey()+"|value="+next.getValue());
} }
}
结果:相同的Key也保存成功了
测试通过key来获取value:关键来了最容易理解错的一步
错误示范:使用前面定义的对象引用来获取
// 错误示范:使用前面定义的对象引用来获取
Student student = studentHashMap.get(student3);
System.out.println(student);
错误示范结果:在存有相同的key的map集合中,拿到了value值
正确示范:使用新定义的对象引用来获取(定义的对象字段值是相同的)
// 正确示范:使用新定义的对象引用来获取:定义的对象字段值是相同的
Student student4 = new Student("张三", 18);
Student student = studentHashMap.get(student4);
System.out.println(student);
测试结果
实现equals 和 hashCode方法后再来正确示范
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
} @Override
public int hashCode() {
return Objects.hash(name,age);
}
实现后的测试结果
之所以会测不出来null的问题是因为在同一个类中测试往往喜欢使用前面的存储时的引用来操作map.get,那肯定是测不出来的,因为new的所有对象的地址肯定都是唯一的,所以用唯一的地址去获取,那肯定是可以get到的。正确的是新建一个内容相同,地址不同的引用去获取。
当使用TreeMap 时,作为key的对象还需要定制排序,这个操作和Set集合的定制排序一样,具体看Set 集合定制排序。
6.3.Map集合常用方法
HashMap<String, String> hashMap = new HashMap<>();
// 新增
hashMap.put("test1","test1-value");
hashMap.put("test2","test2-value");
// map和其他集合都重写了toString
System.out.println(hashMap);
System.out.println("集合大小"+hashMap.size());
System.out.println("集合大小是否=0:"+hashMap.isEmpty());
System.out.println("是否包含key=test1的元素:"+hashMap.containsKey("test1"));
System.out.println("是否包含value=test1的元素:"+hashMap.containsValue("test1"));
System.out.println("获取Key=test1的值:"+hashMap.get("test1"));
// 移除元素
hashMap.remove("test1");
System.out.println(hashMap);
// 集合合并
HashMap<String, String> hashMap1 = new HashMap<>();
hashMap1.put("test1","test1"); // 相同key的会覆盖原来的值
hashMap1.put("hashMap1","hashMap1-value");
hashMap.putAll(hashMap1);
System.out.println(hashMap);
输出结果
通过迭代器来遍历:「key 集合」、「value集合」和「key-value集合(entry)」
看代码就知道key集合是Set集合,value集合是Collection集合,entry集合是Set集合
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("testKey1","testValue1");
hashMap.put("testKey2","testValue2");
// 1.通过 键 来遍历,返回值是Set集合,Map的key是Set集合,特性也和Set一样的
//获取集合中所有key的Set集合
Set<String> keySet = hashMap.keySet();
//通过Set集合的迭代器进行遍历 (为了好理解分开两步来获取,开发时可以一步到位)
Iterator<String> iteratorKey = keySet.iterator();
while (iteratorKey.hasNext()) {
String next = iteratorKey.next();
System.out.println("key="+next);
}
// 2.通过 值 来遍历,返回值是Collection
// 获取集合中所有value的集合
Collection<String> collection = hashMap.values();
//通过集合的迭代器进行遍历
Iterator<String> iteratorValue = collection.iterator();
while (iteratorValue.hasNext()){
String next = iteratorValue.next();
System.out.println("value="+next);
}
// 3.通过 键值对 来遍历,返回值是Set集合
//获取Map元素对象的Set集合
Set<Map.Entry<String, String>> entrySet = hashMap.entrySet();
//通过Set集合的迭代器进行遍历
Iterator<Map.Entry<String, String>> iteratorEntry = entrySet.iterator();
while (iteratorEntry.hasNext()){
//单个map元素对象
Map.Entry<String, String> entry = iteratorEntry.next();
System.out.println("key="+entry.getKey()+",value="+entry.getValue());
}
输出结果
6.4.Map集合的三种遍历方式
前面讲了三种方式,前两种只是准对key 和 value的遍历。
在这里汇总的都是针对整个Map的遍历方式:「通用迭代器方式」,「forEach」和「forEach方法」。
// 通用遍历方式
Iterator<Map.Entry<String, String>> iteratorEntry = hashMap.entrySet().iterator();
while (iteratorEntry.hasNext()){
Map.Entry<String, String> entry = iteratorEntry.next();
System.out.println("key="+entry.getKey()+",value="+entry.getValue());
}
// forEach
for (Map.Entry<String, String> entry : hashMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println("key=" + key + ",value=" + value);
}
// 调用forEach方法,lambda语法
hashMap.forEach((key,value)->{
System.out.println("key="+key+",value="+value);
});
HashMap、LinkedHashMap、TressMap和ConcurrentHashMap……那么多的方法,只能自己去学习了。
7.Collections 集合工具类
Collections 是用于操作List、Set和Map等集合的工具类。该工具类提供了大量方法(功能)对集合元素进行排序、查询、修改等操作,对集合对象实现同步控制等方法。
7.1.排序问题
常用于对List集合元素排序的方法有
- 自然排序:void sort(List list);
- 比较器定制排序:void sort(List list,Comparator c);
- 反转排序:void reverse(List list);
- 随机排序:void shuffle(List list);
// 0.数据准备
List<String> arrayList = new ArrayList<>();
arrayList.add("z");
arrayList.add("a");
arrayList.add("c");
arrayList.add("b");
System.out.println("插入顺序:"+arrayList);
// 1.自然排序
Collections.sort(arrayList);
System.out.println("自然排序:"+arrayList);
// 2.比较器定制排序
Collections.sort(arrayList, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
System.out.println("定制排序:"+arrayList);
// 3.反转排序
Collections.reverse(arrayList);
System.out.println("反转排序:"+arrayList);
// 4.随机排序
Collections.shuffle(arrayList);
System.out.println("随机排序:"+arrayList);
7.2.解决线程安全问题
ArrayList 和 Vector 的主要区别是线程安全,虽然Vector可以解决线程安全,但不建议使用。可以通过集合工具类(Collections类)来解决ArrayList 并发访问线程安全问题。对于Set和Map集合线程安全问题,也可以通过Collections类来解决。
// 创建对象时使用
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<String>());
Collections 工具类还有查找操作、修改操作等等,更多知识自行学习。
原创不易,还请三联。更多优质文章
Java 集合详解 | 一篇文章解决Java 三大集合的更多相关文章
- Java异常详解——一篇文章搞定Java异常
目录 1. 异常的体系结构 2. 常见的异常 2.1 运行时异常 2.2 编译时异常 (编译时异常必须进行处理否则无法运行) 3. 异常的抓抛模型原理 4. 异常的处理 4.1 try - catch ...
- Java多线程详解——一篇文章搞懂Java多线程
目录 1. 基本概念 2. 线程的创建和启动 2.1. 多线程实现的原理 2.2.多线程的创建,方式一:继承于Thread类 2.3.多线程的创建,方式一:创建Thread匿名子类(也属于方法一) 2 ...
- Java枚举类与注解详解——一篇文章读懂枚举类与注解详
目录 一.枚举类 ① 自定义枚举类 ② enum关键字定义枚举类 ③ enum 枚举类的方法 ④ enum 枚举类实现接口 二.注解 ① 生成文档相关注解 ②注解在编译时进行格式检查 ③注解跟踪代码的 ...
- Java基础详解 (一)Java的类成员访问权限修饰词(以及类访问权限)
在一个类的内部,其成员(包括成员变量和成员函数)能否被其他类所访问,取决于该成员的修饰词.Java的类成员访问权限修饰词有四类:private,无(默认情况下),protected和public.其权 ...
- Java泛型详解(转)
文章转自 importNew:Java 泛型详解 引言 泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用.本文我们将从零开始来看一下Java泛型的设计,将会涉及到通配符处理 ...
- Java ClassLoad详解
Java ClassLoad详解 类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一.它使得 Java 类可以被动态加载到 Java 虚拟机中并执行.类加载器从 JDK 1. ...
- Java内部类详解(一)
(转自:http://blog.csdn.net/wangpeng047/article/details/12344593) 很多人对于Java内部类(Inner Class)都十分陌生,甚至听都没听 ...
- 集合详解之 Collection
集合详解之 Collection 先来看看集合的继承关系图,如下图所示: 其中: 外框为虚线的表示接口,边框为实线的表示类: 箭头为虚线的表示实现了接口,箭头为实线的表示继承了类. 为了方便理解,我隐 ...
- Java集合详解6:TreeMap和红黑树
Java集合详解6:TreeMap和红黑树 初识TreeMap 之前的文章讲解了两种Map,分别是HashMap与LinkedHashMap,它们保证了以O(1)的时间复杂度进行增.删.改.查,从存储 ...
随机推荐
- [BUUCTF]PWN——jarvisoj_test_your_memory
jarvisoj_test_your_memory 附件 步骤: 例行检查,32位程序,开启了nx保护 试运行一下程序,看看大概的情况 32位ida打开,习惯性的检索程序里的字符串,看到了有关flag ...
- 利用模块加载回调函数修改PE导入表实现注入
最近整理PE文件相关代码的时候,想到如果能在PE刚刚读进内存的时候再去修改内存PE镜像,那不是比直接对PE文件进行操作隐秘多了么? PE文件在运行时会根据导入表来进行dll库的"动态链接&q ...
- 替DateDif哭诉一把(Excel函数集团)
Excel中有个工作表函数DateDif,专门用来计算两日期之间的日差.月差.年差,传说十分好用. 具体用法在此就省略了,好奇的童鞋请自行*度~ 可是,在Excel里,他却是个"没户口&qu ...
- 解决android studio no debuggable process
这个问题可能是由多种因素造成的. 一.可能是buildtypes配置或选择错误,在对应module的build.gradle中确认如下配置 debug { debuggable true//一定要配置 ...
- LuoguP2097 资料分发1 题解
Content 有一些电脑,一部分电脑有双向数据线连接.如果一个电脑得到数据,它可以传送到的电脑都可以得到数据.现在,你有这个数据,问你至少将其输入几台电脑,才能使所有电脑得到数据. 数据范围:\(n ...
- fcntl 加锁模块
#!/usr/bin/python # coding:utf8 import os import sys import time import fcntl # 导入模块 class FLOCK(obj ...
- Flink使用IDEA进行jar打包
pom文件增加 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>mav ...
- Spring工具类 非spring管理环境中获取bean及环境配置
SpringUtils.java import org.springframework.beans.BeansException; import org.springframework.beans.f ...
- 【LeetCode】1064. Fixed Point 解题报告(C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 暴力求解 日期 题目地址:https://leetco ...
- 【LeetCode】66. Plus One 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 数九 采用进位 日期 [LeetCode] 题目地址 ...