Java进阶之Collections&Set接口&Map集合
一、Collections
Collections是一个操作集合的工具类(注意与Colletion的区别)。
1.1 常用方法
static void shuffle(List<?> list)
:打乱集合中内容的顺序static void sort(List list)
:对集合中的元素进行排序(自然排序)static void sort(List list, Comparator c)
:对集合中的内容进行排序,参数c表示比较器(比较器排序)static boolean addAll(Collection c, T... elements)
:批量添加元素
参数c:表示向哪个集合添加元素。
参数elements:表示要添加那些元素。 该参数是可变参数,可以向该参数位置传递任意个数据
1.1.1 自然排序
自然排序指的是事物本身就具备比较的功能,在程序中指集合里面的泛型必须要实现Comparable接口。
如果类实现Comparable接口,那么表示该类的对象就具备了比较的功能,就可以使用Collections.sort
方法对对集合中的元素进行排序。
当使用sort方法进行排序(自然排序)时,内部会自动调用compareTo方法比较两个元素的大小:
- 如果该方法的返回值是正数,表示调用者对象大于参数对象
- 如果该方法的返回值是0,表示两个对象相等
- 如果该方法的返回值是负数,表示调用者对象小于参数对象
实现Comparable接口需要做的是在compareTo方法中编写排序的规则
公式:
- 升序就是(调用者)减(参数对象)
- 需要什么属性进行排序,就让对应的属性值相减
public static void main(String[] args) {
//创建集合,保存整数
List<Integer> intList = new ArrayList<>();
//向集合中添加元素
intList.add(200);
intList.add(100);
intList.add(300);
//对集合中的内容进行排序
Collections.sort(intList);
//输出结果
System.out.println("intList:" + intList);//[100, 200, 300]
System.out.println("===================================");
//创建集合,保存字符串
List<String> strList = new ArrayList<>();
//添加字符串
strList.add("bbb");
strList.add("aaa");
strList.add("ccc");
//对集合中的内容进行排序
Collections.sort(strList);
//输出结果
System.out.println("strList:" + strList);//[aaa, bbb, ccc]
}
public class Person implements Comparable<Person>{
private String name;
private int age;
@Override
public int compareTo(Person o) {
//根据年龄进行升序排序。
return this.getAge() - o.getAge();
}
}
1.1.2 比较器排序
如果某个事物本身不具备比较的功能,那么我们就不能使用自然排序的方式对集合直接进行排序了。
如果事物不具备不叫的功能,那么我们可以找一个裁判(比较器)帮这些对象去比较,这样集合也可以进行排序。
Comparator是一个接口,该接口表示比较器,如果要用该比较器则需要使用实现类,这个实现类需要我们自己定义。
步骤:
- 1.创建集合并添加元素
- 2.定义一个类,实现Comparator接口
- 3.重写compare方法,并在该方法中定义比较的规则
- 4.调用Collections的sort方法,传递集合和比较器进行排序
当调用Collections的sort方法进行比较器排序时,那么系统内部会自动调用compare方法比较两个对象的大小。
- 如果该方法返回值是正数,说明第一个参数大于第二个参数。
- 如果该方法的返回值是0,说明两个对象相等。
- 如果该方法的返回值是负数,说明第一个参数小于第二个参数。
排序公式:
升序 第一个参数减第二个参数
/*
Comparator表示比较器,Rule类实现了Comparator接口,Rule类也就表示比较器了
*/
public class Rule implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
//根据年龄升序排序
return o1.getAge() - o2.getAge();
}
}
public class Student {
private String name;
private int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
get.set.构造方法
}
public static void main(String[] args) {
//创建集合并添加元素
List<Student> list = new ArrayList<>();
//添加元素
list.add(new Student("jack", 20));
list.add(new Student("rose", 18));
list.add(new Student("tom", 22));
//进行排序
Collections.sort(list, new Rule());
//输出结果
System.out.println(list);
}
1.1.3 小结
- 如果事物本身就具备比较的功能,那么我们可以直接使用sort方法进行排序
- 如果事物本身不具备比较的功能,那么我们可以找一个裁判帮这些对象进行排序
1.1.4 shuffle方法
public static void main(String[] args) {
//创建集合
List<String> list = new ArrayList<>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
list.add("php");
//输出集合
System.out.println("list:" + list);//[hello,world,java,php]
//对集合中的内容打乱顺序
Collections.shuffle(list);
//输出集合
System.out.println("list:" + list);//[world,hello,php,java]
}
1.1.5 addAll方法
学习addAll方法之前,我们首先要了解什么是可变参数。
可变参数是JDK5的个新特性。
如果一个方法的参数是可变参数,那么可以在该参数位置传递任意个数据。
可变参数格式:
修饰符 返回值类型 方法名(参数类型... 参数名) {
方法体;
return 返回值;
}
- 1.在调用可变参数的方法时,可以向可变参数位置传递任意个数据
- 2.可变参数的本质就是数组,所以可以将可变参数当成数组去使用
- 3.以为可变参数的本质是数组,所以调用可变参数的方法时,也可以向可变参数位置传递数组
注意:
- 1.一个方法中最多只能有一个可变参数
- 2.方法中可变参数必须在最后一个位置
public static int getSum(int... nums) {
//定义变量sum保存累加和
int sum = 0;
//遍历nums,将每一个元素累加到sum
for (int num : nums) {
sum += num;
}
return sum;
}
public static void main(String[] args) {
int sum = getSum(1,2,3,4,5);
System.out.println(sum);
int[] arr = {1,2,3};
int sum = getSum(arr);
System.out.println(sum);
}
了解了可变参数后,我们再来看addAll方法
static boolean addAll(Collection c, T... elements)
:批量向集合中添加元素。
参数c:表示向哪个集合中添加元素
参数elements:是一个可变参数,可以向该参数位置传递任意个数据。 该参数表示要添加的元素
public static void main(String[] args) {
//创建集合
List<String> list = new ArrayList<>();
//使用Collections中的addAll批量添加元素
Collections.addAll(list, "hello", "world", "java", "php");
System.out.println(list);//[hello,world,java,php]
}
二、Set接口
2.1 Set接口概述
Set是Collection下面的一个子接口,不能直接使用,需要使用实现类。
Set接口有以下特点:
- 无索引(不能根据索引获取元素的)
- 不可重复(不能保存重复元素)
- (大部分Set集合满足的特点)无序(按照什么顺序存,不一定按照什么顺序取)
public static void main(String[] args) {
//创建Set集合
Set<String> set = new HashSet<>();
//向集合中添加元素
set.add("张三丰");
set.add("张无忌");
set.add("灭绝师太");
set.add("金花婆婆");
//输出集合
//无序
System.out.println(set);//[灭绝师太, 张三丰, 张无忌, 金花婆婆]
//无索引
//System.out.println(set.get(1));
//不可重复
set.add("灭绝师太");
set.add("灭绝师太");
set.add("灭绝师太");
set.add("灭绝师太");
System.out.println(set);//[灭绝师太, 张三丰, 张无忌, 金花婆婆]
}
2.2 Set接口遍历
因为Set集合是没有索引的,所以不能使用普通for遍历,可以使用迭代器或增强for遍历,推荐使用增强for遍历。
public static void main(String[] args) {
//创建Set集合
Set<String> set = new HashSet<>();
//添加元素
set.add("张三丰");
set.add("张无忌");
set.add("灭绝师太");
set.add("金花婆婆");
//使用增强for遍历集合(和之前增强for遍历一模一样)
for (String str : set) {
System.out.println(str);
}
}
2.3 哈希值
哈希值是一个int数字,我们可以将哈希值看成对象的一个标识(特征码),如同每个人对应的身份证号码。
在Object中有一个方法叫做hashCode,可以获取对象的哈希值:
int hashCode()
:获取对象的哈希值
这个方法是根据对象的地址值计算的哈希值,但是根据地址值计算一般来说意义不大,我们更多的是希望哈希值是根据属性计算的,如果两个对象的属性完全相同,那么哈希值也应该相同。
如果想要自己定义哈希值的计算规则,需要重写hashCode方法。
public class Person {
private String name;
private int age;
//alt + insert 直接生成hashCode方法
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;//31是根据数学概率计算的值,让哈希值重复的概率适中
return result;
}
}
public static void main(String[] args) {
//创建Person对象
Person p = new Person("张三丰", 100);
//获取该对象的哈希值然后输出
System.out.println(p.hashCode());
Person p2 = new Person("张三丰", 100);
System.out.println(p2.hashCode());
System.out.println(p1.hashCode()==p2.hashCode());//true
}
哈希值是对象的一个标识,但并不是唯一的标识,对象的哈希值允许重复。
2.4 哈希表
2.5 HashSet保证唯一性
HashSet判断唯一性的过程
- 1.先比较对象的哈希值。
如果哈希值不同,肯定是不同的对象。
如果哈希值相同,不一定是同一个对象。 - 2.如果哈希值相同,还会调用equals进行比较。
如果equals的结果是true,表示对象相同。
如果equals的结果是false,表示对象不同
结论:
如果使用HashSet存储自定义对象并保证唯一性(对象的属性相同就看成是同一个对象),需要同时重写hashCode和equals,缺一不可。
public class Student {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
get..set..构造方法
}
public static void main(String[] args) {
//创建HashSet集合
Set<Student> set = new HashSet<>();
//添加元素
set.add(new Student("柳岩", 36));
set.add(new Student("李小璐", 34));
set.add(new Student("马蓉", 32));
set.add(new Student("柳岩", 36));
//遍历集合,输出集合中的每一个学生对象
for (Student stu : set) {
System.out.println(stu);
}
}
三、Map接口
3.1 Map概述
我们之前学的Collection是单列集合,即一次只保存一个数据。
Map是双列集合,里面的每一个元素都是键值对,一个键对应一个值。Map<K,V>
有两个泛型,K表示键的类型,V表示值的类型。
3.2 Map常用方法
- (常用)
V put(K key, V value)
:向Map集合中添加键值对元素。key表示键,value表示值。如果在添加时key重复,会使用新的值覆盖掉原来的值 - (常用)
V get(Object key)
:根据键获取对应的值 boolean containsKey(Object key)
:判断map集合中是否包含指定的键V remove(Object key)
:根据键删除整个的键值对,返回值是被删除掉的值
Map是一个接口,如果要用需要使用实现类,最常用的实现类是HashMap()
;
public static void main(String[] args) {
//创建双列集合,key使用整数(编号),value使用字符串(人名)
Map<Integer, String> map = new HashMap<>();
//(常用)V put(K key, V value):向Map集合中添加键值对元素
map.put(100, "刘德华");
map.put(200, "张学友");
map.put(300, "郭富城");
//添加元素
map.put(200, "黎明");
//输出
System.out.println(map);
// (常用)V get(Object key):根据键获取对应的值。
System.out.println("获取100键对应的值:" + map.get(100));//刘德华
System.out.println("获取10000键对应的值:" + map.get(10000));//null 如果获取的键不存在,结果是null。
//boolean containsKey(Object key):判断map集合中是否包含指定的键。
System.out.println("判断Map集合中是否有100键:" + map.containsKey(100));
System.out.println("判断Map集合中是否有10000键:" + map.containsKey(10000));
//V remove(Object key):根据键删除整个的键值对,返回值是被删除掉的值。
//删除100这个键对应的整个元素
String value = map.remove(100);
System.out.println(map);//{200=黎明, 300=郭富城}
System.out.println("value:" + value);//刘德华
}
3.3 Map的遍历
3.3.1 keySet遍历
Map集合不能通过迭代器或者增强for直接进行遍历。
如果要遍历Map集合,我们可以获取到Map集合中所有的键,将所有的键放入到Set集合中, 然后遍历Set集合,拿到集合中的每一个键,然后根据键去Map集合中获取对应的值。
Map中有一个方法叫做keySet,可以获取到所有的键
Set<K> keySet()
:获取Map集合中所有的键,并放入到Set集合中返回
keySet遍历步骤:
- 1.调用Map集合的keySet方法获取到所有的键,然后放入到Set集合中。
- 2.遍历Set集合,拿到里面的每一个键。
- 3.调用Map集合的get方法,根据键获取对应的值
public static void main(String[] args) {
//创建集合
Map<String, String> map = new HashMap<>();
//添加元素
map.put("it001", "张三");
map.put("it002", "李四");
map.put("it003", "王叔叔");
//开始遍历
//1. 调用Map集合的keySet方法获取到所有的键,然后放入到Set集合中。
Set<String> keys = map.keySet();
//2. 遍历Set集合,拿到里面的每一个键。
for (String key : keys) {
//3. 调用Map集合的get方法,根据键获取对应的值。
String value = map.get(key);
System.out.println(key + "-----" + value);
}
}
3.3.2 entrySet遍历
Map集合还有一种遍历方式是通过Entry对象的方式进行遍历。
Map集合中每一个元素其实都是一个键值对,每一个键值对都是一个Entry对象。
如果要通过Entry对象的方式遍历Map集合,我们需要先获取到Map集合中所有的Entry对象,将这些Entry对象放入到一个Set集合,然后 遍历Set集合,拿到Set集合中每一个Entry对象,根据该Entry对象获取键以及对应的值。
在Map集合中有一个方法叫做entrySet,可以获取到所有的Entry对象:
Set<Map.Entry<K,V>> entrySet()
:获取到所有的Entry对象并放入到Set集合中返回
Entry中获取键和值的方法:
K getKey()
:获取到Entry对象中的键V getValue()
:获取Entry对象中的值
entrySet遍历步骤:
- 1.调用Map集合的entrySet方法获取到所有的Entry对象并放入到Set集合中返回
- 2.遍历Set集合,拿到里面的每一个Entry对象
- 3.调用Entry对象的getKey和getValue获取到键和值
public static void main(String[] args) {
//创建集合
Map<String, String> map = new HashMap<>();
//添加元素
map.put("it001", "张三");
map.put("it002", "李四");
map.put("it003", "王叔叔");
//开始遍历
//1. 调用Map集合的entrySet方法获取到所有的Entry对象并放入到Set集合中返回
Set<Map.Entry<String, String>> entries = map.entrySet();
//2. 遍历Set集合,拿到里面的每一个Entry对象
for (Map.Entry<String, String> entry :entries) {
//3. 调用Entry对象的getKey和getValue获取到键和值
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "----" + value);
}
}
推荐使用 keySet
方式(键找值)。
3.3.3 HashMap判断键的唯一性
HashMap保证唯一性(键)的方式和HashSet是一模一样,因为HashSet内部就是在使用HashMap保存数据。
判断唯一性的方式:
- 1.先比较两个对象的哈希值。
如果对象的哈希值不同,肯定是不同的对象
如果对象的哈希值相同,不一定是同一个对象 - 2.然后比较两个对象的equals方法
如果equals方法结果是true,表示两个对象相同
如果equals方法结果是false,表示两个对象不同
如果HashMap要保证键的唯一性(属性相同就看成是同一个对象),需要同时重写hashCode和equals方法。
public class Student {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
get..set..构造方法...toString
}
public static void main(String[] args) {
//创建Map集合,键是学生对象,值家庭住址
Map<Student, String> map = new HashMap<>();
//向Map集合中添加元素
map.put(new Student("刘备", 18), "蜀国");
map.put(new Student("曹操", 16), "魏国");
map.put(new Student("孙权", 20), "吴国");
//添加
map.put(new Student("刘备", 20), "巴蜀");
//遍历Map集合,输出里面的每一个键和每一个值
//调用keySet方法,获取到Map集合中所有的键
Set<Student> keys = map.keySet();
//遍历Set集合,拿到里面的每一个键
for (Student key : keys) {
//根据键获取对应的值
String value = map.get(key);
System.out.println(key + "---------------" + value);
}
}
3.3.4 LinkedHashMap
LinkedHashMap是Map接口下一个不常用的实现类,内部除了有一个哈希表之外还有一个链表,链表可以保证有序。
public static void main(String[] args) {
//创建Map集合
Map<String, String> map = new LinkedHashMap<>();
map.put("it001", "张三");
map.put("it002", "李四");
map.put("it003", "王五");
System.out.println(map);//{it001=张三, it002=李四, it003=王五}
}