异步Apex

Apex 提供了多种异步运行 Apex 代码的方法。选择 最适合您需求的异步 Apex 功能。

下表列出了异步 Apex 功能以及何时使用每个功能。

异步 Apex 功能何时使用
可排队的Apex启动长时间运行的操作并获取其 ID将复杂类型传递给作业链接作业
预定Apex计划 Apex 类按特定计划运行
批处理Apex对于需要批量执行的具有大量数据的长时间运行的作业,例如 作为数据库维护作业对于需要比常规事务允许的更大的查询结果的作业
未来方法当您有一个长时间运行的方法并且需要防止延迟 Apex 时 交易对外部 Web 服务进行标注时隔离 DML 操作并绕过混合保存 DML 错误
  • 可排队的 Apex 使用界面控制异步 Apex
    进程。通过此接口,您可以将作业添加到队列中并对其进行监视。与使用未来方法相比,使用该接口是运行异步 Apex 代码的增强方式。Queueable
  • Apex 调度程序
  • 批处理Apex
  • 未来方法

可排队的Apex

使用该接口控制异步 Apex 进程。通过此接口,您可以添加 作业到队列中并对其进行监视。使用该界面是运行 异步 Apex 代码与使用未来方法的比较。

Queueable

长时间运行的 Apex 进程,例如大量数据库操作或 外部 Web 服务标注,可以通过实现接口并向 Apex 添加作业来异步运行 作业队列。这样,您的异步 Apex 作业就会在后台自行运行 线程,并且不会延迟主 Apex 逻辑的执行。每个排队的作业都运行 当系统资源可用时。使用接口方法的一个好处是一些 调控器限制高于同步 Apex 的限制,例如堆大小限制。QueueableQueueable

注意

如果 Apex 事务回滚,则任何排队等待执行的可排队作业 交易不被处理。

可排队作业与将来的方法类似,因为它们都排队等待 执行,但它们为您提供了这些额外的好处。

  • 获取作业的 ID:通过调用该方法提交作业时,该方法将返回 新作业的 ID。此 ID 对应于 AsyncApexJob 记录的 ID。 使用此 ID 通过 Salesforce UI 识别和监控您的作业 (Apex Jobs 页面),或以编程方式查询您的记录 异步Apex作业。System.enqueueJob
  • 使用非基元类型:可排队类可以包含 非原始数据类型,例如 sObject 或自定义 Apex 类型。那些对象 可以在作业执行时访问。
  • 链接作业:您可以通过启动第二个作业将一个作业链接到另一个作业 从正在运行的作业。如果您的进程依赖于另一个进程,则链接作业很有用 进程首先运行。

您可以设置链接的可排队作业的最大堆栈深度,覆盖默认值 开发人员和试用版组织中的限制为 5 个。

注意

声明的变量包括 被序列化和反序列化忽略,并且该值在 可排队的Apex。transient

将可排队作业添加到异步执行队列

此示例实现接口。此示例中的方法插入一个新帐户。该方法用于 将作业添加到 队列。

QueueableexecuteSystem.enqueueJob(queueable)

public class AsyncExecutionExample implements Queueable {
    public void execute(QueueableContext context) {
        Account a = new Account(Name='Acme',Phone='(415) 555-1212');
        insert a;        
    }
}

若要将此类添加为队列中的作业,请调用 方法:

ID jobID = System.enqueueJob(new AsyncExecutionExample());

提交可排队类以供执行后,作业将添加到队列中 并将在系统资源可用时进行处理。您可以监控 通过查询 AsyncApexJob 或通过用户以编程方式获取作业的状态 通过输入“快速查找”框,然后选择“Apex”,在“设置”中界面 工作Apex Jobs

若要查询有关已提交作业的信息,请在 AsyncApexJob 上执行 SOQL 查询 通过筛选方法返回的作业 ID。此示例使用 jobID 变量 这是在前面的示例中获得的。System.enqueueJob

AsyncApexJob jobInfo = [SELECT Status,NumberOfErrors FROM AsyncApexJob WHERE Id=:jobID];

与将来的作业类似,可排队的作业不处理批处理,因此 已处理的批次和总批次数始终为零。

添加具有指定最小延迟的可排队作业

使用该方法将可排队作业添加到具有指定 最小延迟(0-10 分钟)。在 Apex 测试期间,延迟将被忽略。System.enqueueJob(queueable, delay)

请参阅 Apex 中的 System.enqueueJob(queueable, delay) 参考指南。

警告

将延迟设置为 0(零)时,可排队作业将作为 越快越好。使用链接的可排队作业,实现一种减慢速度的机制 或在必要时停止作业。如果没有这样的故障安全机制,您可以 快速达到每日异步 Apex 限制。

在以下情况下,在 运行可排队作业。

  • 如果外部系统是有速率限制的,并且可以通过链接过载 可排队的作业,这些作业正在进行快速标注。
  • 轮询结果时,如果执行速度过快可能会导致浪费 每日异步 Apex 限制。

此示例通过传入实例来添加延迟异步执行的作业 用于执行的接口的类实现。在作业开始前,至少需要 5 分钟的延迟 执行。Queueable

Integer delayInMinutes = 5;
ID jobID = System.enqueueJob(new MyQueueableClass(), delayInMinutes);

管理员可以在计划中定义默认的组织范围延迟(1-600 秒) 在没有延迟参数的情况下计划的可排队作业。使用延迟设置 作为减慢默认可排队作业执行速度的机制。如果省略该设置, Apex 使用标准的可排队计时,不会增加延迟。

注意

使用该方法将忽略任何组织范围的排队延迟设置。System.enqueueJob(queueable, delay)

通过以下方式之一定义组织范围的延迟。

  • 在“设置”的“快速查找”框中,输入 、 ,然后为默认最小值输入一个值(1-600 秒) 没有延迟的可排队作业的排队延迟(以秒为单位) 参数Apex Settings
  • 若要使用元数据 API 以编程方式启用此功能,请参阅元数据 API 中的 ApexSettings 开发人员指南。

添加具有指定堆栈深度的可排队作业

使用可在 asyncOptions 参数。System.enqueueJob(queueable, asyncOptions)

System.AsyncInfo 类属性 包含当前和最大堆栈深度以及最小可排队延迟。

System.AsyncInfo 类具有 帮助您确定是否在 Queueable 中设置了最大堆栈深度的方法 请求,并获取可排队对象的堆栈深度和队列延迟 当前正在运行。使用有关当前可排队执行的信息使 关于调整后续通话延迟的决定。

这些是类中的方法。System.AsyncInfo

  • hasMaxStackDepth()
  • getCurrentQueueableStackDepth()
  • getMaximumQueueableStackDepth()
  • getMinimumQueueableDelayInMinutes()

This example uses stack depth to terminate a chained job and prevent it from reaching the daily maximum number of asynchronous Apex method executions.

// Fibonacci
public class FibonacciDepthQueueable implements Queueable {
   
    private long nMinus1, nMinus2;
       
    public static void calculateFibonacciTo(integer depth) {
        AsyncOptions asyncOptions = new AsyncOptions();
        asyncOptions.MaximumQueueableStackDepth = depth;
        System.enqueueJob(new FibonacciDepthQueueable(null, null), asyncOptions);
    }
       
    private FibonacciDepthQueueable(long nMinus1param, long nMinus2param) {
        nMinus1 = nMinus1param;
        nMinus2 = nMinus2param;
    }
   
    public void execute(QueueableContext context) {
       
        integer depth = AsyncInfo.getCurrentQueueableStackDepth();
       
        // Calculate step
        long fibonacciSequenceStep;
        switch on (depth) {
            when 1, 2 {
                fibonacciSequenceStep = 1;
            }
            when else {
                fibonacciSequenceStep = nMinus1 + nMinus2;
            }
        }
       
        System.debug('depth: ' + depth + ' fibonacciSequenceStep: ' + fibonacciSequenceStep);
       
        if(System.AsyncInfo.hasMaxStackDepth() &&
           AsyncInfo.getCurrentQueueableStackDepth() >= 
           AsyncInfo.getMaximumQueueableStackDepth()) {
            // Reached maximum stack depth
            Fibonacci__c result = new Fibonacci__c(
                Depth__c = depth,
                Result = fibonacciSequenceStep
                );
            insert result;
        } else {
            System.enqueueJob(new FibonacciDepthQueueable(fibonacciSequenceStep, nMinus1));
        }
    }
}

测试可排队作业

此示例演示如何在测试方法中测试可排队作业的执行。一个 可排队作业是一个异步进程。确保此过程在 测试方法中,作业被提交到 AND 块之间的队列中。系统执行所有 异步进程在语句之后同步启动测试方法。接下来,测试方法 通过查询作业所在的帐户来验证可排队作业的结果 创建。

Test.startTestTest.stopTestTest.stopTest

@isTest
public class AsyncExecutionExampleTest {
    @isTest
    static void test1() {
        // startTest/stopTest block to force async processes 
        //   to run in the test.
        Test.startTest();        
        System.enqueueJob(new AsyncExecutionExample());
        Test.stopTest();
        
        // Validate that the job has run
        // by verifying that the record was created.
        // This query returns only the account created in test context by the 
        // Queueable class method.
        Account acct = [SELECT Name,Phone FROM Account WHERE Name='Acme' LIMIT 1];
        System.assertNotEquals(null, acct);
        System.assertEquals('(415) 555-1212', acct.Phone);
    }
}

链接作业

若要在另一个作业首先完成其他处理后运行作业,可以链接 可排队的作业。若要将一个作业链接到另一个作业,请从可排队的方法提交第二个作业 类。您只能从正在执行的作业中添加一个作业,这意味着只能添加一个作业 每个父作业都可以存在子作业。例如,如果您有第二个班级 调用来实现接口,您可以将此类添加到 方法中的队列为 遵循:execute()SecondJobQueueableexecute()

public class AsyncExecutionExample implements Queueable {
    public void execute(QueueableContext context) {
        // Your processing logic here       

        // Chain this job to next job by submitting the next job
        System.enqueueJob(new SecondJob());
    }
}

注意

Apex 允许来自可排队作业的 HTTP 和 Web 服务标注(如果它们实现) 标记 接口。在实现此接口的可排队作业中,标注也是 允许在链接的可排队作业中。Database.AllowsCallouts

您可以使用适当的堆栈深度测试链接的可排队作业,但请注意 适用的 Apex 调速器限制。请参阅添加具有指定堆栈深度的可排队作业。

可排队的Apex限制

  • 排队作业的执行计入共享限制一次 异步 Apex 方法执行。请参阅 Lightning 平台Apex限制。
  • 在单个事务中,您最多可以将 50 个作业添加到队列中。异步 事务(例如,从批处理 Apex 作业),您只能将一个作业添加到 带有 的队列。自 检查一个事务中添加了多少个可排队的作业,调用 Limits.getQueueableJobs()。System.enqueueJobSystem.enqueueJob
  • 由于对链接作业的深度没有限制,因此可以链接一个作业 到另一个。您可以对每个新的子作业重复此过程,以将其链接到 新的子工作。对于 Developer Edition 和 Trial 组织,最大堆栈 链式作业的深度为 5,这意味着您可以链式作业四次。这 链中的最大作业数为 5,包括初始父级可排队作业 工作。
  • 将作业与 链接时,只能从正在执行的作业中添加一个作业。 每个父可排队作业只能存在一个子作业。启动多个 不支持来自同一可排队作业的子作业。System.enqueueJob
  • 检测重复的可排队作业 通过基于签名仅对异步可排队作业
    的单个实例进行排队,减少资源争用和争用条件。尝试将多个 Queueable 作业添加到具有相同签名的处理队列中会导致 DuplicateMessageException,当您尝试将后续作业加入队列时。
  • 事务终结
    通过事务终结器功能,您可以使用接口将操作附加到使用 Queueable 框架的异步 Apex 作业。一个特定的用例是在可排队作业失败时设计恢复操作。System.Finalizer
  • 事务终结器错误消息 通过分析这些错误消息
    来解决语义和运行时问题。

检测重复的可排队作业

通过仅将 基于签名的异步可排队作业的单个实例。尝试添加更多内容 将一个 Queueable 作业发送到具有相同签名的处理队列会导致 DuplicateMessageException,当您尝试将后续作业排入队列时。

实施细节

使用 类。 使用 中的这些方法添加不同的字符串、ID 或整数。QueueableDuplicateSignature.BuilderQueueableDuplicateSignature.Builder

  • addString(inputString)
  • addId(inputId)
  • addInteger(inputInteger)

当签名具有所需的组件时,调用该方法并存储唯一的可排队作业 在属性中的签名 类。使用将作业排入队列 带有参数的方法。.build()DuplicateSignatureAsyncOptionsSystem.enqueueJob()AsyncOptions

要确定大小, 剩余大小和可排队作业签名的最大大小(以字节为单位)使用以下命令 类中的方法。QueueableDuplicateSignature.Builder

  • getSize()
  • getRemainingSize()
  • getMaxSize()

例子

此示例使用 UserId 和字符串 .MyQueueable

AsyncOptions options = new AsyncOptions();
options.DuplicateSignature = QueueableDuplicateSignature.Builder()
                                .addId(UserInfo.getUserId())
                                .addString('MyQueueable')
                                .build();
try {
    System.enqueueJob(new MyQueueable(), options);
} catch (DuplicateMessageException ex) {
    //Exception is thrown if there is already an enqueued job with the same 
    //signature
    Assert.areEqual('Attempt to enqueue job with duplicate queueable signature',
        ex.getMessage());
}

此示例使用 ApexClass Id 和 一个 sObject。

AsyncOptions options = new AsyncOptions();
options.DuplicateSignature = QueueableDuplicateSignature.Builder()
                                .addInteger(System.hashCode(someAccount))
                                .addId([SELECT Id FROM ApexClass 
                                     WHERE Name='MyQueueable'].Id)
                                .build();
System.enqueueJob(new MyQueueable(), options);

交易终结器

使用事务终结器功能可以附加操作、 使用接口,以 使用 Queueable 框架的异步 Apex 作业。一个具体的用例是设计 可排队作业失败时的恢复操作。

System.Finalizer“事务终结器”功能为您提供了直接指定 异步作业成功或失败时要执行的操作。交易前 终结器,对于异步作业失败,只能执行以下两项操作:

  • 轮询使用 SOQL 查询的状态,如果作业失败,则重新排队AsyncApexJob
  • 当批处理 Apex 方法遇到未处理的 例外

使用事务终结器,可以将操作后序列附加到 Queueable job,并根据作业执行结果采取相关操作。

一个可排队的作业,该作业 由于未处理的异常而失败,可以通过 事务终结器。此限制适用于一系列连续的可排队作业 失败。当 Queueable 作业完成而没有未处理时,计数器将重置 例外。

终结器可以作为内部类实现。此外,您还可以 使用相同的类实现 Queueable 和 Finalizer 接口。这 可排队作业和终结器在单独的 Apex 和数据库事务中运行。为 例如,Queueable 可以包含 DML,终结器可以包含 REST 标注。 使用终结器不算作针对每日异步 Apex 的额外执行 限制。同步调控器限制适用于终结器事务,但 异步限制适用的以下情况:

  • 总堆大小
  • 添加到队列的最大 Apex 作业数System.enqueueJob
  • 每个 Apex 调用允许的具有注释的方法的最大数量future

有关调控器限制的详细信息,请参阅执行调控器和限制。

System.Finalizer 接口

该接口包括以下方法:

System.Finalizerexecute

global void execute(System.FinalizerContext ctx) {}

这 方法在为每个排队作业提供的 FinalizerContext 实例上调用 附有终结器。在该方法中,您可以定义在 可排队作业。Apex 运行时引擎将 的实例作为 参数添加到 execute 方法。

executeSystem.FinalizerContext

System.FinalizerContext 接口

界面 包含四种方法。

System.FinalizerContext

  • getAsyncApexJobId方法:global Id getAsyncApexJobId {}返回 为其定义此终结器的可排队作业的 ID。
  • getRequestId方法:global String getRequestId {}返回 请求 ID,唯一标识请求的字符串,可以是 与事件监控日志相关联。与 AsyncApexJob 关联 表中,请改用该方法。Queueable 作业和终结器执行都共享 (相同)请求 ID。getAsyncApexJobId
  • getResult方法:global System.ParentJobResult getResult {}返回 枚举, 表示父异步 Apex Queueable 作业的结果 附上终结器。枚举采用以下值:、。System.ParentJobResultSUCCESSUNHANDLED_EXCEPTION
  • getException方法:global System.Exception getException {}返回 Queueable 作业在以下情况下失败的异常为 , null 否则。getResultUNHANDLED_EXCEPTION

使用该方法将终结器附加到可排队作业。

System.attachFinalizer

  1. 定义实现接口的类。System.Finalizer
  2. 在 Queueable 作业的方法中附加终结器。若要附加终结器,请使用 As 参数:实现 System.Finalizer 的实例化类 接口。executeSystem.attachFinalizerglobal void attachFinalizer(Finalizer finalizer) {}

实施细节

  • 只能将一个终结器实例附加到任何可排队作业。
  • 您可以将单个异步 Apex 作业(Queueable、Future 或 Batch)加入队列 在终结器的方法实现中。execute
  • 终结器实现中允许使用标注。
  • 终结器框架使用终结器对象的状态(如果附加) 在可排队执行结束时。终结器状态的突变,在 它是附加的,因此是受支持的。
  • 声明的变量包括 被序列化和反序列化忽略,因此不会保留在 事务终结器。transient

日志记录终结器示例

此示例演示如何使用事务终结器来记录来自 a 可排队作业,无论作业是成功还是失败。这 此处的 LoggingFinalizer 类实现了 Queueable 和 Finalizer 接口。这 可排队实现实例化终结器,附加它,然后调用 addLog() 方法来缓冲日志消息。终结器实现 LoggingFinalizer 包括允许缓冲的 addLog(message, source) 方法 将 Queueable 作业中的消息记录到终结器的状态。当 Queueable 作业 完成后,终结器实例将提交缓冲的日志。终结器状态为 即使 Queueable 作业失败,也可以保留,并且可以在 DML 中使用 终结器实现或执行。

public class LoggingFinalizer implements Finalizer, Queueable {

  // Queueable implementation
  // A queueable job that uses LoggingFinalizer to buffer the log
  // and commit upon exit, even if the queueable execution fails

    public void execute(QueueableContext ctx) {
        String jobId = '' + ctx.getJobId();
        System.debug('Begin: executing queueable job: ' + jobId);
        try {
            // Create an instance of LoggingFinalizer and attach it
            LoggingFinalizer f = new LoggingFinalizer();
            System.attachFinalizer(f);

            // While executing the job, log using LoggingFinalizer.addLog()
            // Note that addlog() modifies the Finalizer's state after it is attached 
            DateTime start = DateTime.now();
            f.addLog('About to do some work...', jobId);

            while (true) {
              // Results in limit error
            }
        } catch (Exception e) {
            System.debug('Error executing the job [' + jobId + ']: ' + e.getMessage());
        } finally {
            System.debug('Completed: execution of queueable job: ' + jobId);
        }
    }

  // Finalizer implementation
  // Logging finalizer provides a public method addLog(message,source) that allows buffering log lines from the Queueable job.
  // When the Queueable job completes, regardless of success or failure, the LoggingFinalizer instance commits this buffered log.
  // Custom object LogMessage__c has four custom fields-see addLog() method.

    // internal log buffer
    private List<LogMessage__c> logRecords = new List<LogMessage__c>();

    public void execute(FinalizerContext ctx) {
        String parentJobId = '' + ctx.getAsyncApexJobId();
        System.debug('Begin: executing finalizer attached to queueable job: ' + parentJobId);

        // Update the log records with the parent queueable job id
        System.Debug('Updating job id on ' + logRecords.size() + ' log records');
        for (LogMessage__c log : logRecords) {
            log.Request__c = parentJobId; // or could be ctx.getRequestId()
        }
        // Commit the buffer
        System.Debug('committing log records to database');
        Database.insert(logRecords, false);

        if (ctx.getResult() == ParentJobResult.SUCCESS) {
            System.debug('Parent queueable job [' + parentJobId + '] completed successfully.');
        } else {
            System.debug('Parent queueable job [' + parentJobId + '] failed due to unhandled exception: ' + ctx.getException().getMessage());
            System.debug('Enqueueing another instance of the queueable...');
        }
        System.debug('Completed: execution of finalizer attached to queueable job: ' + parentJobId);
    }

    public void addLog(String message, String source) {
        // append the log message to the buffer
        logRecords.add(new LogMessage__c(
            DateTime__c = DateTime.now(),
            Message__c = message,
            Request__c = 'setbeforecommit',
            Source__c = source
        ));
    }
}

重试可排队示例

此示例演示如何在终结器中将失败的 Queueable 作业重新排队。 它还显示作业可以重新排队,最多可排队链接限制为 5 重试。

public class RetryLimitDemo implements Finalizer, Queueable {

  // Queueable implementation
  public void execute(QueueableContext ctx) {
    String jobId = '' + ctx.getJobId();
    System.debug('Begin: executing queueable job: ' + jobId);
    try {
        Finalizer finalizer = new RetryLimitDemo();
        System.attachFinalizer(finalizer);
        System.debug('Attached finalizer');
        Integer accountNumber = 1;
        while (true) { // results in limit error
          Account a = new Account();
          a.Name = 'Account-Number-' + accountNumber;
          insert a;
          accountNumber++;
        }
    } catch (Exception e) {
        System.debug('Error executing the job [' + jobId + ']: ' + e.getMessage());
    } finally {
        System.debug('Completed: execution of queueable job: ' + jobId);
    }
  }

  // Finalizer implementation
  public void execute(FinalizerContext ctx) {
    String parentJobId = '' + ctx.getAsyncApexJobId();
    System.debug('Begin: executing finalizer attached to queueable job: ' + parentJobId);
    if (ctx.getResult() == ParentJobResult.SUCCESS) {
        System.debug('Parent queueable job [' + parentJobId + '] completed successfully.');
    } else {
        System.debug('Parent queueable job [' + parentJobId + '] failed due to unhandled exception: ' + ctx.getException().getMessage());
        System.debug('Enqueueing another instance of the queueable...');
        String newJobId = '' + System.enqueueJob(new RetryLimitDemo()); // This call fails after 5 times when it hits the chaining limit
        System.debug('Enqueued new job: ' + newJobId);
    }
    System.debug('Completed: execution of finalizer attached to queueable job: ' + parentJobId);
  }
}

最佳实践

我们敦促 ISV 在使用具有状态突变的全局终结器时要谨慎 包中的方法。如果订阅者组织的实现在 全局终结器,可能会导致意外行为。检查所有 状态突变方法,用于查看它们如何影响终结器状态和整体 行为。

事务终结器错误消息

通过分析语义和运行时问题来排查这些问题 错误消息。下表提供有关 Apex 调试日志中错误消息的信息。

错误信息失败的上下文失败原因
不能将多个终结器附加到同一个异步Apex 工作可排队的执行System.attachFinalizer()被多次调用 在同一个 Queueable 实例中。
类 {0} 必须实现终结器接口可排队的执行实例化的类参数 to 不实现接口。System.attachFinalizer()System.Finalizer
System.attachFinalizer(Finalizer) 是不允许的 上下文不可排队的执行System.attachFinalizer()在 Apex 中调用 未执行 Queueable 实例的上下文。
参数数量无效可排队的执行无效的参数数System.attachFinalizer()
参数不能为 null可排队的执行System.attachFinalizer()以 null 调用 参数。

如果您有适用于 Salesforce 的 Splunk 附加组件,则可以分析错误 Splunk 日志中的消息。下表提供有关 Splunk 日志。

错误信息失败原因
处理可排队作业 ID 的终结器时出错: {0}执行终结器时出现运行时错误。此错误可以是 未处理的可捕获异常或不可捕获的异常(例如 一个 LimitException),或者不太常见的是内部系统 错误。
处理终结器(类名:{0})时出错 可排队作业 ID:{1}(可排队类 ID:{2})执行终结器时出现运行时错误。此错误可以是 未处理的可捕获异常或不可捕获的异常(例如 一个 LimitException),或者不太常见的是内部系统 错误。

Apex 调度程序

要调用 Apex 类在特定时间运行,请首先实现该类的接口,然后 使用 Salesforce 用户中的“计划Apex”页面指定计划 接口或方法。SchedulableSystem.schedule

重要

Salesforce 计划在指定时间执行类。 实际执行可能会根据服务可用性延迟。

您一次只能有 100 个计划的 Apex 作业。您可以评估您当前的 通过在 Salesforce 中查看“计划作业”页面并使用 类型过滤器等于“Scheduled Apex”。还可以以编程方式查询 CronTrigger 和 CronJobDetail 对象来获取计划的 Apex 计数 工作。

如果出现以下情况,请格外小心 您计划从触发器安排课程。您必须能够 保证触发器添加的计划类不会超过限制。在 特别是,考虑 API 批量更新、导入向导、批量记录更改 用户界面,以及可以在 时间。

如果 Apex 类有一个或多个活动计划作业, 您无法通过 Salesforce 用户界面。但是,您可以启用部署以使用 使用元数据 API 激活计划作业(例如,使用 Salesforce 时) Visual Studio Code 的扩展)。请参阅中的“更改集的部署连接” Salesforce 帮助。

实现 Schedulable 接口

要安排 Apex 类定期运行,首先 编写一个实现 Salesforce 提供的接口的 Apex 类。Schedulable

调度程序以 系统 – 执行所有类,无论用户是否具有执行权限 类与否。

监视或停止计划的 Apex 作业的执行 使用 Salesforce 用户界面,从“设置”中输入“快速查找”框,然后选择“计划作业”。Scheduled Jobs该接口包含一个必须实现的方法。

Schedulableexecute

global void execute(SchedulableContext sc){}

这 实现的方法必须声明为 或 。globalpublic用 此方法实例化要调度的类。

提示

虽然这是可能的 在方法中进行额外的处理,我们建议所有处理都必须采取 放在一个单独的班级中。execute

以下示例 实现 类称为:SchedulableMergeNumbers

global class ScheduledMerge implements Schedulable {
   global void execute(SchedulableContext SC) {
      MergeNumbers M = new MergeNumbers(); 
   }
}

要实现该类,请在 Developer 中执行此示例 安慰。

ScheduledMerge m = new ScheduledMerge();
String sch = '20 30 8 10 2 ?';
String jobID = System.schedule('Merge Job', sch, m);

您还可以使用 与批处理 Apex 的接口 类。下面的示例实现名为 的批处理 Apex 类的接口:SchedulableSchedulableBatchable

global class ScheduledBatchable implements Schedulable {
   global void execute(SchedulableContext sc) {
      Batchable b = new Batchable(); 
      Database.executeBatch(b);
   }
}

计划批处理作业的更简单方法是调用该方法,而无需实现接口。System.scheduleBatchSchedulable

在以下情况下使用 SchedulableContext 对象跟踪计划作业 这是预定的。SchedulableContext 方法返回关联的 CronTrigger 对象的 ID 将此计划作业作为字符串。您可以查询以跟踪计划作业的进度。getTriggerIDCronTrigger

自 停止执行已计划的作业时,请使用该方法和该方法返回的 ID。System.abortJobgetTriggerID

使用查询跟踪计划作业的进度

计划 Apex 作业后,您可以通过以下方式获取有关它的详细信息 在 CronTrigger 上运行 SOQL 查询。您可以检索作业的次数 run,以及计划再次运行作业的日期和时间,如下所示 例。

CronTrigger ct = 
    [SELECT TimesTriggered, NextFireTime
    FROM CronTrigger WHERE Id = :jobID];

前面的示例假定您有一个变量,用于保存作业的 ID。该方法返回作业 ID。如果要执行此查询 在您的 Schedulable 方法中 类,可以通过调用 SchedulableContext 参数来获取当前作业的 ID 变量。假设此变量名称为 ,则 修改后的示例变为:jobIDSystem.scheduleexecutegetTriggerIdsc

CronTrigger ct = 
    [SELECT TimesTriggered, NextFireTime
    FROM CronTrigger WHERE Id = :sc.getTriggerId()];

还可以从 CronJobDetail 获取作业的名称和作业类型 与 CronTrigger 记录关联的记录。为此,请在 Cron触发器。此示例检索具有作业名称的最新 CronTrigger 记录 并从 CronJobDetail 键入。CronJobDetail

CronTrigger job = 
    [SELECT Id, CronJobDetail.Id, CronJobDetail.Name, CronJobDetail.JobType 
    FROM CronTrigger ORDER BY CreatedDate DESC LIMIT 1];

或者,您可以直接查询 CronJobDetail 以获取作业的名称和 类型。下一个示例获取 CronTrigger 记录的作业名称和类型 在前面的示例中查询。获取对应的 CronJobDetail 记录 ID 通过表达式 CronTrigger 记录。CronJobDetail.Id

CronJobDetail ctd = 
    [SELECT Id, Name, JobType 
    FROM CronJobDetail WHERE Id = :job.CronJobDetail.Id];

获取所有 Apex 计划作业(不包括所有其他计划作业)的总数 类型,请执行以下查询。请注意,为作业类型指定了值“7”。 这与计划的 Apex 作业类型相对应。

SELECT COUNT() FROM CronTrigger WHERE CronJobDetail.JobType = '7'

测试 Apex 调度

这 以下是如何使用 Apex 调度程序进行测试的示例。

该方法启动异步 过程。测试计划的 Apex 时,必须确保计划的作业是 在对照结果进行测试之前完成。在继续测试之前,请使用测试方法和方法以确保其完成。进行的所有异步调用 在方法被收集后 系统。执行时,所有 异步进程是同步运行的。如果未在 和 方法中包含该方法,则计划作业将在测试方法结束时执行 对于使用 Salesforce API 版本 25.0 及更高版本保存的 Apex,但在早期版本中则不然。System.schedulestartTeststopTestSystem.schedulestartTeststopTestSystem.schedulestartTeststopTest此示例定义要测试的类。

global class TestScheduledApexFromTestMethod implements Schedulable {

// This test runs a scheduled job at midnight Sept. 3rd. 2042

   public static String CRON_EXP = '0 0 0 3 9 ? 2042';
   
   global void execute(SchedulableContext ctx) {
      CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime
                FROM CronTrigger WHERE Id = :ctx.getTriggerId()];

      System.assertEquals(CRON_EXP, ct.CronExpression);
      System.assertEquals(0, ct.TimesTriggered);
      System.assertEquals('2042-09-03 00:00:00', String.valueOf(ct.NextFireTime));

      Account a = [SELECT Id, Name FROM Account WHERE Name = 
                  'testScheduledApexFromTestMethod'];
      a.name = 'testScheduledApexFromTestMethodUpdated';
      update a;
   }   
}

以下测试 类:

@istest
class TestClass {

   static testmethod void test() {
   Test.startTest();

      Account a = new Account();
      a.Name = 'testScheduledApexFromTestMethod';
      insert a;

      // Schedule the test job

      String jobId = System.schedule('testBasicScheduledApex',
      TestScheduledApexFromTestMethod.CRON_EXP, 
         new TestScheduledApexFromTestMethod());

      // Get the information from the CronTrigger API object
      CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, 
         NextFireTime
         FROM CronTrigger WHERE id = :jobId];

      // Verify the expressions are the same
      System.assertEquals(TestScheduledApexFromTestMethod.CRON_EXP, 
         ct.CronExpression);

      // Verify the job has not run
      System.assertEquals(0, ct.TimesTriggered);

      // Verify the next time the job will run
      System.assertEquals('2042-09-03 00:00:00', 
         String.valueOf(ct.NextFireTime));
      System.assertNotEquals('testScheduledApexFromTestMethodUpdated',
         [SELECT id, name FROM account WHERE id = :a.id].name);

   Test.stopTest();

   System.assertEquals('testScheduledApexFromTestMethodUpdated',
   [SELECT Id, Name FROM Account WHERE Id = :a.Id].Name);

   }
}

使用 System.schedule 方法

使用接口实现类后,使用该方法执行该类。调度程序以 系统 – 执行所有类,无论用户是否具有执行权限 类,或不类。

SchedulableSystem.schedule

注意

如果您打算安排 类。您必须能够保证触发器 添加的预定课程不会超过限制。特别要考虑 API 批量更新、导入向导、通过用户批量记录更改 接口,以及可以在 时间。

该方法采用三个参数:作业的名称、 用于表示作业计划运行的时间和日期的表达式, 和类的名称。System.schedule此表达式具有以下特征 语法:

Seconds Minutes Hours Day_of_month Month Day_of_week Optional_year

注意

Salesforce的 计划在指定时间执行类。实际执行可以是 根据服务可用性延迟。

该方法使用 用户时区作为所有计划的基础。System.schedule

以下是表达式的值:

名字特殊字符
Seconds0–59没有
Minutes0–59没有
Hours0–23, – * /
Day_of_month1–31, – * ? / L W
Month1-12 或以下:JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC, – * /
Day_of_week1–7 or the following:SUNMONTUEWEDTHUFRISAT, – * ? / L #
optional_yearnull 或 1970–2099, – * /

特殊字符定义如下:

特殊字符描述
,分隔值。例如,用于指定 一个多月。JAN, MAR, APR
指定范围。例如,use 指定 more than 一个月。JAN-MAR
*指定所有值。例如,如果指定为 ,则作业计划为 每个月。Month*
?不指定任何特定值。此选项仅可用 for 和 .它通常用于以下情况 为一个指定值,而不是为另一个指定值。Day_of_monthDay_of_week
/指定增量。斜杠前的数字指定 间隔何时开始,斜杠后的数字为 间隔量。例如,如果指定 for ,则 Apex 类每隔 每月的第五天,从 月。1/5Day_of_month
L指定范围的结束(最后一个)。此选项仅 可用于 和 。当与 一起使用时,始终表示当月的最后一天,例如 1 月 31 日、2 月 29 日(闰年)等。使用时 就其本身而言,它总是意味着 或 。当与值一起使用时,它表示最后一个 当月的那一天。例如,如果指定 ,则 指定当月的最后一个星期一。不要使用范围 值替换为 结果可能出乎意料。Day_of_monthDay_of_weekDay of monthLDay_of_week7SATDay_of_week2LL
W指定给定的最近的工作日(星期一至星期五) 日。此选项仅适用于 。例如,如果指定 ,而第 20 个是 星期六,上课在19日。如果指定 ,并且第一个是 星期六,课程不是在上个月上课,而是在 第三次,即下周一。Day_of_month20W1W提示用 的 和 一起指定 每月的最后一个工作日。LW
#指定月份中的某一天,在 格式。 此选项仅适用于 。指定的工作日 () 之前的数字。之后的数字 指定日期 的月份。例如,指定意味着类在 每个月的第一个星期一。nthweekday#day_of_monthDay_of_week#SUN-SAT#2#1

以下是如何使用 表达。

表达描述
0 0 13 * * ?每天下午1点上课。
0 5 * * * ?课程每小时上课一次,整点后 5 分钟。注意Apex 不允许将作业计划多次 小时。
0 0 22 ? * 6L每个月的最后一个星期五晚上 10 点上课。
0 0 10 ? * MON-FRI上课时间为周一至周五上午 10 点。
0 0 20 * * ? 2010在2010年,每天晚上8点上课。

在下面的示例中,该类实现接口。该课程计划于上午 8 点在 2月13日。ProscheduleSchedulable

Proschedule p = new Proschedule();
        String sch = '0 0 8 13 2 ?';
        System.schedule('One Time Pro', sch, p);

将 System.scheduleBatch 方法用于批处理作业

您可以调用该方法 将批处理作业安排为在将来的指定时间运行一次。此方法是 仅适用于批处理类,不需要接口的实现。因此,这很容易 为一次执行计划批处理作业。有关如何使用该方法的更多详细信息,请参见使用 System.scheduleBatch 方法。System.scheduleBatchSchedulableSystem.scheduleBatch

Apex Scheduler 限制

  • 您一次只能有 100 个计划的 Apex 作业。您可以评估您的 通过查看 Salesforce 中的“计划作业”页面并创建 具有等于“Scheduled Apex”的类型过滤器的自定义视图。您可以 还以编程方式查询 CronTrigger 和 CronJobDetail 对象以获取 Apex 计划作业的计数。
  • 每 24 小时内计划的最大 Apex 执行次数为 250,000 次或 组织中的用户许可证数乘以 200,以 大。此限制适用于整个组织,并与所有异步 Apex 共享: Batch Apex、Queueable Apex、scheduled Apex 和 future 方法。检查多少 异步 Apex 执行可用,向 REST API 资源发出请求。查看清单 REST 中的组织限制 API 开发人员指南。计入此目的的许可证类型 限制包括完整的 Salesforce 和 Salesforce Platform 用户许可证、应用程序订阅 用户许可证、仅限 Chatter 用户、身份用户和公司社区 用户。limits

Apex Scheduler 说明和最佳实践

  • Salesforce 计划在指定时间执行类。实际执行 可能会根据服务可用性延迟。
  • 如果您打算从触发器安排课程,请格外小心。你 必须能够保证触发器不会添加更多计划类 超过极限。具体而言,请考虑 API 批量更新、导入向导、批量记录 通过用户界面进行更改,以及可以有多个记录的所有情况 一次更新。
  • 尽管可以在该方法中进行其他处理,但我们建议所有 处理必须在单独的类中进行。execute
  • 计划的 Apex 不支持同步 Web 服务标注。要使 异步标注,使用 Queueable Apex,实现标记接口。如果你的 scheduled Apex 使用标记器接口执行批处理作业,支持 批处理类。请参阅使用 Batch Apex。Database.AllowsCalloutsDatabase.AllowsCallouts
  • 计划在 Salesforce 服务维护停机期间运行的 Apex 作业将是 计划在服务恢复后运行,此时系统资源变为 可用。如果在发生停机时计划的 Apex 作业正在运行,则该作业是 在服务恢复后回滚并再次计划。大修后 升级时,启动计划的 Apex 作业可能会有比平时更长的延迟 因为系统使用率激增。
  • 计划作业对象及其成员变量和属性将持续存在 从初始化到后续计划运行。对象状态 persists 的调用 后续作业执行。System.schedule()使用 Batch Apex,可以强制执行新的 使用新作业的序列化状态。对于 Scheduled Apex,请使用关键字,以便成员变量和 属性不会持久化。请参阅使用 transient 关键字。Database.Statefultransient

批处理Apex

开发人员现在可以使用批处理 Apex 来构建复杂的、长时间运行的流程,这些流程可以运行 在闪电平台上的数千条记录上。Batch Apex 在小型 批量记录,涵盖整个记录集,并将处理分解为 可管理的块。例如,开发人员可以构建一个运行 每晚查找超过特定日期的记录并将它们添加到 档案。或者,开发人员可以构建一个数据清理操作,该操作遍及所有 每晚的帐户和业务机会,并在必要时根据 自定义条件。

Batch Apex 公开为必须由 开发人员。可以使用 Apex 在运行时以编程方式调用批处理作业。一次只能有五个排队或活动的批处理作业。您可以评估您的 通过在 Salesforce 中查看“计划作业”页面或以编程方式使用 用于查询对象的 SOAP API。

AsyncApexJob

警告

如果您是 计划从触发器调用批处理作业。您必须能够保证 触发器添加的批处理作业不会超过限制。特别 考虑 API 批量更新、导入向导、通过用户批量记录更改 接口,以及可以在 时间。

还可以使用 Apex 调度程序以编程方式调度批处理作业,以在特定时间运行,或使用调度调度作业 Salesforce 用户界面中的 Apex 页面。有关 Schedule Apex 的更多信息 页面上,请参阅 Salesforce 联机帮助中的“安排 Apex 作业”。

批处理 Apex 接口还用于 Apex 托管共享重新计算。

有关批处理作业的详细信息,请继续使用 Batch Apex。

有关 Apex 托管共享的详细信息,请参阅了解 Apex 托管共享。

有关从批处理 Apex 触发平台事件的详细信息,请参阅从批处理 Apex 触发平台事件

  • 使用 Batch Apex
  • 从 Batch Apex 触发平台事件 Batch Apex
    类可以在遇到错误或异常时触发平台事件。侦听事件的客户端可以获取可操作的信息,例如事件失败的频率以及失败时哪些记录在范围内。对于 Salesforce Platform 内部错误和其他无法捕获的 Apex 异常(如 LimitExceptions),也会触发事件,这些异常是由达到调控器限制引起的。

使用 Batch Apex

要使用批处理 Apex,请编写一个实现 Salesforce 提供的接口的 Apex 类,然后调用 以编程方式类。Database.Batchable

要监控或停止批处理 Apex 作业的执行,请从“设置”中输入“快速查找”框,然后选择“Apex 作业”。Apex Jobs

实现 Database.Batchable 接口

该接口包含 必须实现的三种方法。

Database.Batchable

  • start方法:public (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {}自 收集要传递给接口方法的记录或对象,在 批处理 Apex 作业。此方法返回一个对象或 iterable,包含传递给 工作。executestartDatabase.QueryLocator使用简单查询 () 生成范围时 批处理作业中的对象,请使用该对象。如果使用对象,则调控器 SOQL 查询检索的记录总数限制为 绕过。例如,Account 对象的批处理 Apex 作业可以 全部返回 A 组织中的帐户记录(最多 5000 万条记录)。另一个例子是 对 Contact 对象的共享重新计算,该对象为所有客户记录返回 在组织中。SELECTDatabase.QueryLocatorQueryLocatorQueryLocatorQueryLocator使用 iterable 为 批处理作业。您还可以使用迭代对象来创建自己的自定义 循环访问列表的过程。重要如果您使用 可迭代,记录总数的调控器限制 由 SOQL 查询检索到的仍会强制执行。欲了解更多信息 将可迭代对象用于批处理作业,请参阅 Batch Apex 最佳实践
  • execute方法:public void execute(Database.BatchableContext BC, list<P>){}自 对每个数据块进行所需的处理,请使用该方法。此方法是 为传递给它的每批记录调用。execute此方法 采取以下措施:
    • 对对象的引用。Database.BatchableContext
    • sObject 的列表,例如 或参数化的列表 类型。如果您使用的是 ,请使用返回的列表。List<sObject>Database.QueryLocator
    批量记录往往按照以下顺序执行 它们是从方法接收的。但是,批量记录的执行顺序取决于 在各种因素上。不保证执行顺序。start
  • finish方法:public void finish(Database.BatchableContext BC){}自 发送确认电子邮件或执行后处理操作,请使用该方法。此方法 在处理完所有批处理后调用。finish

批处理 Apex 作业的每次执行都被视为离散事务。例如 包含 1,000 条记录且在没有可选参数的情况下执行的批处理 Apex 作业被视为 200 条记录的 5 个事务 每。每个事务都会重置 Apex 调控器限制。如果第一个 事务成功,但第二个事务失败,第一个事务中的数据库更新 事务不会回滚。scopeDatabase.executeBatch

用 Database.BatchableContext

都 接口中的方法需要对对象的引用。使用此对象来跟踪 批处理作业的进度。Database.BatchableDatabase.BatchableContext以下是具有该对象的实例方法:

Database.BatchableContext

名字参数返回描述
getJobID编号以字符串形式返回与此批处理作业关联的 AsyncApexJob 对象的 ID。使用这个 方法跟踪批处理作业中记录的进度。你 还可以将此 ID 与 System.abortJob 方法一起使用。

以下示例使用 查询与批处理关联的 工作。Database.BatchableContextAsyncApexJob

public void finish(Database.BatchableContext BC){
   // Get the ID of the AsyncApexJob representing this batch job
   // from Database.BatchableContext.
   // Query the AsyncApexJob object to retrieve the current job's information.
   AsyncApexJob a = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed,
      TotalJobItems, CreatedBy.Email
      FROM AsyncApexJob WHERE Id =
      :BC.getJobId()];
   // Send an email to the Apex job's submitter notifying of job completion.
   Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
   String[] toAddresses = new String[] {a.CreatedBy.Email};
   mail.setToAddresses(toAddresses);
   mail.setSubject('Apex Sharing Recalculation ' + a.Status);
   mail.setPlainTextBody
   ('The batch Apex job processed ' + a.TotalJobItems +
   ' batches with '+ a.NumberOfErrors + ' failures.');
   Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}

使用 Database.QueryLocator 定义 范围

该方法可以返回一个对象,该对象包含 要在批处理作业或可迭代对象中使用的记录。startDatabase.QueryLocator以下示例使用 :

Database.QueryLocator

public class SearchAndReplace implements Database.Batchable<sObject>{

   public final String Query;
   public final String Entity;
   public final String Field;
   public final String Value;

   public SearchAndReplace(String q, String e, String f, String v){

      Query=q; Entity=e; Field=f;Value=v;
   }

   public Database.QueryLocator start(Database.BatchableContext BC){
      return Database.getQueryLocator(query);
   }

   public void execute(Database.BatchableContext BC, List<sObject> scope){
     for(sobject s : scope){
     s.put(Field,Value); 
     }
     update scope;
    }

   public void finish(Database.BatchableContext BC){
   }
}

使用 Batch Apex 中的可迭代对象来定义 范围

该方法可以返回一个对象,该对象包含 要在批处理作业或可迭代对象中使用的记录。使用 iterable 单步执行 更容易退回物品。startDatabase.QueryLocator

public class batchClass implements Database.batchable{ 
   public Iterable start(Database.BatchableContext info){ 
       return new CustomAccountIterable(); 
   }     
   public void execute(Database.BatchableContext info, List<Account> scope){
       List<Account> accsToUpdate = new List<Account>();
       for(Account a : scope){ 
           a.Name = 'true'; 
           a.NumberOfEmployees = 70; 
           accsToUpdate.add(a); 
       } 
       update accsToUpdate; 
   }     
   public void finish(Database.BatchableContext info){     
   } 
}

使用 Database.executeBatch 方法提交批处理作业

您可以使用该方法 以编程方式开始批处理作业。

Database.executeBatch

重要

当您调用 时,Salesforce 会将流程添加到 队列。实际执行可能会根据服务延迟 可用性。Database.executeBatch该方法采用两个参数:

Database.executeBatch

  • 实现接口的类的实例。Database.Batchable
  • 可选参数 。此参数指定 要传递到方法中的记录。scopeexecute使用这个 参数,当您对要传递的每条记录执行许多操作时 并且正在遇到调控器限制。通过限制数量 记录,则限制了每笔交易的操作。此值 必须大于零。如果批处理类的方法返回 QueryLocator, 可选范围参数的最大值为startDatabase.executeBatch2,000.如果设置为更高的值,则 Salesforce 将 QueryLocator 返回的记录分块为更小的批次 最多 2,000 条记录。如果批处理的方法 类返回一个可迭代对象,scope 参数值没有上限。 但是,如果使用较高的数字,则可能会遇到其他限制。这 最佳示波器大小是 2000 的系数,例如 100、200、400 和 等等。start

该方法返回 AsyncApexJob 对象的 ID,可用于跟踪作业的进度。 例如:

Database.executeBatch

ID batchprocessid = Database.executeBatch(reassign);

AsyncApexJob aaj = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors 
                    FROM AsyncApexJob WHERE ID =: batchprocessid ];

还可以将此 ID 与 System.abortJob 方法一起使用。

有关详细信息,请参阅对象中的 AsyncApexJob Salesforce 参考。

在 Apex Flex 中保存批处理作业 队列

使用 Apex flex 队列,您最多可以提交 100 个批次 工作。结果如下。

Database.executeBatch

  • 批处理作业将放置在 Apex flex 队列中,其状态设置为 。Holding
  • 如果 Apex flex 队列的最大作业数为 100 个,则抛出一个并且不添加该作业 到队列。Database.executeBatchLimitException

注意

如果您的组织未启用 Apex flex 队列,请将批处理作业添加到 具有状态的批处理作业队列。如果 已达到排队或活动批处理作业的并发限制,引发了 A,但作业未 排队。Database.executeBatchQueuedLimitException

对 Apex Flex 队列中的作业重新排序

提交时 作业的状态为 ,可以 在 Salesforce 用户界面中对它们重新排序,以控制哪些批处理作业是 首先处理。为此,请从“设置”中输入“快速查找”框,然后选择“Apex Flex” 队列HoldingApex Flex Queue

或者,您可以使用 Apex 方法重新排序 Flex 队列中的批处理作业。若要将作业移动到新位置,请调用 System.FlexQueue 方法之一。将作业 ID 传递给该方法, 如果适用,则为移动作业的新职位旁边的作业 ID。为 例:

Boolean isSuccess = System.FlexQueue.moveBeforeJob(jobToMoveId, jobInQueueId);

您可以对 Apex flex 队列中的作业重新排序,以确定作业的优先级。为 例如,您可以将批处理作业移动到保留队列中的第一个位置 当资源可用时,首先处理。否则,将处理作业 “先进先出”——按提交顺序排列。

当系统 资源变为可用,系统将从 Apex flex 队列,并将其移动到批处理作业队列。该系统可以处理多达 每个组织同时有 5 个排队或活动的作业。这些状态 将作业更改从 移动到 。排队的作业在系统执行时执行 已准备好处理新作业。您可以在 Apex 作业上监控排队的作业 页。HoldingQueued

批处理作业状态

下表列出了批处理作业的所有可能状态以及 每个的描述。

地位描述
占有作业已提交并保留在 Apex flex 队列中,直到 系统资源可用于对作业进行排队 加工。
排队作业正在等待执行。
准备方法 job 已被调用。此状态可能会持续几分钟,具体取决于 关于批量记录的大小。start
加工正在处理作业。
中止用户已中止作业。
完成作业已完成,但有或没有失败。
失败作业遇到系统故障。

使用 System.scheduleBatch 方法

您可以使用该方法 将批处理作业安排为在将来运行一次。System.scheduleBatch该方法采用以下参数。

System.scheduleBatch

  • 实现接口的类的实例。Database.Batchable
  • 作业名称。
  • 作业开始执行的时间间隔(以分钟为单位)。
  • 可选范围值。此参数指定要 传入方法。使用这个 参数,当您对要传递的每条记录执行许多操作时 并且正在遇到调控器限制。通过限制数量 记录,则限制了每笔交易的操作。此值 必须大于零。如果批处理类的方法返回 QueryLocator, 可选范围参数的最大值为executestartDatabase.executeBatch2,000.如果设置为更高的值,则 Salesforce 将 QueryLocator 返回的记录分块为更小的批次 最多 2,000 条记录。如果批处理的方法 类返回一个可迭代对象,scope 参数值没有上限。 但是,如果使用较高的数字,则可能会遇到其他限制。这 最佳示波器大小是 2000 的系数,例如 100、200、400 和 等等。start

该方法返回计划的作业 ID (CronTrigger ID)。System.scheduleBatch此示例将批处理作业安排在 60 分钟内运行 现在通过调用 .这 示例向此方法传递批处理类(变量)、作业名称和时间的实例 间隔 60 分钟。可选参数已 省略。该方法返回计划作业 ID,用于查询 CronTrigger 获取相应计划的状态 工作。

System.scheduleBatchreassignscope

String cronID = System.scheduleBatch(reassign, 'job example', 60);

CronTrigger ct = [SELECT Id, TimesTriggered, NextFireTime
                FROM CronTrigger WHERE Id = :cronID];

// TimesTriggered should be 0 because the job hasn't started yet.
System.assertEquals(0, ct.TimesTriggered);
System.debug('Next fire time: ' + ct.NextFireTime); 
// For example:
// Next fire time: 2013-06-03 13:31:23

有关详细信息,请参阅对象中的 CronTrigger Salesforce 参考。

注意

需要注意的一些事项:System.scheduleBatch

  • 当您调用 , Salesforce 计划在指定时间执行作业。实际 在该时间或之后执行,具体取决于服务 可用性。System.scheduleBatch
  • 调度程序作为系统运行——所有类都已执行,无论 用户是否有权执行该类。
  • 触发作业的计划时,系统会将批处理作业排入队列 加工。如果在您的组织中启用了 Apex flex 队列,则批处理作业为 添加到 Flex 队列的末尾。有关更多信息,请参阅将批处理作业保存在 Apex Flex 队列。
  • 所有计划的 Apex 限制都适用于批处理作业 计划使用 。批处理作业排队后(使用 状态为 或 ),所有批处理作业限制都适用 并且作业不再计入计划的 Apex 限制。System.scheduleBatchHoldingQueued
  • 调用此方法后,在批处理作业开始之前,可以使用 返回计划的作业 ID 以使用 System.abortJob 方法中止计划作业。

Batch Apex 示例

以下示例使用 :

Database.QueryLocator

public class UpdateAccountFields implements Database.Batchable<sObject>{
   public final String Query;
   public final String Entity;
   public final String Field;
   public final String Value;

   public UpdateAccountFields(String q, String e, String f, String v){
             Query=q; Entity=e; Field=f;Value=v;
   }

   public Database.QueryLocator start(Database.BatchableContext BC){
      return Database.getQueryLocator(query);
   }

   public void execute(Database.BatchableContext BC, 
                       List<sObject> scope){
      for(Sobject s : scope){s.put(Field,Value); 
      }      update scope;
   }

   public void finish(Database.BatchableContext BC){

   }

}

您可以使用以下代码调用上一个 类。

// Query for 10 accounts
String q = 'SELECT Industry FROM Account LIMIT 10';
String e = 'Account';
String f = 'Industry';
String v = 'Consulting';
Id batchInstanceId = Database.executeBatch(new UpdateAccountFields(q,e,f,v), 5);

若要排除已删除但仍在回收站中的帐户或发票, 包含在 SOQL 查询中 WHERE 子句,如这些修改后的 样品。

isDeleted=false

// Query for accounts that aren't in the Recycle Bin
String q = 'SELECT Industry FROM Account WHERE isDeleted=false LIMIT 10';
String e = 'Account';
String f = 'Industry';
String v = 'Consulting';
Id batchInstanceId = Database.executeBatch(new UpdateAccountFields(q,e,f,v), 5);
// Query for invoices that aren't in the Recycle Bin
String q = 
  'SELECT Description__c FROM Invoice_Statement__c WHERE isDeleted=false LIMIT 10';
String e = 'Invoice_Statement__c';
String f = 'Description__c';
String v = 'Updated description';
Id batchInstanceId = Database.executeBatch(new UpdateInvoiceFields(q,e,f,v), 5);

以下类使用批处理 Apex 重新分配特定用户拥有的所有帐户 给其他用户。

public class OwnerReassignment implements Database.Batchable<sObject>{
String query;
String email;
Id toUserId;
Id fromUserId;

public Database.querylocator start(Database.BatchableContext BC){
            return Database.getQueryLocator(query);}

public void execute(Database.BatchableContext BC, List<sObject> scope){
    List<Account> accns = new List<Account>();

   for(sObject s : scope){Account a = (Account)s;
        if(a.OwnerId==fromUserId){
            a.OwnerId=toUserId;
            accns.add(a);
            }
        }

update accns;
    
}
public void finish(Database.BatchableContext BC){
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();

mail.setToAddresses(new String[] {email});
mail.setReplyTo('batch@acme.com');
mail.setSenderDisplayName('Batch Processing');
mail.setSubject('Batch Process Completed');
mail.setPlainTextBody('Batch Process has completed');

Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}

使用以下命令执行上一个中的类 例。

OwnerReassignment

OwnerReassignment reassign = new OwnerReassignment();
reassign.query = 'SELECT Id, Name, Ownerid FROM Account ' + 
                'WHERE ownerid=\'' + u.id + '\'';
reassign.email='admin@acme.com';
reassign.fromUserId = u;
reassign.toUserId = u2;
ID batchprocessid = Database.executeBatch(reassign);

以下是用于删除的批处理 Apex 类的示例 记录。

public class BatchDelete implements Database.Batchable<sObject> {
   public String query;

   public Database.QueryLocator start(Database.BatchableContext BC){
      return Database.getQueryLocator(query);
   }

   public void execute(Database.BatchableContext BC, List<sObject> scope){
      delete scope;
      DataBase.emptyRecycleBin(scope);
   }

   public void finish(Database.BatchableContext BC){
   }
}

此代码调用批处理 Apex 类删除旧文档。指定的查询选择要删除的文档 指定文件夹中且早于指定文件夹的所有文档 日期。接下来,示例调用批处理 工作。

BatchDelete

BatchDelete BDel = new BatchDelete();
Datetime d = Datetime.now();
d = d.addDays(-1);
// Replace this value with the folder ID that contains
// the documents to delete.
String folderId = '00lD000000116lD';
// Query for selecting the documents to delete
BDel.query = 'SELECT Id FROM Document WHERE FolderId=\'' + folderId + 
    '\' AND CreatedDate < '+d.format('yyyy-MM-dd')+'T'+
    d.format('HH:mm')+':00.000Z';
// Invoke the batch job.
ID batchprocessid = Database.executeBatch(BDel);
System.debug('Returned batch process ID: ' + batchProcessId);

在 Batch Apex 中使用标注

要在批处理 Apex 中使用标注,请在类中指定 定义。为 例:

Database.AllowsCallouts

public class SearchAndReplace implements Database.Batchable<sObject>, 
   Database.AllowsCallouts{
}

标注包括使用 KEYWORD 定义的 HTTP 请求和方法。webservice

在 Batch Apex 中使用状态

批处理 Apex 作业的每次执行都被视为离散事务。例如 包含 1,000 条记录且在没有可选参数的情况下执行的批处理 Apex 作业被视为 200 个事务的 5 个事务 记录每个。scope

如果在 类定义,您可以在这些事务中维护状态。使用 时,仅限实例成员 变量在事务之间保留其值。静态成员变量不会 保留其值并在交易之间重置。维护状态很有用 用于在处理记录时对记录进行计数或汇总。例如,假设你的 作业流程商机记录。您可以定义一种方法来聚合商机的总数 处理时的金额。Database.StatefulDatabase.Statefulexecute

如果未指定 ,则所有 静态和实例成员变量将设置回其原始值。Database.Stateful以下示例将自定义字段total__c汇总为 记录是 处理。

public class SummarizeAccountTotal implements 
    Database.Batchable<sObject>, Database.Stateful{

   public final String Query;
   public integer Summary;
  
   public SummarizeAccountTotal(String q){Query=q;
     Summary = 0;
   }

   public Database.QueryLocator start(Database.BatchableContext BC){
      return Database.getQueryLocator(query);
   }
   
   public void execute(
                Database.BatchableContext BC, 
                List<sObject> scope){
      for(sObject s : scope){
         Summary = Integer.valueOf(s.get('total__c'))+Summary;
      }
   }

public void finish(Database.BatchableContext BC){
   }
}

此外,还可以指定一个变量来访问类的初始状态。你 可以使用此变量与方法的所有实例共享初始状态。为 例:

Database.Batchable

// Implement the interface using a list of Account sObjects
// Note that the initialState variable is declared as final

public class MyBatchable implements Database.Batchable<sObject> {
  private final String initialState;
  String query;
  
  public MyBatchable(String intialState) {
    this.initialState = initialState;
  }

  public Database.QueryLocator start(Database.BatchableContext BC) {
    // Access initialState here 
    
    return Database.getQueryLocator(query);
  }

  public void execute(Database.BatchableContext BC, 
                      List<sObject> batch) {
    // Access initialState here 
    
  }

  public void finish(Database.BatchableContext BC) {
    // Access initialState here 
    
  }
}

仅存储类的初始状态。您不能使用它在 在批处理作业执行期间类的实例。例如,如果将 in 的值,即已处理记录的第二个块 无法访问新值。只有初始值是可访问的。initialStateinitialStateexecute

测试 Batch Apex

在测试批处理 Apex 时,您可以测试 该方法仅执行一次。使用该方法的参数来限制 传递到方法中的记录 以确保您不会遇到调控器限制。executescopeexecuteBatchexecute

该方法启动一个异步进程。测试时 批处理 Apex,确保异步处理的批处理作业已完成 在对照结果进行测试之前。使用测试方法和方法,确保在继续操作之前完成 你的测试。executeBatchstartTeststopTestexecuteBatch系统将收集在该方法之后进行的所有异步调用。 执行时,全部异步 进程是同步运行的。如果未在 和 方法中包含该方法,则批处理作业将在测试结束时执行 方法。此执行顺序适用于使用 API 版本 25.0 保存的 Apex,并且 更高版本,但不适用于早期版本。startTeststopTestexecuteBatchstartTeststopTest

对于使用 API 版本 22.0 及更高版本保存的 Apex, 在执行测试调用的批处理 Apex 作业期间发生的异常 方法被传递给调用测试方法。因此,这些异常 导致测试方法失败。如果要在测试中处理异常 方法,将代码括在 AND 语句中。将块放在方法之后。但是,保存了 Apex 使用 Apex 版本 21.0 及更早版本,此类异常不会传递给 测试方法,并且不会导致测试方法失败。trycatchcatchstopTest

注意

异步调用, 例如 或 ,在 、 块中调用,则不计入 排队的作业。@futureexecuteBatchstartTeststopTest下面的示例测试 OwnerReassignment 类。

public static testMethod void testBatch() {
   user u = [SELECT ID, UserName FROM User 
             WHERE username='testuser1@acme.com'];
   user u2 = [SELECT ID, UserName FROM User 
              WHERE username='testuser2@acme.com'];
   String u2id = u2.id;
// Create 200 test accounts - this simulates one execute.  
// Important - the Salesforce test framework only allows you to 
// test one execute.  

   List <Account> accns = new List<Account>();
      for(integer i = 0; i<200; i++){
         Account a = new Account(Name='testAccount'+ i, 
                     Ownerid = u.ID); 
         accns.add(a);
      }
   
   insert accns;
   
   Test.StartTest();
   OwnerReassignment reassign = new OwnerReassignment();
   reassign.query='SELECT ID, Name, Ownerid ' +
            'FROM Account ' +
            'WHERE OwnerId=\'' + u.Id + '\'' +
            ' LIMIT 200';
   reassign.email='admin@acme.com';
   reassign.fromUserId = u.Id;
   reassign.toUserId = u2.Id;
   ID batchprocessid = Database.executeBatch(reassign);
   Test.StopTest();

   System.AssertEquals(
           database.countquery('SELECT COUNT()'
              +' FROM Account WHERE OwnerId=\'' + u2.Id + '\''),
           200);  
   
   }
}

使用 和 方法执行以下操作 在测试上下文中对无操作作业进行排队和重新排序。System.Test.enqueueBatchJobsSystem.Test.getFlexQueueOrder

Batch Apex 限制

请记住以下调速器限制和其他限制 用于批处理 Apex。

  • 最多可以同时排队或活动 5 个批处理作业。
  • 最多可以有 100 个批处理作业 保存在 Apex flex 队列中。Holding
  • 在运行测试中,您最多可以提交 5 个批处理作业。
  • 每 24 小时期间批量执行 Apex 方法的最大次数为 250,000,或组织中的用户许可证数量乘以 200 – 以较大者为准。方法执行包括 、 和 方法的执行。此限制适用于您的 整个组织,并与所有异步 Apex 共享:Batch Apex、Queueable Apex、计划的 Apex 和未来的方法。检查有多少个异步 Apex 执行可用,向 REST API 资源发出请求。请参阅 REST API 开发人员指南中的列出组织限制。许可证 计入此限制的类型包括完整的 Salesforce 和 Salesforce 平台用户许可证、应用程序订阅用户许可证、仅限 Chatter 用户、 标识用户和公司社区用户。startexecutefinishlimits
  • 最多5000万记录可以是 在对象中返回。如果超过 5000 万条记录 返回,批处理作业将立即终止并标记为 失败。Database.QueryLocator
  • 如果批处理类的方法返回 QueryLocator, 可选范围参数的最大值为startDatabase.executeBatch2,000.如果设置为更高的值,则 Salesforce 将 QueryLocator 返回的记录分块为更小的批次 最多 2,000 条记录。如果批处理的方法 类返回一个可迭代对象,scope 参数值没有上限。 但是,如果使用较高的数字,则可能会遇到其他限制。这 最佳示波器大小是 2000 的系数,例如 100、200、400 和 等等。start
  • 如果未使用可选参数指定大小 之 Salesforce 将该方法返回的记录分块为scopeDatabase.executeBatchstart200记录。然后,系统将每个批次传递给该方法。Apex 调速器限制 在每次执行 时重置。executeexecute
  • 、 和 方法最多可以实现 100 个 每个标注。startexecutefinish
  • 一个组织中一次只能运行一个批处理 Apex 作业的方法。尚未启动的批处理作业 但仍保留在队列中,直到它们开始。此限制不 导致任何批处理作业失败,并且批处理 Apex 作业的方法仍并行运行,如果 多个作业正在运行。startexecute
  • 在 SOQL 查询中使用 更新期间的锁定记录不适用于 Batch Apex。FOR UPDATE
  • 游标和相关查询结果的有效期为 2 天,包括 导致嵌套查询。有关更多信息,请参阅 API 查询游标限制。

Batch Apex 最佳实践

  • 如果计划从触发器调用批处理作业,请格外小心。你 必须能够保证触发器添加的批处理作业不会超过 限制。具体而言,请考虑 API 批量更新、导入向导、批量记录 通过用户界面进行更改,以及多个记录可以更改的所有情况 一次更新。
  • 当您调用 , Salesforce 仅将作业放入队列中。实际执行可能会延迟 基于服务可用性。Database.executeBatch
  • 测试批处理 Apex 时,只能测试该方法的一次执行。使用方法的参数来限制传入该方法的记录数,以确保 没有遇到调速器限制。executescopeexecuteBatchexecute
  • 该方法启动一个 异步进程。测试批处理 Apex 时,请确保 异步处理的批处理作业在针对 结果。使用 Test 方法和围绕该方法确保它 在继续测试之前完成。executeBatchstartTeststopTestexecuteBatch
  • 与类一起使用 如果要在作业之间共享实例成员变量或数据,请进行定义 交易。否则,所有成员变量都将重置为其初始状态 在每笔交易开始时。Database.Stateful
  • 声明为 not 的方法 在实现接口的类中允许。futureDatabase.Batchable
  • 声明为 can’t be 的方法 从批处理 Apex 类调用。future
  • 运行批处理 Apex 作业时,将向以下用户发送电子邮件通知: 提交了批处理作业。如果代码包含在托管包中,并且 订阅组织正在运行批处理作业,通知将发送到 Apex 异常通知中列出的收件人 收件人字段。
  • 每个方法执行都使用标准的 governor limits 匿名块, Visualforce 控制器或 WSDL 方法。
  • 每个批处理 Apex 调用都会创建一条记录。构造 SOQL 查询以检索 作业的状态、错误数、进度和提交者,请使用记录的 ID。AsyncApexJobAsyncApexJob有关该对象的详细信息,请参阅 Salesforce 对象参考中的 AsyncApexJob。AsyncApexJob
  • 对于每 10,000 条记录, Apex 创建记录 供内部使用的类型。 查询所有记录时,建议使用该字段筛选出类型的记录。否则,查询将返回 每 10,000 条记录多一条记录。有关该对象的详细信息,请参阅 Salesforce 对象参考中的 AsyncApexJob。AsyncApexJobAsyncApexJobBatchApexWorkerAsyncApexJobBatchApexWorkerJobTypeAsyncApexJobAsyncApexJob
  • 所有实现的接口方法都必须定义为 或 。Database.Batchablepublicglobal
  • 对于共享重新计算,建议方法删除,然后重新创建 批处理中记录的所有 Apex 托管共享。此过程可确保 共享准确完整。execute
  • 在 Salesforce 服务维护停机之前排队的批处理作业仍保留在 队列。服务停机结束后,当系统资源可用时, 将执行排队的批处理作业。如果批处理作业在停机时正在运行 发生时,批处理执行将在服务后回滚并重新启动 回来了。
  • 如果可能,请尽量减少批次数。Salesforce 使用基于队列的 用于处理来自未来方法等来源的异步过程的框架 和批处理 Apex。此队列用于平衡跨 组织。如果超过 2,000 个未处理的请求来自单个 组织在队列中,来自同一的任何其他请求 当队列处理来自其他组织的请求时,组织将被延迟 组织。
  • 确保批处理作业尽可能快地执行。为确保快速执行 批处理作业,最大限度地减少 Web 服务标注时间,并优化 批处理 Apex 代码。批处理作业执行的时间越长,其他排队的可能性就越大 当队列中有许多作业时,作业会延迟。
  • 如果将批处理 Apex 与通过 OData 访问外部对象 Salesforce Connect 适配器:Database.QueryLocator
    • 在外部数据源上启用请求行计数,并且每个 来自外部系统的响应必须包括总行数 的结果集。
    • 建议在外部数据上启用服务器驱动的分页 源并让外部系统确定页面大小和批次 大型结果集的边界。通常,服务器驱动的分页 可以调整批次边界以适应不断变化的数据集 比客户端驱动的寻呼更有效。当服务器驱动时 在外部数据源 OData 上禁用分页 适配器控制分页行为(客户端驱动)。如果 外部对象记录被添加到外部系统,而 作业运行,其他记录可以处理两次。如果外部 在执行作业时,将从外部系统中删除对象记录 运行,可以跳过其他记录。
    • 在外部数据上启用服务器驱动分页时 source,运行时的批处理大小为以下较小值:
      • 参数中指定的批大小。 默认值为 200 条记录。scopeDatabase.executeBatch
      • 外部系统返回的页面大小。我们建议 将外部系统设置为返回 200 的页面大小 或更少的记录。
  • 当方法通过 子查询。避免在 a 中出现关系子查询允许批处理作业使用更快的分块 实现。如果方法 返回一个可迭代对象或一个具有关系子查询的对象,批处理作业使用较慢的非分块, 实现。例如,如果在 中使用以下查询,则批处理作业使用较慢的 由于关系子查询而实现:startQueryLocatorQueryLocatorstartQueryLocatorQueryLocatorSELECT Id, (SELECT id FROM Contacts) FROM Account更好的策略是从执行中单独执行子查询 方法,允许批处理作业使用更快的分块运行 实现。
  • 若要在批处理作业中实现记录锁定,可以重新查询记录 在 execute() 方法中,如有必要,请使用 FOR UPDATE。这确保了 批处理作业中的 DML 不会覆盖任何冲突的更新。重新查询 记录,只需在批处理作业的主查询定位符中选择 Id 字段即可。

链接批处理作业

从 API 版本 26.0 开始,您可以从现有的 批处理作业,将作业链接在一起。链接批处理作业以在另一个作业之后启动作业 完成以及作业需要批处理时(例如处理大型时) 数据量。否则,如果不需要批处理,请考虑使用 Queueable Apex。

可以通过调用 或 从当前批处理类的方法链接批处理作业。新的批处理作业将开始 当前批处理作业完成后。Database.executeBatchSystem.scheduleBatchfinish

对于以前的 API 版本,您无法从任何批处理 Apex 方法调用或调用。的版本 used 是正在运行的批处理类的版本,该批处理类启动或计划另一个 批处理作业。如果 运行 Batch 类会调用帮助程序类中的方法以启动 Batch 作业,即 API 帮助程序类的版本无关紧要。Database.executeBatchSystem.scheduleBatchfinish

从 Batch Apex 触发平台事件

Batch Apex 类可以在以下情况下触发平台事件 遇到错误或异常。侦听事件的客户端可以获取可操作的 信息,例如事件失败的频率以及哪些记录在 故障时间。对于Salesforce Platform内部错误和其他 无法捕获的 Apex 异常,例如 LimitExceptions,这些异常是由 调速器限制。

事件消息提供比 Apex Jobs UI 更精细的错误跟踪。它包括 正在处理的记录 ID、异常类型、异常消息和堆栈跟踪。你 还可以合并自定义处理和失败重试逻辑。您可以调用自定义 来自此类事件的任何触发器的 Apex 逻辑,因此 Apex 开发人员可以构建 自定义日志记录或自动重试处理等功能。

有关订阅平台事件的信息,请参阅订阅平台事件。

BatchApexErrorEvent 对象表示与批处理 Apex 关联的平台事件 类。此对象在 API 版本 44.0 及更高版本中可用。如果 、 或 方法 批处理 Apex 作业遇到未处理的异常,将触发平台事件。有关详细信息,请参阅《平台事件开发人员指南》中的 BatchApexErrorEvent。startexecutefinishBatchApexErrorEvent要触发平台事件,批处理 Apex 类声明必须实现 Database.RaisesPlatformEvents 接口。

public with sharing class YourSampleBatchJob implements Database.Batchable<SObject>, 
   Database.RaisesPlatformEvents{ 
   // class implementation 
}

此示例创建一个触发器,以确定哪些帐户在批处理中失败 交易。自定义字段 Dirty__c 指示该帐户是失败批次之一 ExceptionType__c表示遇到的异常。JobScope 和 ExceptionType 是 BatchApexErrorEvent 中的字段 对象。

trigger MarkDirtyIfFail on BatchApexErrorEvent (after insert) {
    Set<Id> asyncApexJobIds = new Set<Id>();
    for(BatchApexErrorEvent evt:Trigger.new){
        asyncApexJobIds.add(evt.AsyncApexJobId);
    }
    
    Map<Id,AsyncApexJob> jobs = new Map<Id,AsyncApexJob>(
        [SELECT id, ApexClass.Name FROM AsyncApexJob WHERE Id IN :asyncApexJobIds]
    );
    
    List<Account> records = new List<Account>();
    for(BatchApexErrorEvent evt:Trigger.new){
        //only handle events for the job(s) we care about
        if(jobs.get(evt.AsyncApexJobId).ApexClass.Name == 'AccountUpdaterJob'){
            for (String item : evt.JobScope.split(',')) {
                Account a = new Account(
                    Id = (Id)item,
                    ExceptionType__c = evt.ExceptionType,
                    Dirty__c = true
                );
                records.add(a);
            }
        }
    }
    update records;
}

测试从 Batch Apex 作业发布的 BatchApexErrorEvent 消息

使用方法 以传送由失败的批处理 Apex 作业发布的事件消息。使用 and 语句块执行 批处理作业。Test.getEventBus().deliver()Test.startTest()Test.stopTest()

此代码片段演示如何执行批处理 Apex 作业并传递事件消息。它 在 之后执行批处理作业。此批处理作业发布 BatchApexErrorEvent 消息 当通过实现 发生故障时。运行后,将添加一个单独的语句,以便它可以传递 BatchApexErrorEvent。Test.stopTest()Database.RaisesPlatformEventsTest.stopTest()Test.getEventBus().deliver()

try {
    Test.startTest();
    Database.executeBatch(new SampleBatchApex());
    Test.stopTest();
    // Batch Apex job executes here
} catch(Exception e) {
    // Catch any exceptions thrown in the batch job
}

// The batch job fires BatchApexErrorEvent if it fails, so deliver the event.
Test.getEventBus().deliver();

注意

如果下游进程发布了进一步的平台事件,请添加以交付 每个进程的事件消息。例如,如果一个平台事件触发,则 处理来自 Apex 作业的事件,发布另一个平台事件,添加语句 传递事件消息。Test.getEventBus().deliver();Test.getEventBus().deliver();

未来方法

将来的方法在后台异步运行。您可以调用 future 方法 执行长时间运行的操作,例如对外部 Web 服务或任何 您希望在自己的线程中,在自己的时间运行的操作。您还可以使用 future 对不同 sObject 类型进行 DML 操作隔离的方法,以防止混合 DML 错误。每个将来的方法都排队,并在系统资源可用时执行。 这样,代码的执行就不必等待 长时间运行的操作。使用未来方法的一个好处是,某些调速器限制 更高,例如 SOQL 查询限制和堆大小限制。

要定义将来的方法,只需使用注释对其进行注释,如下所示。future

global class FutureClass
{
    @future
    public static void myFutureMethod()
    {   
         // Perform some operations
    }
}

带有注解的方法必须是 static 方法,并且只能返回 void 类型。指定的参数必须是 基元数据类型、基元数据类型的数组或基元数据的集合 类型。带有注解的方法不能 将 sObjects 或 objects 作为参数。futurefuture

sObjects 不能作为参数传递给未来方法的原因是 sObject 可以在调用方法的时间和执行方法的时间之间更改。在 在这种情况下,future 方法会获取旧的 sObject 值并可以覆盖它们。工作 替换为数据库中已存在的 sObject,而是传递 sObject ID(或 集合),并使用该 ID 对最新记录执行查询。这 以下示例演示如何使用 ID 列表执行此操作。

global class FutureMethodRecordProcessing
{
    @future
    public static void processRecords(List<ID> recordIds)
    {   
         // Get those records based on the IDs
         List<Account> accts = [SELECT Name FROM Account WHERE Id IN :recordIds];
         // Process records
    }
}

下面是一个 future 方法的骨架示例,该方法对 外部服务。请注意,注释需要一个额外的参数 () 来指示标注是 允许。要了解有关标注的更多信息,请参阅使用 Apex 调用标注。callout=true

global class FutureMethodExample
{
    @future(callout=true)
    public static void getStockQuotes(String acctName)
    {   
         // Perform a callout to an external service
    }

}

插入具有非 null 角色的用户必须在与 DML 不同的线程中完成 对其他 sObject 的操作。在此示例中,类中定义的 future 方法执行 首席运营官角色。这种未来的方法需要在 组织。中的方法插入一个帐户并调用 未来的方法,.insertUserWithRoleUtiluseFutureMethodMixedDMLFutureinsertUserWithRole

此类包含 插入具有非 null 角色的用户。Util

public class Util {
    @future
    public static void insertUserWithRole(
        String uname, String al, String em, String lname) {

        Profile p = [SELECT Id FROM Profile WHERE Name='Standard User'];
        UserRole r = [SELECT Id FROM UserRole WHERE Name='COO'];
        // Create new user with a non-null user role ID 
        User u = new User(alias = al, email=em, 
            emailencodingkey='UTF-8', lastname=lname, 
            languagelocalekey='en_US', 
            localesidkey='en_US', profileid = p.Id, userroleid = r.Id,
            timezonesidkey='America/Los_Angeles', 
            username=uname);
        insert u;
    }
}

此类包含调用已定义的未来方法的 main 方法 以前。

public class MixedDMLFuture {
    public static void useFutureMethod() {
        // First DML operation
        Account a = new Account(Name='Acme');
        insert a;
        
        // This next operation (insert a user with a role) 
        // can't be mixed with the previous insert unless 
        // it is within a future method. 
        // Call future method to insert a user with a role.
        Util.insertUserWithRole(
            'mruiz@awcomputing.com', 'mruiz', 
            'mruiz@awcomputing.com', 'Ruiz');        
    }
}

可以像调用任何其他方法一样调用将来的方法。然而,未来 方法不能调用另一个 future 方法。

带有注解的方法具有 以下限制:future

  • 在批处理和未来上下文中不超过 0;50 个可排队的上下文方法调用 每个 Apex 调用。异步调用,例如 或 , 在 、 块中调用,不计入您的限制 表示排队作业的数量。@futureexecuteBatchstartTeststopTest注意将多个未来方法扇出 不建议将可排队作业作为做法,因为它可以快速添加大量 异步队列的 future 方法。请求处理可能会延迟,并且 您可以快速达到异步 Apex 方法的每日最大限制 执行。查看未来方法 性能最佳实践和 Lightning 平台 Apex限制。
  • 方法的最大数量 每 24 小时的调用次数为 250,000 次,或者是 组织乘以 200,以较大者为准。此限制适用于您的整个 组织并与所有异步 Apex 共享:Batch Apex、Queueable Apex、scheduled Apex和未来的方法。检查有多少个异步 Apex 执行 ,则向 REST API 资源发出请求。查看清单 REST API 开发人员指南中的组织限制。计入的许可证类型 此限制包括完整的 Salesforce 和 Salesforce Platform 用户许可证、应用程序 订阅用户许可证、仅限 Chatter 用户、身份用户和公司 社区用户。futurelimits

注意

  • 如果事务滚动,则不会处理事务排队的未来作业 返回。
  • 在 Salesforce 服务维护停机之前排队的未来方法作业 留在队列中。服务停机结束后以及系统资源 变得可用,则将执行排队的未来方法作业。如果将来的方法 在发生停机时正在运行,将来的方法执行将回滚 并在服务恢复后重新启动。

测试未来的方法

若要测试使用注释定义的方法,请在 startTest() 和 stopTest() 代码块中调用包含该方法的类。全部异步 在该方法之后进行的调用是 由系统收集。什么时候是 执行时,所有异步进程都同步运行。futurestartTeststopTest对于我们的示例,下面是测试类。

@isTest
private class MixedDMLFutureTest {
    @isTest static void test1() {
        User thisUser = [SELECT Id FROM User WHERE Id = :UserInfo.getUserId()];
       // System.runAs() allows mixed DML operations in test context
        System.runAs(thisUser) {
            // startTest/stopTest block to run future method synchronously
            Test.startTest();        
            MixedDMLFuture.useFutureMethod();
            Test.stopTest();
        }
        // The future method will run after Test.stopTest();
    
        // Verify account is inserted
        Account[] accts = [SELECT Id from Account WHERE Name='Acme'];
        System.assertEquals(1, accts.size());
        // Verify user is inserted
        User[] users = [SELECT Id from User where username='mruiz@awcomputing.com'];
        System.assertEquals(1, users.size());
    }
}

未来方法性能最佳实践

Salesforce 使用基于队列的框架来处理 来自未来方法和批处理 Apex 等来源的异步过程。这 队列用于在组织之间平衡请求工作负载。使用 遵循最佳实践,以确保您的组织有效地使用队列 用于异步进程。

  • 避免将大量将来的方法添加到异步队列中,如果 可能。如果超过 2,000 来自单个组织的未处理请求在队列中,任何 来自同一组织的其他请求将被延迟,而 队列处理来自其他组织的请求。
  • 确保将来的方法尽可能快地执行。确保快速执行 批处理作业,最大程度地减少 Web 服务标注时间,并优化 未来的方法。未来方法执行的时间越长,其他方法的可能性就越大 当 队列。
  • 大规模测试您未来的方法。为了帮助确定是否可能发生延迟,请测试 使用生成最大数量的未来方法的环境 期待处理。
  • 考虑使用批处理 Apex 而不是将来的方法来处理大量 记录。

ref

Apex 代码版本

为了帮助向后兼容,类和触发器与版本一起存储 特定 Salesforce API 版本的设置。

如果 Apex 类或触发器引用组件,例如 自定义对象,在已安装的托管包中,每个托管包的版本设置 类引用的包也会被保存。这确保了作为 Apex, API,并且托管包中的组件在后续发布的版本中不断发展,一个 类或触发器仍绑定到具有特定已知行为的版本。

为已安装的包设置版本可确定公开的 接口和任何 Apex 代码的行为 已安装的软件包。这允许您继续引用 Apex 在已安装软件包的最新版本中已弃用,如果您安装了 代码被弃用之前的包。

通常,您引用最新的 Salesforce API 版本和每个已安装的软件包 版本。如果您在未指定 Salesforce API 的情况下保存 Apex 类或触发器 version,则类或触发器与最新安装的版本相关联 违约。如果保存引用 托管包,而不指定托管包的版本、类或 触发器与最新安装的托管软件包版本相关联,具体方法如下 违约。

Apex 类和方法的版本控制

将类和方法添加到 Apex 语言时, 这些类和方法可用于保存 Apex 代码的所有 API 版本 ,无论引入的 API 版本(Salesforce 版本)如何。 例如,如果在 API 版本 33.0 中添加了方法,则可以在 使用 API 版本 33.0 保存的自定义类或使用 API 版本保存的其他类 25.0.

此规则有一个例外。ConnectApi 命名空间的类和方法仅在文档中指定的 API 版本中受支持。为 例如,如果在 API 版本 33.0 中引入了某个类或方法,则该类或方法不可用 在早期版本中。有关更多信息,请参见 ConnectApi 版本控制和相等性检查。

设置类和 Salesforce API 版本 触发器

要为类或触发器设置 Salesforce API 和 Apex 版本,请执行以下操作:

  1. 编辑类或触发器,然后单击“版本” 设置
  2. 选择 Salesforce API 的版本。这个版本也是 与类或触发器关联的 Apex 版本。
  3. 点击保存

如果在方法调用中将对象作为参数从一个 Apex 类 C1 传递到另一个 Apex 类 class、C2 和 C2 由于 Salesforce API 版本而公开了不同的字段 设置,对象中的字段由 C2 的版本设置控制。

在此示例中,Categories 字段设置为从 测试类 C1,因为“类别”字段在 API 的 13.0 版。nullinsertIdea

第一个类使用 Salesforce API 版本 13.0 保存:

// This class is saved using Salesforce API version 13.0
// Version 13.0 does not include the Idea.categories field
global class C2
{
    global Idea insertIdea(Idea a) {
        insert a; // category field set to null on insert
        
        // retrieve the new idea
        Idea insertedIdea = [SELECT title FROM Idea WHERE Id =:a.Id];
        
        return insertedIdea;
    }
}

使用 Salesforce API 版本 16.0 保存以下类:

@IsTest
// This class is bound to API version 16.0 by Version Settings
private class C1
{  
    static testMethod void testC2Method() {
        Idea i = new Idea();
        i.CommunityId = '09aD000000004YCIAY';
        i.Title = 'Testing Version Settings';
        i.Body = 'Categories field is included in API version 16.0';
        i.Categories = 'test';

        C2 c2 = new C2();
        Idea returnedIdea = c2.insertIdea(i);
        // retrieve the new idea
        Idea ideaMoreFields = [SELECT title, categories FROM Idea
             WHERE Id = :returnedIdea.Id];

        // assert that the categories field from the object created
        // in this class is not null
        System.assert(i.Categories != null);
        // assert that the categories field created in C2 is null
        System.assert(ideaMoreFields.Categories == null);
    }
}

设置 Apex 类和触发器的包版本

若要配置类或触发器的包版本设置,请执行以下操作:

  1. 编辑类或触发器,然后单击“版本设置”。
  2. 为类引用的每个托管包选择一个版本,或者 触发。如果出现以下情况,类或触发器将继续使用此版本的托管包 除非您手动更新版本,否则将安装更高版本的托管包 设置。若要将已安装的托管包添加到设置列表,请选择 package。仅当您具有 已安装的托管包尚未与类或触发器关联。
  3. 点击保存

使用包版本设置时,请注意以下事项:

  • 如果保存引用托管包的 Apex 类或触发器,但未指定 托管包的版本,Apex 类或触发器与最新的 默认情况下,托管包的已安装版本。
  • 不能删除托管类或触发器的版本设置 package(如果在类或触发器中引用了该包)。使用显示 用于查找类引用托管包的位置的依赖项,或者 触发。

自定义类型和排序列表

列表可以包含用户定义类型(Apex 类)的对象。可以对用户定义类型的列表进行排序。

若要对此类列表进行排序,Apex 类可以实现接口并将其作为参数传递给方法。或者,您的 Apex 类可以 实现接口。ComparatorList.sortComparable

排序条件和排序顺序取决于为 或 方法提供的实现。Comparable.compareToComparator.compare

若要执行区分区域设置的比较和排序,请使用该类。因为区分区域设置的排序可以产生不同的 结果取决于运行代码的用户,请避免在触发器或代码中使用它 这需要特定的排序顺序。Collator

在映射键和集中使用自定义类型

您可以将自己的 Apex 类的实例添加到映射和集合中。

对于映射,可以将 Apex 类的实例添加为键或值。如果 你把它们添加为键,你的类必须实现一些特殊的规则 使地图正常运行;也就是说,让键获取正确的值。 同样,如果 set 元素是自定义类的实例,则类必须 遵循相同的规则。

警告

如果映射键或设置元素中的对象在发生以下变化时发生更改 添加到集合中,由于字段已更改,将不再找到它 值。

将自定义类型(Apex 类)用于映射键或 set 元素时,请在类中提供 和 方法。Apex 使用这些 确定对象键的相等性和唯一性的两种方法。equalshashCode

将 equals 和 hashCode 方法添加到类中

要确保正确比较自定义类型的映射键,并且其 唯一性可以一致地确定,提供 类中的以下两种方法:

  • 这个方法 签名:equalspublic Boolean equals(Object obj) { // Your implementation }保持 在实现该方法时,请记住以下几点。假设 x、y 和 z 是类的非 null 实例,则该方法必须为:equalsequals
  • 反身:x.equals(x)对称:如果出现以下情况,应返回 并且仅当返回x.equals(y)truey.equals(x)true传递:如果 return 和 returns ,则应返回x.equals(y)truey.equals(z)truex.equals(z)true一致:多次调用一致 return 或 始终如一的回报x.equals(y)truefalse对于任何非 null 引用值 x,应返回x.equals(null)false
  • 中的方法 Apex 基于 爪哇。equals
  • 这个方法 签名:hashCodepublic Integer hashCode() { // Your implementation }保持 在实现该方法时,请记住以下几点。hashCode
  • 如果方法 在执行期间对同一对象多次调用 Apex 请求,它必须返回相同的值。hashCode如果两个对象相等,则根据该方法,必须返回 相同的值。equalshashCode如果两个对象不相等,则根据该方法的结果,它不是 需要返回非重复值。equalshashCode
  • 方法 在 Apex 中基于 爪哇。hashCode

在类中提供该方法的另一个好处是,它简化了对象的比较。您将能够 要使用运算符比较对象, 或方法。为 例:

equals==equals

// obj1 and obj2 are instances of MyClass
if (obj1 == obj2) {
    // Do something
}

if (obj1.equals(obj2)) {
    // Do something
}

样本

此示例演示如何实现 and 方法。该类 前提是这些方法首先列出。它还包含一个构造函数,该构造函数采用 两个整数。第二个示例是一个代码片段,它创建了 类,其中两个具有相同的值。接下来,使用对添加地图条目 对象作为键。该示例验证映射是否只有两个条目,因为 最后添加的条目与第一个条目具有相同的键,因此被覆盖 它。然后,该示例使用运算符 它按预期工作,因为该类实现了 .此外,还会执行一些额外的映射操作,例如 检查映射是否包含某些键,并将所有键和值写入 调试日志。最后,该示例创建一个集合,并向其添加相同的对象。它 验证设置的大小是否为 2,因为三个对象中只有 2 个是 独特。equalshashCode==equals

public class PairNumbers {
    Integer x,y;

    public PairNumbers(Integer a, Integer b) {
        x=a;
        y=b;
    }

    public Boolean equals(Object obj) {
        if (obj instanceof PairNumbers) {
            PairNumbers p = (PairNumbers)obj;
            return ((x==p.x) && (y==p.y));
        }
        return false;
    }

    public Integer hashCode() {
        return (31 * x) ^ y;
    }
}

此代码片段使用该类。PairNumbers

Map<PairNumbers, String> m = new Map<PairNumbers, String>();
PairNumbers p1 = new PairNumbers(1,2);
PairNumbers p2 = new PairNumbers(3,4);
// Duplicate key
PairNumbers p3 = new PairNumbers(1,2);
m.put(p1, 'first');
m.put(p2, 'second');
m.put(p3, 'third');

// Map size is 2 because the entry with 
// the duplicate key overwrote the first entry.
System.assertEquals(2, m.size());

// Use the == operator
if (p1 == p3) {
    System.debug('p1 and p3 are equal.');
}

// Perform some other operations
System.assertEquals(true, m.containsKey(p1));
System.assertEquals(true, m.containsKey(p2));
System.assertEquals(false, m.containsKey(new PairNumbers(5,6)));

for(PairNumbers pn : m.keySet()) {
    System.debug('Key: ' + pn);
}

List<String> mValues = m.values();
System.debug('m.values: ' + mValues);

// Create a set
Set<PairNumbers> s1 = new Set<PairNumbers>();
s1.add(p1);
s1.add(p2);
s1.add(p3);

// Verify that we have only two elements
// since the p3 is equal to p1.
System.assertEquals(2, s1.size());

Apex 类和 Java 类之间的差异

Apex 类和 Java 类的工作方式相似,但有一些重要的 差异。以下是 Apex 类和 Java 类之间的主要区别:

  • 内部类和接口只能在外部内部声明一个级别 类。
  • 静态方法和变量只能在顶级类中声明 定义,而不是在内部类中。
  • 内部类的行为类似于静态 Java 内部类,但不需要关键字。内部类可以 具有像外部类一样的实例成员变量,但没有隐式 指向外部类的实例的指针(使用 关键字)。staticthis
  • 访问修饰符是 default,并表示方法或变量只能在 定义它的 Apex 类。如果未指定访问修饰符,则 方法或变量是 。privateprivate
  • 不为方法或变量指定访问修饰符,并且访问修饰符是 同义。private
  • access 修饰符表示 方法或变量可以由此应用程序中的任何 Apex 使用,或者 命名空间。public
  • access 修饰符表示 方法或变量可以由任何有权访问该类的 Apex 代码使用, 而不仅仅是同一应用程序中的 Apex 代码。此访问修饰符应为 用于需要在应用程序外部引用的任何方法, 在 SOAP API 中或通过其他 Apex 代码。如果声明方法或 变量为 ,您还必须 将包含它的类声明为 。globalglobalglobal
  • 默认情况下,方法和类是最终的。
    • 定义修饰符 允许扩展和覆盖。virtual
    • 关键字必须是 显式用于重写基类方法的方法。override
  • 接口方法没有修饰符,它们始终是全局的。
  • 异常类必须扩展异常或其他用户定义的异常。
    • 他们的名字必须以单词 结尾。exception
    • 异常类有四个内置的隐式构造函数, 虽然你可以添加其他人。
  • 类和接口可以在触发器和匿名块中定义,但只能 作为本地人。

创建类定义

使用班级编辑器在 Salesforce 中创建班级。

  1. 在“设置”中,输入“快速” “查找”框,然后选择“Apex 类”。Apex Classes
  2. 单击“新建”。
  3. 单击“版本设置” 指定 Apex 的版本以及与此类一起使用的 API。 如果您的组织已从 AppExchange 安装托管软件包, 您还可以指定要与此一起使用的每个托管包的版本 类。对所有版本使用默认值。这会将 具有最新版本的 Apex 和 API 的类,以及每个托管的 包。您可以指定较旧的 托管包的版本(如果要访问组件或 与最新包版本不同的功能。你 可以指定旧版本的 Apex 和 API 来维护特定行为。
  4. 在类编辑器中,输入类的 Apex 代码。一个类可以启动 长度为 100 万个字符,不包括注释、测试方法或 使用 定义的类。@IsTest
  5. 单击保存”以保存更改并返回到课程 详细信息屏幕,或单击“快速保存”以保存更改 ,然后继续编辑您的课程。您的 Apex 类必须正确编译才能 您可以保存您的课程。

也可以通过单击 Generate 从 WSDL 自动生成类 来自 WSDL。请参阅 SOAP 服务:从 WSDL 文档定义类。

保存后,可以通过类方法或变量通过其他 Apex 代码调用类, 例如触发器。

注意

为了帮助向后兼容,类与 指定的 Apex 版本和 API。如果 Apex 类引用组件,例如 自定义对象,在已安装的托管包中,每个托管的版本设置 类引用的包也会被保存。此外,类使用一个标志进行存储,只要依赖元数据未更改,该标志就会设置为 因为该类是上次编译的。如果对对象名称或字段进行了任何更改 在类中使用的更改,包括表面更改,例如对对象的编辑或 字段说明,或者如果对调用此类的类进行了更改,则该标志将设置为 。当触发器或 Web 服务调用调用时 类,代码被重新编译,如果有任何错误,用户会收到通知。如果 没有错误,标志被重置 自。isValidtrueisValidfalseisValidtrue

Apex 类编辑器

Apex 和 Visualforce 编辑器具有以下功能:语法高亮显示编辑器会自动为关键字应用语法高亮显示,并且 所有函数和运算符。搜索 (search icon)通过搜索,可以在当前页面、类或 触发。若要使用搜索,请在“搜索”文本框中输入字符串,然后单击“查找” 下一步

  • 要将找到的搜索字符串替换为另一个字符串,请输入 “替换”文本框中的新字符串,然后单击“替换”以替换该字符串 实例,或“全部替换”来替换它 实例和出现的搜索字符串的所有其他实例 在页面、类或触发器中。
  • 若要使搜索操作区分大小写,请选择“匹配大小写”选项。
  • 若要使用正则表达式作为搜索字符串,请选择“正则表达式”选项。这 正则表达式遵循 JavaScript 的正则表达式 规则。使用正则表达式进行搜索可以找到 换行多行。如果使用替换操作 使用正则表达式找到的字符串,将 操作还可以绑定正则表达式组变量 (、 等)从 找到搜索字符串。例如,要将标签替换为标签,并保留所有 属性对原来完好无损,搜索并替换 它与.$1$2<h1><h2><h1><h1(\s+)(.*)><h2$1$2>

转到行 (Go to line arrow)此按钮允许您突出显示指定的行号。如果行 当前不可见,编辑器将滚动到该行。撤消 (Undo button) 和重做 (Redo button)使用撤消可撤消编辑操作,使用重做可重新创建编辑 已撤消的操作。字体大小从下拉列表中选择字体大小以控制 编辑器中显示的字符。行和列位置光标的行和列位置显示在状态中 编辑器底部的栏。这可以与转到行一起使用 (Go to line arrow) 至 快速浏览编辑器。行数和字符数行数和字符总数显示在状态栏中 在编辑器的底部。

命名约定

我们建议遵循 Java 标准 命名,即类以大写字母开头,方法以开头 使用小写动词,变量名称应该有意义。

定义具有相同名称的类和接口是不合法的 在同一个班级。内部阶级拥有也是不合法的 与其外部类同名。但是,方法和变量具有 它们在类中自己的命名空间,因此这三种类型的名称 不要相互冲突。特别是,对于变量来说,它是合法的, 方法,以及具有相同名称的类中的类。

名称阴影

成员变量可以被局部变量遮蔽,特别是 函数参数。这允许标准的方法和构造函数 Java 表单:

Public Class Shadow {
  String s;
  Shadow(String s) { this.s = s; } // Same name ok
  setS(String s) { this.s = s; } // Same name ok
}

一个类中的成员变量可以隐藏成员变量 父类中的相同名称。如果两个类 在不同的顶级班级,由不同的团队编写。 例如,如果一个人引用了 C 类并希望获得 访问父类 P 中的成员变量 M(同名 作为 C 中的成员变量),应将引用分配给引用 先到P。

静态变量可以在整个类层次结构中被隐藏,因此 如果 P 定义了一个静态 S,则子类 C 也可以声明一个静态 S。 C 中对 S 的引用是指该静态 – 以便引用 P中的那个,必须使用语法P.S。

静态类变量不能通过类实例引用。 它们必须单独使用原始变量名称(在 该顶级类文件)或以类名为前缀。例如:

public class p1 { 
  public static final Integer CLASS_INT = 1;
  public class c { };
}
p1.c c = new p1.c();
// This is illegal
// Integer i = c.CLASS_INT;
// This is correct
Integer i = p1.CLASS_INT;

命名空间前缀

Salesforce 应用程序支持使用命名空间前缀。命名空间前缀在托管 AppExchange 包中用于 将自定义对象和字段名称与其他对象和字段名称区分开来 组织。

重要

创建命名空间时,请使用有用且 为用户提供信息。但是,不要以人的名字命名命名空间(例如,通过 使用某人的姓名、昵称或私人信息)。分配命名空间后, 它们无法更改。

开发人员注册全局唯一的命名空间前缀并将其注册到 AppExchange 注册表,对自定义对象和字段名称的外部引用 开发人员的托管包采用以下长格式:

namespace_prefix__obj_or_field_name__c

这些完全限定的名称在工作 SOQL 或 SOSL 语句中更新可能很繁琐, 和 Apex 一旦一个类被标记为“托管”。因此,Apex 支持默认 命名空间用于架构名称。在查看标识符时,解析器假定 当前对象的命名空间是所有其他对象和字段的命名空间,除非 另有说明。因此,存储类必须引用自定义对象和字段 直接(使用 )为已定义的对象命名 在其相同的应用程序命名空间中。 obj_or_field_name__c

提示

仅在引用自定义对象时使用命名空间前缀 以及已从 AppExchange。

调用包方法时使用命名空间

若要调用托管包中定义的方法,Apex 允许完全限定 表单的标识符:

namespace_prefix.class.method(args)

版本化行为更改

在 API 版本 34.0 及更高版本中,自定义 SObjectType 上的 Schema.DescribeSObjectResult 包括以命名空间为前缀的映射键,即使命名空间是 当前正在执行的代码。如果您使用多个命名空间并生成运行时 描述数据,请确保您的代码使用命名空间正确访问密钥 前缀。

  1. 使用 System 命名空间
  2. 使用架构命名空间
    命名空间提供用于处理架构元数据信息的类和方法。我们隐式导入 ,但是当命名空间元素与非托管代码中的项发生命名冲突时,必须完全限定命名空间元素的使用。如果您的组织包含与 sObject 同名的 Apex 类,请在代码中将命名空间前缀添加到 sObject 名称中。SchemaSchema.*SchemaSchema
  3. 命名空间、类和变量名称优先级
  4. 类型的类型解析和系统命名空间

使用 System 命名空间

命名空间是 Apex 中的默认命名空间。这意味着您可以在创建新的命名空间时省略命名空间 系统类的实例或调用系统方法时。例如 因为内置的 URL 类在命名空间中,所以这两个语句都要创建一个 类的实例 是等效的:SystemSystemURL

System.URL url1 = new System.URL('https://MyDomainName.my.salesforce.com/');

和:

URL url1 = new URL('https://MyDomainName.my.salesforce.com/');

同样,要在类上调用静态方法,可以编写 以下内容:URL

System.URL.getCurrentRequestUrl();

艺术

URL.getCurrentRequestUrl();

注意

除了命名空间之外,命名空间中还有一个内置类, 它提供了类似 和 的方法。不要 对命名空间和类都具有这一事实感到困惑 在本例中为相同名称。和语句是等效的。SystemSystemSystemassertEqualsdebugSystem.debug(‘debug message’);System.System.debug(‘debug message’);

使用 System 命名空间消除歧义

在调用系统类的静态方法时,不包含命名空间更容易, 但在某些情况下,您必须包含命名空间才能区分 来自同名自定义 Apex 类的内置 Apex 类。如果您的组织包含您定义的 Apex 类,这些类的名称与内置类相同 类,则 Apex 运行时默认为您的自定义类,并调用 类。让我们看一下下面的例子。SystemSystem创造 此自定义 Apex 类:

public class Database {
    public static String query() {
       return 'wherefore art thou namespace?';
    }
}

在开发者控制台中执行以下语句:

sObject[] acct = Database.query('SELECT Name FROM Account LIMIT 1');
System.debug(acct[0].get('Name'));

执行语句时,Apex 首先在自定义类上查找查询方法。但是,这里的查询方法 class 不接受任何参数,因此找不到匹配项 出现错误。自定义类将重写命名空间中的内置类。 若要解决此问题,请在类名中添加命名空间前缀,以显式指示 Apex 运行时调用内置 Database 类上的查询方法 在命名空间中:Database.queryDatabaseDatabaseDatabaseSystemSystemSystem

sObject[] acct = System.Database.query('SELECT Name FROM Account LIMIT 1');
System.debug(acct[0].get('Name'));

使用 Schema 命名空间

命名空间提供类和 用于处理架构元数据信息的方法。我们隐式导入 ,但是当命名空间元素有命名冲突时,您必须完全限定对命名空间元素的使用 与非托管代码中的项一起使用。如果您的组织包含同名的 Apex 类 作为 sObject,将命名空间前缀添加到 s代码中的对象名称。

SchemaSchema.*SchemaSchema

在创建架构类的实例或调用 schema 方法。例如,由于 和 类位于命名空间中,因此这些代码 段是等效的。DescribeSObjectResultFieldSetSchema

Schema.DescribeSObjectResult d = Account.sObjectType.getDescribe();
Map<String, Schema.FieldSet> FSMap = d.fieldSets.getMap();

和:

DescribeSObjectResult d = Account.sObjectType.getDescribe();
Map<String, FieldSet> FSMap = d.fieldSets.getMap();

使用架构命名空间消除歧义

用于 引用与自定义类同名的 sObject。这种消歧义 指示 Apex 运行时使用 sObject。Schema.object_name

public class Account {
   public Integer myInteger;
}

// ...

// Create a standard Account object myAccountSObject
Schema.Account myAccountSObject = new Schema.Account();
// Create accountClassInstance, a custom class in your org
Account accountClassInstance = new Account();
myAccountSObject.Name = 'Snazzy Account';
accountClassInstance.myInteger = 1;

命名空间、类和变量名称优先级

因为局部变量、类名和命名空间都可以假设 使用相同的标识符,Apex 解析器以如下形式评估表达式:

name1.name2.[…].nameN

  1. 解析器首先假定这是一个局部变量,其中 – 作为 字段引用。name1name2nameN
  2. 如果第一个假设不成立,则解析器将假设 这是一个类名 并且是静态变量 带有 – 的名称作为字段引用。name1name2name3nameN
  3. 如果第二个假设不成立,则解析器假定 即命名空间 name,是类名,是静态变量名, 和 – 是字段引用。name1name2name3name4nameN
  4. 如果第三个假设不成立,则解析器报告 一个错误。

如果表达式以一组括号结尾(例如,),则 Apex 解析器将按如下方式计算表达式:

name1.name2.[…].nameM.nameN()

  1. 解析器首先假定它是本地 变量,其中 – 作为字段引用,并作为方法调用。name1name2nameMnameN
  2. 如果第一个假设不成立:
    • 如果表达式仅包含两个标识符 (),则解析器假定该标识符是类名,并且是方法调用。name1.name2()name1name2
    • 如果表达式包含两个以上的标识符,则解析器会假定该标识符是类名,是带有 – 作为字段引用的静态变量名称,并且是方法调用。name1name2name3nameMnameN
  3. 如果第二个假设不成立,则解析器假定是命名空间名称、类名、静态变量名称、 – 是字段引用, 并且是方法调用。name1name2name3name4nameMnameN
  4. 如果第三个假设不成立,则解析器将报告错误。

但是,对于类变量,Apex 还使用点表示法来引用成员变量。那些成员 变量可能引用其他类实例,也可能引用 添加到具有自己的点表示法规则来引用字段的 sObject 名称(可能导航外键)。

在表达式中输入 sObject 字段后,余数 的表达式保留在 sObject 域中,即 sObject 字段不能引用 Apex 表达式。

例如,如果您有以下类:

public class c { 
  c1 c1 = new c1(); 
  class c1 { c2 c2; } 
  class c2 { Account a; } 
}

那么以下表达都是合法的:

c.c1.c2.a.name
c.c1.c2.a.owner.lastName.toLowerCase()
c.c1.c2.a.tasks
c.c1.c2.a.contacts.size()

类型解析和系统命名空间 类型

因为类型系统必须解析定义的用户定义类型 在本地或其他类中,Apex 解析器按如下方式评估类型:

  1. 对于类型引用,解析器首先将该类型查找为标量类型。TypeN
  2. 如果未找到, 分析器查找本地定义的类型。TypeN
  3. 如果仍然不是 found,解析器将查找该名称的类。TypeN
  4. 如果仍然不是 found,解析器会查找系统类型,例如 sObjects。TypeN

对于以下类型 可能表示顶级类中的内部类型,也可能表示命名空间中的顶级类(按该优先级顺序)。

ref

Apex Classes and Casting

通常,所有类型信息在运行时都可用。这意味着 Apex 启用强制转换,即将一个类的数据类型分配给一个数据 另一个类的类型,但仅当一个类是另一个类的子类时。使用铸造 当您要将对象从一种数据类型转换为另一种数据类型时。

在以下示例中,扩展 类 .因此,它是一个子类 那个班级。这意味着您可以使用强制转换将对象与父对象一起分配 数据类型 () 添加到对象的 子类数据类型 ()。CustomReportReportReportCustomReport

public virtual class Report {
}
public class CustomReport extends Report {
}

在下面的代码段中,首先将自定义报表对象添加到报表列表中 对象。然后,自定义报表对象将作为报表对象返回,然后 强制转换回自定义报表对象。

...
  // Create a list of report objects
  Report[] Reports = new Report[5];

  // Create a custom report object
  CustomReport a = new CustomReport();

  // Because the custom report is a sub class of the Report class,
  // you can add the custom report object a to the list of report objects
  Reports.add(a);

  // The following is not legal:
  // CustomReport c = Reports.get(0);
  // because the compiler does not know that what you are
  // returning is a custom report. 

  // You must use cast to tell it that you know what
  // type you are returning. Instead, get the first item in the list
  // by casting it back to a custom report object
  CustomReport c = (CustomReport) Reports.get(0);
...

此外,接口类型可以强制转换为子接口或类类型,该类类型 实现该接口。

提示

若要验证类是否为特定类型的类,请使用关键字。有关更多信息,请参阅使用 instanceof 关键字instanceOf

类和集合

列表和映射可以与类和接口一起使用,其方式与列表和映射相同 与 sObject 一起使用。这意味着,例如,您可以将用户定义的数据类型用于 value 或映射的键。同样,您可以创建一组用户定义的对象。

如果创建映射或接口列表,则 接口可以放入该集合中。例如,如果 List 包含一个接口,并实现 ,然后可以 被放置在列表中。

Collection Casting

由于 Apex 中的集合在运行时具有声明的类型,因此 Apex 允许集合转换。

集合可以采用与数组类似的方式进行强制转换 在 Java 中投射。例如,CustomerPurchaseOrder 对象的列表 如果 class 是子对象,则可以分配给 PurchaseOrder 对象列表 的类。CustomerPurchaseOrderPurchaseOrder

public virtual class PurchaseOrder {

    Public class CustomerPurchaseOrder extends PurchaseOrder {

    }
    {
        List<PurchaseOrder> POs = new PurchaseOrder[] {};
        List<CustomerPurchaseOrder> CPOs = new CustomerPurchaseOrder[]{};
        POs = CPOs;
    }
}

将列表分配给列表变量后,就可以将其强制转换 返回到 CustomerPurchaseOrder 对象的列表,但只是因为该实例是 最初实例化为 CustomerPurchaseOrder 对象的列表。列表 实例化的 PurchaseOrder 对象不能强制转换为 CustomerPurchaseOrder 对象,即使 PurchaseOrder 对象列表仅包含 CustomerPurchaseOrder 对象。CustomerPurchaseOrderPurchaseOrder

如果仅包含 CustomerPurchaseOrders 的 PurchaseOrder 列表的用户 objects 尝试插入 的非 CustomerPurchaseOrder 子类(例如 ),运行时 异常结果。这是因为 Apex 集合在运行时具有声明的类型。PurchaseOrderInternalPurchaseOrder

注意

地图的行为方式与列表在地图的值方面相同。如果值 映射 A 的一侧可以强制转换为映射 B 的值侧,并且它们具有相同的键类型, 然后可以将映射 A 投射到映射 B。如果强制转换无效,则会导致运行时错误 在运行时使用特定映射。

Apex 注解

附注

Apex 注解修改了方法或类的使用方式,类似于 Java 中的注解。注释使用初始符号定义,后跟相应的关键字。

@

若要向方法添加批注,请在方法或类之前指定它 定义。例如:

global class MyClass {
     @Future
     Public static void myMethod(String a)
     {
          //long-running Apex code
     }
}

Apex 支持以下注解。

  • @AuraEnabled
  • @Deprecated
  • @Future
  • @InvocableMethod
  • @InvocableVariable
  • @IsTest
  • @JsonAccess
  • @NamespaceAccessible
  • @ReadOnly
  • @RemoteAction
  • @SuppressWarnings
  • @TestSetup
  • @TestVisible
  • Apex REST注解:
    • @ReadOnly
    • @RestResource(urlMapping=’/yourUrl‘)
    • @HttpDelete
    • @HttpGet
    • @HttpPatch
    • @HttpPost
    • @HttpPut
  1. AuraEnabled 注解
  2. 已弃用的批注
  3. 未来注解
  4. InvocableMethod 注解 使用注解
    来标识可作为可调用操作运行的方法。InvocableMethod
  5. InvocableVariable 批注
    使用批注来标识自定义类中可调用方法使用的变量。InvocableVariable
  6. IsTest 注解
  7. JsonAccess 注解
  8. NamespaceAccessible 批注
  9. ReadOnly 注释
  10. RemoteAction 批注
  11. SuppressWarnings 注释
    此注释在 Apex 中不执行任何操作,但可用于向第三方工具提供信息。
  12. TestSetup 批注 使用批注
    定义的方法用于创建可用于类中所有测试方法的通用测试记录。@TestSetup
  13. TestVisible 注解

AuraEnabled(Lightning启用)注解

该注释支持客户端和服务器端访问 Apex 控制器方法。 提供此注释后,您的方法可用于 Lightning 组件 (Lightning Web 组件和 Aura 组件)。只有这个方法 注解是公开的。@AuraEnabled

在 API 版本 44.0 及更高版本中,您可以通过缓存方法提高运行时性能 使用注释在客户端上生成结果。您只能缓存以下方法的结果 检索数据但不修改数据的方法。使用此注解可消除 需要调用 JavaScript 代码 在调用 Apex 方法的每个操作上。@AuraEnabled(cacheable=true)setStorable()

在 API 版本 55.0 及更高版本中,您可以使用注释使 Apex 方法成为 缓存在全局缓存中。@AuraEnabled(cacheable=true scope=’global’)

版本化行为更改

在 API 版本 55.0 及更高版本中,不允许对带有 注释的方法进行重载。@AuraEnabled

废弃的注解

使用注释来标识方法、类、异常、枚举、 接口或变量,这些接口或变量在后续版本的 被管理 它们所在的包。此注释在以下情况下很有用 随着需求的发展重构托管包中的代码。新订阅者 看不到已弃用的元素,而这些元素继续运行 现有订阅者和 API 集成。Deprecated

以下代码片段显示了已弃用的方法。一样 语法可用于弃用类、异常、枚举、接口、 或变量。

@Deprecated
  // This method is deprecated. Use myOptimizedMethod(String a, String b) instead.
  global void myMethod(String a) {
   
}

弃用 Apex 标识符时,请注意以下规则:

  • 非托管包不能包含使用关键字的代码。deprecated
  • 当 Apex 项被弃用时,引用已弃用的 Apex 项的所有全局访问修饰符 标识符也必须弃用。任何在其中使用弃用类型的全局方法 还必须弃用输入参数或方法返回类型中的签名。已弃用的项(如方法或类)可以 仍由包开发人员在内部引用。
  • webservice方法和变量不能是 荒废的。
  • 您可以弃用 ,但不能弃用 个人价值。enumenum
  • 可以弃用接口,但不能弃用接口中的单个方法。
  • 您可以弃用抽象类,但不能弃用 抽象类。
  • 您无法删除批注 在发布软件包版本后,取消弃用 Apex 中的某些内容,其中 Apex 中的项已弃用。Deprecated

有关包版本的详细信息,请参阅什么是包?。

未来的注解

使用注释进行标识 异步执行的方法。当您指定 时,该方法将在 Salesforce 具有 可用资源。FutureFuture

例如,您可以在以下情况下使用注释 对外部服务进行异步 Web 服务标注。没有 注解时,Web 服务标注是由执行 Apex 代码,在标注完成之前,不能进行其他处理 (同步处理)。Future

带有注解的方法必须是静态方法,并且只能返回 void 类型。指定的 参数必须是基元数据类型、基元数据类型的数组或集合 的原始数据类型。带有注解的方法不能将 sObjects 或对象作为参数。FutureFuture

若要使类中的方法异步执行,请使用注释定义方法。例如:Future

global class MyFutureClass {

  @Future 
  static void myMethod(String a, Integer i) {
    System.debug('Method called with: ' + a + ' and ' + i);
    // Perform long-running code
  }
}

若要允许在方法中使用标注,请指定 。默认值为 ,这会阻止方法 进行标注。Future(callout=true)(callout=false)

以下代码片段演示如何指定方法执行 标注:

@Future (callout=true)
  public static void doCalloutFromFuture() {
   //Add code to perform callout
}

未来方法注意事项

  • 请记住,任何使用注解的方法都需要特别考虑,因为该方法不一定 按调用的相同顺序执行。Future
  • 带有注释的方法不能在 Visualforce 控制器中使用 要么是方法,要么在构造函数中。FuturegetMethodNamesetMethodName
  • 不能从同样具有批注的方法调用批注的方法。也不能从调用 另一个带注释的方法。FutureFuture

InvocableMethod注解

使用注释来标识可以作为可调用运行的方法 行动。InvocableMethod

注意

如果流调用 Apex,则正在运行的用户必须具有相应的 Apex 类安全性 在其用户配置文件或权限集中设置。

可调用方法从交互的 Rest、Apex、Flow 或 Einstein 机器人中本机调用 与外部 API 源一起使用。可调用方法具有动态输入和输出值,并且 支持描述调用。

此代码示例演示具有基元数据类型的可调用方法。

public class AccountQueryAction {
  @InvocableMethod(label='Get Account Names' description='Returns the list of account names corresponding to the specified account IDs.' category='Account')
  public static List<String> getAccountNames(List<ID> ids) {
    List<Account> accounts = [SELECT Name FROM Account WHERE Id in :ids];
    Map<ID, String> idToName = new Map<ID, String>();
    for (Account account : accounts) {
      idToName.put(account.Id, account.Name);
    }
    // put each name in the output at the same position as the id in the input
    List<String> accountNames = new List<String>();
    for (String id : ids) {
      accountNames.add(idToName.get(id));
    }
    return accountNames;
  }
}

此代码示例演示具有特定 sObject 数据类型的可调用方法。

public class AccountInsertAction {
  @InvocableMethod(label='Insert Accounts' description='Inserts the accounts specified and returns the IDs of the new accounts.' category= 'Account')
  public static List<ID> insertAccounts(List<Account> accounts) {
    Database.SaveResult[] results = Database.insert(accounts);
    List<ID> accountIds = new List<ID>();
      for (Database.SaveResult result : results) {
      accountIds.add(result.getId());
    }
    return accountIds;
  }
}

此代码示例演示具有泛型 sObject 数据类型的可调用方法。

public with sharing class GetFirstFromCollection {
  @InvocableMethod
  public static List<Results> execute (List<Requests> requestList) {
    List<Results> results = new List<Results>();
    for (Requests request : requestList) {
      List<SObject> inputCollection = request.inputCollection;
      SObject outputMember = inputCollection[0];
      
      //Create a Results object to hold the return values
      Results result = new Results();
      
      //Add the return values to the Results object
      result.outputMember = outputMember;
      
      //Add Result to the results List at the same position as the request is in the requests List
      results.add(result);
    }
    return results;
  }

  public class Requests {
    @InvocableVariable(label='Records for Input' description='yourDescription' required=true)
    public List<SObject> inputCollection;
  }

  public class Results {
    @InvocableVariable(label='Records for Output' description='yourDescription' required=true)
    public SObject outputMember;
  }
}

此代码示例演示了具有 SVG 文件中自定义图标的可调用方法。

global class CustomSvgIcon { 
  @InvocableMethod(label='myIcon' iconName='resource:myPackageNamespace__google:top')
  global static List<Integer> myMethod(List<Integer> request) {
    List<Integer> results = new List<Integer>();
    for(Integer reqInt : request) { 
       results.add(reqInt);
    }
    return results;
  }
}

此代码示例显示了一个带有 Salesforce Lightning 自定义图标的可调用方法 设计系统(SLDS)。

public class CustomSldsIcon { 
  
  @InvocableMethod(iconName='slds:standard:choice') 
  public static void run() {} 
  
  }

支持的修饰符

所有修饰符都是可选的。标签方法的标签,在流生成器中显示为操作名称。这 默认值是方法名称,但我们建议您提供标签。描述方法的说明,在流中显示为操作说明 建筑工人。缺省值为 。Null标注标注修饰符标识该方法是否调用外部系统。如果 该方法调用外部系统,添加 .缺省值为 。callout=truefalse类别方法的类别,在 Flow Builder 中显示为操作类别。 如果未提供类别(默认情况下),则操作将显示在“未分类”下。配置编辑器使用方法注册并显示在 Flow 中的自定义属性编辑器 管理员配置操作时的生成器。如果未指定此修饰符,则 Flow 生成器使用标准属性编辑器。图标名称要用作 Flow Builder 中操作的自定义图标的图标的名称 帆布。您可以将上传的 SVG 文件指定为静态资源,也可以将 Salesforce Lightning Design System 标准图标。

InvocableMethod 注意事项

实施说明

  • 可调用方法必须是 and 或 ,并且其类必须是外部类。staticpublicglobal
  • 一个类中只有一个方法可以具有注释。InvocableMethod
  • 其他批注不能与批注一起使用。InvocableMethod

输入和输出最多可以有一个输入参数,其数据类型必须为以下参数之一:

  • 基元数据类型的列表或基元数据类型的列表列表 – 泛型类型不是 支持。Object
  • sObject 类型的列表或 sObject 类型的列表列表。
  • 泛型 sObject 类型 () 的列表或泛型 sObject 类型的列表 ().List<sObject>List<List<sObject>>
  • 用户定义类型的列表,包含受支持类型的变量或 用户定义的 Apex 类型,带有注释。要实现您的数据类型,请创建一个 自定义全局或公共 Apex 类。该类必须包含至少一个成员 变量,带有可调用的变量注解。InvocableVariable

如果返回类型不是 ,则数据 方法返回的类型必须是下列类型之一:Null

  • 基元数据类型的列表或基元数据类型的列表列表 – 泛型类型不是 支持。Object
  • sObject 类型的列表或 sObject 类型的列表列表。
  • 泛型 sObject 类型 () 的列表或泛型 sObject 类型的列表 ().List<sObject>List<List<sObject>>
  • 用户定义类型的列表,包含受支持类型的变量或 用户定义的 Apex 类型,带有注释。要实现您的数据类型,请创建一个 自定义全局或公共 Apex 类。该类必须包含至少一个成员 变量,带有可调用的变量注解。InvocableVariable注意为了正确的膨胀 实现时,输入和输出的大小和顺序必须匹配。 例如,第 i 个“输出”条目必须与第 i 个“输入”条目相对应。 当您的操作在 批量执行,例如在记录触发器中使用 Apex 操作时 流。

托管软件包

  • 可以在包中使用可调用方法,但在添加可调用方法后 无法将其从更高版本的包中删除。
  • 公共可调用方法可由 托管包。
  • 全局可调用方法可以在订阅者组织中的任何位置引用。只 全局可调用方法显示在 Flow Builder 和 Process Builder 的 订阅者组织。

有关可调用操作的详细信息,请参阅操作开发人员指南。

Invocable变量注解

使用注释来标识 自定义类。InvocableVariable

注释标识类 变量,用作方法的可调用操作的输入或输出参数。如果创建自己的自定义类以 用作可调用方法的输入或输出,可以对单个类成员进行注释 变量,使它们可供方法使用。InvocableVariableInvocableMethod

下面的代码示例演示了具有 invocable 变量的 invocable 方法。

global class ConvertLeadAction {
  @InvocableMethod(label='Convert Leads')
  global static List<ConvertLeadActionResult> convertLeads(List<ConvertLeadActionRequest> requests) {
    List<ConvertLeadActionResult> results = new List<ConvertLeadActionResult>();
    for (ConvertLeadActionRequest request : requests) {
      results.add(convertLead(request));
    }
    return results;
  }

  public static ConvertLeadActionResult convertLead(ConvertLeadActionRequest request) {
    Database.LeadConvert lc = new Database.LeadConvert();
    lc.setLeadId(request.leadId);
    lc.setConvertedStatus(request.convertedStatus);

    if (request.accountId != null) {
        lc.setAccountId(request.accountId);
    }

    if (request.contactId != null) {
      lc.setContactId(request.contactId);
    }

    if (request.overWriteLeadSource != null && request.overWriteLeadSource) {
      lc.setOverwriteLeadSource(request.overWriteLeadSource);
    }

    if (request.createOpportunity != null && !request.createOpportunity) {
      lc.setDoNotCreateOpportunity(!request.createOpportunity);
    }

    if (request.opportunityName != null) {
      lc.setOpportunityName(request.opportunityName);
    }

    if (request.ownerId != null) {
      lc.setOwnerId(request.ownerId);
    }

    if (request.sendEmailToOwner != null && request.sendEmailToOwner) {
      lc.setSendNotificationEmail(request.sendEmailToOwner);
    }

    Database.LeadConvertResult lcr = Database.convertLead(lc, true);
    if (lcr.isSuccess()) {
      ConvertLeadActionResult result = new ConvertLeadActionResult();
      result.accountId = lcr.getAccountId();
      result.contactId = lcr.getContactId();
      result.opportunityId = lcr.getOpportunityId();
      return result;
    } else {
      throw new ConvertLeadActionException(lcr.getErrors()[0].getMessage());
    }
  }

  global class ConvertLeadActionRequest {
    @InvocableVariable(required=true)
    global ID leadId;

    @InvocableVariable(required=true)
    global String convertedStatus;

    @InvocableVariable
    global ID accountId;

    @InvocableVariable
    global ID contactId;

    @InvocableVariable
    global Boolean overWriteLeadSource;

    @InvocableVariable
    global Boolean createOpportunity;

    @InvocableVariable
    global String opportunityName;

    @InvocableVariable
    global ID ownerId;

    @InvocableVariable
    global Boolean sendEmailToOwner;
  }

  global class ConvertLeadActionResult {
    @InvocableVariable
    global ID accountId;

    @InvocableVariable
    global ID contactId;

    @InvocableVariable
    global ID opportunityId;
  }

  class ConvertLeadActionException extends Exception {}
}

以下代码示例演示了一个 invocable 方法,该方法具有 泛型 sObject 数据类型。

public with sharing class GetFirstFromCollection {
  @InvocableMethod
  public static List <Results> execute (List<Requests> requestList) {
    List<SObject> inputCollection = requestList[0].inputCollection;
    SObject outputMember = inputCollection[0];

    //Create a Results object to hold the return values
    Results response = new Results();

    //Add the return values to the Results object
    response.outputMember = outputMember;

    //Wrap the Results object in a List container 
    //(an extra step added to allow this interface to also support bulkification)
    List<Results> responseWrapper= new List<Results>();
    responseWrapper.add(response);
    return responseWrapper;    
  }
}

public class Requests {
  @InvocableVariable(label='Records for Input' description='yourDescription' required=true)
  public List<SObject> inputCollection;
  }

public class Results {
  @InvocableVariable(label='Records for Output' description='yourDescription' required=true)
  public SObject outputMember;
  }
}

支持的修饰符

可调用变量注释支持此示例中所示的修饰符。

@InvocableVariable(label=’yourLabel‘ description=’yourDescription‘ required=(true | false))所有修饰符都是可选的。标签变量的标签。默认值为变量名称。

提示

此标签显示在 Flow Builder 中 Action 元素的 Action 元素中 对应于可调用的方法。此标签可帮助管理员了解如何使用 流中的变量。描述变量的说明。缺省值为 。Null必填指定变量是否为必需变量。如果未指定,则默认值为 false。 对于输出变量,将忽略该值。

InvocableVariable 注意事项

  • 其他批注不能与批注一起使用。InvocableVariable
  • 只有全局变量和公共变量才能是可调用变量。
  • 可调用变量不能是以下变量之一:
    • 非成员变量,如 a 或 变量。staticlocal
    • 属性。
    • 一个变量。final
    • Protected或。private
  • 可调用变量的数据类型必须为下列类型之一:
    • Object 以外的基元
    • sObject,泛型 sObject 或特定 sObject
    • 从 Apex 创建的基元、sObjects、对象列表的列表或列表 类或集合
  • Apex 中的可调用变量名称必须与流程中的名称匹配。名称区分大小写。
  • 对于托管软件包:
    • 公共可调用变量可以在同一托管的流和流程中设置 包。
    • 全局可调用变量可以在订阅者组织中的任何位置设置。只有全球 可调用变量显示在订阅者的 Flow Builder 和 Process Builder 中 组织。

IsTest的注解

使用注释 定义仅包含用于测试应用程序的代码的类和方法。这 注释可以在括号内使用多个修饰符,并用空格分隔。@IsTest

注意

上的注释 methods 等同于关键字。如 最佳做法,Salesforce 建议您使用 而不是 .关键字可以在 未来版本。@IsTesttestMethod@IsTesttestMethodtestMethod

类和方法定义为 can be 或 。定义为的类必须是顶级类。@IsTestprivatepublic@IsTest

注意

使用注释定义的类不计入组织 6 MB 的限制 所有 Apex 代码。@IsTest

下面是一个包含两个测试的私有测试类的示例 方法。

@IsTest
private class MyTestClass {

   // Methods for testing
   @IsTest
   static void test1() {
      // Implement test code
   }

   @IsTest
   static void test2() {
      // Implement test code
   }

}

下面是包含实用程序的公共测试类的示例 测试数据的方法 创造:

@IsTest 
public class TestUtil {

   public static void createTestAccounts() { 
      // Create some test accounts
   }

   public static void createTestContacts() {
      // Create some test contacts
   }

}

定义为 不能是接口或 枚举。@IsTest

公共测试类的方法只能从 运行测试,即测试方法或测试方法调用的代码。非测试 请求不能调用公共方法。.要了解各种方法,请执行以下操作 运行测试方法,请参阅运行单元测试方法。

@IsTest(SeeAllData=true)注解

对于使用 Salesforce API 版本 24.0 保存的 Apex 代码 稍后,使用注解授予测试类和 单个测试方法访问组织中的所有数据。交通 包括测试未创建的预先存在的数据。从 Apex 代码开始 使用 Salesforce API 版本 24.0 保存,测试方法无权访问 组织中预先存在的数据。但是,保存的测试代码 Salesforce API 版本 23.0 及更早版本继续可以访问 组织。@IsTest(SeeAllData=true)请参见在单元测试中将测试数据与组织数据隔离。注释的注意事项@IsTest(SeeAllData=true)

  • 如果测试类是用注解定义的,则 未显式设置关键字的测试方法。@IsTest(SeeAllData=true)SeeAllData=trueSeeAllData
  • 注解用于打开 在类或方法级别应用时的数据访问。但是,如果 包含类已用 、 对于方法,将忽略对方法进行批注。 在这种情况下,该方法仍然可以访问 组织。使用重写对方法进行注释,对于该方法,对 类。@IsTest(SeeAllData=true)@IsTest(SeeAllData=true)@IsTest(SeeAllData=false)@IsTest(SeeAllData=true)@IsTest(SeeAllData=false)
  • @IsTest(SeeAllData=true)并且不能使用注释 一起使用相同的 Apex 方法。@IsTest(IsParallel=true)

此示例演示如何使用注释定义测试类。所有 此类中的测试方法可以访问 组织。

@IsTest(SeeAllData=true)

// All test methods in this class can access all data.
@IsTest(SeeAllData=true)
public class TestDataAccessClass {

    // This test accesses an existing account. 
    // It also creates and accesses a new test account.
    @IsTest
    static void myTestMethod1() {
        // Query an existing account in the organization. 
        Account a = [SELECT Id, Name FROM Account WHERE Name='Acme' LIMIT 1];
        System.assert(a != null);
        
        // Create a test account based on the queried account.
        Account testAccount = a.clone();
        testAccount.Name = 'Acme Test';
        insert testAccount;
        
        // Query the test account that was inserted.
        Account testAccount2 = [SELECT Id, Name FROM Account 
                                WHERE Name='Acme Test' LIMIT 1];
        System.assert(testAccount2 != null);
    }
       
    
    // Like the previous method, this test method can also access all data
    // because the containing class is annotated with @IsTest(SeeAllData=true).
    @IsTest
    static void myTestMethod2() {
        // Can access all data in the organization.
   }
  
}

第二个示例演示如何在测试上应用注释 方法。由于测试方法的类没有注释,因此必须注释 方法,以便能够访问该方法的所有数据。第二种测试方法没有 具有此注释,因此它只能访问它创建的数据。此外,它还可以 访问用于管理组织的对象,例如 用户。

@IsTest(SeeAllData=true)

// This class contains test methods with different data access levels.
@IsTest
private class ClassWithDifferentDataAccess {

    // Test method that has access to all data.
    @IsTest(SeeAllData=true)
    static void testWithAllDataAccess() {
        // Can query all data in the organization.      
    }
    
    // Test method that has access to only the data it creates
    // and organization setup and metadata objects.
    @IsTest
    static void testWithOwnDataAccess() {
        // This method can still access the User object.
        // This query returns the first user object.
        User u = [SELECT UserName,Email FROM User LIMIT 1]; 
        System.debug('UserName: ' + u.UserName);
        System.debug('Email: ' + u.Email);
        
        // Can access the test account that is created here.
        Account a = new Account(Name='Test Account');
        insert a;      
        // Access the account that was just created.
        Account insertedAcct = [SELECT Id,Name FROM Account 
                                WHERE Name='Test Account'];
        System.assert(insertedAcct != null);
    }
}

@IsTest(OnInstall=true)注解

使用注释指定哪些 Apex 测试是 在软件包安装期间执行。此注释用于托管中的测试 或非托管包。仅测试具有此注释的方法,或具有此注释的方法 具有此注释的测试类的一部分在打包过程中执行 安装。@IsTest(OnInstall=true)注释为在打包期间运行的测试 安装必须通过才能成功安装包。不是 在软件包安装期间绕过失败测试的可能性更长。没有此注释的测试方法或类, 或者,在安装过程中不会执行带有 或 注释的。@IsTest(OnInstall=false)@IsTest

在包安装和升级期间运行的带批注的测试不计入代码覆盖率。 但是,在包创建操作期间会跟踪和计算代码覆盖率。 因为从托管软件包安装的 Apex 代码被排除在组织级别之外 对代码覆盖率的要求,您不太可能受到影响。但是,如果你 跟踪托管包测试覆盖率,则必须在 要更新代码覆盖率统计信息的包安装或升级操作。 包安装不会被代码覆盖率要求阻止。IsTest(OnInstall=true)

此示例演示如何对测试方法进行批注,该方法为 在软件包安装期间执行。在此示例中,已执行,但未执行。test1test2test3

public class OnInstallClass {
   // Implement logic for the class.
   public void method1(){
      // Some code
   }
}
@IsTest
private class OnInstallClassTest {
   // This test method will be executed
   // during the installation of the package.
   @IsTest(OnInstall=true)
   static void test1() {
      // Some test code
   }

   // Tests excluded from running during the
   // the installation of a package.

   @IsTest
   static void test2() {
      // Some test code
   }

   @IsTest
   static void test3() {
      // Some test code
   }
}

@IsTest(IsParallel=true)注解

使用注释来指示测试类 可以并行运行。对并发测试数的默认限制没有 适用于这些测试课程。此注解使测试类的执行 效率更高,因为可以并行运行更多测试。@IsTest(IsParallel=true)注释的注意事项@IsTest(IsParallel=true)

  • 此注释将覆盖禁用并行的设置 测试。
  • @IsTest(SeeAllData=true)和注释不能在同一个 Apex 方法上一起使用。@IsTest(IsParallel=true)

JsonAccess注解

在 Apex 类中定义的注释 级别控制类的实例是否可以序列化或反序列化。如果 注解限制了 JSON 序列化和反序列化,抛出运行时异常。@JsonAccessJSONException注解的 和 参数强制执行 Apex 允许序列化和 反序列化。您可以指定一个或两个参数,但不能指定注释 没有参数。参数的有效值,用于指示序列化和 允许反序列化:

serializabledeserializable@JsonAccess

  • never:从不允许
  • sameNamespace:仅允许 Apex 代码 相同的命名空间
  • samePackage:仅允许 Apex 代码 相同的包(仅影响第二代包)
  • always:始终允许任何 Apex 代码

JsonAccess考虑

  • 如果注释为 扩展时,扩展类不继承此属性。JsonAccess
  • 如果将该方法应用于对象 这不能被序列化,私人数据可以被暴露。您必须在必须保护其数据的对象上重写该方法。 例如,序列化在 Map 中存储为键的对象会调用该方法。生成的映射包括键(字符串) 和值条目,从而公开对象的所有字段。toStringtoStringtoString

此示例代码显示了一个标有注释的 Apex 类。@JsonAccess

// SomeSerializableClass is serializable in the same package and deserializable in the wider namespace

@JsonAccess(serializable='samePackage' deserializable=’sameNamespace’)
public class SomeSerializableClass { }


// AlwaysDeserializable class is always deserializable and serializable only in the same namespace (default value from version 49.0 onwards)

@JsonAccess(deserializable=’always’)
public class AlwaysDeserializable { }

版本化行为更改

在版本 48.0 及更早版本中,反序列化的默认访问权限是,序列化的默认访问权限是保留现有行为。从 从版本 49.0 开始,序列化和反序列化的默认访问权限为 。alwayssameNamespacesameNamespace

命名空间可访问注解

在 包可用于使用相同命名空间的其他包。没有这个 注解、定义的 Apex 类、方法、接口、属性和抽象类 在 2GP 包中,无法被与它们共享的其他包访问 命名空间。声明为全局的 Apex 在所有命名空间中始终可用,并且 不需要注释。@NamespaceAccessible

有关 2GP 托管包的详细信息,请参阅第二代托管包 Salesforce DX 开发人员指南中的软件包。

跨 Apex 可访问性的注意事项 包

  • 不能将注释用于 Apex 方法。@NamespaceAccessible@AuraEnabled
  • 您可以随时添加或删除批注,即使在托管上也是如此 并发布了 Apex 代码。确保没有依赖包 在添加或删除之前依赖注释的功能 它。@NamespaceAccessible
  • 在软件包中添加或删除 Apex 时,请考虑影响 对于安装了引用此包的其他包版本的客户 包的注解。在推送软件包升级之前,请确保没有 客户正在运行的包版本在以下情况下无法完全编译 已推送升级。@NamespaceAccessible
  • 如果公共接口声明为 ,则所有接口成员都继承 注解。不能使用 对单个接口成员进行注释。@NamespaceAccessible@NamespaceAccessible
  • 如果将公共变量或受保护的变量或方法声明为 ,则其定义类 必须是全局的或带有注释的公共的。@NamespaceAccessible@NamespaceAccessible
  • 如果将公共或受保护的内部类声明为 ,则其封闭式 类必须是带有注释的全局类或公共类。@NamespaceAccessible@NamespaceAccessible

此示例显示一个标有批注的 Apex 类。这 类可供同一命名空间中的其他包访问。第一个 构造函数在命名空间中也可见,但第二个构造函数不可见。

@NamespaceAccessible

// A namespace-visible Apex class
@NamespaceAccessible
public class MyClass {
    private Boolean bypassFLS;

    // A namespace-visible constructor that only allows secure use
    @NamespaceAccessible
    public MyClass() {
        bypassFLS = false;
    }

    // A package private constructor that allows use in trusted contexts,
    // but only internal to the package
    public MyClass (Boolean bypassFLS) {
        this.bypassFLS = bypassFLS;
    }
    @NamespaceAccessible
    protected Boolean getBypassFLS() {
       return bypassFLS;
    }
}

版本化行为更改

在 API 版本 47.0 及更高版本中@NamespaceAccessible,不允许在标有 @AuraEnabled。因此,从软件包安装的 Aura 或 Lightning Web 组件不能 从另一个包调用 Apex 方法,即使两个包位于同一命名空间中。

在 API 版本 50.0 及更高版本中,范围和可访问性规则在 Apex 上强制执行 用 注释的变量、方法、内部类和接口。出于辅助功能考虑, 请参阅 Apex 的注意事项 跨软件包的可访问性。有关基于命名空间的可见性的详细信息, 请参阅 Apex 的基于命名空间的可见性 第二代包中的类。@NamespaceAccessible

只读注解

该注释允许您 通过增加限制,对 Lightning 平台数据库执行限制较少的查询 请求返回的行数为 1,000,000。所有其他限制仍然适用。这 注释会阻止请求中的以下操作:DML 操作、对 的调用和排队的异步 Apex 作业。@ReadOnlySystem.schedule

注释可用于 REST 和 SOAP Web 服务以及接口。若要使用注释,顶级请求必须位于计划中 执行或 Web 服务调用。例如,如果 Visualforce 页面调用 包含批注的 Web 服务,请求失败,因为 Visualforce 是顶级请求,而不是 Web 服务。@ReadOnlySchedulable@ReadOnly@ReadOnly

Visualforce 页面可以使用注释调用控制器方法,这些方法使用 同样放宽了限制。要增加其他特定于 Visualforce 的限制,例如 作为可供迭代组件使用的集合的大小,例如 ,您可以将标记上的属性设置为 。有关更多信息,请参见 Visualforce Developer 的 指南。@ReadOnly<apex:pageBlockTable>readonly<apex:page>true

版本化行为更改

在 API 版本 49.0 之前,在 Apex 上使用 还需要 REST 方法(@HttpDelete、@HttpGet、@HttpPatch、@HttpPost 或 @HttpPut) 用 注释方法。在 API 中 版本 49.0 及更高版本中,您可以仅使用 .@ReadOnly@RemoteAction@ReadOnly

远程操作注解

注释 支持通过 JavaScript 调用 Visualforce 中使用的 Apex 方法。这 进程通常称为 JavaScript 远程处理。RemoteAction

注意

带有注释的方法必须是 和 或 。RemoteActionstaticglobalpublic将 Apex 类作为自定义控制器或控制器扩展添加到 页。

<apex:page controller="MyController" extension="MyExtension">

警告

添加控制器或控制器扩展将授予对该 Apex 类中所有方法的访问权限,即使这些方法 页面中不使用方法。任何可以查看该页面的人都可以执行所有方法,并向 控制器。@RemoteAction@RemoteAction然后,将请求添加为 JavaScript 函数调用。一个简单的 JavaScript 远程调用采用以下方法 形式。

[namespace.]MyController.method(
    [parameters...,]
    callbackFunction,
    [configuration]
);
元素描述
namespace控制器类的命名空间。如果出现以下情况,则 namespace 元素是必需的 您的组织定义了一个命名空间,或者该类是否来自已安装的 包。
MyController,MyExtensionApex 控制器或扩展的名称。
method要调用的 Apex 方法的名称。
parameters方法采用的参数的逗号分隔列表。
callbackFunction处理来自 控制器。还可以以内联方式声明匿名函数。 接收方法的状态 调用和结果作为参数。callbackFunction
configuration配置远程呼叫和响应的处理。使用此元素可以 更改远程处理调用的行为,例如是否转义 Apex 方法的响应。

在控制器中,Apex 方法声明前面带有类似 这:

@RemoteAction

@RemoteAction
global static String getItemId(String objectName) { ... }

Apex 方法必须是 和 或 。

@RemoteActionstaticglobalpublic

您的方法可以采用 Apex 基元、集合、类型化和泛型 sObjects,以及 用户定义的 Apex 类和接口作为参数。通用 sObject 必须具有 ID 或 sobjectType 值来标识实际类型。接口参数必须具有 apexType 确定实际类型。您的方法可以返回 Apex 基元、sObjects、集合、 用户定义的 Apex 类和枚举、 、 、 或 。SaveResultUpsertResultDeleteResultSelectOptionPageReference

有关详细信息,请参阅《Visualforce 开发人员指南》中的“适用于 Apex 控制器的 JavaScript 远程处理”。

抑制警告注解

此注释在 Apex 中不执行任何操作,但可用于向 第三方工具。

注释在 Apex 中不执行任何操作,但可用于向 第三方工具。@SuppressWarnings

测试设置注解

使用注解定义的方法 用于创建可用于 类。

@TestSetup

语法

测试设置方法是在测试类中定义的,不带任何参数,也不返回任何值。这 以下是测试设置方法的语法。

@TestSetup static void methodName() {

}

如果测试类包含测试设置方法,则测试框架将执行测试设置 方法,在类中的任何测试方法之前。在测试设置中创建的记录 方法可用于测试类中的所有测试方法,并在 测试类执行。如果测试方法更改了这些记录,例如记录字段更新或 记录删除,这些更改将在每个测试方法完成执行后回滚。这 接下来,执行测试方法可以访问这些记录的原始未修改状态。

注意

每个测试类只能有一种测试设置方法。

只有测试类的默认数据隔离模式才支持测试设置方法。 如果测试类或测试方法可以使用批注访问组织数据, 此类不支持测试设置方法。因为测试的数据隔离 适用于 API 版本 24.0 及更高版本,也提供测试设置方法 仅适用于这些版本。@IsTest(SeeAllData=true)

测试可见注解

使用注释 允许测试方法访问另一个成员的私有或受保护成员 测试类之外的类。这些成员包括方法、成员 变量和内部类。此注释可实现更宽松的 仅用于运行测试的访问级别。此注解不 如果非测试类访问成员,则更改成员的可见性。TestVisible

使用此注释,您不必更改方法的访问修饰符,并且 如果要在测试方法中访问它们,则将变量添加到 public。例如,如果 私有成员变量不应该暴露给外部类,但它 必须可通过测试方法访问,才能将注释添加到变量定义中。TestVisible

此示例演示如何对私有类成员变量进行批注 和私有方法。TestVisible

public class TestVisibleExample {
    // Private member variable
    @TestVisible private static Integer recordNumber = 1;

    // Private method
    @TestVisible private static void updateRecord(String name) {
        // Do something
    }
}

此测试类使用上一个类,并包含访问带注释的 成员变量和方法。

@IsTest
private class TestVisibleExampleTest {
    @IsTest static void test1() {
        // Access private variable annotated with TestVisible
        Integer i = TestVisibleExample.recordNumber;
        System.assertEquals(1, i);

        // Access private method annotated with TestVisible
        TestVisibleExample.updateRecord('RecordName');
        // Perform some verification
    }
}

Apex REST 注解

使用这些注解将 Apex 类公开为 RESTful Web 服务。

  • @ReadOnly
  • @RestResource(urlMapping=’/yourUrl‘)
  • @HttpDelete
  • @HttpGet
  • @HttpPatch
  • @HttpPost
  • @HttpPut

RestResource Annotation

注释用于 类级别,使您能够将 Apex 类公开为 REST 资源。@RestResource使用此注释时的一些注意事项:

  • URL 映射是相对于 https:// instance.salesforce.com/services/apexrest/ 的。
  • URL 映射可以包含通配符 (*)。
  • URL 映射区分大小写。例如,的 URL 映射与包含 而不是 的 REST 资源匹配。my_urlmy_urlMy_Url
  • 要使用此注释,必须将 Apex 类定义为全局类。

URL 准则

URL 路径映射如下所示:

  • 路径必须以正斜杠 (/) 开头。
  • 路径长度最多为 255 个字符。
  • 路径中出现的通配符 (*) 前面必须有正斜杠 (/)。 此外,除非通配符是路径中的最后一个字符,否则必须遵循通配符 正斜杠 (/)。

映射 URL 的规则如下:

  • 完全匹配总是获胜。
  • 如果未找到完全匹配项,请查找具有匹配通配符的所有模式,然后选择 其中最长的(按字符串长度计算)。
  • 如果未找到通配符匹配项,则返回 HTTP 响应状态代码 404。

命名空间类的 URL 包含命名空间。例如,如果您的类位于命名空间中,并且该类映射到 ,则 API URL 将修改如下:https:// instance.salesforce.com/services/apexrest/abc/your_url/。 在发生 URL 冲突的情况下,始终使用命名空间类。abcyour_url

Http删除注解

注释在方法级别使用 并使您能够将 Apex 方法公开为 REST 资源。此方法在发送 HTTP 请求时调用,并删除指定的 资源。@HttpDeleteDELETE

若要使用此批注,必须将 Apex 方法定义为 全局静态。

HttpGet的注解

注释用于方法级别和 使您能够将 Apex 方法公开为 REST 资源。此方法在发送 HTTP 请求时调用,并返回指定的 资源。@HttpGetGET以下是使用此注释时的一些注意事项:

  • 若要使用此注释,必须将 Apex 方法定义为全局静态方法。
  • 注释的方法也称为 if HTTP 请求使用 request 方法。@HttpGetHEAD

HttpPatch 的注解

注释在方法级别使用 并使您能够将 Apex 方法公开为 REST 资源。此方法在发送 HTTP 请求时调用,并更新指定的 资源。@HttpPatchPATCH

若要使用此注释,必须将 Apex 方法定义为全局静态方法。

HttpPost的注解

注释用于方法 级别,并使您能够将 Apex 方法公开为 REST 资源。此方法称为 发送 HTTP 请求时,并创建一个 新资源。@HttpPostPOST

若要使用此注释,必须将 Apex 方法定义为全局静态方法。

HttpPut 的注解

注释用于方法级别和 使您能够将 Apex 方法公开为 REST 资源。此方法在发送 HTTP 请求时调用,并创建或更新指定的 资源。@HttpPutPUT

若要使用此注释,必须将 Apex 方法定义为全局静态方法。

Apex关键字

Apex 提供了 关键字 finalinstanceofsuperthistransientwith sharing and without sharing

  1. 使用 final 关键字
  2. 使用 instanceof 关键字
  3. 使用 super 关键字
  4. 使用 this 关键字
  5. 使用 transient 关键字
  6. 使用 with sharing、without sharing 和 inherited sharing 关键字 在类上使用 or 关键字
    指定是否必须强制执行共享规则。在类上使用关键字,以在调用该类的类的共享模式下运行该类。with sharing without sharing inherited sharing

使用 final 关键字

您可以使用关键字 修改变量。

final

  • 最终变量只能赋值一次,无论是在声明变量时还是在 构造函数。您必须在以下两个位置之一为其赋值。
  • 静态最终变量可以在静态初始化中更改 代码或定义的位置。
  • 成员最终变量可以在初始化代码块中更改, 构造函数,或使用其他变量声明。
  • 要定义常量,请将变量标记为 和 。staticfinal
  • 非最终静态变量用于在 类级别(例如触发器之间的状态)。然而,事实并非如此 在请求之间共享。
  • 默认情况下,方法和类是最终的。不能在声明中使用关键字 的类或方法。这意味着它们不能被覆盖。如果需要覆盖,请使用关键字 方法或类。finalvirtua

使用 instanceof 关键字

如果需要在运行时验证对象是否实际上是 特定类,请使用关键字。 关键字只能用于 验证关键字右侧表达式中的目标类型是否可行 左侧表达式的声明类型的替代项。instanceofinstanceof您可以将以下检查添加到类中的类中,然后 将项目投射回对象之前的投射示例。

ReportCustomReport

If (Reports.get(0) instanceof CustomReport) {
    // Can safely cast it back to a custom report object
   CustomReport c = (CustomReport) Reports.get(0);
   } Else {
   // Do something with the non-custom-report.
}

注意

在使用 API 版本 32.0 及更高版本保存的 Apex 中,如果左操作数为 null 对象,则返回。为 示例,以下示例返回 。instanceoffalsefalse

Object o = null;
Boolean result = o instanceof Account;
System.assertEquals(false, result);

在 API 版本 31.0 及更早版本中,在本例中返回。instanceoftrue

使用 super 关键字

关键字可以 由从虚拟类或抽象类扩展而来的类使用。 通过使用 ,可以覆盖 父类中的构造函数和方法。supersuper例如,如果您有以下虚拟类:

public virtual class SuperClass {
    public String mySalutation;
    public String myFirstName;
    public String myLastName;

    public SuperClass() {

        mySalutation = 'Mr.';
        myFirstName = 'Carl';
        myLastName = 'Vonderburg';
    }

    public SuperClass(String salutation, String firstName, String lastName) {

        mySalutation = salutation;
        myFirstName = firstName;
        myLastName = lastName;
    }

    public virtual void printName() {

        System.debug('My name is ' + mySalutation + myLastName);
    }

   public virtual String getFirstName() {
       return myFirstName;
   }
}

您可以创建以下类来扩展和重写其方法:

SuperclassprintName

public class Subclass extends Superclass {
  public override void printName() {
        super.printName();
        System.debug('But you can call me ' + super.getFirstName());
    }
}

调用时的预期输出为Subclass.printNameMy name is Mr. Vonderburg. But you can call me Carl.还可以用于调用构造函数。将以下构造函数添加到:

superSubClass

public Subclass() {
    super('Madam', 'Brenda', 'Clapentrap');
}

现在,预期的输出是Subclass.printNameMy name is Madam Clapentrap. But you can call me Brenda.

使用 super 关键字的最佳做法

  • 只有扩展自 或 类的类才能使用 .virtualabstractsuper
  • 只能在用关键字指定的方法中使用。superoverride

使用 this 关键字

有两种不同的使用关键字的方法。this

您可以使用关键字 以点表示法,不带括号,表示当前实例 它出现在哪个类中。使用这种形式的关键字来访问实例变量 和方法。例如:thisthis

public class myTestThis {

string s;
  {
      this.s = 'TestString';
  }
}

在上面的示例中,该类声明了一个实例变量。初始化代码填充 使用 关键字的变量。myTestThissthis

或者你可以使用关键字来做构造函数链接,即在一个构造函数中, 调用另一个构造函数。在此格式中,使用带括号的关键字。为 例:thisthis

public class testThis {

// First constructor for the class. It requires a string parameter.
   public testThis(string s2) {
   }

// Second constructor for the class. It does not require a parameter.
// This constructor calls the first constructor using the this keyword.
   public testThis() {
       this('None');
   }
}

当您使用关键字时 在构造函数中,要执行构造函数链接,它必须是第一个 语句。this

使用 transient 关键字

使用关键字声明实例 无法保存且不应作为视图状态的一部分传输的变量 用于 Visualforce 页面。为 例:

transient

Transient Integer currentTotal;

您还可以在 Apex 中使用关键字 可序列化的类,即在控制器、控制器扩展或类中 实现 or 接口。此外,还可以在定义类型的类中使用 在可序列化类中声明的字段。transientBatchableSchedulabletransient

将变量声明为缩减视图 状态大小。关键字的一个常见用例是 Visualforce 页面上的字段,该字段仅在页面持续时间内需要 请求,但不应是页面视图状态的一部分,并且会使用过多的系统 在请求期间要多次重新计算的资源。transienttransient

某些 Apex 对象会自动被视为瞬态对象,也就是说,它们的值不会 保存为页面视图状态的一部分。这些对象包括:

  • 页面引用
  • XmlStream 类
  • 集合自动标记为暂时性,仅当它们的对象类型 保留会自动标记为暂时性保留,例如保存点的集合
  • 大多数对象由系统方法生成,例如 .Schema.getGlobalDescribe
  • JSONParser类实例。

静态变量也不会得到 通过视图状态传输。

以下示例包含一个 Visualforce 页面和一个自定义控制器。点击 页面上的“刷新”按钮使瞬态日期为 已更新,因为每次刷新页面时都会重新创建它。非瞬态 date 继续具有其原始值,该值已从视图中反序列化 状态,所以它保持不变。

<apex:page controller="ExampleController">
  T1: {!t1} <br/>
  T2: {!t2} <br/>
  <apex:form>
    <apex:commandLink value="refresh"/>
  </apex:form>
</apex:page>
public class ExampleController {

    DateTime t1;
    transient DateTime t2;

    public String getT1() {
        if (t1 == null) t1 = System.now();
        return '' + t1;
    }

    public String getT2() {
        if (t2 == null) t2 = System.now();
        return '' + t2;
    }
}

使用有共享、无共享和继承共享关键字

在类上使用 or 关键字来指定是否 必须强制执行共享规则。在类上使用关键字以在类的共享模式下运行该类 称其为。

with sharingwithout sharinginherited sharing

与共享

在声明 类来强制执行当前用户的共享规则。显式设置此关键字可确保 Apex 代码在当前用户上下文中运行。通过调用执行的 Apex 代码和 Apex 中的 Connect 始终执行 使用当前用户的共享规则。有关 的详细信息,请参阅匿名块。with sharingexecuteAnonymousexecuteAnonymous

在声明 类来强制执行适用于当前用户的共享规则。例如:with sharing

public with sharing class sharingClass {

   // Code here

}

无共享

在声明 类来确保不强制执行当前用户的共享规则。例如 当一个类从另一个类调用时,您可以显式关闭共享规则强制执行 使用 声明的类。without sharingwith sharing

public without sharing class noSharing {

   // Code here

}

继承共享

在声明 类来强制执行调用它的类的共享规则。使用是一种先进的技术来确定 运行时的共享模式,并设计可以在任一模式下运行的 Apex 类。inherited sharinginherited sharingwith sharingwithout sharing

警告

由于共享模式是在运行时确定的,因此必须采取极端措施 注意确保您的 Apex 代码可以安全地在两种模式下运行。with sharingwithout sharing将 一起使用 和其他 适当的安全检查,有助于通过 AppExchange 安全审查并确保 您的特权 Apex 代码不会以意外或不安全的方式使用。一个 Apex 类,其运行方式为:

inherited sharinginherited sharingwith sharing

  • Aura 组件控制器
  • Visualforce 控制器
  • Apex REST 服务
  • Apex 事务的任何其他入口点

标有 Apex 类和省略共享的类之间有明显的区别 声明。如果该类用作 Apex 事务的入口点,则省略 共享声明以 . 但是,确保默认 是以 .声明为 的类仅在从 已经建立的上下文。inherited sharingwithout sharinginherited sharingwith sharinginherited sharingwithout sharingwithout sharing

此示例声明一个 Apex 类,其中包含该 Apex 代码的 Visualforce 调用。由于声明,只有联系人 显示正在运行的用户具有共享访问权限。如果省略声明,请联系 由于不安全的默认值,用户没有查看权限 行为。

inherited sharinginherited sharing

public inherited sharing class InheritedSharingClass {
    public List<Contact> getAllTheSecrets() {
        return [SELECT Name FROM Contact];
    }
}
<apex:page controller="InheritedSharingClass">
    <apex:repeat value="{!allTheSecrets}" var="record">
        {!record.Name}
    </apex:repeat>
</apex:page>

实施细节

  • 应用定义方法的类的共享设置,而不是 从中调用方法的类。例如,如果在类中定义了方法 声明为由类调用 声明为 ,方法 在强制执行共享规则的情况下执行。with sharingwithout sharing
  • 如果类未显式声明为 either 或 , 当前共享规则仍然有效。因此,该类不强制执行共享 规则,除非它从另一个类获取共享规则。例如,如果 类由另一个强制执行共享的类调用,然后强制执行共享 被调用的类。with sharingwithout sharing
  • 内部类和外部类都可以声明为 。内部类不继承 共享容器类中的设置。否则,共享设置将应用于 类中包含的所有代码,包括初始化代码、构造函数和 方法。with sharing
  • 当一个类扩展或 实现另一个。
  • Apex 触发器不能具有显式共享声明,不能以 .without sharing

最佳实践

默认情况下,没有显式共享声明的 Apex 是不安全的。我们强烈建议 您始终为类指定共享声明。

无论采用何种共享模式,对象级访问和字段级安全性都不是 由 Apex 强制执行。您必须在 您的 SOQL 查询或代码。例如,机制不强制用户访问查看报表和 仪表 板。您必须显式强制执行正在运行的用户的 CRUD(创建、读取、更新、 Delete) 和代码中的字段级安全性。请参阅强制执行对象和字段权限。with sharing

共享模式何时使用
with sharing使用此模式作为默认模式,除非您的用例另有要求。
without sharing请谨慎使用此模式。确保您不会无意中暴露敏感内容 通常由共享模型隐藏的数据。这种共享机制是 最适合用于向当前授予有针对性地提升共享权限 用户。例如,使用 允许社区用户读取他们原本不会拥有的记录 访问。without sharing
inherited sharing对于必须灵活且支持使用的服务类,请使用此模式 具有不同共享模式的情况,同时也默认为更安全的模式。with sharing

Apex接口

接口就像一个类,其中没有任何方法 已实现 – 方法签名存在,但每个方法的主体为空。自 使用接口,另一个类必须通过为所有方法提供主体来实现它 包含在接口中。

接口可以为代码提供抽象层。他们分离特定的 从该方法的声明中实现方法。这样你就可以拥有 基于特定应用程序的方法的不同实现。

定义接口类似于定义新类。例如,一家公司可以有 两种类型的采购订单,一种来自客户,另一种来自客户 他们的员工。两者都是一种采购订单。假设您需要一种方法来 提供折扣。折扣金额可能取决于采购订单的类型。

您可以将采购订单的一般概念建模为接口,并具有特定的 为客户和员工实施。在以下示例中,焦点仅为 在采购订单的折扣方面。

下面是接口的定义。PurchaseOrder

// An interface that defines what a purchase order looks like in general
public interface PurchaseOrder {
    // All other functionality excluded
    Double discount();
}

此类实现接口 用于客户采购订单。PurchaseOrder

// One implementation of the interface for customers
public class CustomerPurchaseOrder implements PurchaseOrder {
    public Double discount() {
        return .05;  // Flat 5% discount
    }
}

此类实现接口 用于员工采购订单。PurchaseOrder

// Another implementation of the interface for employees
public class EmployeePurchaseOrder implements PurchaseOrder {
      public Double discount() {
        return .10;  // It’s worth it being an employee! 10% discount
      } 
}

请注意有关该示例的以下信息:

  • 接口已定义 作为通用原型。接口中定义的方法无权访问 修饰符,并且只包含它们的签名。PurchaseOrder
  • 班级 实现此接口;因此,它必须为方法提供定义。任何类 实现接口必须定义接口中包含的所有方法。CustomerPurchaseOrderdiscount

定义新接口时,就是在定义新的数据类型。您可以使用 接口名称:在任何地方都可以使用其他数据类型名称。分配给 interface 类型的变量必须是实现该接口的类的实例, 或子接口数据类型。

另请参见类和强制转换

注意

在类之后,无法将方法添加到全局接口 在托管 – 已发布的包版本中上传。

版本化行为更改

在 API 版本 50.0 及更高版本中,范围和可访问性规则在 Apex 上强制执行 用 注释的变量、方法、内部类和接口。对于可访问性 注意事项,请参阅 NamespaceAccessible 注释。有关基于命名空间的可见性的详细信息,请参阅基于命名空间的可见性 适用于第二代软件包中的 Apex 类。@namespaceAccessible

  1. 自定义迭代器

自定义迭代器

迭代器遍历集合中的每个项。例如,在 Apex 的循环中,您定义了一个 退出循环的条件,并且您必须提供一些遍历 集合,即迭代器。while在此示例中,每次循环 执行。

count

while (count < 11) {
   System.debug(count);
      count++;
   }

使用该界面,您可以创建自定义集 的指令,用于通过循环遍历列表。迭代器对数据很有用 存在于 Salesforce 之外的源中,您通常会定义其范围 使用语句。迭代器也可以是 如果有多个语句,则使用。IteratorSELECTSELECT

使用自定义 迭代器

自 使用自定义迭代器时,必须创建一个实现接口的 Apex 类。Iterator该接口具有以下实例 方法:

Iterator

名字参数返回描述
hasNext布尔如果出现以下情况,则返回 否则,集合中还有另一个项目正在遍历。truefalse
next任何类型返回集合中的下一项。

接口中的所有方法都必须声明为 或 。Iteratorglobalpublic您只能在循环中使用自定义迭代器。为 例:

while

IterableString x = new IterableString('This is a really cool test.');

while(x.hasNext()){
   system.debug(x.next());
}

循环中目前不支持迭代器。

for

将自定义迭代器与 Iterable 一起使用

如果 您不想将自定义迭代器与列表一起使用,而是希望创建 自己的数据结构,可以使用接口生成数据结构。Iterable该接口具有以下方法:

Iterable

名字参数返回描述
iteratorIterator 类返回对此接口的迭代器的引用。

该方法必须是 声明为 或 。它创建对迭代器的引用 然后,您可以使用它来遍历数据结构。iteratorglobalpublicIn the following example a custom iterator iterates through a collection:

public class CustomIterator
   implements Iterator<Account>{ 
 
   private List<Account> accounts;
   private Integer currentIndex;
 
   public CustomIterator(List<Account> accounts){
       this.accounts = accounts;
       this.currentIndex = 0;
   }
 
   public Boolean hasNext(){ 
       return currentIndex < accounts.size();
   }    
 
   public Account next(){
       if(hasNext()) {
           return accounts[currentIndex++];
       } else {
           throw new NoSuchElementException('Iterator has no more elements.');
       }
   } 
}
public class CustomIterable implements Iterable<Account> {
   public Iterator<Account> iterator(){
      List<Account> accounts =
      [SELECT Id, Name,
       NumberOfEmployees 
       FROM Account
       LIMIT 10];
      return new CustomIterator(accounts);
   }
}

下面是一个批处理作业,它使用 迭 代:

public class BatchClass implements Database.Batchable<Account>{
   public Iterable<Account> start(Database.BatchableContext info){
       return new CustomIterable();
   }
   public void execute(Database.BatchableContext info, List<Account> scope){
       List<Account> accsToUpdate = new List<Account>();
       for(Account acc : scope){
           acc.Name = 'changed';
           acc.NumberOfEmployees = 69;
           accsToUpdate.add(acc);
       }
       update accsToUpdate;
   }
   public void finish(Database.BatchableContext info){
   }
}

Apex Class类

与 Java 一样,您可以在 Apex Class中创建类。是用于创建对象的模板或蓝图。对象是类的实例。

例如,该类描述整个购买 订单,以及您可以使用采购订单执行的所有操作。类的实例是特定的采购订单 您发送或接收的内容。PurchaseOrderPurchaseOrder

所有对象都有状态行为,即 对象知道自己,以及对象可以做的事情。状态 PurchaseOrder 对象(它所知道的对象)包括发送它的用户、日期 以及它的创建时间,以及它是否被标记为重要。行为 PurchaseOrder 对象(它可以做什么)包括检查库存、发货 产品,或通知客户。

类可以包含变量和方法。变量用于指定 对象,例如对象的 或 .由于这些变量与 类并且是它的成员,它们通常被称为成员 变量。方法用于控制行为,例如 或 。NameTypegetOtherQuotescopyLineItems

类可以包含其他类、异常类型和初始化代码。

接口就像一个类,其中没有一个 方法已实现 – 方法签名在那里,但 每个方法都是空的。要使用接口,另一个类必须通过提供 接口中包含的所有方法的主体。

有关类、对象和接口的更多常规信息,请参阅 http://java.sun.com/docs/books/tutorial/java/concepts/index.html

除了类之外,Apex 还提供触发器、 类似于数据库触发器。触发器是执行的 Apex 代码 在数据库操作之前或之后。请参阅触发器。

  1. Apex 类定义
  2. 类变量
  3. 类方法
  4. 使用构造函数
  5. 访问修饰符
  6. 静态和实例方法、变量和初始化代码
    在 Apex 中,您可以拥有静态方法、变量和初始化代码。但是,Apex 类不能是静态的。还可以具有实例方法、成员变量和初始化代码(没有修饰符)和局部变量。
  7. Apex 属性
  8. 扩展类
    可以扩展类以提供更专业的行为。
  9. 扩展类示例

Apex 类定义

在 Apex 中,您可以定义顶级类(也称为外部类)以及内部类, 也就是说,在另一个类中定义的类。内部类只能有一个级别深。为 例:

public class myOuterClass {
   // Additional myOuterClass code here
   class myInnerClass {
     // myInnerClass code here
   }
}

若要定义类,请指定以下内容:

  1. 访问修饰符:
    • 您必须在顶级的声明中使用访问修饰符之一(如 或 ) 类。publicglobal
    • 不必在内部类的声明中使用访问修饰符。
  2. 可选的定义修饰符(如 、 等)virtualabstract
  3. 必需:关键字后跟类名称class
  4. 可选扩展或实现,或两者兼而有之

注意

避免对类名使用标准对象名。这样做 导致意外结果。有关标准对象的列表,请参阅 Salesforce 的对象参考。

使用以下语法定义类:

private | public | global 
[virtual | abstract | with sharing | without sharing] 
class ClassName [implements InterfaceNameList] [extends ClassName] 
{ 
// The body of the class
}
  • access 修饰符声明此类是 仅在本地知道,即仅通过这部分代码知道。这是默认设置 内部类的访问权限 – 也就是说,如果未指定访问修饰符 对于内部类,它被认为是 .此关键字只能与内部类(或 标有注解的顶级测试类)。privateprivate@IsTest
  • 访问修饰符 声明此类在应用程序或命名空间中可见。public
  • access 修饰符声明此类是 所有 Apex 代码都广为人知。global所有班级 包含用关键字定义的方法必须声明为 。如果方法或内部类是 声明为 ,外部, 顶级类也必须定义为 。webserviceglobalglobalglobal
  • 和关键字指定此类的共享模式。欲了解更多信息, 请参阅使用有共享、无共享和继承共享关键字。with sharingwithout sharing
  • 定义修饰符声明此 类允许扩展和覆盖。不能使用关键字重写方法,除非类 已定义为 。virtualoverridevirtual
  • 定义 modifier 声明此类包含抽象方法, 是,只声明其签名而不定义主体的方法。abstract

注意

  • 在将抽象方法上传到全局类后,无法将抽象方法添加到全局类中 托管 – 已发布的包版本。
  • 如果“托管 – 已发布”包中的类是虚拟的,则可以向其添加的方法 还必须是虚拟的,并且必须具有实现。
  • 不能重写已安装的全局类的公共或受保护的虚拟方法 托管包。

有关托管包的详细信息,请参阅什么是包?。

一个类可以实现多个接口,但只能扩展一个现有类。此限制 表示 Apex 不支持多重继承。列表中的接口名称 用逗号分隔。有关接口的详细信息,请参阅接口。

有关方法和变量访问修饰符的更多信息,请参见访问修饰符。

类变量

若要声明变量,请指定以下内容:

  • 可选:修饰符,例如 或 ,以及 。publicfinalstatic
  • 必需:变量的数据类型,例如 String 或 Boolean。
  • 必需:变量的名称。
  • 可选:变量的值。

定义 变量:

[public | private | protected | global] [final] [static] data_type variable_name 
[= value]

例如:

private static final Integer MY_INT; 
      private final Integer i = 1;

版本化行为更改

在 API 版本 50.0 及更高版本中,范围和可访问性规则强制执行 带注释的 Apex 变量、方法、内部类和接口 跟。为 辅助功能注意事项,请参阅 NamespaceAccessible 注释。有关基于命名空间的可见性的详细信息, 请参阅基于命名空间 第二代软件包中 Apex 类的可见性。@namespaceAccessible

类方法

若要定义方法,请指定以下内容:

  • 可选:修饰符,例如 或 .publicprotected
  • 必需:方法返回的值的数据类型,例如 String 或 整数。如果方法不使用,请使用 返回一个值。void
  • 必需:方法的输入参数列表,用逗号分隔,每个参数 前面是其数据类型,并括在括号中。如果没有参数,请使用 空括号。一个方法只能有 32 个输入参数。()
  • 必需:方法的主体,用大括号括起来。该方法的所有代码,包括 此处包含任何局部变量声明。{}

定义 方法:

[public | private | protected | global] [override] [static] data_type method_name 
(input parameters) 
{
// The body of the method
}

注意

您可以使用 仅在已定义为 或 的类中覆盖方法。overridevirtualabstract例如:

public static Integer getInt() { 
     return MY_INT; 
  }

与 Java 一样,返回值的方法也可以作为语句运行,如果其结果 未分配给其他变量。用户定义的方法:

  • 可以在使用系统方法的任何地方使用。
  • 可以是递归的。
  • 可能会产生副作用,例如初始化 sObject 记录 ID 的 DML 语句。请参阅 Apex DML 语句。insert
  • 可以引用它们自己或稍后在同一类中定义的方法,或者 匿名块。Apex 分两个阶段解析方法,因此 forward 声明 不需要。
  • 可以是多态性的。例如,可以通过两种方式实现名为的方法,一种是使用单个 Integer 参数,一个包含两个 Integer 参数。取决于方法是否 调用一个或两个整数,Apex 解析器选择适当的 要执行的实现。如果解析器找不到完全匹配,则 使用类型强制规则寻求近似匹配。欲了解更多信息 数据转换,详见 转换。example注意如果解析器找到多个近似匹配项,则 生成解析时异常。
  • 具有 void 返回类型的方法通常作为独立语句调用 在 Apex 代码中。为 例:System.debug('Here is a note for the log.');
  • 可以有返回值作为语句运行的语句,如果它们的 结果不会分配给其他变量。此规则在 Java 中相同。

按值传递方法参数

在 Apex 中,所有原始数据类型参数,例如 Integer 或 String,按值传递到方法中。这一事实意味着对 参数仅存在于方法的范围内。当方法 返回时,对参数的更改将丢失。

非基元数据类型参数(如 sObjects)是 通过引用传递到方法中。因此,当该方法返回时, 传入的参数仍引用与方法调用之前相同的对象。 在该方法中,不能更改引用以指向另一个对象,但 可以更改对象字段的值。

以下是传递基元和非基元数据类型参数的示例 into 方法。

示例:传递基元数据类型参数此示例演示如何将 String 类型的基元参数按值传递到 另一种方法。此示例中的方法创建一个 String 变量 , 和 为其赋值。然后,它将此变量作为参数传递给另一个方法, 修改此 String 的值。但是,由于 String 是基元类型, 它是按值传递的,当方法返回时,原始的值 变量 , 保持不变。assert 语句验证 的价值仍然是旧的 价值。

debugStatusMessagemsgmsgmsg

public class PassPrimitiveTypeExample {
    public static void debugStatusMessage() {
        String msg = 'Original value';
        processString(msg);
        // The value of the msg variable didn't
        // change; it is still the old value.
        System.assertEquals(msg, 'Original value');
    }
    
    public static void processString(String s) {
        s = 'Modified value';
    }
}

示例:传递非基元数据类型参数

此示例演示如何通过引用将 List 参数传递到方法中并对其进行修改。然后 在方法中,显示 不能将 List 参数更改为指向另一个 List 对象。reference()referenceNew()一、方法 创建一个变量 ,该变量是整数列表并传递 它变成了一种方法。调用的方法使用表示的整数值填充此列表 四舍五入的温度值。当该方法返回时,assert 语句将验证 原始 List 变量的内容已更改,现在包含五个 值。接下来,该示例创建第二个 List 变量 ,并将其传递给另一个方法。调用的方法 将传入的参数分配给新创建的包含新 Integer 的 List 值。当方法返回时,原始变量 不指向新列表,但仍指向原始列表,该列表为空。 assert 语句验证不包含 值。

createTemperatureHistoryfillMecreateMecreateMecreateMe

public class PassNonPrimitiveTypeExample {
    
    public static void createTemperatureHistory() {
        List<Integer> fillMe = new List<Integer>();        
        reference(fillMe);
        // The list is modified and contains five items
        // as expected.
        System.assertEquals(fillMe.size(),5);        
        
        List<Integer> createMe = new List<Integer>();
        referenceNew(createMe);
        // The list is not modified because it still points
        // to the original list, not the new list 
        // that the method created.
        System.assertEquals(createMe.size(),0);     
    }
            
    public static void reference(List<Integer> m) {
        // Add rounded temperatures for the last five days.
        m.add(70);
        m.add(68);
        m.add(75);
        m.add(80);
        m.add(82);
    }    
        
    public static void referenceNew(List<Integer> m) {
        // Assign argument to a new List of
        // five temperature values.
        m = new List<Integer>{55, 59, 62, 60, 63};
    }    
}

版本化行为更改

在 API 版本 50.0 及更高版本中,范围和可访问性规则在 Apex 上强制执行 用 注释的变量、方法、内部类和接口。对于可访问性 注意事项,请参阅 NamespaceAccessible 注释。有关基于命名空间的可见性的详细信息,请参阅基于命名空间的可见性 适用于第二代软件包中的 Apex 类。@namespaceAccessible

使用构造函数

构造函数是从类创建对象时调用的代码 蓝图。您不需要为每个类编写构造函数。如果一个类没有 用户定义的构造函数,使用默认的、无参数的公共构造函数。

构造函数的语法类似于方法,但有所不同 从方法定义,因为它从来就没有显式返回类型 并且它不会被从它创建的对象继承。编写类的构造函数后,必须使用关键字才能实例化 该类中的对象,使用该构造函数。例如,使用 以下类:

new

public class TestObject {

   // The no argument constructor 
   public TestObject() {
      // more code here
  }
}

可以使用以下命令实例化此类型的新对象 法典:

TestObject myTest = new TestObject();

如果编写一个接受参数的构造函数,则可以使用该构造函数创建一个 对象。

如果创建接受参数的构造函数,并且仍希望使用无参数 构造函数,则必须在代码中创建自己的无参数构造函数。创建 构造函数,则您不再有权访问默认的无参数公共 构造 函数。在 Apex 中,构造函数可以重载,也就是说,可以 一个类的多个构造函数,每个构造函数都有不同的参数。 下面的示例演示一个具有两个构造函数的类:一个 没有参数,并且采用简单的 Integer 参数。它 还阐释了一个构造函数如何使用 语法,也 称为构造函数链接

this(…)

public class TestObject2 {

private static final Integer DEFAULT_SIZE = 10;

Integer size;

   //Constructor with no arguments
   public TestObject2() {
       this(DEFAULT_SIZE); // Using this(...) calls the one argument constructor    
   }

   // Constructor with one argument 
   public TestObject2(Integer ObjectSize) {
     size = ObjectSize;  
   }
}

可以使用以下命令实例化此类型的新对象 法典:

TestObject2 myObject1 = new TestObject2(42);
  TestObject2 myObject2 = new TestObject2();

为类创建的每个构造函数都必须具有不同 参数列表。在下面的示例中,所有构造函数都是 可能:

public class Leads {

  // First a no-argument constructor 
  public Leads () {}

  // A constructor with one argument
  public Leads (Boolean call) {}

  // A constructor with two arguments
  public Leads (String email, Boolean call) {}

  // Though this constructor has the same arguments as the 
  // one above, they are in a different order, so this is legal
  public Leads (Boolean call, String email) {}
}

定义新类时,就是在定义新的数据类型。 您可以在任何可以使用其他数据类型名称的地方使用类名, 例如 String、Boolean 或 Account。如果定义一个变量,其 type 是一个类,分配给它的任何对象都必须是 该类或子类。

访问修饰符

Apex 允许您使用 、 、 和 access 修饰符 定义方法和变量时。privateprotectedpublicglobal

虽然触发器和匿名块也可以使用这些访问修饰符,但它们在 Apex 的较小部分。例如,将方法声明为匿名块中的方法并不能调用 它来自该代码的外部。global

有关类访问修饰符的更多信息,请参见 Apex 类定义。

注意

接口方法没有访问修饰符。它们始终是全球性的。有关详细信息,请参阅接口。

默认情况下,方法或变量仅对定义中的 Apex 代码可见 类。显式指定方法或变量为公共方法或变量,以便将其指定为 public 可用于同一应用程序命名空间中的其他类(请参阅命名空间前缀)。您可以更改 使用以下访问修饰符的可见性级别:private此访问修饰符是默认值,表示 方法或变量只能在定义它的 Apex 类中访问。 如果未指定访问修饰符,则方法或变量为 .privateprotected这意味着该方法或变量对 定义 Apex 类,以及扩展定义 Apex 类的类。您可以 仅将此访问修饰符用于实例方法和成员变量。此设置 严格来说,它比默认(私有)设置更宽松,就像 Java 一样。public这表示方法或变量可供所有人访问 特定包中的 Apex。适用于所有第二代 (2GP) 共享命名空间的托管包与注释一起使用。在 no-namespace 包将 Apex 代码隐式呈现为 @NamespaceAccessible。public@NamespaceAccessible

注意

在 Apex 中,访问 modifier 与 Java 中的修饰符不同。这样做是为了阻止加入 应用程序,以将每个应用程序的代码分开。在 Apex 中,如果您愿意 要像在 Java 中那样公开某些内容,必须使用 access 修饰符。publicglobal有关基于命名空间的可见性的详细信息,请参阅基于命名空间的可见性 适用于第二代软件包中的 Apex 类。global这表示该方法或变量可以由任何 Apex 代码使用 可以访问该类,而不仅仅是同一应用程序中的 Apex 代码。这 访问修饰符必须用于必须在 应用程序,无论是在 SOAP API 中还是通过其他 Apex 代码。如果声明方法或 变量为 ,您还必须声明 以 .globalglobal

注意

我们建议很少使用访问修饰符(如果有的话)。跨应用程序 依赖关系难以维护。global

要使用 、 、 或 access 修饰符,请使用 语法如下:privateprotectedpublicglobal

[(none)|private|protected|public|global] declaration

例如:

// private variable s1
private string s1 = '1';

// public method getsz()
public string getsz() { 
   ... 
}

静态和实例方法、变量和初始化 法典

在 Apex 中,您可以拥有静态方法、变量和初始化 法典。但是,Apex 类不能是静态的。还可以具有实例方法、成员变量和初始化代码(没有修饰符)和局部变量。

特性

静态方法、变量和初始化代码具有这些特征。

  • 它们与类相关联。
  • 它们只允许在外部类中使用。
  • 它们仅在加载类时初始化。
  • 它们不会作为 Visualforce 页面视图状态的一部分进行传输。

实例方法、成员变量和初始化代码具有这些 特性。

  • 它们与特定对象相关联。
  • 它们没有定义修饰符。
  • 它们是使用从它们所在的类实例化的每个对象创建的 宣布。

局部变量具有这些特征。

  • 它们与声明它们的代码块相关联。
  • 在使用它们之前,必须对其进行初始化。

下面的示例演示一个局部变量,其作用域是代码块的持续时间。if

Boolean myCondition = true;
if (myCondition) {
    integer localVariable = 10;
}

使用静态方法和变量

只能将静态方法和变量用于外部类。内部类有 没有静态方法或变量。静态方法或变量不需要 类的实例。

在创建类的对象之前,类中的所有静态成员变量都是 初始化,并执行所有静态初始化代码块。这些项目是 按照它们在类中出现的顺序进行处理。

静态方法用作实用程序方法,它从不依赖于 实例成员变量。因为静态方法只与类相关联, 它无法访问其类的实例成员变量值。

静态变量仅在 Apex 事务范围内是静态的。 它不是整个服务器或整个组织的静态。a 的值 静态变量在单个事务的上下文中保留并重置 跨越事务边界。例如,如果 Apex DML 请求导致触发器 若要多次触发,静态变量会在这些触发器中保留 调用。

若要存储跨类实例共享的信息,请使用静态 变量。同一类的所有实例共享静态的单个副本 变量。例如,单个事务生成的所有触发器都可以通信 通过查看和更新相关类中的静态变量来相互关联。一个 递归触发器可以使用类变量的值来确定何时退出 递归。

假设您有以下类。

public class P { 
   public static boolean firstRun = true; 
}

然后,使用此类的触发器可能会选择性地使 触发。

trigger T1 on Account (before delete, after delete, after undelete) { 
       if(Trigger.isBefore){
          if(Trigger.isDelete){
             if(p.firstRun){
                 Trigger.old[0].addError('Before Account Delete Error');
                  p.firstRun=false;
              } 
           }
        }
}

触发器中定义的静态变量不会在不同 同一事务中的触发上下文,例如在插入之前和 插入调用后。相反,在类中定义静态变量,以便 触发器可以访问这些类成员变量并检查其静态变量 值。

无法通过类的实例访问类静态变量。如果类有一个静态变量,并且是 的实例,则不是合法的表达式。MyClassmyStaticVariablemyClassInstanceMyClassmyClassInstance.myStaticVariable

实例方法也是如此。如果是静态方法,则不合法。相反,请参考那些 使用类的静态标识符: 和 .myStaticMethod()myClassInstance.myStaticMethod()MyClass.myStaticVariableMyClass.myStaticMethod()局部变量名在类名之前计算。如果局部变量具有 与类同名,局部变量隐藏了 同名。例如,如果注释掉该行,则此方法有效。但是,如果包含该行,则该方法不会 compile,因为 Salesforce 报告该方法不存在或具有 不對 签名。

StringString

public static void method() {
String Database = '';
Database.insert(new Account());
}

内部类的行为类似于静态 Java 内部类, 但不需要关键字。 内部类可以像外部类一样具有实例成员变量,但 没有指向外部类实例的隐式指针(使用 关键字)。staticthis

注意

在 API 版本 20.0 及更早版本中,如果批量 API 请求导致触发器触发, 触发器要处理的 200 条记录的每个块被拆分为 100 条记录的块 记录。在 Salesforce API 版本 21.0 及更高版本中,不再拆分 API 区块 发生。如果批量 API 请求导致触发器多次触发 200 条记录,调控器限制在这些触发器调用之间重置 相同的 HTTP 请求。

使用实例方法和变量

实例方法和成员变量由类的实例使用,即 一个对象。实例成员变量在类中声明,但不在类中声明 方法。实例方法通常使用实例成员变量来影响 方法的行为。

假设您希望有一个收集二维点和绘图的类 它们在图表上。下面的框架类使用成员变量来保存列表 的点和一个内部类来管理二维点列表。

public class Plotter {

    // This inner class manages the points
    class Point {
        Double x;
        Double y;

        Point(Double x, Double y) {
             this.x = x;
             this.y = y;
        }
        Double getXCoordinate() {
             return x;
        }

        Double getYCoordinate() {
             return y;
        }
    }

    List<Point> points = new List<Point>();

    public void plot(Double x, Double y) {
        points.add(new Point(x, y));
    }
    
    // The following method takes the list of points and does something with them
    public void render() {
    }
}

使用初始化代码

实例初始化代码是定义以下形式的代码块 在课堂上。

{ 

   //code body

}

每次对象执行时,都会执行类中的实例初始化代码 从该类实例化。这些代码块在构造函数之前运行。

如果不想为类编写自己的构造函数,可以使用实例 初始化代码块,用于初始化实例变量。在简单的情况下, 使用普通的初始值设定项。为复杂情况保留初始化代码, 例如初始化静态映射。静态初始化块只运行一次, 不管您访问包含它的类多少次。

静态初始化代码是以关键字 开头的代码块。static

static {

   //code body

}

与其他静态代码类似,静态初始化代码块仅初始化 第一次使用类时一次。

一个类可以具有任意数量的静态或实例初始化代码块。 它们可以出现在代码正文中的任何位置。代码块按以下顺序执行 它们出现在文件中,就像它们在 Java 中一样。

您可以使用静态初始化代码来初始化静态最终变量,并 声明静态信息,例如值映射。例如:

public class MyClass {
 
    class RGB {

        Integer red;
        Integer green;
        Integer blue;

        RGB(Integer red, Integer green, Integer blue) {
            this.red = red;
            this.green = green;
            this.blue = blue;
        }
     }

   static Map<String, RGB> colorMap = new Map<String, RGB>();

    static {
        colorMap.put('red', new RGB(255, 0, 0));
        colorMap.put('cyan', new RGB(0, 255, 255));
        colorMap.put('magenta', new RGB(255, 0, 255));
    }
}

版本化行为更改

在 API 版本 50.0 及更高版本中,范围和可访问性规则在 Apex 上强制执行 用 注释的变量、方法、内部类和接口。对于可访问性 注意事项,请参阅 NamespaceAccessible 注释。有关基于命名空间的可见性的详细信息,请参阅基于命名空间的可见性 适用于第二代软件包中的 Apex 类。@namespaceAccessible

Apex 属性

Apex 属性类似于变量; 但是,您可以在代码中对属性值执行其他操作,然后再将其 访问或返回。属性可用于在更改之前验证数据 made,用于在数据发生更改时提示操作(例如更改其他值 成员变量),或公开从其他源检索到的数据(例如 作为另一个类)。属性定义包括一个或两个代码块,表示 get 访问器和 SET 访问器

  • get 访问器中的代码在读取属性时执行。
  • 当为属性分配新值时,将执行 set 访问器中的代码。

如果属性只有 get 访问器,则将其视为只读。如果属性只有 set 访问器,则将其视为只写。考虑具有两个访问器的属性 读写。

若要声明属性,请在类的主体中使用以下语法:

Public class BasicClass {

   // Property declaration
   access_modifier return_type property_name {
      get {
         //Get accessor code block
      }
      set {
         //Set accessor code block
      }
   } 
}

哪里:

  • access_modifier是属性的访问修饰符。这 可应用于属性的访问修饰符包括:、、 和 。此外,这些定义 可以应用修饰符:和 。有关访问的更多信息 修饰符,请参阅 Access 修饰符。publicprivateglobalprotectedstatictransient
  • return_type是属性的类型,例如 Integer、Double、 sObject 等。有关详细信息,请参阅数据类型。
  • property_name是属性的名称

例如,下面的类定义了一个名为 的属性。该物业是公共的。该属性返回整数数据类型。prop

public class BasicProperty {
   public integer prop {
      get { return prop; }
      set { prop = value; }
   }
}

下面的代码段调用 BasicProperty 类,执行 get 和 set 访问:

BasicProperty bp = new BasicProperty();
bp.prop = 5;                   // Calls set accessor
System.assertEquals(5, bp.prop);   // Calls get accessor

请注意以下事项:

  • get 访问器的主体类似于方法的主体。它必须返回值 属性类型。执行 get 访问器与读取 变量。
  • get 访问器必须以 return 语句结尾。
  • 建议 get 访问器不要更改其对象的状态 定义上。
  • set 访问器类似于返回类型为 void 的方法。
  • 为属性赋值时,将使用参数调用 set 访问器 这提供了新的价值。
  • 在 API 版本 42.0 及更高版本中,除非在 set 访问器中设置了变量值,否则 无法在 GET 访问器中更新其值。
  • 调用 set 访问器时,系统会将隐式参数传递给 setter 调用的数据类型与 财产。value
  • 不能在接口上定义属性。
  • Apex 属性基于 C# 中的对应属性,但存在以下差异:
    • 属性直接为值提供存储。您无需创建 支持用于存储值的成员。
    • 可以在 Apex 中创建自动属性。有关详细信息,请参阅使用自动属性。

使用自动属性

属性不需要在其 get 或 set 访问器代码块中使用其他代码。 相反,您可以将 get 和 set 访问器代码块留空以定义自动属性。自动属性允许您编写更多内容 紧凑的代码,更易于调试和维护。它们可以声明为 只读、读写或只写。以下示例创建三个自动 性能:

public class AutomaticProperty {
   public integer MyReadOnlyProp { get; }
   public double MyReadWriteProp { get; set; }
   public string MyWriteOnlyProp { set; }
}

以下代码段练习这些属性:

AutomaticProperty ap = new AutomaticProperty();
ap.MyReadOnlyProp = 5;                 // This produces a compile error: not writable
ap.MyReadWriteProp = 5;                // No error
System.assertEquals(5, ap.MyWriteOnlyProp);   // This produces a compile error: not readable

使用静态属性

当属性声明为 时, 属性的访问器方法在静态上下文中执行。因此,访问器不会 有权访问类中定义的非静态成员变量。以下 示例创建一个同时具有静态属性和实例属性的类:static

public class StaticProperty {
   private static integer StaticMember;
   private integer NonStaticMember;

   // The following produces a system error
   // public static integer MyBadStaticProp { return NonStaticMember; }

   public static integer MyGoodStaticProp { 
     get {return StaticMember;} 
     set { StaticMember = value; } 
   }  
   public integer MyGoodNonStaticProp { 
     get {return NonStaticMember;} 
     set { NonStaticMember = value; } 
   } 
}

以下代码段调用静态属性和实例属性:

StaticProperty sp = new StaticProperty();
// The following produces a system error: a static variable cannot be
// accessed through an object instance
// sp.MyGoodStaticProp = 5;

// The following does not produce an error
StaticProperty.MyGoodStaticProp = 5;

在属性上使用访问修饰符 访问

可以使用自己的访问修饰符定义属性访问器。如果访问器 包含它自己的访问修饰符,这个修饰符覆盖了 财产。单个访问器的访问修饰符必须更具限制性 而不是属性本身的访问修饰符。例如,如果属性具有 被定义为 ,个人 访问器不能定义为 。这 以下类定义显示了其他示例:publicglobal

global virtual class PropertyVisibility {
   // X is private for read and public for write
   public integer X { private get; set; }
   // Y can be globally read but only written within a class
   global integer Y { get; public set; }
   // Z can be read within the class but only subclasses can set it
   public integer Z { get; protected set; }
}

扩展类

您可以扩展类以提供更专业的行为。

扩展另一个类的类继承了 扩展类。此外,扩展类可以覆盖现有的虚拟类 方法,方法是在方法定义中使用 override 关键字。覆盖虚拟 方法允许您为现有方法提供不同的实现。这 表示特定方法的行为因对象而异 你正在召唤它。这称为多态性。

一个类使用类定义中的关键字扩展另一个类。一个类只能扩展另一个类,但它可以 实现多个接口。extends

此示例演示类如何 扩展类。运行继承 示例 在本节中,首先创建类。YellowMarkerMarkerMarker

public virtual class Marker {
    public virtual void write() {
        System.debug('Writing some text.');
    }

    public virtual Double discount() {
        return .05;
    }
}

然后创建类,该类扩展 类。YellowMarkerMarker

// Extension for the Marker class
public class YellowMarker extends Marker {
    public override void write() {
        System.debug('Writing some text using the yellow marker.');
    } 
}

此代码段显示多态性。该示例声明两个相同类型的对象 ().即使两个对象都是 标记,则将第二个对象分配给类的实例。因此,在其上调用该方法会产生与 在第一个对象上调用此方法,因为此方法已被重写。 但是,您可以在 第二个对象,即使此方法不是类定义的一部分。但它是扩展类的一部分,并且 因此,可用于扩展类 .在 Execute Anonymous 窗口中运行此代码段 开发 人员 安慰。

MarkerYellowMarkerwritediscountYellowMarkerYellowMarker

Marker obj1, obj2;
obj1 = new Marker();
// This outputs 'Writing some text.'
obj1.write();

obj2 = new YellowMarker();
// This outputs 'Writing some text using the yellow marker.'
obj2.write();
// We get the discount method for free
// and can call it from the YellowMarker instance.
Double d = obj2.discount();

扩展类可以具有更多与原始类不常见的方法定义 扩展类。在此示例中,该类扩展了该类,并具有一个额外的类 方法,不适用于 类。要调用额外的方法,请 对象类型必须是扩展类。RedMarkerMarkercomputePriceMarker

在运行下一个代码段之前,请创建该类,这需要组织中的类。RedMarkerMarker

// Extension for the Marker class
public class RedMarker extends Marker {
    public override void write() {
        System.debug('Writing some text in red.');
    } 

    // Method only in this class
    public Double computePrice() {
        return 1.5;
    }
}

此代码片段演示如何对类调用其他方法。在“执行”中运行此代码片段 开发者控制台的匿名窗口。RedMarker

RedMarker obj = new RedMarker();
// Call method specific to RedMarker only
Double price = obj.computePrice();

扩展也适用于接口 – 一个接口可以扩展另一个接口。如 使用类,当一个接口扩展另一个接口时,所有方法和 扩展接口的属性可用于扩展接口。

版本化行为更改

在 API 版本 50.0 及更高版本中,范围和可访问性规则在 Apex 上强制执行 用 注释的变量、方法、内部类和接口。对于可访问性 注意事项,请参阅 NamespaceAccessible 注释。有关基于命名空间的可见性的详细信息,请参阅基于命名空间的可见性 适用于第二代软件包中的 Apex 类。@namespaceAccessible

扩展类示例

下面是一个类的扩展示例,显示了所有 Apex 类的功能。示例中介绍的关键字和概念是 在本章中进行了更详细的解释。

// Top-level (outer) class must be public or global (usually public unless they contain
// a Web Service, then they must be global)
public class OuterClass {

  // Static final variable (constant) – outer class level only
  private static final Integer MY_INT;

  // Non-final static variable - use this to communicate state across triggers
  // within a single request)
  public static String sharedState;
  
  // Static method - outer class level only
  public static Integer getInt() { return MY_INT; }

  // Static initialization (can be included where the variable is defined)
  static {
    MY_INT = 2; 
  }

  // Member variable for outer class
  private final String m;

  // Instance initialization block - can be done where the variable is declared, 
  // or in a constructor
  {
    m = 'a';  
  }
  
  // Because no constructor is explicitly defined in this outer class, an implicit, 
  // no-argument, public constructor exists

  // Inner interface
  public virtual interface MyInterface { 

    // No access modifier is necessary for interface methods - these are always 
    // public or global depending on the interface visibility
    void myMethod(); 
  }

  // Interface extension
  interface MySecondInterface extends MyInterface { 
    Integer method2(Integer i); 
  }

  // Inner class - because it is virtual it can be extended.
  // This class implements an interface that, in turn, extends another interface.
  // Consequently the class must implement all methods.
  public virtual class InnerClass implements MySecondInterface {

    // Inner member variables
    private final String s;
    private final String s2;

    // Inner instance initialization block (this code could be located above)
    {
       this.s = 'x';
    }

    // Inline initialization (happens after the block above executes)
    private final Integer i = s.length();
 
    // Explicit no argument constructor
    InnerClass() {
       // This invokes another constructor that is defined later
       this('none');
    }

    // Constructor that assigns a final variable value
    public InnerClass(String s2) { 
      this.s2 = s2; 
    }

    // Instance method that implements a method from MyInterface.
    // Because it is declared virtual it can be overridden by a subclass.
    public virtual void myMethod() { /* does nothing */ }

    // Implementation of the second interface method above.
    // This method references member variables (with and without the "this" prefix)
    public Integer method2(Integer i) { return this.i + s.length(); }
  }

  // Abstract class (that subclasses the class above). No constructor is needed since
  // parent class has a no-argument constructor
  public abstract class AbstractChildClass extends InnerClass {

    // Override the parent class method with this signature.
    // Must use the override keyword
    public override void myMethod() { /* do something else */ }

    // Same name as parent class method, but different signature.
    // This is a different method (displaying polymorphism) so it does not need
    // to use the override keyword
    protected void method2() {}

    // Abstract method - subclasses of this class must implement this method
    abstract Integer abstractMethod();
  }

  // Complete the abstract class by implementing its abstract method
  public class ConcreteChildClass extends AbstractChildClass {
    // Here we expand the visibility of the parent method - note that visibility 
    // cannot be restricted by a sub-class
    public override Integer abstractMethod() { return 5; }
  }

  // A second sub-class of the original InnerClass
  public class AnotherChildClass extends InnerClass {
    AnotherChildClass(String s) {
      // Explicitly invoke a different super constructor than one with no arguments
      super(s);
    }
  }
  
  // Exception inner class
  public virtual class MyException extends Exception {
    // Exception class member variable
    public Double d;

    // Exception class constructor    
    MyException(Double d) {
      this.d = d;
    }
    
    // Exception class method, marked as protected
    protected void doIt() {}
  }
  
  // Exception classes can be abstract and implement interfaces
  public abstract class MySecondException extends Exception implements MyInterface {
  }
}

此代码示例演示:

  • 顶级类定义(也称为外部类)
  • 顶级类中的静态变量和静态方法,作为 以及静态初始化代码块
  • 顶级类的成员变量和方法
  • 没有用户定义构造函数的类 — 这些类具有 隐式、无参数构造函数
  • 顶级类中的接口定义
  • 扩展另一个接口的接口
  • 顶级类中的内部类定义(一级深度)
  • 实现接口(因此,其关联的接口)的类 子接口)来实现方法签名的公共版本
  • 内部类构造函数定义和调用
  • 内部类成员变量以及使用关键字对它的引用(不带参数)this
  • 一个内部类构造函数,它使用关键字(带参数)来 调用其他构造函数this
  • 构造函数之外的初始化代码 — 其中 定义变量,以及大括号中的匿名块 ().请注意,这些执行 每个构造都按照它们在文件中出现的顺序出现,如 爪哇岛。{}
  • 类扩展和抽象类
  • 重写基类方法(必须声明)的方法virtual)
  • 关键字 对于重写子类方法的方法override
  • 抽象方法及其通过具体子类的实现
  • 交通 修饰语protected
  • 作为具有成员、方法和构造函数的第一类对象的异常

此示例演示其他 Apex 代码如何调用上述类:

// Construct an instance of an inner concrete class, with a user-defined constructor
OuterClass.InnerClass ic = new OuterClass.InnerClass('x');

// Call user-defined methods in the class
System.assertEquals(2, ic.method2(1));

// Define a variable with an interface data type, and assign it a value that is of 
// a type that implements that interface
OuterClass.MyInterface mi = ic;

// Use instanceof and casting as usual
OuterClass.InnerClass ic2 = mi instanceof OuterClass.InnerClass ? 
                            (OuterClass.InnerClass)mi : null;
System.assert(ic2 != null);

// Construct the outer type
OuterClass o = new OuterClass();
System.assertEquals(2, OuterClass.getInt());

// Construct instances of abstract class children
System.assertEquals(5, new OuterClass.ConcreteChildClass().abstractMethod());

// Illegal - cannot construct an abstract class
// new OuterClass.AbstractChildClass();

// Illegal – cannot access a static method through an instance
// o.getInt();

// Illegal - cannot call protected method externally
// new OuterClass.ConcreteChildClass().method2();

此代码示例演示:

  • 外层阶级的构造
  • 内部类的构造和内部类的声明 接口类型
  • 可以为声明为接口类型的变量分配一个实例 实现该接口的类
  • 将接口变量转换为实现 该接口(使用运算符验证后)instanceof

ref

Apex控制流语句

Apex 提供了 if-else 语句、switch 语句和循环来控制 代码执行。语句通常按照它们出现的顺序逐行执行。 使用控制流语句,可以根据某个条件使 Apex 代码执行, 或者让代码块重复执行。

  • 条件 (If-else) 语句 Apex 中的条件语句
    的工作方式与 Java 类似。
  • Switch 语句 Apex 提供了一个语句
    ,用于测试表达式是否与多个值之一匹配,并相应地分支。switch
  • Loops
    Apex 支持五种类型的过程循环。

条件 (If-else) 语句

Apex 中的条件语句的工作方式与 Java 类似。

if ([Boolean_condition]) 
    // Statement 1
else
    // Statement 2

该部分始终是可选的,并且始终是可选的 具有最接近 .为 例:

elseif

Integer x, sign;
// Your code
if (x <= 0) if (x == 0) sign = 0; else sign = -1;

是 等效 自:

Integer x, sign;
// Your code
if (x <= 0) {
    if (x == 0) {
           sign = 0; 
    } else  {
           sign = -1;
    }
}

重复语句也是允许的。 为 例:

else if

if (place == 1) {
    medal_color = 'gold';
} else if (place == 2) {
    medal_color = 'silver';
} else if (place == 3) {
    medal_color = 'bronze';
} else {
    medal_color = null;
}

Switch 语句

Apex 提供了一个测试 表达式是否与多个值之一匹配,并相应地进行分支。

switch

语法为:

switch on expression {
    when value1 {		// when block 1
        // code block 1
    }	
    when value2 {		// when block 2
        // code block 2
    }
    when value3 {		// when block 3
        // code block 3
    }
    when else {		  // default block, optional
        // code block 4
    }
}

该值可以是单个值,也可以是多个值 值或 sObject 类型。例如:when

when value1 {
}
when value2, value3 {
}
when TypeName VariableName {
}

该语句计算表达式和 执行匹配值的代码块。如果没有匹配的值,则代码 块被执行。如果没有阻止,则不执行任何操作。switchwhenwhen elsewhen else

注意

没有落差。执行代码块后,语句将退出。switchApex 语句表达式可以是以下表达式之一 以下类型。

switch

  • 整数
  • s对象
  • 字符串
  • 枚举

当阻止

每个块都有一个值,即 expression 是匹配的。这些值可以采用以下形式之一。when

  • when {} (a when 块可以有 多个逗号分隔的文字子句)literal
  • 当 SObjectTypeidentifier {}
  • 什么时候enum_value {}

该价值对所有人来说都是合法的价值 类型。null

每个值必须是唯一的。例如 您只能在一个块子句中使用文本。一个块最多匹配一次。whenxwhenwhen

何时 Else 阻止

如果没有值与表达式匹配,则执行该块。whenwhen else

注意

Salesforce 建议包含块,尤其是枚举类型,尽管这不是必需的。 使用枚举生成语句时 托管包提供的值,则代码可能无法按预期方式运行,如果 新版本的包包含其他枚举值。您可以防止这种情况发生 问题,包括一个块 处理意外值。when elseswitchwhen else

如果包含块,则它必须是 语句中的最后一个块。when elseswitch

文本示例

您可以使用文本值进行切换 在 Integer、Long 和 String 类型上。字符串子句区分大小写。例如 “orange”与“ORANGE”的值不同。when

单值示例

下面的示例对值使用整数文本。when

switch on i {
   when 2 {
       System.debug('when block 2');
   }
   when -3 {
       System.debug('when block -3');
   }
   when else {
       System.debug('default');
   }
}

Null 值示例

由于 Apex 中的所有类型都可以为 null,因此值可以为 .whennull

switch on i {
   when 2 {
       System.debug('when block 2');
   }
   when null {
       System.debug('bad integer');
   }
   when else {
       System.debug('default ' + i);
   }
}

多值示例

Apex 语句没有 回退,但子句可以包括 要匹配的多个文本值。您还可以嵌套 Apex 语句以提供多个执行 子句中的路径。switchwhenswitchwhen

switch on i {
   when 2, 3, 4 {
       System.debug('when block 2 and 3 and 4');
   }
   when 5, 6 {
       System.debug('when block 5 and 6');
   }
   when 7 {
       System.debug('when block 7');
   }
   when else {
       System.debug('default');
   }
}

方法示例

以下示例不是打开变量表达式,而是打开 方法调用的结果。

switch on someInteger(i) {
   when 2 {
       System.debug('when block 2');
   }
   when 3 {
       System.debug('when block 3');
   }
   when else {
       System.debug('default');
   }
}

sObjects 示例

通过打开 sObject 值,可以隐式执行检查和强制转换。例如 请考虑以下使用 if-else 语句的代码。instanceof

if (sobject instanceof Account) {
    Account a = (Account) sobject;
    System.debug('account ' + a);
} else if (sobject instanceof Contact) {
    Contact c = (Contact) sobject;
    System.debug('contact ' + c);
} else {
    System.debug('default');
}

您可以使用以下语句替换和简化此代码。switch

switch on sobject {
   when Account a {
       System.debug('account ' + a);
   }
   when Contact c {
       System.debug('contact ' + c);
   }
   when null {
       System.debug('null');
   }
   when else {
       System.debug('default');
   }
}

注意

每个块只能使用一种 sObject 类型。when

枚举示例

使用枚举值的语句不需要块,但建议这样做。您可以 每个块使用多个枚举值 第。switchwhenwhen elsewhen

switch on season {
   when WINTER {
       System.debug('boots');
   }
   when SPRING, SUMMER {
       System.debug('sandals');
   }
   when else {
       System.debug('none of the above');
   }
}

循环

Apex 支持五种类型的过程循环。支持以下类型的过程循环:

  • do {statement} while (Boolean_condition);
  • while (Boolean_conditionstatement;
  • for (initializationBoolean_exit_conditionincrementstatement;
  • for (variable : array_or_setstatement;
  • for (variable : [inline_soql_query]) statement;

所有环路都允许环路控制结构:

  • break;退出整个循环
  • continue;跳到下一个迭代 的循环
  1. Do-While 循环
  2. While 循环
  3. For 循环

Do-While 循环

Apex 循环重复执行 代码块,只要特定的布尔条件保持为真。其语法 是:

do-while

do {
   code_block
} while (condition);

注意

大括号 () 是 始终需要围绕 .{} code_block

与 Java 一样,Apex 循环没有 检查布尔条件语句,直到执行第一个循环之后。 因此,代码块始终至少运行一次。do-while例如,以下代码将数字 1 – 10 输出到调试中 日志:

Integer count = 1;

do {
    System.debug(count);
    count++;
} while (count < 11);

While 循环

Apex 循环重复执行一个块 只要特定的布尔条件保持为真。其语法 是:

while

while (condition) {
    code_block
}

注意

仅当块包含多个大括号 () 时,才需要在 a 周围加上大括号 () 陈述。{} code_block

与 不同的是,循环检查布尔条件语句 在执行第一个循环之前。因此,代码块可以 从不执行。do-whilewhile例如,以下代码将数字 1 – 10 输出到调试中 日志:

Integer count = 1;

while (count < 11) {
    System.debug(count);
    count++;
}

For 循环

Apex 支持循环的三种变体:

for

  • 传统循环:forfor (init_stmt; exit_condition; increment_stmt) { code_block }
  • 列表或设置迭代循环:forfor (variable : list_or_set) { code_block }其中必须是相同的基元或 sObject 类型为 。 variable list_or_set
  • SOQL 循环:forfor (variable : [soql_query]) { code_block }for (variable_list : [soql_query]) { code_block }两者必须属于相同的 sObject 类型 返回的 . variable variable_list soql_query

注意

仅当块包含多个大括号 () 时,才需要在 a 周围加上大括号 () 陈述。{} code_block

以下各节将进一步讨论每种方法。

  • 传统 For 循环
  • 列出或设置循环的迭代
  • 迭代集合

传统 For 循环

Apex 中的传统循环对应于 Java 和其他语言中使用的传统语法。其语法 是:

for

for (init_stmt; exit_condition; increment_stmt) {
    code_block
}

执行此类循环时,Apex 运行时引擎按顺序执行以下步骤:

for

  1. 执行循环的组件。请注意, 可以在此语句中声明和/或初始化多个变量。 init_stmt
  2. 执行检查。如果为 true,则循环 继续。如果为 false,则循环退出。 exit_condition
  3. 执行 . code_block
  4. 执行语句。 increment_stmt
  5. 返回到步骤 2。

例如,以下代码将数字 1 – 10 输出到调试日志中。注意 一个额外的初始化变量, 包含以演示语法:

j

for (Integer i = 0, j = 0; i < 10; i++) {
    System.debug(i+1);
}

列出或设置循环的迭代

列表或设置迭代循环循环 列表或集合中的所有元素。其语法 是:

for

for (variable : list_or_set) {
    code_block
}

其中必须具有相同的基元或 sObject 类型 如。

 variable list_or_set

执行此类循环时,Apex 运行时引擎将 分配给 中的每个元素,并运行 for each 值。for variable list_or_set code_block例如,以下代码将数字 1 – 10 输出到调试日志:

Integer[] myInts = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

for (Integer i : myInts) {
    System.debug(i);
}

迭代集合

集合可以由列表、集合或地图组成。修改集合的 元素,而不支持循环访问该集合 并导致错误。请勿在以下情况下直接添加或删除元素 循环访问包含它们的集合。

在迭代期间添加元素

要添加 元素,在迭代列表、集合或映射时,保留新元素 在临时列表中,设置或映射并将它们添加到原始列表之后 完成循环访问。

在迭代期间删除元素

删除 元素,创建一个新列表,然后复制 您希望保留的元素。或者,添加您想要的元素 删除到临时列表并在完成迭代后将其删除 集合。

注意

该方法以线性方式执行。用它来删除元素有时间和 资源影响。List.remove

在迭代时删除元素 地图或集合,将要删除的键保存在临时列表中, 然后在完成循环访问集合后删除它们。

参考

Apex表达式和运算符

表达式是由变量、运算符和方法调用组成的构造,这些变量、运算符和方法调用 计算结果为单个值。

  • 表达式
    是由变量、运算符和方法调用组成的构造,其计算结果为单个值。
  • 表达式运算符 表达式可以使用运算符
    相互连接以创建复合表达式。
  • 安全导航运算符 使用安全导航运算符
    () 替换对 null 引用的显式顺序检查。此运算符使尝试对 null 值进行操作的表达式短路,并返回 null 而不是引发 NullPointerException。?.
  • 运算符优先级
    运算符根据规则按顺序进行解释。
  • 注释
    Apex 代码支持单行和多行注释。

表达式

表达式是由变量、运算符和方法调用组成的构造 计算结果为单个值。

在 Apex 中,表达式始终是以下类型之一:

  • 文字表达式。例如:1 + 1
  • 新的 sObject、Apex 对象、列表、集或映射。为 例:new Account(<field_initializers>) new Integer[<n>] new Account[]{<elements>} new List<Account>() new Set<String>{} new Map<String, Integer>() new myRenamingClass(string oldName, string newName)
  • 任何可以充当赋值运算符左侧的值(L 值), 包括变量、一维列表位置以及大多数 sObject 或 Apex 对象 字段引用。例如:Integer i myList[3] myContact.name myRenamingClass.oldName
  • 任何不是 L 值的 sObject 字段引用,包括:
    • 列表中 sObject 的 ID(请参阅列表)
    • 与 sObject 关联的一组子记录(例如,一组 与特定帐户关联的联系人)。这种类型的表达式 生成查询结果,与 SOQL 和 SOSL 查询非常相似。
  • 用方括号括起来的 SOQL 或 SOSL 查询,允许动态 在 Apex 中评估。为 例:Account[] aa = [SELECT Id, Name FROM Account WHERE Name ='Acme']; Integer i = [SELECT COUNT() FROM Contact WHERE LastName ='Weissman']; List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead];为 信息,请参阅 SOQL 和 SOSL 查询。
  • 静态方法或实例方法调用。为 例:System.assert(true) myRenamingClass.replaceNames() changePoint(new Point(x, y));

表达式运算符

表达式可以使用运算符相互连接以创建复合 表达 式。

Apex 支持以下运算符:

算子 语法 描述
= x = y 赋值运算符(右关联)。将 的值分配给 L 值。的数据类型必须与 的数据类型匹配,并且不能是 。yxxynull
+= x += y 加法赋值运算符(右关联)。添加 到原始值的值 的值,然后重新赋值 的新值。有关其他信息,请参阅。 而且不可能.yxx+xynull
*= x *= y 乘法赋值运算符(右关联)。 将 with 的值相乘 和 的原始值 然后将新值重新分配给 。yxx注意x并且必须是整数或 双打或组合。yx而且不可能.ynull
-= x -= y 减法赋值运算符(右关联)。减去 的值从 和 的原始值 将新值重新分配给 。yxx注意x并且必须是整数或双精度 或两者兼而有之。yx而且不可能.ynull
/= x /= y 除法赋值运算符(右关联)。除以 的原始值与 的值,然后重新赋值 的新值。xyx注意x并且必须是整数或双精度 或两者兼而有之。yx而且不可能.ynull
|= x |= y OR 赋值运算符(右关联)。如果 、 布尔值和 布尔值均为 false,则保持 false。否则,将赋值为 真。 而且不可能.xyxxxynull
&= x &= y AND 赋值运算符(右关联)。如果 、 布尔值和 布尔值都为 true,则保持为 true。否则,将赋值为 假。 而且不可能.xyxxxynull
<<= x <<= y 按位左移赋值运算符。将每个位向左移动一位,以便高阶位 丢失,新的右位设置为 0。此值为 重新分配给 。xyx
>>= x >>= y 按位右移有符号赋值运算符。每个班次 逐位向右插入,以便 低阶位丢失,新的左位设置为 0 表示正数 的值为 和 1 为负数 的值。此值为 重新分配给 。xyyyx
>>>= x >>>= y 按位右移无符号赋值运算符。每个班次 逐位向右插入,以便 低阶位将丢失,新的左位将全部设置为 0 的值。此值为 重新分配给 。xyyx
? : x ? y : z 三元运算符(右关联)。此运算符充当 if-then-else 语句的简写。如果 ,布尔值为 true,则为结果。否则就是结果。xyz注意x不可能.null
&& x && y AND 逻辑运算符(左关联)。如果 、 布尔值和 布尔值均为 true,则 表达式的计算结果为 true。否则,表达式的计算结果为 假。xy注意:&&有 优先于||该算子表现出短路行为,这 手段是 仅当为 true 时才计算。yxx而且不可能.ynull
|| x || y OR 逻辑运算符(左关联)。如果 、布尔值和布尔值均为 false,则 表达式的计算结果为 false。否则,表达式的计算结果为 真。xy注意:&&有 优先于||该算子表现出短路行为,这 手段是 仅当为 false 时才计算。yxx而且不可能.ynull
== x == y 相等运算符。如果 的值等于 的值,则表达式的计算结果为 真。否则,表达式的计算结果为 false。xy注意与 Java 不同,在 Apex 比较对象值相等性而不是引用相等性, 用户定义类型除外。因此:==对于 sObjects 和 sObject 数组,执行深度 在返回其之前检查所有 sObject 字段值 结果。 同样 用于集合和内置 Apex 对象。==对于记录,每个字段必须具有相同的值才能进行评估 到真。==x或者可以是字面上的。ynull任何两个值的比较都不能产生 。nullSOQL 和 SOSL 用于它们的相等运算符,而不是 .虽然 Apex 和 SOQL 和 SOSL 是紧密相连的,这种不幸的语法 之所以存在差异,是因为大多数现代语言都用于赋值和 平等。Apex 的设计者认为它更有价值 保持这种范式,而不是强迫开发人员学习 新的赋值运算符。因此,Apex 开发人员必须 用于平等 在 Apex 代码的主体中进行测试,并在 SOQL 中进行相等性测试 和 SOSL 查询。=========使用 is 进行字符串比较 不区分大小写,并根据 上下文用户的区域设置==ID 比较使用区分大小写,不区分大小写 区分 15 个字符和 18 个字符 格式==用户定义的类型通过引用进行比较,其中 表示两个对象只有在它们 引用内存中的相同位置。您可以 通过以下方式覆盖此默认比较行为 提供和方法 来比较对象值。equalshashCode
=== x === y 完全相等运算符。If 和引用 表达式在内存中计算结果为 true 的位置完全相同。 否则,表达式的计算结果为 false。xy
< x < y 小于操作员。如果小于 , 表达式的计算结果为 true。否则,表达式的计算结果为 假。xy注意与其他数据库存储过程不同,Apex 没有 支持三态布尔逻辑和任何两个值的比较都不能产生 。null如果 或 等于 并且是整数, Doubles、Dates 或 Datetimes,则表达式为 false。xynull非字符串或 ID 值始终较大 比一个值。nullnull如果 和 是 ID, 它们必须引用相同类型的对象。否则 运行时错误结果。xy如果 or 是 ID 和 other value 是 String,则验证 String 值 并被视为 ID。xyx而且不可能 布尔 值。y两个字符串的比较是根据 上下文用户的区域设置,并且不区分大小写。
> x > y 大于运算符。如果大于 ,则表达式的计算结果为 true。否则, 表达式的计算结果为 false。xy注意任何两个值的比较都不能产生 。null如果 或 等于 并且是整数, Doubles、Dates 或 Datetimes,则表达式为 false。xynull非字符串 或 ID 值始终大于某个值。nullnull如果 和 是 ID,则它们必须 引用相同类型的对象。否则为运行时错误 结果。xy如果 or 是 ID 和 other value 是 String,对 String 值进行验证,并且 视为 ID。xyx而且不可能 布尔 值。y两个字符串的比较是根据 上下文用户的区域设置,并且不区分大小写。
<= x <= y 小于或等于运算符。如果小于或等于 ,则表达式的计算结果为 真。否则,表达式的计算结果为 false。xyNoteThe comparison of any two values can never result in .nullIf or equal and are Integers, Doubles, Dates, or Datetimes, the expression is false.xynullA non- String or ID value is always greater than a value.nullnullIf and are IDs, they must reference the same type of object. Otherwise a runtime error results.xy如果 or 是 ID 和 other value 是 String,对 String 值进行验证,并且 视为 ID。xyx而且不可能 布尔 值。y两个字符串的比较是根据 上下文用户的区域设置,并且不区分大小写。
>= x >= y 大于或等于运算符。如果大于或等于 ,则表达式的计算结果 到真。否则,表达式的计算结果为 false。xy注意任何两个值的比较都不能产生 。null如果 或 等于 并且是整数, Doubles、Dates 或 Datetimes,则表达式为 false。xynull非字符串 或 ID 值始终大于某个值。nullnull如果 和 是 ID,则它们必须 引用相同类型的对象。否则为运行时错误 结果。xy如果 or 是 ID 和 other value 是 String,对 String 值进行验证,并且 视为 ID。xyx而且不可能 布尔 值。y两个字符串的比较是根据 上下文用户的区域设置,并且不区分大小写。
!= x != y 不等式运算符。如果 的值不等于 的值 ,则表达式的计算结果为 到真。否则,表达式的计算结果为 false。xy注意字符串比较使用不区分大小写!=与 Java 不同,在 Apex 比较对象值相等性而不是引用相等性, 用户定义类型除外。!=对于 sObjects 和 sObject 数组,执行深度 在返回其之前检查所有 sObject 字段值 结果。!=对于记录,如果记录具有不同的值,则计算结果为 true 任何字段。!=用户定义的类型通过引用进行比较,这意味着 只有当两个对象引用时,它们才不同 内存中的不同位置。您可以覆盖此默认值 通过提供 和 方法来比较行为 类来比较对象值。equalshashCodex或者可以是字面上的。ynull任何两个值的比较都不能产生 。null
!== x !== y 精确不等式运算符。如果和不 引用内存中完全相同的位置,表达式的计算结果为 真。否则,表达式的计算结果为 false。xy
+ x + y 加法运算符。根据以下规则将 的值添加到 的值中:xy如果 和 是整数或双精度, 运算符将 的值与 的值相加。如果使用 Double,则结果为 双。xyxy如果是 Date 并且是 Integer, 返回按指定数字递增的新日期 天。xyIf 是 Datetime 并且是 Integer 或 Double,返回一个新的 Date,该日期按指定的 天数,小数部分对应于 一天的一部分。xyIf 是 String 并且是 String 或 任何其他类型的非参数,都连接到 的末尾。xynullyx
x – y 减法运算符。根据以下规则从 的值中减去 的值:yx如果 和 是整数或双精度, 运算符从 的值中减去 的值。如果使用 Double,则 结果是 Double。xyyx如果是 Date 并且是 Integer, 返回一个按指定数字递减的新 Date 天。xyIf 是 Datetime 并且是 Integer 或 Double,返回一个减去指定值的新日期 天数,小数部分对应于 一天的一部分。xy
* x * y 乘法运算符。将 、 整数或 Double 与 、 另一个 Integer 或 相乘 双。如果使用 double,则结果为 double。xy
/ x / y 部门操作员。将一个整数或双精度除以另一个整数或双精度。如果 double,结果是 Double。xy
! !x 逻辑补码运算符。反转布尔值,因此 真变成假,假变成真。
-x 一元否定运算符。将 、 整数或双精度的值乘以 -1。这 正等价物也是 语法上有效,但没有数学效果。x+
++ x++++x 增量运算符。将 1 添加到数值类型的变量 的值上。如果 前缀 (),表达式 增量后的计算结果为 x 的值。如果后缀为 (),则表达式的计算结果为 增量前的 x 值。x++xx++
x––x 递减运算符。从 的值中减去 1,这是一个数值类型的变量。如果 前缀 (),表达式 计算结果为递减后的 x 值。如果后缀为 (),则表达式的计算结果为 递减前的 x 值。x–xx–
& x & y 按位 AND 运算符。AND 将每个位 in 与相应的位 in 一起,使结果位为 如果两个位都设置为 1,则设置为 1。xy
| x | y 按位 OR 运算符。OR将每个位输入到相应的位,以便结果位为 如果至少有一个位设置为 1,则设置为 1。xy
^ x ^ y 按位独占 OR 运算符。每个位的独占 OR 与相应的 位,以便结果位 如果恰好其中一个位设置为 1,而另一个位设置为 1,则设置为 1 设置为 0。xy
^= x ^= y 按位独占 OR 运算符。每个位的独占 OR 与相应的 位,以便结果位 如果恰好其中一个位设置为 1,而另一个位设置为 1,则设置为 1 设置为 0。将独占 OR 运算的结果分配给 。xyx
<< x << y 按位左移运算符。将每个位向左移动一位,以便高阶位 丢失,新的右位设置为 0。xy
>> x >> y 按位右移有符号运算符。将每个位向右移动一位,以便低阶位 丢失,新的左位设置为 0 表示正值,1 表示负值 之。xyyy
>>> x >>> y 按位右移无符号运算符。将每个位向右移动一位,以便低阶位 丢失,并且 的所有值的新左位都设置为 0。xyy
~ ~x 按位 Not 或 Complement 运算符。切换每个二进制数字 的 ,将 0 转换为 1 和 1 更改为 0。布尔值从 to 和 vice 转换 反之亦然。xTrueFalse
() (x) 括号。提升表达式的优先级,以便首先在 复合表达式。x
?. x?.y 安全导航操作员。短路表达式 尝试对 null 值进行操作,并返回 null 而不是抛出 一个 NullPointerException。如果链表达式的左侧 计算结果为 null,则链表达式的右侧不是 评价。

安全导航操作员

使用安全导航运算符 () 替换对 null 引用的显式顺序检查。这 运算符短路尝试对 null 值进行操作并返回的表达式 null 而不是抛出 NullPointerException。?.

重要

在可能的情况下,我们更改了非包容性条款,以符合我们的 平等的公司价值观。我们保留了某些条款,以避免对 客户实施。

如果链表达式的左侧计算结果为 null,则右侧的计算结果为 null 未进行评估。在方法、变量和属性链接中使用安全导航运算符 ()。部分 未计算的表达式可以包括变量引用、方法引用、 或数组表达式。?.

注意

所有 Apex 类型都可以隐式为 null,并且可以保存从 算子。

例子

  • 此示例首先计算 ,并且 如果为 null,则返回 null。否则, 返回值为 。aaa.ba?.b // Evaluates to: a == null ? null : a.b
  • 如果计算结果为 null,则此示例返回 null。如果没有 计算结果为 null 并返回 null,则此表达式将抛出一个 NullPointerException。a[x]a[x]aMethod()a[x]?.aMethod().aField // Evaluates to null if a[x] == null
  • 如果计算结果为 null,则此示例返回 null。a[x].aMethod()a[x].aMethod()?.aField
  • 此示例指示表达式的类型是否相同,无论 在表达式中使用安全导航运算符,或者 不。Integer x = anObject?.anIntegerField; // The expression is of type Integer because the field is of type Integer
  • 此示例演示一个语句,该语句替换了检查 null 值。// Previous code checking for nulls String profileUrl = null; if (user.getProfileUrl() != null) { profileUrl = user.getProfileUrl().toExternalForm(); }// New code using the safe navigation operator String profileUrl = user.getProfileUrl()?.toExternalForm();
  • 此示例演示使用安全导航运算符的单行 SOQL 查询。// Previous code checking for nulls results = [SELECT Name FROM Account WHERE Id = :accId]; if (results.size() == 0) { // Account was deleted return null; } return results[0].Name;// New code using the safe navigation operator return [SELECT Name FROM Account WHERE Id = :accId]?.Name;
允许的用例 更多信息
方法、变量或参数链 aObject?.aMethod(); 可用作顶级语句。
使用括号,例如在强制转换中。 ((T)a1?.b1)?.c1() 运算符将方法链跳过到第一次关闭 括号。通过在括号后添加运算符,代码 保护整个表达式。如果运算符在其他地方使用, 而不是在括号之后,整个强制转换表达式不是 be 保障。例如,行为//Incorrect use of safe navigation operator ((T)a1?.b1).c1()相当于:T ref = null; if (a1 != null) { ref = (T)a1.b1; } result = ref.c1();
SObject 链接 String s = contact.Account?.BillingCity; 当关系为 零。该行为等效于 。String s = contact.Account.BillingCity
SOQL 查询 String s = [SELECT LastName FROM Contact]?.LastName; 如果 SOQL 查询未返回任何对象,则表达式 计算结果为 null。该行为等效于:List<Contact> contacts = [SELECT LastName FROM Contact]; String s; if (contacts.size() == 0) { s = null; // New behavior when using Safe Navigation. Earlier, this would throw an exception. } else if (contacts.size() == 1) { s = contacts.get(0).LastName; } else { // contacts.size() > 1 throw new QueryException(...); }

在某些情况下,您不能使用安全导航运算符。尝试使用 运算符在编译过程中会导致错误:

  • 带点的类型和静态表达式。例如:
    • 命名空间
    • {命名空间}。{类}
    • 触发器.new
    • Flow.interview。{流名称}
    • {类型}.class
  • 静态变量访问、方法调用和表达式。例如:
    • AClass.AStaticMethodCall()
    • AClass.AStaticVariable
    • String.format(‘{0}’, ‘hello world’)
    • Page.{pageName}
  • Assignable expressions. For example:
    • foo?.bar = 42;
    • ++foo?.bar;
  • SOQL 绑定表达式。为 例:class X { public String query = 'xyz';} X x = new X(); List<Account> accounts = [SELECT Name FROM Account WHERE Name = :X?.query] List<List<SObject>> moreAccounts = [FIND :X?.query IN ALL FIELDS RETURNING Account(Name)];
  • 使用 SObject 标量 领域。例如:addError()Contact c; c.LastName?.addError('The field must have a value');注意你 可以将运算符与 SObjects 一起使用,包括查找和主从细节 领域。addError()

运算符优先级

运算符根据规则按顺序解释。

Apex 使用以下运算符优先级规则:

优先 运营商 描述
1 {} () ++ — 分组和前缀递增和递减
2 ~ ! -x +x (type) new 一元运算符、加法运算符、类型转换和对象 创造
3 * / 乘法和除法
4 + – 加法和减法
5 << >> >>> 轮班操作员
6 < <= > >= instanceof 大于和小于比较、参考测试
7 == != 比较:相等和不相等
8 & 按位 AND
9 ^ 按位异或
10 | 按位 OR
11 && 逻辑 AND
12 || 逻辑 OR
13 = += -= *= /= &= <<= >>= >>>=

注释

Apex 代码支持单行和多行注释。

  • 要创建单行注释,请使用 . 解析器将忽略 右侧同一行上的所有字符。为 例:////Integer i = 1; // This comment is ignored by the parser
  • 若要创建多行注释,请使用 和 来分隔开头和结尾 的评论块。为 例:/**/Integer i = 1; /* This comment can wrap over multiple lines without getting interpreted by the parser. */

赋值语句

赋值语句是将值放入 变量。赋值语句通常采用以下两种方式之一 形式:

[LValue] = [new_value_expression];
[LValue] = [[inline_soql_query]];

在上面的表格中,代表任何 可以放置在赋值运算符左侧的表达式。这些包括:

[LValue]

  • 一个简单的变量。为 例:Integer i = 1; Account a = new Account(); Account[] accts = [SELECT Id FROM Account];
  • 取消引用的列表元素。为 例:ints[0] = 1; accts[0].Name = 'Acme';
  • 上下文用户有权编辑的 sObject 字段引用。为 例:Account a = new Account(Name = 'Acme', BillingCity = 'San Francisco'); // IDs cannot be set prior to an insert call // a.Id = '00300000003T2PGAA0'; // Instead, insert the record. The system automatically assigns it an ID. insert a; // Fields also must be writable for the context user // a.CreatedDate = System.today(); This code is invalid because // createdDate is read-only! // Since the account a has been inserted, it is now possible to // create a new contact that is related to it Contact c = new Contact(LastName = 'Roth', Account = a); // Notice that you can write to the account name directly through the contact c.Account.Name = 'salesforce.com';

赋值始终通过引用完成。为 例:

Account a = new Account();
Account b;
Account[] c = new Account[]{};
a.Name = 'Acme';
b = a;         
c.add(a);      

// These asserts should now be true. You can reference the data
// originally allocated to account a through account b and account list c.
System.assertEquals(b.Name, 'Acme');          
System.assertEquals(c[0].Name, 'Acme');

同样,两个列表可以指向内存中的相同值。为 例:

Account[] a = new Account[]{new Account()};
Account[] b = a;
a[0].Name = 'Acme';
System.assert(b[0].Name == 'Acme');

除了 之外,其他有效分配 运算符包括 、 、 、 、 和 。请参阅表达式 运算符=+=*=/=|=&=++

转换规则

通常,Apex 要求您显式将一种数据类型转换为另一种数据类型。为 例如,Integer 数据类型的变量不能隐式转换为 String。你 必须使用该方法。但是,一些 数据类型可以隐式转换,而无需使用方法。

string.format数字形成类型的层次结构。较低数值类型的变量始终可以是 分配给更高的类型,而不进行显式转换。以下是 数字,从低到高:

  1. 整数
  2. 十进制

注意

一旦将值从较低类型的数字传递到较高类型的数字 type,则该值将转换为更高类型的数字。

请注意,层次结构和隐式转换与数字的 Java 层次结构不同, 其中使用基本接口号,并且从不进行隐式对象转换 允许。除数字外,还可以隐式转换其他数据类型。以下规则适用:

  • ID 始终可以分配给字符串。
  • 可以将字符串分配给 ID。但是,在运行时,该值将检查为 确保它是合法的 ID。如果不是,则运行时异常是 扔。
  • 关键字始终可以是 用于测试字符串是否为 ID。instanceOf

数据类型的其他注意事项

数值的数据类型数值表示整数值,除非它们附加了 L a Long 或 .0 表示双精度或小数。例如,表达式声明一个 Long 名为 d 的变量并将其赋值给一个整数数值 (123),即 隐式转换为 Long。右侧的整数值为 在整数范围内,赋值成功。但是,如果 右侧的数值超出了 整数,则会出现编译错误。在这种情况下,解决方案是 将 L 追加到数值,以便它表示具有 范围更广,如以下示例所示:。Long d = 123;Long d = 2147483648L;数据类型值溢出产生大于最大值的算术计算 电流类型被称为溢出。例如,产生一个 值为 –2147483648,因为 2147483647 是 整数,因此向其添加 1 会将值包装到最小的负数 整数的值为 –2147483648。Integer i = 2147483647 + 1;如果算术计算生成的结果大于最大值 对于当前类型,最终结果将不正确,因为计算出的 大于最大值的值将溢出。例如, 表达式会导致不正确的结果,因为 右侧整数的乘积大于最大值 整数值,它们会溢出。因此,最终产品不是 期待一个。您可以通过确保数值的类型来避免这种情况 或者您在算术运算中使用的变量足够大,可以容纳 结果。在此示例中,将 L 追加到数值以使其成为 Long 所以中间产品也会很长,不会发生溢出。 以下示例演示如何正确计算 一年中的毫秒乘以长数字 值。Long MillsPerYear = 365 * 24 * 60 * 60 * 1000;

Long MillsPerYear = 365L * 24L * 60L * 60L * 1000L;
Long ExpectedValue = 31536000000L;
System.assertEquals(MillsPerYear, ExpectedValue);

分部分数损失将数值 Integer 或 Long 值相除时,将 结果(如果有)在执行任何隐式转换为 双精度或十进制。例如,返回 1.0,因为实际结果 (1.666…) 是 整数,在隐式转换为 Double 之前四舍五入为 1。 要保留小数值,请确保使用 Double 或 除法中的十进制数值。例如,返回 1.66666666666666667,因为 5.0 和 3.0 表示 Double 值,其中 结果商也是双精度,没有小数值是 失去。Double d = 5/3;Double d = 5.0/3.0;