编写 Apex 代码:数据类型、集合、变量与运算符完全参考

Apex 语言核心完全参考:涵盖所有数据类型(原始类型、sObject、集合)、List/Set/Map 三种集合的完整用法与注意事项、多行字符串、枚举与系统枚举、变量声明与命名规则、Null 处理与作用域、常量、安全导航运算符(?.)与空值合并运算符(??)的完整说明、运算符优先级表、赋值语句与类型转换规则,以及溢出和除法精度等重要注意事项。...

📅 2025/3/9 ✍️ ponybai 🏷️ apex, salesforce

编写 Apex 代码

s76

Apex 是 Salesforce 的编程语言,类似于 Java。它允许你在 Lightning Platform 持久层中添加和操作数据。Apex 使用类、数据类型、变量和 if-else 语句,你可以基于条件执行代码,或者让一段代码重复执行。本章是 Apex 语言核心参考,涵盖所有基础构建块:数据类型、集合、枚举、变量、表达式和运算符。

s77

数据类型概述

在 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 常量,可以赋值给任何变量
s78

原始数据类型

Apex 使用与 SOAP API 相同的原始数据类型(高精度 Decimal 类型在某些情况下例外)。所有 Apex 变量(无论是类成员变量还是方法变量)都初始化为 null。请确保在使用前将变量初始化为适当的值。例如,将 Boolean 变量初始化为 false。

s79

核心原始类型概览

类型描述
Blob以单个对象存储的二进制数据集合。可通过 toString() 和 valueOf() 方法与 String 互转。可用于文档、附件和 Web 服务参数。
Boolean值只能为 true、false 或 null。始终在使用前初始化为 false。
Date表示特定日期,不含时间信息。始终使用系统静态方法创建日期值。可与 Integer 进行加减运算。
Datetime表示特定日期和时间(时间戳)。始终使用系统静态方法创建。可与 Integer 或 Double 进行加减运算。
Decimal任意精度的数字,包含小数点。货币字段自动分配为 Decimal 类型。使用 setScale() 设置小数位数。

更多原始类型

类型描述
Double64 位浮点数。最小值 -2^63,最大值 2^63-1。不支持科学计数法(e)。
ID18 字符的 Salesforce 记录标识符。设置为 15 字符值会自动转换为 18 字符。无效 ID 值会引发运行时异常。
Integer32 位整数。范围:-2,147,483,648 到 2,147,483,647。
Long64 位整数。范围:-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(映射)。集合没有项目数量限制,但受堆大小一般限制。

s80

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。列表最多可嵌套七层(总共八层)。

s81
s82

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 子句
s83

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()};
s84

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
s85

参数化类型

Apex 是静态类型语言——在使用变量之前必须声明其数据类型。List、Map 和 Set 使用参数化类型,在构造时用实际数据类型替换占位符:

List<String> myList = new List<String>();

子类型关系:如果类型 T 是 U 的子类型,则 List<T> 是 List<U> 的子类型。这意味着你可以将 List<String> 赋值给 List<Object>。

s86

枚举(Enum)

枚举是一种抽象数据类型,其值从你指定的一组有限标识符中取一个。枚举通常用于定义没有数值顺序的可能值集合,例如卡牌的花色或一年中的季节。

public enum Season {WINTER, SPRING, SUMMER, FALL}
Season current = Season.SUMMER;

特点:

  • 每个值对应一个独特的整数,但实现被隐藏(防止算术误用)
  • 与 Java 不同,枚举类型本身没有构造器语法
  • 可以将类定义为枚举(不使用 class 关键字)
  • 任何 webservice 方法都可以使用枚举类型——WSDL 文件将包含枚举及其值的定义
s87

系统定义的枚举

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 服务方法中使用。所有枚举值(包括系统枚举)都有通用方法,但不能向枚举值添加用户定义的方法。
s88

变量

局部变量使用 Java 风格语法声明:

Integer i = 0;
String str;
List<String> strList;
Set<String> s;
Map<String, String> m;

// 单语句声明多个变量
Integer i, j, k;
s89

变量命名规则

  • 变量名不区分大小写
  • 只能包含字母(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!

变量作用域:变量可以在代码块内的任何位置定义,并从该点开始生效。子代码块不能重新定义父代码块中已使用的变量名,但并行代码块可以重用变量名。

s90

常量

常量是初始化后值不会改变的变量,使用 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();
  }
}
s91

表达式与运算符

表达式是由变量、运算符和方法调用组成的构造,求值结果为单个值。表达式可以通过运算符连接形成复合表达式。

s92

表达式类型

Apex 中的表达式可以是:字面量表达式;新的 sObject/Apex 对象/List/Set/Map;L-值(变量、列表位置、字段引用);sObject 字段引用;方括号中的 SOQL/SOSL 查询;或静态/实例方法调用。

s93

核心运算符

赋值运算符:=、+=、-=、*=、/=、|=、&=、++、--

算术运算符:+、-、*、/

  • 加法规则: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

s94

位运算符与特殊运算符

位运算符:&(AND)、|(OR)、^(XOR)、~(取反)、<<(左移)、>>(有符号右移)、>>>(无符号右移)

整数除法陷阱:

Double d = 5/3;     // 返回 1.0(非 1.666...)小数部分被截断!
Double d = 5.0/3.0; // 返回 1.6666666666666667 —— 保留小数
s95

安全导航运算符(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()。

s96

空值合并运算符(Null Coalescing Operator)—— ??

?? 运算符在其左侧操作数为 null 时返回右侧操作数。类似于 ?.,它替代了代码中冗长的 null 检查。

// 之前:
Integer notNullReturnValue = (anInteger != null) ? anInteger : 100;

// 使用 ??:
Integer notNullReturnValue = anInteger ?? 100;

特点:

  • 二元运算符 a ?? b:如果 a 不为 null 返回 a,否则返回 b
  • 左结合;左操作数只求值一次;右操作数仅在左操作数为 null 时求值
  • 类型必须兼容——两个操作数必须是同一类型的实例
s97

空值合并运算符与 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 查询和 ?? 运算符。

s98

运算符优先级

运算符按优先级规则解释,从高到低:

  1. {} () ++ --(分组、前缀增减)
  2. ~ ! -x +x (type) new(一元、类型转换、对象创建)
  3. * /(乘法除法)
  4. + -(加法减法)
  5. << >> >>>(移位)
  6. < > <= >= instanceof(比较、引用测试)
  7. == !=(相等比较)
  8. & ^ |(位运算 AND/XOR/OR)
  9. && ||(逻辑 AND/OR)
  10. ??(空值合并)
  11. ?:(三元)
  12. = += -= *= /= &= |= <<= >>= >>>=(赋值)
s99

注释

Apex 支持单行注释和多行注释:

// 这是单行注释
Integer i = 0;  // 行内注释

/*
  这是
  多行注释
*/

我们推荐使用标准化的 ApexDoc 注释格式以提高代码可读性和长期可维护性(基于 JavaDoc 标准,专为 Apex 和 Salesforce 生态系统定制)。

s100

赋值语句与类型转换

s101

赋值语句

赋值语句将值放入变量中。赋值始终按引用进行:

Account a = new Account();
Account b;
a.Name = 'Acme';
b = a;  // b 现在引用与 a 相同的对象
System.assertEquals(b.Name, 'Acme');  // true

除了 = 之外,其他有效的赋值运算符包括 +=、*=、/=、|=、&=、++ 和 --。

s102

类型转换规则

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
s103

数据类型的额外注意事项

  • 数值默认类型:数值默认为 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 代码所需的所有语言基础。后续章节将深入探讨控制流语句和类的更多细节。