文章目录
- 前言
- 一、 Map(双列集合)接口
- 二、 源码详解
- 三、 不可变集合
- 四、Stream流(适用于单列集合、双列集合、数组、一堆零散的数据)
-
- 1 Stream流是什么?
- 2 Stream的使用
-
- (1)获取Stream流
- (2)Stream流的中间方法
-
- ---- Stream\<T> filter(Predicate\<? super T> predicate) :过滤
- ---- Stream\<T> limit(long maxSize) :获取前几个元素
- ---- Stream\<T> skip(long n) :跳过前几个元素
- ---- Stream\<T> distinct() :元素去重,依赖(hashCode和equals方法)
- ---- static \<T> Stream\<T> concat(Stream a, Stream b) :合并a和b两个流为一个流
- ---- Stream\<R> map(Function\<T , R> mapper) :将流中的每个元素映射到另一个值,常用于进行转换流中的数据类型(常用!!!!!!!!!!)
- ---- Stream\<T> sorted() :排序
- ---- Stream\<T> sorted(Comparator\<? super T> comparator):按指定规则排序
- (3)Stream流的终结方法
-
- ---- void forEach(Consumer\<? super T> action) :遍历流中的数据
- ---- A[] toArray(IntFunction\<A[]> generator) :返回流中的数据组成的数组(常用!!!!!!)
- ---- R collect(Collector\<? super T, A, R> collector) :返回流中的数据组成的集合(返回值是List、Set、Map系列)(常用!!!!!!!!!)
- ---- Optional\<T> max(Comparator\<? super T> comparator):返回流中最大的元素的 Optional对象
- ---- Optional\<T> min(Comparator\<? super T> comparator):返回流中最小的元素的 Optional对象
- ---- long count() :返回流中元素个数
- ---- Optional\<T> reduce(BinaryOperator\<T> accumulator) | 将流中的元素反复结合起来,得到一个值
- 3 Stream的一些小练习
前言
- 双列集合的特点
(1)双列集合一次需要存一对数据,分别为键和值
(2)键不能重复,值可以重复
(3)键和值是一一对应的,每一个键只能找到自己对应的值
(4)键+值这个整体我们称之为“键值对”或者“键值对对象”,在Java中叫做“Entry对象”
在双列集合的体现中,一个接口、三个实现类
一、 Map(双列集合)接口
Map系列的常用方法,我们只要学习这个最顶层的接口里面有的。底下的实现类里面没有什么特殊方法。
1 常用方法
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() | 集合的长度,也就是集合中键值对的个数 |
v get(Object key) | 返回指定键映射到的值,如果此map不包含键的映射,则返回 null。 |
关于put方法需要单独讲一下,其有添加元素和修改元素两个功能
-
put方法的细节:添加、覆盖
(1)在添加数据的时候,如果键不存在,那么直接把键值对对象添加到map集合当中,方法返回null
(2)在添加数据的时候,如果键是存在的,那么会把原有的键值对对象覆盖,会把被覆盖的值进行返回。 -
关于get方法我喜欢和Objects工具类里面的判null方法Objects.isNull(obj)一起用。
Map<String,String> map = new HashMap<>();
// put方法
map.put("郭靖","黄蓉");
map.put("韦小宝","沐剑屏");
map.put("张无忌","周芷若");
map.put("杨过","小龙女");
map.put("令狐冲","任盈盈");
System.out.println(map); // {韦小宝=沐剑屏, 令狐冲=任盈盈, 张无忌=周芷若, 杨过=小龙女, 郭靖=黄蓉}
map.put("韦小宝","双儿"); // key相同,value覆盖
System.out.println(map); // {韦小宝=双儿, 令狐冲=任盈盈, 张无忌=周芷若, 杨过=小龙女, 郭靖=黄蓉}
// remove方法
map.remove("韦小宝"); // 删除key为韦小宝的键值对
System.out.println(map); // {令狐冲=任盈盈, 张无忌=周芷若, 杨过=小龙女, 郭靖=黄蓉}
// get方法
System.out.println(map.get("郭靖")); // 黄蓉
System.out.println(map.get("韦小宝")); // null
if (Objects.isNull(map.get("韦小宝"))) {
System.out.println("韦小宝不存在"); // 韦小宝不存在
} else {
System.out.println("韦小宝存在");
}
2 遍历方式
(1)使用map.keySet()方法
- Set <K> keySet() : 返回此map中包含的所有键组成的Set单列集合。
Map<String,String> map = new HashMap<>();
// put方法
map.put("郭靖","黄蓉");
map.put("韦小宝","沐剑屏");
map.put("张无忌","周芷若");
map.put("杨过","小龙女");
map.put("令狐冲","任盈盈");
System.out.println(map); // {令狐冲=任盈盈, 韦小宝=沐剑屏, 杨过=小龙女, 郭靖=黄蓉, 张无忌=周芷若}
// 遍历
// 1. keySet()方法
/*
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println(key + " : " + map.get(key));
}
*/
// 快捷键: 集合对象.keySet().for
for (String s : map.keySet()) {
System.out.println(s + " : " + map.get(s));
}
/*// 快捷键: map.keySet().forEach
map.keySet().forEach(s -> System.out.println(s + " : " + map.get(s)));*/
// 迭代器版本就不写了
/*
令狐冲 : 任盈盈
韦小宝 : 沐剑屏
杨过 : 小龙女
郭靖 : 黄蓉
张无忌 : 周芷若 */
(2)使用map.entrySet()方法
- Set<Map.Entry<K,V>> entrySet() :返回此map中包含的所有键值对(Entry对象)组成的Set单列集合。
- Entry.getKey() : 获取键
- Entry.getValue() : 获取值
Map<String,String> map = new HashMap<>();
// put方法
map.put("郭靖","黄蓉");
map.put("韦小宝","沐剑屏");
map.put("张无忌","周芷若");
map.put("杨过","小龙女");
map.put("令狐冲","任盈盈");
System.out.println(map); // {令狐冲=任盈盈, 韦小宝=沐剑屏, 杨过=小龙女, 郭靖=黄蓉, 张无忌=周芷若}
// 遍历
/*
// entrySet()方法
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
*/
// 快捷键: map.entrySet().for
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
/*令狐冲 : 任盈盈
韦小宝 : 沐剑屏
杨过 : 小龙女
郭靖 : 黄蓉
张无忌 : 周芷若*/
(3)lambda表达式遍历(这个最简单)
map也有直接的 forEach()方法遍历
Map<String,String> map = new HashMap<>();
// put方法
map.put("郭靖","黄蓉");
map.put("韦小宝","沐剑屏");
map.put("张无忌","周芷若");
map.put("杨过","小龙女");
map.put("令狐冲","任盈盈");
System.out.println(map); // {令狐冲=任盈盈, 韦小宝=沐剑屏, 杨过=小龙女, 郭靖=黄蓉, 张无忌=周芷若}
// lambda表达式遍历
/*
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String key, String value) { // 这个里面变量名是可以修改的
System.out.println(key + " : " + value);
}
}); */
map.forEach((key,value) -> System.out.println(key + " : " + value));
/*令狐冲 : 任盈盈
韦小宝 : 沐剑屏
杨过 : 小龙女
郭靖 : 黄蓉
张无忌 : 周芷若*/
3 实现类:HashMap、LinkedHashMap、TreeMap
(1)HashMap
---- HashMap的特点
(1)HashMap是Map里面的一个实现类。
(2)没有额外需要学习的特有方法,直接使用Map里面的方法就可以了。
(3)特点都是由键决定的: 无序(存和取的顺序不一样)、不重复、无索引 ---- 都是指键啊!
(4)HashMap跟HashSet底层原理是一模一样的,都是哈希表结构
---- HashMap的底层原理
参考HashSet底层原理:参考博客
---- 特别注意:自定义类作为键的处理方案
- HashMap底层是哈希表结构的
- 依赖hashCode方法和equals方法保证键的唯一如果键存储的是自定义对象,需要重写hashcode(利用哈希值和底层数组容量计算应插入的index)和equals方法(判断插入的是不是同一个东西)— 因为有哈希冲突的情况存在,所以必须还要进行一遍equals比较
(1)通常我们键都是使用不可变数据类型,数型和字符串等。因此万不得已一定要使用自定义类作为键,一定要重写hashcode和equals方法(底层原理是哈希表都需要注意这个)
(2)参考博客前面讲HashSet也强调过这个事情 - 如果值存储自定义对象,不需要重写hashCode和equals方法
【注】:下面的练习就有这方面的练习
---- 练习
Java的IDEA可以自动插入,很方便
public class Test {
public static void main(String[] args) {
Student s1 = new Student("zhangsan",23);
Student s2 = new Student("lisi",24);
Student s3 = new Student("wangwu",25);
HashMap<Student,String> map = new HashMap<>();
map.put(s1,"江苏");
map.put(s2,"浙江");
map.put(s3,"福建");
// 遍历map
map.forEach((key,value) -> {
System.out.println(key + "---->" + value);
}); // Student{name='zhangsan', age=23}---->江苏
// Student{name='lisi', age=24}---->浙江
// Student{name='wangwu', age=25}---->福建
Student s4 = new Student("wangwu",25);
map.put(s4,"湖南");
map.forEach((key,value) -> {
System.out.println(key + "---->" + value);
}); // Student{name='zhangsan', age=23}---->江苏
// Student{name='lisi', age=24}---->浙江
// Student{name='wangwu', age=25}---->湖南
// 可以看到,重写了Student类的equals()和hashCode()方法后,s4和s3的hash值相同,并且equals()方法也返回true,不在是比较地址值,所以s4会覆盖s3的值;
}
}
class Student{
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@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); // 使用Objects.hash()方法将name和age组合起来生成一个hash值;
}
}
我们也来写一写Python版本的吧
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __hash__(self):
# return super().__hash__() 这是默认的 hash 值,就还是基于内存地址的,调用的是 object 的 __hash__ 方法(和没有重写这个方法是没有区别的)
# 这种才是正确的重写 hash 方法的方式
# 基于 name 和 age 生成 hash 值
return hash((self.name, self.age))
def __eq__(self, other):
# return super().__eq__(other) 这是默认的判断相等的方法,就是比较内存地址,调用的是 object 的 __eq__ 方法(和没有重写这个方法是没有区别的)
# 这种才是正确的重写 eq 方法的方式
# 基于 name 和 age 判断对象是否相等
if isinstance(other, Student):
return self.name == other.name and self.age == other.age
return False
def __str__(self):
# 重写 __str__方法
return f"Student(name={
self.name}, age={
self.age})"
if __name__ == '__main__':
idx = 1
if idx == 0:
# 示例
student1 = Student("Alice", 20)
student2 = Student("Alice", 20)
student3 = Student("Bob", 21)
print(hash(student1)) # 和 student2 的 hash 一样
print(hash(student2)) # 和 student1 的 hash 一样
print(student1 == student2) # True
print(student1 == student3) # False
if idx == 1:
s1 &