编写 Apex 代码
Apex 是 Salesforce 的编程语言,类似于 Java。它允许你在 Lightning Platform 持久层中添加和操作数据。Apex 使用类、数据类型、变量和 if-else 语句,你可以基于条件执行代码,或者让一段代码重复执行。本章是 Apex 语言核心参考,涵盖所有基础构建块:数据类型、集合、枚举、变量、表达式和运算符。
数据类型概述
在 Apex 中,所有变量和表达式都有一个数据类型。类型检查在编译时严格执行——解析器会在类型不匹配时生成包含行号和列号的错误信息。
Apex 支持的数据类型包括:
- 原始类型(Primitive):Integer、Double、Long、Date、Datetime、String、ID、Boolean、Decimal、Blob、Time、Object
- sObject:通用 sObject 或特定 sObject(如 Account、Contact、MyCustomObject__c)
- 集合(Collection):List(有序、可重复)、Set(无序、唯一)、Map(键值对)
- 枚举(Enum):具有有限标识符集的类型化值列表
- 用户定义的 Apex 类和系统提供的 Apex 类
- Null:null 常量,可以赋值给任何变量
原始数据类型
Apex 使用与 SOAP API 相同的原始数据类型(高精度 Decimal 类型在某些情况下例外)。所有 Apex 变量(无论是类成员变量还是方法变量)都初始化为 null。请确保在使用前将变量初始化为适当的值。例如,将 Boolean 变量初始化为 false。
核心原始类型概览
| 类型 | 描述 |
|---|---|
| Blob | 以单个对象存储的二进制数据集合。可通过 toString() 和 valueOf() 方法与 String 互转。可用于文档、附件和 Web 服务参数。 |
| Boolean | 值只能为 true、false 或 null。始终在使用前初始化为 false。 |
| Date | 表示特定日期,不含时间信息。始终使用系统静态方法创建日期值。可与 Integer 进行加减运算。 |
| Datetime | 表示特定日期和时间(时间戳)。始终使用系统静态方法创建。可与 Integer 或 Double 进行加减运算。 |
| Decimal | 任意精度的数字,包含小数点。货币字段自动分配为 Decimal 类型。使用 setScale() 设置小数位数。 |
更多原始类型
| 类型 | 描述 |
|---|---|
| Double | 64 位浮点数。最小值 -2^63,最大值 2^63-1。不支持科学计数法(e)。 |
| ID | 18 字符的 Salesforce 记录标识符。设置为 15 字符值会自动转换为 18 字符。无效 ID 值会引发运行时异常。 |
| Integer | 32 位整数。范围:-2,147,483,648 到 2,147,483,647。 |
| Long | 64 位整数。范围:-2^63 到 2^63-1。字面量使用 L 后缀:Long l = 2147483648L; |
| Object | 任何 Apex 支持的数据类型。所有 Apex 数据类型都继承自 Object。可强制转换为其底层数据类型。 |
| String | 单引号包围的字符集。大小限制由堆大小限制决定。sObject String 字段值不能为空字符串(只能为 null)且不能包含首尾空格。支持与 SOQL 相同的转义序列。 |
| Time | 表示特定时间。始终使用系统静态方法创建。 |
String 特别说明:与 Java 不同,Apex Strings 支持比较运算符(==、!=、<、>、<=、>=),比较基于上下文用户的语言环境且不区分大小写。
多行字符串(Multiline Strings)
Apex 支持使用三个单引号(''')定义多行字符串:
String str = '''
第一行
第二行
第三行
''';
关键规则:
- 换行:换行符自动转换为结果字符串中的换行序列
- 空白:字符串最左侧非空白字符之前的所有空白都会被去除;每行的尾部空白也会被去除。空白去除在编译时进行,在转义序列处理之前
- 单引号:支持不转义的单引号(
')。但在闭合的三个单引号之前必须转义:\''''
转义序列与拼接
- 使用
\s在行尾保留尾部空白 - 使用
\在行尾拼接多行而不插入换行符:
String str = '''
这是一行不能在一行写完 \ 但我不希望它包含换行 \
所以使用了转义序列
''';
SOQL 中的限制:多行字符串变量可用于 SOQL/SOSL 查询,但多行字符串字面量不能直接用在 SOQL/SOSL 查询中(在绑定表达式中除外)。
版本化行为变更
- API 16.0+:Apex 在货币等类型中使用更高精度的 Decimal
- API 15.0+:如果为字段赋值的 String 值太长,Apex 类和触发器会产生运行时错误
还有两个非标准原始类型不能在变量中使用但出现在系统方法中:AnyType(用于字段历史跟踪表)和 Currency(仅用于 SOQL/SOSL WHERE 子句)。
集合:List、Set 和 Map
集合在 Apex 中可以是 List(列表)、Set(集合)或 Map(映射)。集合没有项目数量限制,但受堆大小一般限制。
List(列表)—— 有序、可重复
List 是有序的元素集合,通过索引区分。List 元素可以是任何数据类型。第一个元素的索引位置始终为 0。
创建语法:
List<datatype> listName = new List<datatype>();
List<datatype> listName = new List<datatype>{v1, v2};
数组表示法(一维列表):
String[] colors = new List<String>();
// 等效于:
String[] colors = new String[1];
colors[0] = 'Green'; // 基于索引的访问
重要:使用方括号添加元素时,列表行为类似于数组(不可弹性扩展)。使用 List 的 add() 方法可实现弹性增长。
排序:List.sort() 对原始类型按升序排序。自定义类型可实现 Comparable 接口或传入 Comparator。列表最多可嵌套七层(总共八层)。
Set(集合)—— 无序、唯一
Set 是无序的元素集合,不允许重复。Set 没有索引位置的概念,你只能迭代遍历 Set 元素。
Set<Integer> mySet = new Set<Integer>();
mySet.add(1);
mySet.add(3);
System.assert(mySet.contains(1));
mySet.remove(1);
特点:
- 使用哈希结构(无 HashSet/TreeSet 区分)
- 迭代顺序是确定性的(每次执行相同)
- 常见模式:收集唯一 ID 用于 SOQL WHERE IN 子句
Map(映射)—— 键值对
Map 是键值对的集合,每个唯一键映射到一个值。键必须唯一——使用已存在的键 put 新值会覆盖旧值。
Map<Integer, String> m = new Map<Integer, String>();
m.put(1, 'First entry');
m.put(2, 'Second entry');
String value = m.get(2); // 返回 'Second entry'
Boolean has = m.containsKey(4); // false
快速初始化:
Map<String, String> MyStrings = new Map<String, String>
{'a' => 'b', 'c' => 'd'.toUpperCase()};
Map 注意事项
- 所有 Map 使用哈希结构(无 HashMap/TreeMap 区分)
- 迭代顺序是确定性的,但建议始终通过键访问
- String 键区分大小写!'Key' 和 'key' 是不同的键
- Map 键可以持有 null 值
- 用户定义类型键的唯一性由
equals()和hashCode()方法决定 - sObject 键注意事项:sObject 变更后不再映射到相同的值
- JSON 序列化:Map 仅在使用特定键类型(Boolean、Date、DateTime、Decimal、Double、Enum、Id、Integer、Long、String、Time)时才能序列化为 JSON
参数化类型
Apex 是静态类型语言——在使用变量之前必须声明其数据类型。List、Map 和 Set 使用参数化类型,在构造时用实际数据类型替换占位符:
List<String> myList = new List<String>();
子类型关系:如果类型 T 是 U 的子类型,则 List<T> 是 List<U> 的子类型。这意味着你可以将 List<String> 赋值给 List<Object>。
枚举(Enum)
枚举是一种抽象数据类型,其值从你指定的一组有限标识符中取一个。枚举通常用于定义没有数值顺序的可能值集合,例如卡牌的花色或一年中的季节。
public enum Season {WINTER, SPRING, SUMMER, FALL}
Season current = Season.SUMMER;
特点:
- 每个值对应一个独特的整数,但实现被隐藏(防止算术误用)
- 与 Java 不同,枚举类型本身没有构造器语法
- 可以将类定义为枚举(不使用 class 关键字)
- 任何
webservice方法都可以使用枚举类型——WSDL 文件将包含枚举及其值的定义
系统定义的枚举
Apex 提供以下系统枚举:
- System.StatusCode —— 对应 WSDL 中公开的 API 错误代码
- System.LoggingLevel —— 用于
System.debug()的日志级别 - System.RoundingMode —— 数学运算的舍入行为(如 Decimal divide、Double round)
- System.SoapType —— 字段描述结果
getSoapType()的返回值 - System.DisplayType —— 字段描述结果
getType()的返回值 - System.JSONToken —— 用于解析 JSON 内容
- System.ApplicationReadWriteMode —— 指示组织在升级期间是否处于只读模式
- ApexPages.Severity —— Visualforce 消息的严重程度
系统定义的枚举不能在 Web 服务方法中使用。所有枚举值(包括系统枚举)都有通用方法,但不能向枚举值添加用户定义的方法。
变量
局部变量使用 Java 风格语法声明:
Integer i = 0;
String str;
List<String> strList;
Set<String> s;
Map<String, String> m;
// 单语句声明多个变量
Integer i, j, k;
变量命名规则
- 变量名不区分大小写
- 只能包含字母(A-Z、a-z)、数字(0-9)和下划线(_),不能包含空格或特殊字符
- 必须以字母开头,不能以数字或下划线开头
- 不能以下划线结尾
- 不能包含连续下划线(__)
- 不能使用 Apex 保留关键字
- 最大长度:255 个字符
Null 变量与变量作用域
如果声明变量但未初始化,其值为 null。常见陷阱:未初始化的 Boolean 变量是 null,不是 false!
Boolean x = null;
Decimal d; // 隐式为 null
Date d; d.addDays(2); // 抛出 NullPointerException!
变量作用域:变量可以在代码块内的任何位置定义,并从该点开始生效。子代码块不能重新定义父代码块中已使用的变量名,但并行代码块可以重用变量名。
常量
常量是初始化后值不会改变的变量,使用 final 关键字定义。final 关键字意味着变量最多可以被赋值一次,可以在声明中直接赋值,也可以通过静态初始化器方法赋值:
public class myCls {
static final Integer PRIVATE_INT_CONST = 200;
static final Integer PRIVATE_INT_CONST2;
public static Integer calculate() { return 2 + 7; }
static {
PRIVATE_INT_CONST2 = calculate();
}
}
表达式与运算符
表达式是由变量、运算符和方法调用组成的构造,求值结果为单个值。表达式可以通过运算符连接形成复合表达式。
表达式类型
Apex 中的表达式可以是:字面量表达式;新的 sObject/Apex 对象/List/Set/Map;L-值(变量、列表位置、字段引用);sObject 字段引用;方括号中的 SOQL/SOSL 查询;或静态/实例方法调用。
核心运算符
赋值运算符:=、+=、-=、*=、/=、|=、&=、++、--
算术运算符:+、-、*、/
- 加法规则:Integer + Integer = Integer;含 Double 则结果为 Double;Date + Integer = 新 Date;Datetime + Integer/Double = 新 Date;String + 任何非 null = 拼接
比较运算符:<、>、<=、>=、==、!=、===、!==
- 与 Java 的关键区别:
==在 Apex 中比较的是对象值相等而非引用相等(用户定义类型除外) - String 比较使用
==时不区分大小写,基于用户语言环境 ===和!==检查精确相等(相同的内存位置)
逻辑运算符(短路求值):&&(AND)、||(OR)、!(NOT)
三元运算符:x ? y : z —— 如果 x 为 true 则返回 y,否则返回 z
位运算符与特殊运算符
位运算符:&(AND)、|(OR)、^(XOR)、~(取反)、<<(左移)、>>(有符号右移)、>>>(无符号右移)
整数除法陷阱:
Double d = 5/3; // 返回 1.0(非 1.666...)小数部分被截断!
Double d = 5.0/3.0; // 返回 1.6666666666666667 —— 保留小数
安全导航运算符(Safe Navigation Operator)—— ?.
使用 ?. 替代显式的、顺序的 null 引用检查。当左侧表达式为 null 时短路并返回 null,而不是抛出 NullPointerException。
// 如果 a 为 null,返回 null;否则返回 a.b
a?.b;
// sObject 链式访问
String s = contact.Account?.BillingCity;
// SOQL 查询
String lastName = [SELECT LastName FROM Contact]?.LastName;
不允许使用的场景:带点号的类型/静态表达式(命名空间、Trigger.new、{Type}.class)、静态变量/方法调用(AClass.AStaticMethod())、可赋值表达式(foo?.bar = 42;)、SOQL 绑定表达式、sObject 标量字段的 addError()。
空值合并运算符(Null Coalescing Operator)—— ??
?? 运算符在其左侧操作数为 null 时返回右侧操作数。类似于 ?.,它替代了代码中冗长的 null 检查。
// 之前:
Integer notNullReturnValue = (anInteger != null) ? anInteger : 100;
// 使用 ??:
Integer notNullReturnValue = anInteger ?? 100;
特点:
- 二元运算符
a ?? b:如果 a 不为 null 返回 a,否则返回 b - 左结合;左操作数只求值一次;右操作数仅在左操作数为 null 时求值
- 类型必须兼容——两个操作数必须是同一类型的实例
空值合并运算符与 SOQL
使用 ?? 优雅地处理 SOQL 查询无结果的情况:
Account defaultAccount = new Account(name = 'Acme');
Account a = [SELECT Id FROM Account WHERE Id = '001000000FAKEID'] ?? defaultAccount;
注意运算符优先级:top ?? 100 - bottom ?? 0被解析为top ?? (100 - bottom ?? 0),而非(top ?? 100) - (bottom ?? 0)。必要时使用括号明确意图。
限制:不能作为赋值的左侧(foo ?? bar = 42; 无效);SOQL 绑定表达式不支持;Salesforce 建议不要在单个语句中同时使用多个 SOQL 查询和 ?? 运算符。
运算符优先级
运算符按优先级规则解释,从高到低:
{} () ++ --(分组、前缀增减)~ ! -x +x (type) new(一元、类型转换、对象创建)* /(乘法除法)+ -(加法减法)<< >> >>>(移位)< > <= >= instanceof(比较、引用测试)== !=(相等比较)& ^ |(位运算 AND/XOR/OR)&& ||(逻辑 AND/OR)??(空值合并)?:(三元)= += -= *= /= &= |= <<= >>= >>>=(赋值)
注释
Apex 支持单行注释和多行注释:
// 这是单行注释
Integer i = 0; // 行内注释
/*
这是
多行注释
*/
我们推荐使用标准化的 ApexDoc 注释格式以提高代码可读性和长期可维护性(基于 JavaDoc 标准,专为 Apex 和 Salesforce 生态系统定制)。
赋值语句与类型转换
赋值语句
赋值语句将值放入变量中。赋值始终按引用进行:
Account a = new Account();
Account b;
a.Name = 'Acme';
b = a; // b 现在引用与 a 相同的对象
System.assertEquals(b.Name, 'Acme'); // true
除了 = 之外,其他有效的赋值运算符包括 +=、*=、/=、|=、&=、++ 和 --。
类型转换规则
Apex 一般要求你显式地将一种数据类型转换为另一种。数字类型存在层级结构(从低到高):
Integer → Long → Double → Decimal
较低数字类型的变量可以始终赋值给较高类型而无需显式转换。
隐式转换(自动,无需方法):
- Integer → Long、Integer → Double、Long → Double
- ID → String(始终允许)
- String → ID(运行时检查——如果不是合法 ID 则抛出异常)
- Date → Datetime(时间组件清零)
显式转换(需要方法):
- String → Integer:
Integer.valueOf(str) - String → Decimal:
Decimal.valueOf(str) - Integer → String:
String.valueOf(int) - 可使用
instanceof关键字测试字符串是否为 ID
数据类型的额外注意事项
- 数值默认类型:数值默认为 Integer,Long 需要 L 后缀(如
2147483648L),Double/Decimal 需要 .0 后缀 - 溢出与下溢:Apex 对数据类型值的溢出和下溢不抛出异常。例如
Integer i = 2147483647 + 1;结果为 -2147483648(回绕到最小值)。为避免此问题,请在算术运算中使用足够大的类型并添加适当的字面量后缀 - 整数除法丢失小数:
Double d = 5/3;返回 1.0 而非 1.666...因为在隐式转换为 Double 之前小数部分已被移除。使用Double d = 5.0/3.0;保留小数 - Date 到 Datetime 转换:Apex 支持 Date 到 Datetime 的隐式和显式转换,时间组件清零
本章涵盖了编写 Apex 代码所需的所有语言基础。后续章节将深入探讨控制流语句和类的更多细节。