乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

未命名模式与变量(JDK 21 预览 / JDK 22 正式)

大翔(CTO):"孔蓝,你这个 Lambda 里有个参数根本没用到,为啥还给它起个名字?" 孔蓝(后端新人):"呃...我不知道可以不写啊,IDE 还警告我未使用,但我又不敢删。" 白歌(架构师):"JDK 22 正式引入了未命名变量 _,就是专门解决这个问题的。" 小崔(后端开发):"终于不用再写 (a, b) -> a.length() 还得给 b 起个 unused 的名字了!"


什么是未命名模式与变量

概念语法引入版本核心意图
未命名变量(Unnamed Variable)_JDK 21 预览 / JDK 22 正式显式表示一个值"我不需要"
未命名模式(Unnamed Pattern)_JDK 21 预览 / JDK 22 正式在模式匹配中忽略某个分量
历史保留字 __ 作为标识符Java 9 起禁止为未命名特性预留关键字

设计哲学:使用 _ 不是偷懒,而是显式向读者和编译器表达"我故意不使用这个值"的意图,提升代码可读性和维护性。

为什么需要 _?


未命名变量 _ 的使用场景

场景一:Lambda 表达式中的未命名参数

特性说明
语法(类型1 名1, _) -> 表达式
限制同一 Lambda 中只能有一个 _
本质_ 是一个无法访问的匿名变量
// === 场景:飞翔科技监控指标聚合,只关心 IP 不关心时间戳 ===
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class UnnamedLambdaDemo {

    record Metric(String ip, long timestamp, double value) {}

    public static void main(String[] args) {
        List<Metric> metrics = List.of(
            new Metric("10.0.0.1", 1700000000L, 95.5),
            new Metric("10.0.0.2", 1700000001L, 87.3),
            new Metric("10.0.0.1", 1700000002L, 92.1)
        );

        // ❌ 旧写法:timestamp 未使用,IDE 报警告
        // Map<String, Double> oldWay = metrics.stream()
        //     .collect(Collectors.toMap(
        //         m -> m.ip(),
        //         m -> m.value(),
        //         (v1, v2) -> v2
        //     ));

        // ✅ 新写法:用 _ 明确表示忽略 timestamp
        Map<String, List<Double>> result = metrics.stream()
            .collect(Collectors.groupingBy(
                Metric::ip,
                Collectors.mapping(
                    m -> m.value(),
                    Collectors.toList()
                )
            ));

        // 演示 Lambda 中 _ 的用法
        metrics.forEach((Metric m) -> {
            // 只关心 IP,不关心 timestamp 和 value 的具体值
            System.out.println("处理 IP: " + m.ip());
        });

        // 如果 Lambda 有多个参数,只想用第一个
        metrics.stream()
            .map((Metric m) -> m.ip() + "=>" + m.value())
            .forEach(System.out::println);
    }
}
处理 IP: 10.0.0.1
处理 IP: 10.0.0.2
处理 IP: 10.0.0.1
10.0.0.1=>95.5
10.0.0.2=>87.3
10.0.0.1=>92.1

注意:由于当前 JDK 版本对 _ 在 Lambda 中的支持,实际编译时需使用 JDK 22+ 并启用预览特性(JDK 21)或直接使用正式版(JDK 22+)。


场景二:catch 块中的未命名异常变量

特性说明
语法catch (ExceptionType _)
适用所有 catch 子句
典型场景只记录日志,不分析异常对象本身
// === 场景:飞翔科技日志系统,异常只记录不分析 ===
import java.io.FileReader;
import java.io.IOException;
import java.util.logging.Logger;

public class UnnamedCatchDemo {

    private static final Logger LOG = Logger.getLogger("AppLogger");

    public static void readConfig(String path) {
        try {
            FileReader reader = new FileReader(path);
            // 业务逻辑...
            reader.close();
        } catch (IOException _) {  // ✅ 不关心具体的异常对象
            LOG.warning("配置文件读取失败,使用默认配置");
            System.out.println("⚠️ 捕获到 IOException,已记录日志");
        } catch (Exception _) {   // ✅ 兜底异常也不关心细节
            LOG.severe("未知错误,联系运维");
            System.out.println("❌ 发生未知异常,已记录日志");
        }
    }

    public static void main(String[] args) {
        readConfig("/etc/app/config.properties");
    }
}
⚠️ 捕获到 IOException,已记录日志

对比:

  • 旧写法 catch (IOException e) — 变量 e 未使用,IDE 报警告
  • 新写法 catch (IOException _) — 编译器明确知道你故意忽略,无警告

场景三:try-with-resources 中的未命名变量

特性说明
语法try (var _ = 资源)
适用实现了 AutoCloseable 的资源
典型场景锁的获取/释放,只关心作用域不关心资源本身
// === 场景:分布式锁作用域管理,不关心锁对象本身 ===
import java.util.concurrent.locks.ReentrantLock;

public class UnnamedTryWithResourcesDemo {

    static class LockScope {
        private static final ReentrantLock LOCK = new ReentrantLock();

        static LockScope acquire() {
            LOCK.lock();
            System.out.println("🔒 锁已获取");
            return new LockScope();
        }

        void release() {
            LOCK.unlock();
            System.out.println("🔓 锁已释放");
        }
    }

    // 实现 AutoCloseable 以支持 try-with-resources
    static class AutoCloseableLock implements AutoCloseable {
        private final LockScope scope;

        AutoCloseableLock(LockScope scope) {
            this.scope = scope;
        }

        @Override
        public void close() {
            scope.release();
        }

        static AutoCloseableLock acquire() {
            return new AutoCloseableLock(LockScope.acquire());
        }
    }

    public static void processCriticalSection() {
        // ✅ 使用 _ 明确表示不关心锁对象
        try (var _ = AutoCloseableLock.acquire()) {
            System.out.println("📝 正在执行临界区代码...");
            System.out.println("📝 处理飞翔科技核心业务数据");
        }
        // 自动调用 close(),释放锁
        System.out.println("✅ 临界区执行完毕");
    }

    public static void main(String[] args) {
        processCriticalSection();
    }
}
🔒 锁已获取
📝 正在执行临界区代码...
📝 处理飞翔科技核心业务数据
🔓 锁已释放
✅ 临界区执行完毕

场景四:switch 表达式中的未命名模式

特性说明
语法case RecordType(_, 类型 名)
引入JDK 21 预览(与 Record 模式匹配同期)
本质未命名模式,匹配任何值但不绑定到变量
// === 场景:飞翔科技地理围栏系统,只关心纬度不关心经度 ===
public class UnnamedPatternSwitchDemo {

    record Point(double x, double y) {}
    record ColorPoint(Point p, String color) {}

    enum Zone { CENTER, NORTH, SOUTH, EAST, WEST, UNKNOWN }

    public static Zone classifyPoint(Point p) {
        // ✅ 使用未命名模式 _ 忽略不关心的分量
        return switch (p) {
            case Point(_, double y) when y > 0  -> Zone.NORTH;
            case Point(_, double y) when y < 0  -> Zone.SOUTH;
            case Point(double x, _) when x > 0  -> Zone.EAST;
            case Point(double x, _) when x < 0  -> Zone.WEST;
            case Point(0.0, 0.0)               -> Zone.CENTER;
            default                              -> Zone.UNKNOWN;
        };
    }

    public static String getColor(ColorPoint cp) {
        // ✅ 嵌套模式中也可以使用 _
        return switch (cp) {
            case ColorPoint(Point(_, double y), String color) when y > 0
                -> "北纬" + y + "°,颜色:" + color;
            case ColorPoint(_, String color)
                -> "颜色:" + color;
        };
    }

    public static void main(String[] args) {
        Point p1 = new Point(3.0, 5.0);
        Point p2 = new Point(-2.0, -7.0);

        System.out.println("p1 区域: " + classifyPoint(p1));
        System.out.println("p2 区域: " + classifyPoint(p2));

        ColorPoint cp = new ColorPoint(new Point(0, 10.0), "红色");
        System.out.println(cp.color() + " 点: " + getColor(cp));
    }
}
p1 区域: NORTH
p2 区域: SOUTH
红色 点: 北纬10.0°,颜色:红色

历史背景:_ 的演进之路

Java 版本_ 的状态示例代码
Java 8 及之前普通合法标识符String _ = "hello"; ✅
Java 9 - 20保留关键字,禁止使用String _ = "hello"; ❌ 编译错误
JDK 21预览特性:未命名变量catch (Exception _) ✅(需 --enable-preview)
JDK 22+正式特性:未命名变量/模式(_, int y) -> y ✅

限制与约束

核心原则:_ 是一个无法访问的占位符,编译器会主动阻止任何试图"使用"它的行为。

限制说明错误示例
不能访问 _无法读取其值System.out.println(_); ❌
不能重复声明 _同一作用域只能有一个 _(var _, var _) ❌
不能与命名变量混用歧义某些上下文需明确见下方易错场景
不能赋值给 _显式赋值被禁止_ = 10; ❌

易错场景

错误 1:试图访问未命名变量

// ❌ 错误:试图访问 _
public class ErrorAccessDemo {
    public static void main(String[] args) {
        List<String> list = List.of("a", "b", "c");
        list.forEach((String s, _) -> {
            System.out.println(s);
            System.out.println(_);  // ❌ 编译错误:_ 不可访问
        });
    }
}
// ✅ 正确:只使用需要的变量
public class CorrectAccessDemo {
    public static void main(String[] args) {
        List<String> list = List.of("a", "b", "c");
        list.forEach(s -> System.out.println(s));  // ✅ 只用一个参数
    }
}

错误 2:同一作用域重复声明 _

// ❌ 错误:Lambda 中不能有多个 _
public class ErrorDuplicateDemo {
    public static void main(String[] args) {
        // 假设有个接受三个参数的函数接口
        // BiFunction3<String, Integer, Double, String> f =
        //     (s, _, _) -> s;  // ❌ 编译错误:duplicate _
    }
}
// ✅ 正确:需要忽略多个值时,考虑重构设计
public class CorrectDuplicateDemo {
    // 如果真的需要忽略多个参数,说明设计可能有问题
    // 考虑拆分接口或使用更精确的类型
    record NameAndAge(String name, int age) {}

    public static void main(String[] args) {
        // 使用 Record 只提取需要的字段
        List<NameAndAge> list = List.of(
            new NameAndAge("Alice", 25),
            new NameAndAge("Bob", 30)
        );
        list.forEach(p -> System.out.println(p.name()));  // ✅ 只使用 name
    }
}

错误 3:在 Kubernetes Job 场景中与命名变量混用导致歧义

// ❌ 错误:在增强 for 循环中混用 _ 和命名变量(某些上下文不允许)
public class ErrorMixedDemo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        for (int _ : arr) {  // 在某些版本中不允许
            // ...
        }
    }
}

说明:_ 作为未命名变量主要用于 Lambda 参数、catch 子句、try-with-resources 和模式匹配中。在传统的 for 循环中是否能使用取决于具体 JDK 版本的实现。


实战场景:飞翔科技监控指标聚合

// === 实战:飞翔科技 Kafka Consumer,只关心消息 key 不关心 value ===
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class MonitoringAggregationDemo {

    // 模拟 Kafka 消息:key=服务器IP,value=完整消息(不关心)
    record KafkaMessage(String key, String value) {}

    // 监控指标记录
    record ServerMetric(String ip, String metricName, double value) {}

    public static void main(String[] args) {
        // 模拟从 Kafka 消费的消息
        List<KafkaMessage> messages = List.of(
            new KafkaMessage("10.0.0.1", "{\"cpu\":95.5}"),
            new KafkaMessage("10.0.0.2", "{\"mem\":87.3}"),
            new KafkaMessage("10.0.0.1", "{\"cpu\":92.1}")
        );

        // ✅ 使用 _ 明确表示不关心消息的 value 部分
        Map<String, Long> messageCountByIp = messages.stream()
            .collect(Collectors.groupingBy(
                KafkaMessage::key,
                Collectors.counting()
            ));

        messageCountByIp.forEach((ip, count) -> {
            // 另一个 _ 的使用场景:不关心 Map.Entry 的类型名
            System.out.println("IP: " + ip + ",消息数: " + count);
        });

        // ✅ 异常处理:只记录不分析
        processMetrics(List.of(
            new ServerMetric("10.0.0.1", "cpu", 95.5),
            new ServerMetric("10.0.0.2", "mem", 87.3)
        ));
    }

    public static void processMetrics(List<ServerMetric> metrics) {
        metrics.forEach(m -> {
            try {
                if (m.value() > 100) {
                    throw new IllegalArgumentException("指标超出范围: " + m.value());
                }
                System.out.println(m.ip() + " 的 " + m.metricName() + ": " + m.value());
            } catch (IllegalArgumentException _) {  // ✅ 不关心异常细节,只记录
                System.out.println("⚠️ [" + m.ip() + "] 指标异常,已跳过");
            }
        });
    }
}
IP: 10.0.0.1,消息数: 2
IP: 10.0.0.2,消息数: 1
10.0.0.1 的 cpu: 95.5
10.0.0.2 的 mem: 87.3

面试考点

问题 1:_ 在 Java 8 和 Java 22 中的区别是什么?

答:

  • Java 8 及之前:_ 是合法的标识符,可以作为变量名使用,如 String _ = "hello";。
  • Java 9:_ 被提升为保留关键字,禁止作为标识符使用,为未来特性预留。
  • JDK 21(预览)/ JDK 22(正式):_ 成为未命名变量/模式的语法,用于表示"故意忽略"的值。

这一演进体现了 Java 语言设计的严谨性:先保留关键字,等特性设计成熟后再赋予其语义。

问题 2:未命名变量 _ 有哪些使用限制?

答:

  1. 不可访问:不能读取 _ 的值,任何访问都会导致编译错误。
  2. 不可重复:同一作用域中不能声明多个 _。
  3. 不可赋值:不能对 _ 进行赋值操作。
  4. 使用场景受限:主要用于 Lambda 参数、catch 子句、try-with-resources 和模式匹配中。

这些限制都是有意为之的,目的是确保 _ 的语义清晰:它就是一个占位符,不代表任何可操作的值。

问题 3:使用 _ 而不是起名 ignored 或 unused 有什么好处?

答:

  1. 编译器保证:编译器会检查 _ 确实未被使用,如果误用了会立即报错;而 ignored 变量仍然可以被访问,只是命名约定。
  2. 代码意图更清晰:读者看到 _ 立刻知道"这里故意忽略",不需要猜测是代码不完整还是故意的。
  3. 命名一致性:不用再纠结是叫 ignored、unused、dummy 还是 _ignored,统一使用 _。
  4. 减少噪音:代码中没有无意义的变量名,更加简洁。

这体现了"让编译器帮你写文档"的理念。


总结

"Code is like humor. When you have to explain it, it's bad." — Cory House

使用 _ 正是让代码"不需要解释"的实践:一看便知,此处故意忽略。

上一页
Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)