自学Java手记:Map集合,Arrays工具类和Lambda表达式

Map常见API和小tips

tips:put方法的细节,在添加数据的时候,如果键不存在,那么直接把键值对对象添加到map集合当中,方法返回null;如果键是存在的,那么会把原有的键值对对象覆盖,会把被覆盖的值进行返回。

对象的创建 复制代码
Map<String,String> map=new HashMap<>();

Map的遍历方式

<1>

增强for 复制代码
Set<String> keys=map.keySet();//获取所有的键,把这些键放到一个单列集合中
for (String key:keys){
    System.out.println(map.get(key));
    //利用map集合中的键获取对应的值
    String value=map.get(key);
    System.out.println(value);
}

<2>

通过键值对对象进行遍历 复制代码
Set<Map.Entry<String,String>> entries=map.entrySet();
//通过一个方法获取所有的键值对对象,返回一个set集合
for(Map.Entry<String,String> entry:entries){
    String key=entry.getKey();
    String value=entry.getValue();
    System.out.println(key+":"+value);
}

前提:借Arrays工具类初步学习Lambda表达式

tips:binarySearch(二分查找法查找元素),数组中的元素必须是有序的、升序的,如果要查找的元素是存在的,那么返回的是真实索引;如果查找的元素是不存在的,返回的是应该插入点(指该元素在插入后数组仍能保持升序的位置)的负值-1。copyOfRange(指定范围拷贝数组),包头不包尾,包左不包右。sort,

sort升序排列的原理和降序排列的实现 复制代码
Integer[] arr={4,3,5,6,8,11,9};
//第二个参数是一个接口,在调用方法的时候,需要传递这个接口的实现类对象,作为排序的规则。
//在这里,这个实现类我们只要使用一次,所以就没有必要单独的去写一个类,直接采取匿名内部类的方式。
//原理:插入排序+二分查找的方式进行排序。
//默认把0索引的数据当作有序的序列,1索引到最后认为是无序序列。
//遍历无序序列得到里面每一个元素,假设当前遍历到的元素是A元素。
//把A往有序序列中进行插入,在插入的时候,利用二分查找确定A元素的插入点。
//拿着A元素和插入点的元素进行比较,比较的规则是compare方法的方法体。
//如果方法的返回值是负数,拿着A和前面的数据进行比较。
//如果方法的返回值是正数或0,拿着A和后面的数据进行比较。
//直到能确定A的最终位置。

//compare方法的形参:参数一o1,表示在无序序列中,遍历得到的每一个元素;参数二o2,有序序列中的元素

//返回值:负数,表示当前要插入的元素是小的,放在前面;正数,和负数相反;0,表示当前要插入的元素和现在的元素比是一样的,也要放在后面

//简单理解:o1-o2,升序排列;o2-o1,降序排列
Arrays.sort(arr,new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1-o2;
    }
});

知识衍生

1.匿名内部类:就是一个"没有名字"的类,在创建对象时直接定义。(
  • 正常类:class MyComparator implements Comparator<Integer>然后起名 MyComparator
  • 匿名类:直接new Comparator<Integer>() { ... }没有类名

匿名内部类就是"临时工",用完就扔,专门解决这种一次性的实现需求。

2.怎么知道哪个接口只有一个抽象方法?
方法1:看文档/源码注解:
csharp 复制代码
@FunctionalInterface  // ← 有这个注解就是函数式接口
public interface Comparator<T> {
    int compare(T o1, T o2);
    // 可以有多个默认方法、静态方法,但只能有一个抽象方法
}
常见函数式接口(函数式接口(或称为只有一个抽象方法的接口)必须提供具体实现(重写)才能使用):
  • Comparator<T>- 比较器
  • Runnable- 线程任务(只有一个run()方法)
  • Callable<V>- 带返回值的任务
  • Consumer<T>- 消费者(接收参数无返回值)
  • Supplier<T>- 供应商(无参数有返回值)
  • Function<T,R>- 函数(接收T返回R)
  • Predicate<T>- 断言(判断条件)
方法2:看抽象方法数量:
  • 只有一个抽象方法 (不包括default默认方法和static静态方法)
  • 比如Comparator:只有compare是抽象方法,其他如reversed()是默认方法
方法3:用Lambda表达式验证,能用Lambda表达式简写的接口就是函数式接口:
java 复制代码
// 匿名内部类
Arrays.sort(arr, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
});
// Lambda简写(证明Comparator是函数式接口)
Arrays.sort(arr, (o1, o2) -> o1 - o2);
3.接口的实现类对象是什么?

接口就像"标准/合同",Comparator这个接口规定:

vbnet 复制代码
    interface Comparator<Integer> {
    int compare(Integer o1, Integer o2);
}

任何想当我"实现类"的,(在这个情境下)必须有一个compare方法,能比较两个数谁大谁小,并返回整数。但接口自己是个空壳,没有具体比较逻辑(不知道是升序还是降序)。

"实现类对象"就是"按合同干活的具体工人"。你要用Arrays.sort排序,Java问: "你想按什么规则排序?" 你不能只说"按Comparator规则",你要真的给一个能干活的具体对象。

new Comparator() { ... }→ 当场造一个"临时工人",这个临时工人实现了Comparator接口(因为有compare方法),这个"临时工人对象"直接被传给sort方法使用,用完后这个"临时工人"就被丢弃(因为只需要这一次)。

Lambda表达式(简化匿名内部类的书写)

函数式编程思想,忽略面向对象的复杂语法,强调做什么,而不是谁去做

格式
typescript 复制代码
public class methodDemo {
    public static void main(String[] args) {
        //调用一个方法的时候,如果方法的形参是一个接口,那我们要传递这个接口的实现类对象
        //如果实现类对象只要用到一次,就可以用匿名内部类的形式进行书写
        method(new swim() {
            @Override
            public void swimming() {
                System.out.println("正在游泳~~~");
            }
        });
        //利用lambda表达式进行改写
        method(
                ()->{
                    System.out.println("正在游泳~~~");
                }
        );
    }
    public static void method(swim s){
        s.swimming();
    }
    interface swim{
        public abstract void swimming();
    }
}

<3>

Lambda表达式 复制代码
Map<String,String> map=new HashMap<>();
//forEach其实就是利用第二种方式进行遍历,依次得到每一个键和值
//再调用accept方法
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);
});

HashMap的特点

  • HashMap是Map里的一个实现类
  • 没有额外需要学习的特有方法,直接使用Map里面的方法就行了
  • 无序、不重复、无索引
  • HashMap和HashSet底层原理是一样的,都是哈希表结构(哈希表是一种高效的 "键-值"存储结构 。)你可以把它想象成一个智能抽屉柜

核心原理(现实比喻):

假设你有一个抽屉柜(哈希表),每件物品(值)都有一个唯一标签(键)

但你不用逐个抽屉找,而是有一个智能计算器(哈希函数) ,输入标签就能直接算出抽屉编号

markdown 复制代码
例如:存"张三:电话138xxxx"
1. 对"张三"用哈希函数计算 → 得到编号"3"
2. 直接打开3号抽屉 → 存入电话
3. 取电话时同样计算编号 → 瞬间找到

关键特点
  1. 快速存取:理想情况下,无论存多少数据,查找时间几乎恒定(O(1))。

  2. 哈希碰撞

    如果"李四"也计算出编号3(不同标签算出相同编号),则两个物品会放在同一个抽屉的链表里

  3. 动态扩容:抽屉不够时,自动增加抽屉并重新分配物品。


在HashMap中的实现
  • 数组(抽屉柜) :存放数据。
  • 链表/红黑树(抽屉内的格子) :解决哈希碰撞。
  • 哈希函数(智能计算器) :将键(如"张三")转换为数组下标。

为什么高效?

普通列表找"张三"需要遍历所有项(O(n)),而哈希表通过计算直接定位 ,跳过了大部分比较步骤。这就是为什么HashMap在Java中广泛用于快速查找、去重和缓存。

LinkedHashMap

  • 由键决定:有序、不重复、无索引
  • 这里的有序指的是保证存储和取出的元素顺序一致
  • 原理:底层数据结构依然是哈希表,只是每个键值对元素又额外多一个双链表的机制记录存储的顺序

TreeMap

  • TreeMap和TreeSet底层原理一样,都是红黑树结构
  • 由键决定特性:不重复、无索引、可排序
  • 可排序:对键进行排序
  • 小tips:默认按照键的从小到大进行排序,也可以自己规定键的排序规则
示例1
java 复制代码
package TreeMapDdemo;

import java.util.Comparator;
import java.util.TreeMap;

public class TreeMapdemo {
    public static void main(String[] args) {
        //创建集合对象
        TreeMap<Integer,String> tm = new TreeMap<>();
        //添加元素
        tm.put(1,"奥利奥");
        tm.put(5,"雪碧");
        tm.put(3,"可乐");
        tm.put(2,"乐事");
        tm.put(4,"好丽友");
        System.out.println(tm);//Integer,Double按照键升序排列,String按照键字母在ASCII码表中对应的数字升序进行排列
        TreeMap<Integer,String> tm2 = new TreeMap<>(new Comparator<Integer>(){
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;//降序,o1:当前要添加的元素,o2:当前已经在红黑树中存在的元素
            }
        });
        tm2.put(1,"奥利奥");
        tm2.put(5,"雪碧");
        tm2.put(3,"可乐");
        tm2.put(2,"乐事");
        tm2.put(4,"好丽友");
        System.out.println(tm2);
    }
}

输出结果:

ini 复制代码
{1=奥利奥, 2=乐事, 3=可乐, 4=好丽友, 5=雪碧}
{5=雪碧, 4=好丽友, 3=可乐, 2=乐事, 1=奥利奥}
示例2
ini 复制代码
package TreeMapDdemo;

import java.util.TreeMap;

public class TreeMapdemo2 {
    public static void main(String[] args) {
        TreeMap<Student,String> tm=new TreeMap<>();
        Student s1=new Student("zhangsan",19);
        Student s2=new Student("lisi",18);
        Student s3=new Student("wangwu",20);
        tm.put(s1,"江苏");
        tm.put(s2,"浙江");
        tm.put(s3,"上海");
        System.out.println(tm);
    }
}
typescript 复制代码
package TreeMapDdemo;

public class Student implements Comparable<Student>{
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student(String name, int age) {
        this.age = age;
        this.name = name;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student [age=" + age + ", name=" + name + "]";
    }

    @Override
    public int compareTo(Student o) {
        //比较规则:按照学生年龄升序排列,年龄一样按照姓名的字母排列,同姓名年龄视为同一个人
        //this:表示当前要添加的元素
        //o:表示已经在红黑树中存在的元素
        //返回值:负数表示当前要添加的元素是小的,存左边,整数反之,0表示已经存在,舍弃
        int i=this.getAge()-o.getAge();
        i=i==0?this.getName().compareTo(o.getName()):i;
        return i;
    }
}
示例3
arduino 复制代码
package TreeMapDdemo;

import java.util.TreeMap;

public class TreaaMapdemo3 {
    public static void main(String[] args) {
        //统计字符串中每一个字符出现的次数
        //利用map集合进行统计,HashMAp、TreeMap,键表示要统计的内容,值表示次数
        //未要求对结果排序,默认使用HashMap,要求排序使用TreeMap
        String str="aabdcbcbbcaabb";
        TreeMap<Character,Integer> map=new TreeMap<>();
        for (int i = 0; i < str.length(); i++) {
            char ch=str.charAt(i);
            if(map.containsKey(ch)){
                int count=map.get(ch);
                count++;
                map.put(ch,count);
            }
            else{
                map.put(ch,1);
            }
        }
        //遍历集合,更改格式
        StringBuilder sb=new StringBuilder();
        map.forEach((k,v)-> sb.append(k).append("(").append(v).append(")"));
        System.out.println(sb.toString());
    }
}

输出结果:a(4)b(6)c(3)d(1)

知识衍生

StringBuilder、StringJoiner与String的区别与联系

一、String(不可变字符串)

核心特性:字符串常量,一旦创建内容不可改变

ini 复制代码
String s1 = "hello";
s1 = s1 + " world";  // 看似修改,实际创建了新对象
特点:
  1. 不可变性:每次"修改"都会创建新对象
  2. 存储位置:字符串常量池(享元模式)
  3. 线程安全:因为不可变,天然线程安全
  4. 效率问题:频繁拼接时性能差,产生大量垃圾对象

二、StringBuilder(可变字符串)
基本特性:
ini 复制代码
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
String result = sb.toString();  // 一次性转换为String
核心知识点:
  1. 可变性:内部维护可变的char数组,直接修改内容

  2. 非线程安全:不提供同步机制,性能更高

  3. 初始容量:默认16字符,可自动扩容

  4. 常用方法

    scss 复制代码
    append()    // 追加内容
    insert()    // 插入内容
    delete()    // 删除指定范围
    reverse()   // 反转字符串
    toString()  // 转换为String
使用场景:
  • 循环拼接字符串(图中用法)
  • 需要大量字符串操作的场景
  • 单线程环境下
性能优势示例:
ini 复制代码
// 错误:产生大量临时对象
String result = "";
for (int i = 0; i < 1000; i++) {
    result += i;  // 每次循环创建新String对象
}

// 正确:只创建一个StringBuilder对象
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);  // 修改同一个对象
}
String result = sb.toString();

三、StringJoiner(字符串连接器)
基本特性(Java 8+):
csharp 复制代码
StringJoiner sj = new StringJoiner(", ", "[", "]");
sj.add("A").add("B").add("C");
String result = sj.toString();  // 结果:[A, B, C]
核心知识点:
  1. 专门用于连接多个字符串

  2. 支持分隔符、前缀、后缀

  3. 底层实现:基于StringBuilder

  4. 常用方法

    scss 复制代码
    add()      // 添加元素
    merge()    // 合并另一个StringJoiner
    length()   // 当前长度
    toString() // 转换为String
使用场景:
  1. 集合转字符串(完美替代手动拼接)
  2. 格式化输出
  3. CSV文件生成
示例对比:
ini 复制代码
// 传统方式(繁琐)
StringBuilder sb = new StringBuilder("[");
for (String s : list) {
    if (sb.length() > 1) sb.append(", ");
    sb.append(s);
}
sb.append("]");

// StringJoiner方式(简洁)
StringJoiner sj = new StringJoiner(", ", "[", "]");
list.forEach(sj::add);

四、三者关系与选择策略
关系图:
scss 复制代码
┌─────────────────────────────────────────┐
│               String(不可变)            │
│           toString()方法返回             │
└─────┬────────────────────┬──────────────┘
      │                    │
      ↓                    ↓
┌─────────────────┐   ┌─────────────────────┐
│ StringBuilder   │←──│  StringJoiner       │
│ (可变,高效拼接) │   │ (专门用于连接,封装了│
│                 │   │  StringBuilder功能)│
└─────────────────┘   └─────────────────────┘
选择策略:
场景 推荐工具 理由
固定的字符串常量 String 不可变,线程安全,可缓存
单线程大量字符串拼接 StringBuilder 性能最优,避免对象创建
多线程字符串操作 StringBuffer 线程安全版本
集合转指定格式字符串 StringJoiner 语法简洁,功能专一
简单连接少量字符串 String的 + 代码简洁,编译器优化

五、最佳实践总结
1. 性能优先原则
ini 复制代码
// 避免
String s = "";
for (Data data : list) {
    s += data.toString();  // 性能杀手!
}

// 采用
StringBuilder sb = new StringBuilder();
for (Data data : list) {
    sb.append(data.toString());
}
String result = sb.toString();
2. 格式化优先原则
ini 复制代码
// 避免手动拼接分隔符
StringBuilder sb = new StringBuilder();
boolean first = true;
for (String item : items) {
    if (!first) sb.append(", ");
    sb.append(item);
    first = false;
}

// 使用StringJoiner
StringJoiner sj = new StringJoiner(", ");
items.forEach(sj::add);
3. 链式调用与流畅API
sql 复制代码
String result = new StringJoiner("\n")
    .add("姓名: " + name)
    .add("年龄: " + age)
    .add("地址: " + address)
    .toString();
4. 内存优化
scss 复制代码
// 预估容量,避免频繁扩容
StringBuilder sb = new StringBuilder(1024);  // 预分配空间

// 清空重用(注意:new StringBuilder更清晰)
sb.setLength(0);  // 清空内容

六、补充知识点
1. StringBuffer
  • 线程安全的StringBuilder
  • 方法使用synchronized修饰
  • 性能略低于StringBuilder
2. 编译器优化
vbscript 复制代码
// 编译器会将以下代码优化为StringBuilder
String s = "A" + "B" + "C";
// 优化后:new StringBuilder().append("A").append("B").append("C").toString()
3. Java 8+的String增强
vbnet 复制代码
// 静态join方法
String result = String.join(", ", "A", "B", "C");  // "A, B, C"
String result = String.join(", ", list);          // 连接集合

七、一句话总结
  • String :字符串常量,只读,适用于固定文本
  • StringBuilder :字符串构造器 ,适用于单线程大量拼接
  • StringJoiner :字符串连接器 ,适用于格式化拼接集合元素
  • 三者最终都要调用toString()返回真正的String对象
相关推荐
码头整点薯条2 小时前
对接第三方服务踩坑:属性大小写不匹配导致数据解析失败,一个注解搞定!
java
Wpa.wk2 小时前
性能测试工具 - JMeter工具组件介绍一
java·经验分享·测试工具·jmeter·性能测试
虫小宝2 小时前
个微iPad协议场景下Java后端的协议解析异常排查与问题定位技巧
java·svn·ipad
程序媛徐师姐2 小时前
Java基于微信小程序的鲜花销售系统,附源码+文档说明
java·微信小程序·鲜花销售小程序·java鲜花销售小程序·鲜花销售微信小程序·java鲜花销售系统小程序·java鲜花销售微信小程序
菜还不练就废了2 小时前
26.1.12|JavaSE复盘补充,整到哪里算哪里(一)
java·开发语言
摇滚侠2 小时前
Kong API 列表加 curl 访问案例 通过 curl 修改 router 的 method
java·kong
ShuiShenHuoLe2 小时前
maven配置阿里源
java·数据库·maven
H_z_q24012 小时前
RHCE的时间服务器与NTP、chrony
java·运维·服务器
悟空码字2 小时前
三步搞定短信验证码!SpringBoot集成阿里云短信实战
java·spring boot·后端