Apex sObject 集合完整指南

Apex sObject集合完整指南:覆盖sObject的List(声明/从SOQL自动填充/批量处理/重复引用陷阱/数组表示法)、sObject列表默认排序四步序列(类型标签→Name→标准字段→自定义字段/Unicode排序/空字段优先)、自定义排序(Comparator接口/Comparable包装类/Collator语言环境排序)、展开sObject和List表达式(链式访问)、sObject的Set(字段值比较唯一性/字段变更后无法找到)、sObject的Map(从SOQL自动填充/内存内联表连接/Map方法)以及sObject作为Map键的关键陷阱(ID自动填充导致键失效/触发器前后上下文差异)。...

📅 2026/7/16 ✍️ ponybai 🏷️ apex, salesforce

sObject 集合

sObject集合

你可以在 List、Set 和 Map 中管理 sObject。集合提供强大的方式来批量处理数据、对记录排序以及执行高效的内存内查找。

sObject 的 List

sObject List

List 可以包含 sObject 以及其他类型元素。sObject 的 List 可用于数据的批量处理。使用 List<sObjectType> 声明。

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

从 SOQL 查询自动填充 List:可以直接将 List 变量赋值给 SOQL 查询结果。确保 List 变量与查询的 sObject 类型相同(或使用通用 sObject)。查询最多返回 1,000 条记录包含 Id 和 Name 字段。

List<Account> accts = [SELECT Id, Name FROM Account LIMIT 1000];

// 添加和获取元素
List<Account> myList = new List<Account>();
Account a = new Account(Name='Acme');
myList.add(a);
Account a2 = myList.get(0);  // 获取索引 0 处的元素

// 批量处理——一条 DML 插入整个列表
List<Account> acctList = new List<Account>();
acctList.add(new Account(Name='Account1'));
acctList.add(new Account(Name='Account2'));
insert acctList;  // 批量插入

记录 ID 生成与重复引用陷阱:Apex 自动为被 DML 插入或 upsert 的 sObject 列表中的每个对象生成 ID。因此,包含对同一 sObject 实例多个引用的列表不能插入或 upsert——会导致 ListException:

Account a = new Account();
List<Account> accs = new List<Account>{a, a};  // 同一对象两次
try {
    insert accs;  // 抛出 ListException
} catch (ListException e) {
    // 两个 ID 无法写入同一内存结构
}

数组表示法:也可以使用方括号声明和引用 sObject 列表:Account[] accts = new Account[1];accts[0] = new Account(Name='Acme2');

排序 sObject 列表

排序

使用 List.sort 方法对 sObject 列表进行升序排序。默认排序按以下比较序列步骤进行:

  1. sObject 类型的标签——例如,Account 出现在 Contact 之前
  2. Name 字段(如果适用)——Alpha 在 Beta 之前
  3. 标准字段(按字母顺序)——排除 Id 和 Name。如果两个账户同名,使用 AccountNumber 等
  4. 自定义字段(按字母顺序)

排序在找到可区分两个 sObject 的步骤时停止。文本字段使用 Unicode 排序顺序,空字段排在非空字段之前。

Account[] acctList = new List<Account>();
acctList.add(new Account(Name='sForce', Industry='Biotechnology', Site='Austin'));
acctList.add(new Account(Name='sForce', Industry='Agriculture', Site='New York'));
acctList.add(new Account(Name='Acme'));
acctList.sort();
// 结果:Acme, sForce(Agriculture), sForce(Biotechnology)
// Name 字段先排序,同名再用 Industry 字段(Industry 在字母顺序中排在 Site 之前)

sObject 的自定义排序

自定义排序

要为 sObject 创建自定义排序顺序,有两种方式:

方式一:实现 Comparator 接口——创建比较器类并作为参数传递给 List.sort

public class OpportunityAmountComparator implements Comparator<Opportunity> {
    public Integer compare(Opportunity o1, Opportunity o2) {
        if (o1.Amount == o2.Amount) return 0;
        return (o1.Amount > o2.Amount) ? 1 : -1;
    }
}
// 使用
List<Opportunity> opps = [SELECT Amount FROM Opportunity];
opps.sort(new OpportunityAmountComparator());

方式二:实现 Comparable 接口——创建包装类并实现 compareTo 方法:

public class OpportunityWrapper implements Comparable {
    public Opportunity opp;
    public OpportunityWrapper(Opportunity o) { opp = o; }
    public Integer compareTo(Object other) {
        OpportunityWrapper ow = (OpportunityWrapper) other;
        if (opp.Amount == ow.opp.Amount) return 0;
        return (opp.Amount > ow.opp.Amount) ? 1 : -1;
    }
}

要执行基于语言环境的排序,使用 Collator 类。

展开 sObject 和 List 表达式

表达式展开

与 Java 一样,sObject 和 List 表达式可以通过方法引用和列表表达式展开来形成新表达式:

// 链式访问:创建列表→取第一个→访问Name→获取长度
Integer acctNameLength = new Account[]{new Account(Name='Acme')}[0].Name.length();

// SOQL 结果链式操作
String nameChange = [SELECT Name FROM Account][0].Name.toLowerCase();

sObject 的 Set

sObject Set

Set 可以包含 sObject。sObject 的唯一性通过比较所有对象字段值来确定。如果两个 Account 只有相同的 Name(没有其他字段设置),Set 只保留一个:

Account a1 = new Account(name='MyAccount');
Account a2 = new Account(name='MyAccount');
Set<Account> accountSet = new Set<Account>{a1, a2};
System.assertEquals(accountSet.size(), 1);  // 只有一条

// 添加不同字段值后变为唯一
a2 = new Account(name='MyAccount', description='My test account');
accountSet = new Set<Account>{a1, a2};
System.assertEquals(accountSet.size(), 2);  // 现在两条
警告:Set 元素是对象时,如果对象添加到集合后发生更改,使用 containscontainsAll 方法将无法再找到它们(因为字段值改变了)。

sObject 的 Map

sObject Map

Map 的键和值都可以是 sObject 类型。最常见的模式是用 ID 作为键映射到 sObject。可以从 SOQL 查询结果自动填充 Map(键必须是 ID 或 String,值必须是 sObject):

// 定义——ID 为键,Account 为值
Map<ID, Account> m = new Map<ID, Account>();

// 从 SOQL 查询自动填充——常用于内存内联表连接
Map<ID, Account> m = new Map<ID, Account>([SELECT Id, Name FROM Account LIMIT 10]);

// 遍历 Map 条目
for (ID idKey : m.keySet()) {
    Account a = m.get(idKey);
    System.debug(a);
}

// 使用 Map 方法
Account myAcct = new Account();
Map<Integer, Account> m = new Map<Integer, Account>();
m.put(1, myAcct);
System.assert(!m.containsKey(3));
Account a = m.get(1);
Set<Integer> s = m.keySet();

sObject 作为 Map 键的注意事项

Map键注意

使用 sObject 作为 Map 键需要极其谨慎。键匹配基于所有字段值的比较。如果在将 sObject 添加到 Map 之后其字段值发生更改,尝试从 Map 中检索该 sObject 会返回 null——因为修改后的 sObject 由于字段值不同而无法被找到。

经典陷阱——插入后的 ID 自动填充:

Account a1 = new Account(Name='A1');
Map<Account, Integer> m = new Map<Account, Integer>{a1 => 1};
System.assertEquals(1, m.get(a1));   // 插入前可以找到
System.assertEquals(null, a1.Id);    // ID 为 null

insert a1;  // ID 现在被自动填充!
System.assertNotEquals(null, a1.Id);
System.assertEquals(null, m.get(a1)); // 无法再找到——ID 字段值改变了

触发器中的场景:before insert 触发器中 Trigger.New 的 sObject 被添加到静态 Map 中后,在 after insert 触发器中这些 sObject 无法在该 Map 中被找到——因为 after trigger 中的 Trigger.New sObject 的系统字段已被填充(ID、CreatedDate、CreatedById、LastModifiedDate、LastModifiedById、SystemModStamp),与添加到 Map 中的原始 sObject 字段值不同。

理解 sObject 集合的正确使用方式——从批量处理到排序再到 Map 键的陷阱——是编写高效、正确的 Apex 代码的关键。