鹏磊我在实际开发中经常遇到这样的场景:需要检查一个 double 值能否安全转换为 int,或者想在 switch 中直接匹配原始类型。在 JDK 25 之前,这些操作往往需要手动拆箱装箱,代码显得繁琐。JEP 507 引入的原始类型模式匹配正是为了解决这个问题。
作为第三次预览特性,原始类型模式匹配让 instanceof 和 switch 表达式能够直接支持原始类型,无需额外的类型转换步骤。鹏磊我觉得这个特性不仅简化了代码,还提高了类型安全性,让模式匹配功能更加完善。
原始类型模式匹配是啥
先说说啥是原始类型模式匹配吧。简单点说,就是让模式匹配支持原始类型了。Java 里的原始类型有 8 个:byte、short、int、long、float、double、char、boolean。以前模式匹配只能用在引用类型上,现在原始类型也能用了。
这个特性的核心思想是类型安全转换。当你用 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,那就能安全转换了。
安全转换是啥意思呢?就是转换的时候不会丢失信息。比如 int 转 double 是安全的,因为 double 能表示所有 int 的值;但 double 转 int 就不一定安全了,因为 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 表达式匹配的是对象类型,不是原始类型。如果要直接匹配原始类型,得用不同的方式。
原始类型之间的转换规则
原始类型模式匹配的核心是安全转换规则。不是所有类型之间都能安全转换的,得遵循一定的规则。
可以安全转换的情况
-
窄化转换(Narrowing):从大范围类型转到小范围类型,但值在小范围内
double→float:如果值在 float 范围内double→int:如果值是整数且在 int 范围内float→int:如果值是整数且在 int 范围内long→int:如果值在 int 范围内int→short:如果值在 short 范围内int→byte:如果值在 byte 范围内
-
扩展转换(Widening):从小范围类型转到大范围类型,总是安全的
byte→short→int→long→float→doublechar→int→long→float→double
// 扩展转换总是安全的
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"); // 输出这个
}
不能安全转换的情况
- 有精度损失:比如
double或float有小数部分,转int会丢失小数 - 超出范围:比如
long值超出int范围,或者int值超出short范围 - 类型不兼容:比如
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,得在项目设置里启用预览特性。
性能考虑
原始类型模式匹配在性能上没啥问题,因为编译器会优化,不会有多余的装箱拆箱操作。但要注意,如果频繁做类型检查,可能还是会有一定开销,不过这个开销很小,一般可以忽略。
类型安全
用原始类型模式匹配能提高类型安全,因为编译器会检查转换是否安全。但要注意,安全转换的规则可能和你想的不一样,比如 double 转 int 需要是整数且在范围内才算安全。
// 这个例子说明安全转换的规则
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. 处理边界情况
写代码的时候要考虑边界情况,比如 NaN、Infinity 这些特殊值。
// 处理特殊值
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 的模式匹配更完善了,能直接支持原始类型,不用再手动拆箱装箱。主要好处有:
- 代码更简洁:不用手动转换类型,模式匹配自动处理
- 类型更安全:编译器检查转换是否安全,避免运行时错误
- 表达更清晰:代码意图更明确,更容易理解
这个特性还在预览阶段,但基本功能已经挺稳定的了。如果项目里经常要做类型转换和检查,可以试试这个特性,能让代码简洁不少。
当然,用的时候要注意:
- 得加
--enable-preview参数 - 理解安全转换的规则
- 考虑边界情况和特殊值
好了,关于原始类型模式匹配就聊到这。下一篇文章咱要讲模块导入声明(JEP 511),那个特性也挺实用的,能简化模块化项目的使用。兄弟们准备好,咱们继续整活!