02、JDK 25 新特性:原始类型模式匹配(JEP 507)详解

鹏磊我在实际开发中经常遇到这样的场景:需要检查一个 double 值能否安全转换为 int,或者想在 switch 中直接匹配原始类型。在 JDK 25 之前,这些操作往往需要手动拆箱装箱,代码显得繁琐。JEP 507 引入的原始类型模式匹配正是为了解决这个问题。

作为第三次预览特性,原始类型模式匹配让 instanceofswitch 表达式能够直接支持原始类型,无需额外的类型转换步骤。鹏磊我觉得这个特性不仅简化了代码,还提高了类型安全性,让模式匹配功能更加完善。

原始类型模式匹配是啥

先说说啥是原始类型模式匹配吧。简单点说,就是让模式匹配支持原始类型了。Java 里的原始类型有 8 个:byteshortintlongfloatdoublecharboolean。以前模式匹配只能用在引用类型上,现在原始类型也能用了。

这个特性的核心思想是类型安全转换。当你用 instanceof 检查一个值能不能安全转换成某个原始类型的时候,如果能转换,就直接把值赋给一个变量,不用再手动转换了。比如一个 float 值,你可以检查它能不能安全转换成 int,如果能,就直接用,不能就进 else 分支。

instanceof 支持原始类型

先看看 instanceof 怎么用原始类型模式匹配。以前咱要判断一个值是不是某个类型,只能判断引用类型,现在原始类型也能判断了。

// 创建一个 float 值,准备测试
float v = 1000.01f;  // 这个值有小数部分,不能安全转换成 int

// 检查 v 能不能安全转换成 int,如果能,就把转换后的值赋给 i
if (v instanceof int i) {
    // 如果能进来这里,说明 v 可以安全转换成 int,i 就是转换后的值
    System.out.println("int 值: " + i);
} else {
    // 进这里说明不能安全转换,比如有小数部分或者超出范围
    System.out.println("不能安全转换成 int,会丢失精度");
}

这个例子里面,v 是 1000.01f,有小数部分,所以不能安全转换成 int,会进 else 分支。如果 v 是 1000.0f,那就能安全转换了。

安全转换是啥意思呢?就是转换的时候不会丢失信息。比如 intdouble 是安全的,因为 double 能表示所有 int 的值;但 doubleint 就不一定安全了,因为 double 有小数部分,转成 int 会丢失小数。

// int 转 double 是安全的,因为 double 能表示所有 int 的值
int intValue = 100;
if (intValue instanceof double d) {
    // 这里一定能进来,因为 int 转 double 是安全的
    System.out.println("double 值: " + d);  // 输出 100.0
}

// double 转 int 不一定安全,要看有没有小数部分
double doubleValue1 = 100.0;  // 没有小数,可以安全转换
if (doubleValue1 instanceof int i1) {
    System.out.println("可以转换成 int: " + i1);  // 输出 100
}

double doubleValue2 = 100.5;  // 有小数,不能安全转换
if (doubleValue2 instanceof int i2) {
    System.out.println("可以转换成 int: " + i2);
} else {
    System.out.println("不能安全转换成 int");  // 会输出这个
}

switch 表达式支持原始类型模式

switch 表达式现在也支持原始类型模式了,这让写代码更方便。以前 switch 只能匹配常量值,现在可以用类型模式了。

// 定义一个方法,把 float 评分转换成字符串描述
static String floatToRating(float rating) {
    // switch 表达式,直接匹配 float 类型
    return switch (rating) {
        // 匹配具体的 float 值
        case 0f -> "0 星";  // 如果是 0.0,返回 0 星
        case 2.5f -> "平均";  // 如果是 2.5,返回平均
        case 5f -> "最好";  // 如果是 5.0,返回最好
        // 匹配所有其他 float 值,f 是匹配到的值
        case float f -> "无效评分: " + f;  // 其他情况返回无效评分
    };
}

// 测试一下
public static void main(String[] args) {
    float rating1 = 2.5f;
    String result1 = floatToRating(rating1);
    System.out.println(result1);  // 输出: 平均

    float rating2 = 3.7f;
    String result2 = floatToRating(rating2);
    System.out.println(result2);  // 输出: 无效评分: 3.7
}

这个例子里面,switch 表达式直接匹配 float 类型,可以匹配具体的值,也可以匹配所有 float 值。匹配到的时候,可以直接用匹配到的值,不用再手动转换了。

实际应用场景

原始类型模式匹配在实际开发中还是挺有用的,特别是处理数值计算和类型转换的时候。下面举几个实际场景。

场景一:数值范围检查

有时候咱需要检查一个数值在不在某个范围内,或者能不能安全转换成某个类型。以前得写一堆 if-else,现在用模式匹配就简洁多了。

// 处理用户输入的数值,检查类型和范围
public static void processNumber(Object input) {
    // 先检查是不是数字类型
    if (input instanceof Integer integerValue) {
        // 如果是 Integer,检查能不能安全转换成 int
        int intVal = integerValue;  // 自动拆箱
        if (intVal instanceof int i) {
            // 检查范围
            if (i >= 0 && i <= 100) {
                System.out.println("有效整数: " + i);
            } else {
                System.out.println("整数超出范围: " + i);
            }
        }
    } else if (input instanceof Double doubleValue) {
        // 如果是 Double,检查能不能安全转换成 int
        double d = doubleValue;  // 自动拆箱
        if (d instanceof int i) {
            // 能安全转换,说明没有小数部分
            System.out.println("可以转换成整数: " + i);
        } else {
            // 不能安全转换,说明有小数部分
            System.out.println("有小数部分,值: " + d);
        }
    } else {
        System.out.println("不是数字类型");
    }
}

场景二:类型转换工具

写个工具方法,安全地把一个值转换成目标类型,转换不了就返回默认值或者抛异常。

// 安全类型转换工具类
public class SafeTypeConverter {
    
    // 把 float 安全转换成 int,转换不了返回 Optional.empty()
    public static Optional<Integer> safeFloatToInt(float value) {
        // 检查能不能安全转换
        if (value instanceof int i) {
            // 能转换,返回 Optional.of(i)
            return Optional.of(i);
        } else {
            // 不能转换,返回空
            return Optional.empty();
        }
    }
    
    // 把 double 安全转换成 int,转换不了返回默认值
    public static int safeDoubleToInt(double value, int defaultValue) {
        // 检查能不能安全转换
        if (value instanceof int i) {
            // 能转换,返回转换后的值
            return i;
        } else {
            // 不能转换,返回默认值
            return defaultValue;
        }
    }
    
    // 测试方法
    public static void main(String[] args) {
        // 测试 float 转 int
        float f1 = 100.0f;  // 可以转换
        Optional<Integer> result1 = safeFloatToInt(f1);
        System.out.println("100.0f 转换结果: " + result1);  // 输出: Optional[100]
        
        float f2 = 100.5f;  // 不能转换
        Optional<Integer> result2 = safeFloatToInt(f2);
        System.out.println("100.5f 转换结果: " + result2);  // 输出: Optional.empty
        
        // 测试 double 转 int
        double d1 = 200.0;  // 可以转换
        int int1 = safeDoubleToInt(d1, -1);
        System.out.println("200.0 转换结果: " + int1);  // 输出: 200
        
        double d2 = 200.7;  // 不能转换
        int int2 = safeDoubleToInt(d2, -1);
        System.out.println("200.7 转换结果: " + int2);  // 输出: -1
    }
}

场景三:多类型数据处理

处理来自不同来源的数据,可能是不同的原始类型,用 switch 表达式统一处理。

// 处理不同原始类型的数值
public static String processPrimitiveValue(Object value) {
    // 用 switch 表达式匹配不同的原始类型
    return switch (value) {
        // 匹配 int 类型
        case Integer i when i instanceof int intVal -> {
            // 检查范围
            if (intVal >= 0 && intVal <= 100) {
                yield "有效整数: " + intVal;
            } else {
                yield "整数超出范围: " + intVal;
            }
        }
        // 匹配 double 类型,检查能不能转 int
        case Double d when d instanceof double doubleVal -> {
            if (doubleVal instanceof int i) {
                yield "可以转换成整数: " + i;
            } else {
                yield "有小数部分: " + doubleVal;
            }
        }
        // 匹配 float 类型
        case Float f when f instanceof float floatVal -> {
            if (floatVal instanceof int i) {
                yield "float 可以转 int: " + i;
            } else {
                yield "float 值: " + floatVal;
            }
        }
        // 其他类型
        default -> "不支持的类型: " + value.getClass().getSimpleName();
    };
}

不过这个例子有点复杂,因为 switch 表达式匹配的是对象类型,不是原始类型。如果要直接匹配原始类型,得用不同的方式。

原始类型之间的转换规则

原始类型模式匹配的核心是安全转换规则。不是所有类型之间都能安全转换的,得遵循一定的规则。

可以安全转换的情况

  1. 窄化转换(Narrowing):从大范围类型转到小范围类型,但值在小范围内

    • doublefloat:如果值在 float 范围内
    • doubleint:如果值是整数且在 int 范围内
    • floatint:如果值是整数且在 int 范围内
    • longint:如果值在 int 范围内
    • intshort:如果值在 short 范围内
    • intbyte:如果值在 byte 范围内
  2. 扩展转换(Widening):从小范围类型转到大范围类型,总是安全的

    • byteshortintlongfloatdouble
    • charintlongfloatdouble
// 扩展转换总是安全的
byte b = 100;
if (b instanceof int i) {
    System.out.println("byte 可以安全转 int: " + i);  // 输出: 100
}

int i = 100;
if (i instanceof double d) {
    System.out.println("int 可以安全转 double: " + d);  // 输出: 100.0
}

// 窄化转换需要检查值
double d1 = 100.0;  // 整数,可以转 int
if (d1 instanceof int i1) {
    System.out.println("double 100.0 可以转 int: " + i1);  // 输出: 100
}

double d2 = 100.5;  // 有小数,不能转 int
if (d2 instanceof int i2) {
    System.out.println("可以转");
} else {
    System.out.println("double 100.5 不能转 int");  // 输出这个
}

不能安全转换的情况

  1. 有精度损失:比如 doublefloat 有小数部分,转 int 会丢失小数
  2. 超出范围:比如 long 值超出 int 范围,或者 int 值超出 short 范围
  3. 类型不兼容:比如 boolean 和其他数值类型之间不能转换
// 测试各种不能安全转换的情况
double d1 = 100.5;  // 有小数,不能转 int
if (d1 instanceof int i) {
    System.out.println("可以转");
} else {
    System.out.println("有小数,不能转");  // 输出这个
}

long l1 = 3000000000L;  // 超出 int 范围
if (l1 instanceof int i) {
    System.out.println("可以转");
} else {
    System.out.println("超出 int 范围,不能转");  // 输出这个
}

int i1 = 50000;  // 超出 short 范围(-32768 到 32767)
if (i1 instanceof short s) {
    System.out.println("可以转");
} else {
    System.out.println("超出 short 范围,不能转");  // 输出这个
}

在 switch 中使用原始类型模式

switch 表达式和语句都支持原始类型模式,这让代码更简洁。可以用类型模式匹配,也可以用常量值匹配,还可以组合使用。

基本用法

// 处理不同原始类型的值
public static String handlePrimitive(Object value) {
    return switch (value) {
        // 匹配 Integer,然后检查能不能转 int
        case Integer i when i instanceof int intVal -> {
            // 根据值的大小返回不同结果
            if (intVal > 0) {
                yield "正整数: " + intVal;
            } else if (intVal < 0) {
                yield "负整数: " + intVal;
            } else {
                yield "零";
            }
        }
        // 匹配 Double,检查能不能转 int
        case Double d when d instanceof double doubleVal -> {
            if (doubleVal instanceof int i) {
                yield "可以转整数: " + i;
            } else {
                yield "浮点数: " + doubleVal;
            }
        }
        // 其他情况
        default -> "不支持的类型";
    };
}

直接匹配原始类型值

如果 switch 的表达式本身就是原始类型,可以直接匹配:

// 处理 float 评分
public static String categorizeRating(float rating) {
    return switch (rating) {
        // 匹配具体的 float 值
        case 0f -> "零分";
        case 1f -> "一分";
        case 2f -> "两分";
        case 3f -> "三分";
        case 4f -> "四分";
        case 5f -> "五分";
        // 匹配所有其他 float 值
        case float f -> "其他评分: " + f;
    };
}

// 处理 int 年龄
public static String categorizeAge(int age) {
    return switch (age) {
        // 匹配范围,用 when 条件
        case int a when a < 0 -> "无效年龄";
        case int a when a < 18 -> "未成年";
        case int a when a < 60 -> "成年人";
        case int a when a < 100 -> "老年人";
        default -> "超高龄";
    };
}

组合使用类型模式和常量

可以同时使用类型模式和常量值,让匹配更灵活:

// 处理数值,可能是不同的原始类型
public static String processNumber(Object num) {
    return switch (num) {
        // 匹配 Integer,然后检查具体值
        case Integer i when i instanceof int intVal && intVal == 0 -> "零";
        case Integer i when i instanceof int intVal && intVal > 0 -> "正整数: " + intVal;
        case Integer i when i instanceof int intVal -> "负整数: " + intVal;
        
        // 匹配 Double,检查能不能转 int
        case Double d when d instanceof double doubleVal -> {
            if (doubleVal instanceof int i) {
                yield "整数形式的 double: " + i;
            } else {
                yield "有小数: " + doubleVal;
            }
        }
        
        // 其他类型
        default -> "不支持: " + num.getClass().getSimpleName();
    };
}

注意事项和限制

用原始类型模式匹配的时候,有几个地方得注意。

预览特性

这个特性还在预览阶段,用的时候得加 --enable-preview 参数。

# 编译时加 --enable-preview
javac --enable-preview --release 25 YourClass.java

# 运行时也得加 --enable-preview
java --enable-preview YourClass

在 IDE 里也得配置,比如 IntelliJ IDEA,得在项目设置里启用预览特性。

性能考虑

原始类型模式匹配在性能上没啥问题,因为编译器会优化,不会有多余的装箱拆箱操作。但要注意,如果频繁做类型检查,可能还是会有一定开销,不过这个开销很小,一般可以忽略。

类型安全

用原始类型模式匹配能提高类型安全,因为编译器会检查转换是否安全。但要注意,安全转换的规则可能和你想的不一样,比如 doubleint 需要是整数且在范围内才算安全。

// 这个例子说明安全转换的规则
double d = 100.0;  // 整数,可以安全转 int
if (d instanceof int i) {
    System.out.println("可以转: " + i);  // 输出: 可以转: 100
}

double d2 = 100.1;  // 有小数,不能安全转
if (d2 instanceof int i) {
    System.out.println("可以转");
} else {
    System.out.println("不能转");  // 输出: 不能转
}

// 注意:即使值很大,只要在范围内且是整数,也能转
double d3 = 2147483647.0;  // int 的最大值
if (d3 instanceof int i) {
    System.out.println("可以转: " + i);  // 输出: 可以转: 2147483647
}

double d4 = 2147483648.0;  // 超出 int 范围
if (d4 instanceof int i) {
    System.out.println("可以转");
} else {
    System.out.println("超出范围,不能转");  // 输出这个
}

与现有代码的兼容性

这个特性是向后兼容的,不会影响现有代码。如果不用原始类型模式匹配,代码行为和以前一样。只有显式用了这个特性,才会启用新的行为。

最佳实践

用原始类型模式匹配的时候,有几个最佳实践可以参考。

1. 优先使用类型安全转换

能用模式匹配做类型安全转换的时候,优先用模式匹配,而不是手动转换。这样代码更安全,也更容易理解。

// 好的做法:用模式匹配
double value = getUserInput();
if (value instanceof int i) {
    // 安全转换,直接用 i
    processInt(i);
} else {
    // 不能安全转换,处理其他情况
    processDouble(value);
}

// 不好的做法:手动转换,可能不安全
double value = getUserInput();
int i = (int) value;  // 强制转换,可能丢失精度
processInt(i);

2. 合理使用 switch 表达式

switch 表达式配合原始类型模式匹配,能让代码更简洁。特别是处理多个类型或者多个值的时候,用 switch 比一堆 if-else 清晰多了。

// 好的做法:用 switch 表达式
public static String processValue(Object value) {
    return switch (value) {
        case Integer i when i instanceof int intVal -> "整数: " + intVal;
        case Double d when d instanceof double doubleVal -> "浮点数: " + doubleVal;
        default -> "其他类型";
    };
}

// 不好的做法:用 if-else,代码冗长
public static String processValue(Object value) {
    if (value instanceof Integer) {
        Integer i = (Integer) value;
        int intVal = i;
        return "整数: " + intVal;
    } else if (value instanceof Double) {
        Double d = (Double) value;
        double doubleVal = d;
        return "浮点数: " + doubleVal;
    } else {
        return "其他类型";
    }
}

3. 注意转换规则

理解安全转换的规则很重要,这样才能写出正确的代码。特别是窄化转换的时候,要确保值在目标类型的范围内。

// 理解转换规则,写出正确的代码
public static Optional<Integer> safeConvert(double value) {
    // 检查能不能安全转换成 int
    if (value instanceof int i) {
        // 能转换,说明是整数且在 int 范围内
        return Optional.of(i);
    } else {
        // 不能转换,可能是小数或者超出范围
        return Optional.empty();
    }
}

4. 处理边界情况

写代码的时候要考虑边界情况,比如 NaNInfinity 这些特殊值。

// 处理特殊值
public static String handleSpecialValue(double value) {
    // 先检查特殊值
    if (Double.isNaN(value)) {
        return "NaN";
    }
    if (Double.isInfinite(value)) {
        return value > 0 ? "正无穷" : "负无穷";
    }
    
    // 再检查能不能转 int
    if (value instanceof int i) {
        return "整数: " + i;
    } else {
        return "浮点数: " + value;
    }
}

总结

原始类型模式匹配(JEP 507)这个特性让 Java 的模式匹配更完善了,能直接支持原始类型,不用再手动拆箱装箱。主要好处有:

  1. 代码更简洁:不用手动转换类型,模式匹配自动处理
  2. 类型更安全:编译器检查转换是否安全,避免运行时错误
  3. 表达更清晰:代码意图更明确,更容易理解

这个特性还在预览阶段,但基本功能已经挺稳定的了。如果项目里经常要做类型转换和检查,可以试试这个特性,能让代码简洁不少。

当然,用的时候要注意:

  • 得加 --enable-preview 参数
  • 理解安全转换的规则
  • 考虑边界情况和特殊值

好了,关于原始类型模式匹配就聊到这。下一篇文章咱要讲模块导入声明(JEP 511),那个特性也挺实用的,能简化模块化项目的使用。兄弟们准备好,咱们继续整活!

本文章最后更新于 2025-11-27