Elvis 运算符 (The Elvis Operator)
Elvis 运算符 (?:) 是三元运算符语法的简写,其灵感源自 Groovy 语言。
在使用标准的三元运算符语法时,你通常需要将一个变量重复写两次,如下面的 Java 示例所示:
String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");相比之下,你可以使用 Elvis 运算符(其命名是因为该符号酷似猫王 Elvis Presley 的发型)。以下示例展示了如何在 SpEL 表达式中使用它:
ExpressionParser parser = new SpelExpressionParser();
// 如果 name 为 null 或空字符串,则返回 'Unknown'
String name = parser.parseExpression("name ?: 'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name); // 'Unknown'val parser = SpelExpressionParser()
// 如果 name 为 null 或空字符串,则返回 'Unknown'
val name = parser.parseExpression("name ?: 'Unknown'").getValue(Inventor(), String::class.java)
println(name) // 'Unknown'注意:对空字符串的处理
SpEL 的 Elvis 运算符不仅会将 null 视为缺失,还会将空字符串 ("") 视同 null 处理。因此,前文的 Java 示例若要完全模拟 SpEL 的语义,谓词部分应写为 name != null && !name.isEmpty()。
提示:对 Optional 的支持 (Spring 7.0+)
从 Spring Framework 7.0 开始,SpEL 的 Elvis 运算符支持对 java.util.Optional 进行透明解包。
例如,对于表达式 A ?: B:
- 如果
A为null或空的Optional,则表达式结果为B。 - 如果
A是一个非空的Optional,则表达式会自动解包并返回该Optional中包含的对象(等同于调用A.get())。
以下是一个更复杂的示例:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
// 1. name 有值,返回原值
String name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Nikola Tesla
// 2. 将 name 设置为空字符串
tesla.setName("");
// 3. 空字符串触发默认值,返回 'Elvis Presley'
name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Elvis Presleyval parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
// 1. name 有值
var name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name) // Nikola Tesla
// 2. 设置为空字符串
tesla.setName("")
// 3. 触发默认值
name = parser.parseExpression("name ?: 'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name) // Elvis PresleyElvis 运算符非常适合用于在表达式中应用默认值。以下示例展示了如何在 @Value 注解中使用它:
@Value("#{systemProperties['pop3.port'] ?: 25}")如果定义了名为 pop3.port 的系统属性,则注入该属性值;如果未定义(即返回 null),则注入 25。
补充教学
1. 为什么叫猫王 (Elvis)?
这是一个有趣的编程梗:将符号 ?: 顺时针旋转 90 度,? 就像是猫王标志性的偏分斜刘海,而 : 则是他的眼睛。在编程界,这就是“默认值”运算符的代名词。
2. 核心区别:对“空字符串”的宽容度
在原生 Java 或大部分语言中,value ?: default 往往只针对 null。 但 Spring 团队考虑到配置文件(如 .properties 或 .yml)中经常会出现配置了 Key 但 Value 为空的情况,因此 SpEL 的实现逻辑是:
- 触发默认值的条件:
value == null或者value.equals("")。 - 注意:如果字符串全是空格(
" "),它不会触发默认值,除非你链式调用了.trim(),如name?.trim() ?: 'Default'。
3. Spring 7.0 的 Optional 飞跃
在 Spring 7 之前,如果你处理一个返回 Optional 的属性,你可能需要写成: myOptional.isPresent() ? myOptional.get() : 'Default' 现在,通过透明解包,你只需要: myOptional ?: 'Default' 这极大地简化了 SpEL 在响应式编程和现代化 Java API 中的应用。
4. Elvis 运算符 vs 属性占位符默认值
在 Spring 中有两种常见的默认值写法,它们的使用场景不同:
${timeout:1000}:这是属性占位符。它在容器启动阶段解析,逻辑简单,仅用于系统配置。#{systemProperties['timeout'] ?: 1000}:这是 SpEL。它在运行时解析,可以包含复杂的逻辑运算、方法调用等。- 建议:如果是纯配置项注入,优先使用
${...:...};如果涉及计算或从 Bean 属性动态获取,使用#{... ?: ...}。
5. 组合技:安全引用 + Elvis
Elvis 运算符经常与下一章的“安全引用运算符 (?.)”配合使用: @Value("#{user?.address?.city ?: '未知城市'}") 这样即使 user 或 address 为 null,整个表达式也不会抛出 NPE,而是最终优雅地落在 '未知城市' 上。