sObject 集合

您可以在列表、集和映射中管理 sObject。

  • sObjects 列表 列表可以包含其他类型的元素中的 sObjects
    。sObject 列表可用于批量处理数据。
  • 对 sObject 列表进行排序 使用该方法,可以对 sObject
    列表进行排序。List.sort
  • 扩展 sObject 和列表表达式
  • 对象
    集 集可以包含 sObject 以及其他类型的元素。
  • sObject 映射映射键和值可以是任何数据类型,包括 sObject
    类型,例如 Account。

sObject 列表

列表可以包含其他类型的元素中的 sObjects。 sObject 列表可用于批量处理数据。

您可以使用列表来存储 sObjects。列表在工作时很有用 使用 SOQL 查询。SOQL 查询返回 sObject 数据和此数据 可以存储在 sObject 列表中。此外,您可以使用列表来执行 批量操作,例如通过一次调用插入 sObject 列表。

若要声明 sObject 列表,请使用关键字后跟 sObject 键入 <> 个字符以内。例如:List

// Create an empty list of Accounts
List<Account> myList = new List<Account>();

从 SOQL 查询自动填充列表

您可以将 List 变量直接分配给 SOQL 查询的结果。SOQL 查询返回一个 使用返回的记录填充的新列表。确保声明的 List 变量 包含正在查询的相同 sObject。或者,您可以使用通用的 sObject 数据 类型。

此示例演示如何声明和分配帐户列表 更改为 SOQL 查询的返回值。查询最多返回 1,000 个 返回包含 Id 和 Name 字段的帐户记录。

// Create a list of account records from a SOQL query
List<Account> accts = [SELECT Id, Name FROM Account LIMIT 1000];

添加和检索列表元素

与原始数据类型列表一样,您可以使用 Apex 提供的方法访问和设置 sObject 列表的元素。例如:List

List<Account> myList = new List<Account>(); // Define a new list
Account a = new Account(Name='Acme'); // Create the account first
myList.add(a);                    // Add the account sObject
Account a2 = myList.get(0);      // Retrieve the element at index 0

批量处理

您可以通过将列表传递给 DML 操作来批量处理 sObject 列表。这 示例演示如何插入 帐户。

// Define the list
List<Account> acctList = new List<Account>(); 
// Create account sObjects
Account a1 = new Account(Name='Account1'); 
Account a2 = new Account(Name='Account2'); 
// Add accounts to the list
acctList.add(a1);
acctList.add(a2);
// Bulk insert the list
insert acctList;

注意

如果批量插入知识文章版本,请将所有 记录相同。

记录 ID 生成

Apex 会自动为插入或更新插入的 sObject 列表中的每个对象生成 ID 使用 DML。因此,包含多个 sObject 实例的列表不能 即使它有 ID,也会插入或更新插入。这种情况意味着需要将两个 ID 写入相同的 ID 内存中的结构,这是非法的。null

例如,以下代码块中的语句生成 a 因为它 尝试插入一个列表,其中包含对同一 sObject () 的两个引用:insertListExceptiona

try {

   // Create a list with two references to the same sObject element
   Account a = new Account();
   List<Account> accs = new List<Account>{a, a};

   // Attempt to insert it...
   insert accs;

   // Will not get here
   System.assert(false);
} catch (ListException e) {
   // But will get here
}

对 s对象

或者,您可以使用数组表示法(正方形 括号)来声明和引用 sObject 的列表。此示例使用数组声明帐户列表 表示法。

Account[] accts = new Account[1];

以下示例使用方括号向列表中添加一个元素。

accts[0] = new Account(Name='Acme2');

这些示例还将数组表示法用于 sObject 列表。

描述
List<Account> accts = new Account[]{};定义不带元素的帐户列表。
List<Account> accts = new Account[] {new Account(), null, new Account()};定义一个 Account 列表,其中包含为三个 Account 分配的内存:一个新的 Account 对象 第一个位置,在第二个位置, 以及第三个中的另一个新 Account 对象。null
List<Contact> contacts = new List<Contact> (otherList);使用新列表定义联系人列表。

对 sObject 列表进行排序

使用该方法,您可以排序 sObject 的列表。

List.sort

对于 sObjects,排序按升序排列,并使用一系列比较步骤 在下一节中概述。您可以通过以下方式为 sObjects 创建自定义排序顺序 将 sObject 包装在实现接口的 Apex 类中。您还可以创建自定义 通过将作为参数实现的类传递给 sort 方法来排序顺序。请参阅 sObject 的自定义排序顺序。ComparableComparator

sObjects 的默认排序顺序

该方法对 sObjects 进行排序 升序并使用有序的步骤序列比较 sObjects 指定使用的标签或字段。比较从第一步开始 序列,并在使用指定的标签或字段对两个 sObject 进行排序时结束。这 以下是使用的比较顺序:

List.sort

  1. sObject 类型的标签。例如,将显示 Account sObject 在联系人之前。
  2. “名称”字段(如果适用)。例如,如果列表包含两个 名为 Alpha 和 Beta 的帐户,帐户 Alpha 在帐户之前 试用版。
  3. 标准字段,从按字母顺序排在第一位的字段开始 顺序,但 Id 和 Name 字段除外。例如,如果两个帐户 具有相同的名称,用于排序的第一个标准字段是 帐号。
  4. 自定义字段,从按字母顺序排在第一位的字段开始 次序。例如,假设两个帐户具有相同的名称和 相同的标准字段,并且有两个自定义字段,FieldA 和 FieldB,首先使用 FieldA 的值进行排序。

并非必须执行此序列中的所有步骤。例如,列表 包含两个相同类型且具有唯一 Name 值的 sObject 的排序依据 ,排序在步骤 2 处停止。否则,如果名称相同 或者 sObject 没有 Name 字段,则排序将转到步骤 3 进行排序 按标准字段。

对于文本字段,排序算法使用 Unicode 排序 次序。此外,在排序顺序中,空字段位于非空字段之前。

下面是对帐户 sObject 列表进行排序的示例。此示例演示 Name 字段用于将 Acme 帐户置于 列表。由于有两个名为 sForce 的帐户,因此 Industry 字段用于排序 这些剩余的帐户,因为 “行业 ”字段位于 按字母顺序排列。

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'));
System.debug(acctList);

acctList.sort();
Assert.areEqual('Acme', acctList[0].Name);
Assert.areEqual('sForce', acctList[1].Name);
Assert.areEqual('Agriculture', acctList[1].Industry);
Assert.areEqual('sForce', acctList[2].Name);
Assert.areEqual('Biotechnology', acctList[2].Industry);
System.debug(acctList);

此示例与上一个示例类似,只不过它使用了 Merchandise__c 自定义对象。此示例演示如何使用 Name 字段来放置笔记本 商品在列表中排在钢笔之前。因为商品有两个 sObject 对于“笔”的“名称”字段值,“说明”字段用于对它们进行排序 剩余商品。“说明”字段用于排序,因为它 按字母顺序位于 Price 和 Total_Inventory 字段之前。

Merchandise__c[] merchList = new List<Merchandise__c>();        
merchList.add( new Merchandise__c(
    Name='Pens',
    Description__c='Red pens',
    Price__c=2,
    Total_Inventory__c=1000));
merchList.add( new Merchandise__c(
    Name='Notebooks',
    Description__c='Cool notebooks',
    Price__c=3.50,
    Total_Inventory__c=2000));
merchList.add( new Merchandise__c(
    Name='Pens',
    Description__c='Blue pens',
    Price__c=1.75,
    Total_Inventory__c=800));
System.debug(merchList);

merchList.sort();
Assert.areEqual('Notebooks', merchList[0].Name);
Assert.areEqual('Pens', merchList[1].Name);
Assert.areEqual('Blue pens', merchList[1].Description__c);
Assert.areEqual('Pens', merchList[2].Name);
Assert.areEqual('Red pens', merchList[2].Description__c);
System.debug(merchList);

sObjects 的自定义排序顺序

若要为列表中的 sObject 创建自定义排序顺序,请实现接口并将其作为参数传递 到方法。ComparatorList.sort

或者,为 sObject 创建一个包装类并实现接口。包装类包含 所讨论的 sObject 并实现指定排序逻辑的方法。ComparableComparable.compareTo

此示例实现基于“金额”字段比较两个商机的接口。Comparator

public class OpportunityComparator implements Comparator<Opportunity> {
    public Integer compare(Opportunity o1, Opportunity o2) {
        // The return value of 0 indicates that both elements are equal.
        Integer returnValue = 0;
        
        if(o1 == null && o2 == null) {
            returnValue = 0;
        } else if(o1 == null) {
            // nulls-first implementation
            returnValue = -1; 
        } else if(o2 == null) {
            // nulls-first implementation
            returnValue = 1;
        } else if ((o1.Amount == null) && (o2.Amount == null)) {
            // both have null Amounts
            returnValue = 0;
        } else if (o1.Amount == null){
            // nulls-first implementation
            returnValue = -1;
        } else if (o2.Amount == null){
            // nulls-first implementation
            returnValue = 1;
        } else if (o1.Amount < o2.Amount) {
            // Set return value to a negative value.
            returnValue = -1;
        } else if (o1.Amount > o2.Amount) {
            // Set return value to a positive value.
            returnValue = 1;
        }
        return returnValue;
    }
}

此测试对对象列表进行排序 并验证列表元素是否按商机数量排序。Comparator

@isTest
private class OpportunityComparator_Test {
 
    @isTest
    static void sortViaComparator() {
        // Add the opportunity wrapper objects to a list.
        List<Opportunity> oppyList = new List<Opportunity>();
        Date closeDate = Date.today().addDays(10);
        oppyList.add( new Opportunity(
            Name='Edge Installation',
            CloseDate=closeDate,
            StageName='Prospecting',
            Amount=50000));
        oppyList.add( new Opportunity(
            Name='United Oil Installations',
            CloseDate=closeDate,
            StageName='Needs Analysis',
            Amount=100000));
        oppyList.add( new Opportunity(
            Name='Grand Hotels SLA',
            CloseDate=closeDate,
            StageName='Prospecting',
            Amount=25000));
        oppyList.add(null);
        
        // Sort the objects using the Comparator implementation
        oppyList.sort(new OpportunityComparator());
        // Verify the sort order
        Assert.isNull(oppyList[0]);
        Assert.areEqual('Grand Hotels SLA', oppyList[1].Name);
        Assert.areEqual(25000, oppyList[1].Amount);
        Assert.areEqual('Edge Installation', oppyList[2].Name);
        Assert.areEqual(50000, oppyList[2].Amount);
        Assert.areEqual('United Oil Installations', oppyList[3].Name);
        Assert.areEqual(100000, oppyList[3].Amount);
        // Write the sorted list contents to the debug log.
        System.debug(oppyList);
    }
}

此示例演示如何为 Opportunity 创建包装类。此类中该方法的实现比较了两个 基于“金额”字段的商机 – 包含的类成员变量 在本例中,将 Opportunity 对象传递到方法中。ComparablecompareTo

public class OpportunityWrapper implements Comparable {

    public Opportunity oppy;
    
    // Constructor
    public OpportunityWrapper(Opportunity op) {
    	// Guard against wrapping a null 
    	if(op == null) {
    		Exception ex = new NullPointerException();
    		ex.setMessage('Opportunity argument cannot be null'); 
    		throw ex;
    	}
        oppy = op;
    }
    
    // Compare opportunities based on the opportunity amount.
    public Integer compareTo(Object compareTo) {
        // Cast argument to OpportunityWrapper
        OpportunityWrapper compareToOppy = (OpportunityWrapper)compareTo;
        
        // The return value of 0 indicates that both elements are equal.
        Integer returnValue = 0;
        if ((oppy.Amount == null) && (compareToOppy.oppy.Amount == null)) {
            // both wrappers have null Amounts
            returnValue = 0;
        } else if ((oppy.Amount == null) && (compareToOppy.oppy.Amount != null)){
            // nulls-first implementation
            returnValue = -1;
        } else if ((oppy.Amount != null) && (compareToOppy.oppy.Amount == null)){
            // nulls-first implementation
            returnValue = 1;
        } else if (oppy.Amount > compareToOppy.oppy.Amount) {
            // Set return value to a positive value.
            returnValue = 1;
        } else if (oppy.Amount < compareToOppy.oppy.Amount) {
            // Set return value to a negative value.
            returnValue = -1;
        } 
        return returnValue;
    }
}

此测试对对象列表进行排序,并验证列表元素是否按商机排序 量。OpportunityWrapper

@isTest 
private class OpportunityWrapperTest {
    static testmethod void test1() {
        // Add the opportunity wrapper objects to a list.
        OpportunityWrapper[] oppyList = new List<OpportunityWrapper>();
        Date closeDate = Date.today().addDays(10);
        oppyList.add( new OpportunityWrapper(new Opportunity(
            Name='Edge Installation',
            CloseDate=closeDate,
            StageName='Prospecting',
            Amount=50000)));
        oppyList.add( new OpportunityWrapper(new Opportunity(
            Name='United Oil Installations',
            CloseDate=closeDate,
            StageName='Needs Analysis',
            Amount=100000)));
        oppyList.add( new OpportunityWrapper(new Opportunity(
            Name='Grand Hotels SLA',
            CloseDate=closeDate,
            StageName='Prospecting',
            Amount=25000)));
        
        // Sort the wrapper objects using the implementation of the 
        // compareTo method.
        oppyList.sort();
        
        // Verify the sort order
        Assert.areEqual('Grand Hotels SLA', oppyList[0].oppy.Name);
        Assert.areEqual(25000, oppyList[0].oppy.Amount);
        Assert.areEqual('Edge Installation', oppyList[1].oppy.Name);
        Assert.areEqual(50000, oppyList[1].oppy.Amount);
        Assert.areEqual('United Oil Installations', oppyList[2].oppy.Name);
        Assert.areEqual(100000, oppyList[2].oppy.Amount);
        
        // Write the sorted list contents to the debug log.
        System.debug(oppyList);
    }
}

扩展 sObject 和列表表达式

与 Java 一样,可以使用 method 扩展 sObject 和 list 表达式 引用和列表表达式,以形成新的表达式。在以下示例中,包含长度的新变量 的新帐户名称分配给 。

acctNameLength

Integer acctNameLength = new Account[]{new Account(Name='Acme')}[0].Name.length();

在上面,生成一个列表。new Account[]

该列表由语句填充一个元素。new{new Account(name=’Acme’)}

项目 0 是列表中的第一项,然后由下一个项目访问 字符串的一部分。[0]

访问列表中 sObject 的名称,后跟 返回长度的方法。name.length()在以下示例中,已移至 lower 的名称 大小写。SOQL 语句返回一个列表,其中第一个元素(索引为 0) 可通过 访问。 接下来,访问“名称”字段并将其转换为小写 表达。

[0].Name.toLowerCase()

String nameChange = [SELECT Name FROM Account][0].Name.toLowerCase();

对象集

集合可以包含 sObject 以及其他类型的元素。集合包含独特的元素。确定 sObject 的唯一性 通过比较对象的字段。例如,如果您尝试 将两个同名的帐户添加到一个集合中,没有其他字段 set,则该集合中仅添加一个 sObject。

// Create two accounts, a1 and a2
Account a1 = new account(name='MyAccount');
Account a2 = new account(name='MyAccount');

// Add both accounts to the new set 
Set<Account> accountSet = new Set<Account>{a1, a2};

// Verify that the set only contains one item
System.assertEquals(accountSet.size(), 1);

如果您向其中一个帐户添加描述,则会将其视为 唯一,并且两个帐户都会添加到集合中。

// Create two accounts, a1 and a2, and add a description to a2
Account a1 = new account(name='MyAccount');
Account a2 = new account(name='MyAccount', description='My test account');

// Add both accounts to the new set
Set<Account> accountSet = new Set<Account>{a1, a2};

// Verify that the set contains two items
System.assertEquals(accountSet.size(), 2);

警告

如果 set 元素是对象,并且这些对象 添加到集合后更改,则找不到它们 例如,在使用 OR 方法时,由于字段值已更改。containscontainsAll

sObjects 的映射

映射键和值可以是任何数据类型,包括 sObject 类型,例如 Account。映射可以在其键和值中保存 sObject。地图键 表示映射到映射值的唯一值。例如,一个 通用键是映射到帐户的 ID(特定的 sObject 类型)。此示例演示如何定义其键类型为 ID,其值的类型为 Account。

Map<ID, Account> m = new Map<ID, Account>();

与基元类型一样,您可以在以下情况下填充映射键值对 映射是使用大括号 () 语法声明的。在大括号内, 首先指定键,然后使用 指定该键的值。本示例创建 将整数添加到帐户列表,并使用帐户列表添加一个条目 创建时间较早。{}=>

Account[] accs = new Account[5]; // Account[] is synonymous with List<Account>
Map<Integer, List<Account>> m4 = new Map<Integer, List<Account>>{1 => accs};

映射允许在其键中使用 sObject。仅当 sObject 字段时,才必须在键中使用 sObject 值不会改变。

从 SOQL 查询自动填充映射条目

使用 SOQL 查询时,可以从 SOQL 返回的结果填充映射 查询。映射键必须使用 ID 或 String 数据类型声明,并且映射 value 必须声明为 sObject 数据类型。此示例演示如何从查询填充新映射。在 例如,SOQL 查询返回带有其 和 字段的帐户列表。操作员使用返回的帐户列表来创建地图。

IdNamenew

// Populate map from SOQL query
Map<ID, Account> m = new Map<ID, Account>([SELECT Id, Name FROM Account LIMIT 10]);
// After populating the map, iterate through the map entries
for (ID idKey : m.keyset()) {
    Account a = m.get(idKey);
    System.debug(a);
}

此映射类型的一种常见用法是内存中 两个表之间的“联接”。

注意

作为多个社区成员的用户的最近查看的记录不能是 通过 Apex 自动检索到地图中。这是因为用户的记录 不同的网络可能会导致地图不支持的重复 ID。查看更多 信息,请参阅最近查看

使用 Map 方法

该类公开了各种方法 可用于处理地图元素,例如添加、删除、 或检索元素。此示例使用 Map 方法添加新元素 并从地图中检索现有元素。此示例还检查 对于键的存在,并获取所有键的集合。地图在 此示例有一个元素,其中包含一个整数键和一个帐户值。Map

Account myAcct = new Account();                        //Define a new account
Map<Integer, Account> m = new Map<Integer, Account>(); // Define a new map
m.put(1, myAcct);                  // Insert a new key-value pair in the map
System.assert(!m.containsKey(3));  // Assert that the map contains a key
Account a = m.get(1);              // Retrieve a value, given a particular key
Set<Integer> s = m.keySet();       // Return a set that contains all of the keys in the map
  • sObject 映射注意事项

sObject 映射注意事项

使用 sObject 作为映射键时要小心。sObject 的键匹配 基于所有 sObject 字段值的比较。如果一个或 将 sObject 添加到地图后,更多字段值发生变化,尝试 要从映射中检索此 sObject,则返回 。这是因为修改后的 由于字段值不同,在映射中找不到 sObject。 如果显式更改 sObject 上的字段,或者 如果系统隐式更改了 sObject 字段;例如 插入 sObject 后,sObject 变量具有 ID 字段 自动填充。尝试从它所到的映射中获取此对象 在操作不会生成映射条目之前添加,如本示例所示。

nullinsert

// Create an account and add it to the map
Account a1 = new Account(Name='A1');
Map<sObject, Integer> m = new Map<sObject, Integer>{
a1 => 1};
    
// Get a1's value from the map.
// Returns the value of 1.
System.assertEquals(1, m.get(a1));
// Id field is null.
System.assertEquals(null, a1.Id);

// Insert a1.
// This causes the ID field on a1 to be auto-filled
insert a1;
// Id field is now populated.
System.assertNotEquals(null, a1.Id);

// Get a1's value from the map again.
// Returns null because Map.get(sObject) doesn't find
// the entry based on the sObject with an auto-filled ID.
// This is because when a1 was originally added to the map
// before the insert operation, the ID of a1 was null.
System.assertEquals(null, m.get(a1));

另一种情况 其中自动填充的 sObject 字段位于触发器中,例如,当 对 sObject 使用插入触发器之前和之后。如果这些触发器 共享一个类中定义的静态映射,并将 中的 sObject 添加到该映射中 在 before 触发器中,在 after 触发器中找不到 映射,因为两组 sObject 的字段不同 是自动填充的。after 触发器中的 sObjects 在插入后填充了系统字段, 即:ID、CreatedDate、CreatedById、LastModifiedDate、LastModifiedById、 和 SystemModStamp。

Trigger.NewTrigger.NewTrigger.New

ref