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;

编写 Apex

Apex 就像 Salesforce 的 Java。它使您能够添加数据并与之交互 Lightning 平台持久性层。它使用类、数据类型、变量和 if-else 语句。您可以根据条件使其执行,也可以执行代码块 反复。

  • 数据类型和变量
    Apex 使用数据类型、变量和相关语言结构,例如枚举、常量、表达式、运算符和赋值语句。
  • 控制流语句
    Apex 提供 if-else 语句、switch 语句和循环来控制代码执行的流程。语句通常按照它们出现的顺序逐行执行。使用控制流语句,您可以根据特定条件执行 Apex 代码,也可以让代码块重复执行。
  • 在 Apex
    中使用数据 您可以在 Lightning 平台持久性层中添加数据并与之交互。sObject 数据类型是保存数据对象的主要数据类型。您将使用数据操作语言 (DML) 来处理数据,并使用查询语言来检索数据,例如 () 等。

数据类型和变量

Apex 使用数据类型、变量和相关语言结构,例如枚举、 常量、表达式、运算符和赋值语句。

  1. 数据类型
    在 Apex 中,所有变量和表达式都具有数据类型,例如 sObject、primitive 或 enum。
  2. 基元数据类型
    Apex 使用与 SOAP API 相同的基元数据类型,但在某些情况下使用更高精度的 Decimal 类型。所有基元数据类型都是按值传递的。
  3. 集合
    Apex 中的集合可以是列表、集合或映射。

  4. 枚举 枚举是一种抽象数据类型,其值每个值都恰好采用您指定的一组有限标识符中的一个。枚举通常用于定义一组可能的值,否则这些值没有数字顺序。典型的例子包括一张牌的花色,或一年中的特定季节。
  5. 变量 局部变量
    使用 Java 样式语法声明。与 Java 一样,可以在单个语句中声明和初始化多个变量。
  6. 常量 顶点常量
    是其值在初始化一次后不会更改的变量。可以使用关键字定义常量。final
  7. 表达式和运算符 表达式是由变量、运算符
    和方法调用组成的构造,其计算结果为单个值。
  8. 赋值语句 赋值语句是将值放入变量的任何语句
  9. 转换
    规则 通常,Apex 要求您显式将一种数据类型转换为另一种数据类型。例如,Integer 数据类型的变量不能隐式转换为 String。您必须使用该方法。但是,可以隐式转换一些数据类型,而无需使用方法。string.format

数据类型

在 Apex 中,所有变量和表达式都有一个数据类型,例如 sObject、primitive、 或枚举。

  • 基元,例如 Integer、Double、Long、Date、Datetime、String、ID 或 布尔值(请参阅原始数据 类型)
  • sObject,可以作为通用 sObject 或特定 sObject,例如 客户、联系人或MyCustomObject__c(请参阅第 4 章中的使用 sObjects。
  • 集合,包括:
    • 基元、sObjects、用户定义对象、对象的列表(或数组) 从 Apex 类或集合创建(请参阅列表)
    • 一组基元(参见 集合)
    • 从基元到基元、sObject 或集合的映射(请参阅映射)
  • 值的类型化列表,也称为举(请参阅枚举)
  • 从用户定义的 Apex 类创建的对象(请参阅类、对象和 接口)
  • 从系统提供的 Apex 类创建的对象
  • Null(对于常量,可以是 分配给任何变量)null

方法可以返回任何列出的类型的值,也可以返回不返回任何值且类型为 无效。

在编译时严格执行类型检查。例如,解析器生成一个 如果为 Integer 类型的对象字段分配了 String 类型的值,则出错。然而 所有编译时异常都作为特定的故障代码返回,并带有行号 和 error 列。有关详细信息,请参阅调试 Apex。

基元数据类型

Apex 使用与 SOAP API 相同的原始数据类型,但精度更高 在某些情况下为十进制类型。所有基元数据类型都是按值传递的。

所有 Apex 变量,无论它们是类成员变量还是方法变量, 初始化为 。确保您 在使用变量之前,请将其初始化为适当的值。例如 将布尔变量初始化为 。nullfalse

Apex 基元数据类型包括:

数据类型描述
斑点存储为单个对象的二进制数据的集合。您可以 分别使用 和 方法将此数据类型转换为 String 或从 String 转换。 Blob 可以作为 Web 服务参数被接受,存储在文档中 (文档的正文是 Blob),或作为附件发送。查看更多 信息,请参阅 Crypto 类。toStringvalueOf
布尔只能赋值 、 或 的值。为 例:truefalsenullBoolean isWinner = true;
日期指示特定日期的值。与 Datetime 值不同,Date 值不包含有关时间的信息。始终使用 系统静态方法。您可以添加或减去 Integer 值 一个 Date 值,返回一个 Date 值。加法和减法 整数值是唯一适用于 Date 的算术函数 值。不能执行包含两个或 更多日期值。请改用 Date 方法。用 方法 获取没有附加时间戳的日期。使用隐式 具有 Date 值的字符串转换将生成带有 附加时间戳。String.valueOf()
日期时间指示特定日期和时间的值,例如 时间戳。始终使用系统静态方法创建日期时间值。您可以在 Datetime 值,返回 Date 值。加法和减法 整数和双精度值是唯一的算术函数 使用 Datetime 值。无法执行算术函数 包含两个或多个 Datetime 值。请改用 Datetime 方法。
十进制包含小数点的数字。十进制是任意的 精度数字。系统会自动为货币字段分配类型 十进制。如果未明确设置 a Decimal,从中创建 Decimal 的项目决定了 小数的刻度。比例是小数位数的计数。 使用该方法 设置小数点的刻度。setScale如果 Decimal 是作为查询的一部分创建的,则小数位数为 基于从 查询。如果 Decimal 是从 String 创建的,则小数位数为 小数点后的小数点后的字符数 字符串。如果 Decimal 是从非十进制数字创建的,则 number 首先转换为 String。然后设置刻度 使用小数点后的字符数。注意两个数值等效的 Decimal 对象 但比例不同(如1.1和1.10)一般没有 相同的哈希码。在以下位置使用此类 Decimal 对象时要小心 设置 或 作为 Map 键。
包含小数点的 64 位数字。双打有最低要求 的值 –263最大值为263-1.为 例:Double pi = 3.14159; Double e = 2.7182818284D;科学记数法 (e) 不支持双打。
编号任何有效的 18 个字符的 Lightning Platform 记录标识符。为 例:ID id='00300000003T2PGAA0';如果 您设置为 15 个字符的值,Apex 将该值转换为其 18 个字符 表示法。运行时将拒绝所有无效值 例外。IDID
整数不包含小数点的 32 位数字。整数有一个 最小值-2,147,483,648和最大值 值2,147,483,647.为 例:Integer i = 1;
不包含小数点的 64 位数字。多头有一个 最小值为 -263最大值为 263-1.当您需要一系列值时,请使用此数据类型 比 Integer 提供的范围更宽。为 例:Long l = 2147483648L;
对象Apex 中支持的任何数据类型。Apex 支持原始数据 类型(如 Integer)、用户定义的自定义类、sObject 泛型类型或特定于 sObject 的类型(如 Account)。都 Apex 数据类型继承自 Object。可以将表示更具体数据类型的对象强制转换为 其基础数据类型。例如:Object obj = 10; // Cast the object to an integer. Integer i = (Integer)obj; System.assertEquals(10, i);下一个示例演示如何将对象强制转换为用户定义的对象 type – 一个自定义 Apex 类,该类在 组织。MyApexClassObject obj = new MyApexClass(); // Cast the object to the MyApexClass custom type. MyApexClass mc = (MyApexClass)obj; // Access a method on the user-defined class. mc.someClassMethod();
字符串用单引号括起来的任何字符集。例如String s = 'The quick brown fox jumped over the lazy dog.';字符串 size:字符串对字符数没有限制 可以包括。相反,堆大小限制用于确保 Apex 计划不会变得太大。空字符串和 尾随空格:sObject String 字段值遵循 与SOAP API中的规则相同:它们永远不能为空(仅),并且永远不能为空 包括前导和尾随空格。这些约定是 数据库存储所必需的。null相反,Apex 中的字符串 可以是 或 空 和 可以包括前导和尾随空格,可用于 构造消息。nullSolution sObject 字段 SolutionNote 作为特殊类型的 String 运行。如果您有 HTML 解决方案 启用后,此字段中使用的任何 HTML 标签都会在 对象已创建或更新。如果输入的 HTML 无效,则会出现错误 被抛出。此字段中使用的任何 JavaScript 都会在 对象已创建或更新。在以下示例中,当 解决方案显示在详细信息页面上,“解决方案注释”字段为 H1 应用于它的 HTML 格式:trigger t on Solution (before insert) { Trigger.new[0].SolutionNote ='<h1>hello</h1>'; }在以下示例中,当解决方案 显示在详细信息页面上,SolutionNote 字段仅包含:HelloGoodbyetrigger t2 on Solution (before insert) { Trigger.new[0].SolutionNote = '<javascript>Hello</javascript>Goodbye'; }有关详细信息,请参阅“HTML” Salesforce 中的“解决方案概述” 帮助。EscapeSequences:Apex 中的所有字符串都使用 与 SOQL 字符串相同的转义序列:(退格)、(制表符)、(换行符)、(换行符)、(回车符)、(双引号)、(单引号)和 (反斜杠)。\b\t \n\f\r\”\’\\比较运算符:与 Java 不同,Apex 字符串 支持使用比较运算符 、 、 、 和 。因为 Apex 使用 SOQL 比较语义,字符串的结果根据 上下文用户的区域设置,不区分大小写。查看更多 信息,请参阅表达式 运算符。==!=<<=>>=字符串方法:与 Java 一样, 可以使用多种标准方法操作字符串。查看更多 信息,请参阅 String 类。顶点 使用 API 版本 15.0 保存(编译)的类和触发器,以及 如果分配的 String 值为 对于这个领域来说太长了。
时间指示特定时间的值。始终创建时间值 使用系统静态方法。请参阅时间类。

此外,两种非标准基元数据类型不能用作变量或方法 类型,但确实出现在系统静态方法中:

  • 任意类型。静态方法转换 将 AnyType 类型的 sObject 字段设置为标准基元。AnyType 在 Lightning Platform 数据库专门用于字段历史记录跟踪中的 sObject 字段 表。valueOf
  • 货币。静态的 方法创建一个 Currency 类型的文本。此方法仅在 SOQL 中使用 和 SOSL 条款进行过滤 sObject 货币字段。您不能在任何其他类型的 顶点。Currency.newInstanceWHERE

有关 AnyType 数据类型的详细信息,请参阅对象中的字段类型 Salesforce 参考。

版本化行为更改

在 API 版本 16 (Summer ’09) 及更高版本中,Apex 使用更高精度的 Decimal 数据 键入某些类型,例如货币。

收集

Apex 中的集合可以是列表、集合或映射。

注意

没有限制 集合可以容纳的项数。但是,有一个一般的限制 堆大小。

  • 列表 列表
    是元素的有序集合,这些元素通过其索引进行区分。列表元素可以是任何数据类型,包括基元类型、集合、sObjects、用户定义类型和内置 Apex 类型。
  • 集合 集合是不包含任何重复项的元素的无序集合
    。Set 元素可以是任何数据类型,包括基元类型、集合、sObjects、用户定义类型和内置 Apex 类型。
  • 映射 映射
    是键值对的集合,其中每个唯一键映射到单个值。键和值可以是任何数据类型,包括基元类型、集合、sObjects、用户定义类型和内置 Apex 类型。
  • 通常,参数化类型化 Apex
    是一种静态类型的编程语言,这意味着用户必须先指定变量的数据类型,然后才能使用该变量。

列表

列表是元素的有序集合,这些元素按其索引进行区分。 列表元素可以是任何数据类型,包括基元类型、集合、sObjects、 用户定义类型和内置 Apex 类型。

下表是字符串列表的可视化表示形式:

索引 0索引 1索引 2索引 3索引 4索引 5
“红色”“橙色”“黄色”“绿色”“蓝色”“紫色”

列表中第一个元素的索引位置始终为 0。

列表可以包含任何集合,并且可以相互嵌套并成为 多面的。例如,您可以有一个整数集列表列表。一个列表 其中最多可以包含七级嵌套集合,即最多八级 整体水平。

若要声明列表,请使用关键字 followed 基元数据、sObject、嵌套列表、映射或集类型在 <> 个字符以内。 例如:List

// Create an empty list of String
List<String> my_list = new List<String>();
// Create a nested list
List<List<Set<Integer>>> my_list_2 = new List<List<Set<Integer>>>();

若要访问列表中的元素,请使用 Apex 提供的方法。例如:List

List<Integer> myList = new List<Integer>(); // Define a new list
myList.add(47);                    // Adds a second element of value 47 to the end 
                                       // of the list
Integer i = myList.get(0);                   // Retrieves the element at index 0
myList.set(0, 1);                           // Adds the integer 1 to the list at index 0
myList.clear();                    // Removes all elements from the list

有关更多信息(包括所有支持的方法的完整列表),请参见 List Class。

使用一维数组表示法 列表

使用基元或对象的一维列表时,可以 还可以使用更传统的数组表示法来声明和引用列表元素。为 例如,可以通过以下方式声明基元或对象的一维列表 在数据类型名称后面加上 [] 字符:

String[] colors = new List<String>();

这两个语句等效于前面的语句:

List<String> colors = new String[1];
String[] colors = new String[1];

自 引用一维列表的一个元素,也可以跟在 在方括号中包含元素索引位置的列表。为 例:

colors[0] = 'Green';

甚至 虽然前一个数组的大小 定义为一个元素(括号中的数字),列表是弹性的,可以增长 根据需要,前提是使用该方法添加新元素。例如 您可以向列表中添加两个或多个元素。但是,如果您使用方括号添加 元素,则该列表的行为类似于数组,并且没有弹性,也就是说, 不允许添加比声明的数组更多的元素 大小。Stringnew String[1]Listaddcolors

所有列表都初始化为 。可以使用文字为列表赋值和分配内存 表示法。例如:null

描述
List<Integer> ints = new Integer[0];定义大小为零且不带元素的整数列表
List<Integer> ints = new Integer[6];定义一个整数列表,其中为六个分配了内存 整数
  • 列表排序
    可以对列表元素进行排序,排序顺序取决于元素的数据类型。

列表排序

您可以对列表元素进行排序,排序顺序取决于 元素。

使用该方法,您可以对 一个列表。对于基元数据类型的元素(如字符串),排序按升序排列。 其他更复杂的数据类型的排序顺序在介绍这些类型的章节中进行了描述 数据类型。List.sort

此示例演示如何对字符串列表进行排序并验证颜色是否在 列表中的升序。

List<String> colors = new List<String>{
    'Yellow',
    'Red',
    'Green'};
colors.sort();
System.assertEquals('Green', colors.get(0));
System.assertEquals('Red', colors.get(1));
System.assertEquals('Yellow', colors.get(2));

对于 Visualforce SelectOption 控件,根据值按升序排序 和标签字段。请参阅下一节,了解用于 选择选项。

的默认排序顺序 选择选项

该方法对 SelectOption 元素进行排序 使用“值”和“标签”字段进行升序排序,并基于此比较序列。

List.sort

  1. 值字段首先用于排序。
  2. 如果两个值字段具有相同的值或都为空,则标签字段为 使用。

请注意,禁用的字段不用于排序。

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

在此示例中,列表包含三个 SelectOption 元素。Two elements, 美国 和墨西哥,具有相同的值字段(‘A’)。该方法根据 label 字段,并将墨西哥置于美国之前,如输出中所示。最后 排序列表中的元素是 Canada,并按其值字段 ‘C’ 排序, 它位于“A”之后。List.sort

List<SelectOption> options = new List<SelectOption>();
options.add(new SelectOption('A','United States'));
options.add(new SelectOption('C','Canada'));
options.add(new SelectOption('A','Mexico'));
System.debug('Before sorting: ' + options);
options.sort();
System.debug('After sorting: ' + options);

这是 debug 语句的输出。它显示 排序。

DEBUG|Before sorting: (System.SelectOption[value="A", label="United States", disabled="false"], 
  System.SelectOption[value="C", label="Canada", disabled="false"], 
  System.SelectOption[value="A", label="Mexico", disabled="false"])
DEBUG|After sorting: (System.SelectOption[value="A", label="Mexico", disabled="false"], 
  System.SelectOption[value="A", label="United States", disabled="false"], 
  System.SelectOption[value="C", label="Canada", disabled="false"])

Set

集合是不包含任何重复项的元素的无序集合。设置 元素可以是任何数据类型,包括基元类型、集合、sObjects、用户定义 类型和内置 Apex 类型。

下表表示一组使用城市名称的字符串:

“旧金山”“纽约”“巴黎”“东京”

集合可以包含可以相互嵌套的集合。例如,您可以 有一组整数集的列表。一个集合最多可以包含七个嵌套级别 其中的集合,即总共最多八个级别。若要声明集合,请使用关键字 followed 按 <> 个字符以内的基元数据类型名称。为 例:

Set

Set<String> myStringSet = new Set<String>();

下面的示例演示如何创建具有两个硬编码字符串值的集合。

// Defines a new set with two elements
Set<String> set1 = new Set<String>{'New York', 'Paris'};

要访问集合中的元素,请使用 Apex 提供的系统方法。例如:

// Define a new set
Set<Integer> mySet = new Set<Integer>();
// Add two elements to the set
mySet.add(1);
mySet.add(3);
// Assert that the set contains the integer value we added
System.assert(mySet.contains(1)); 
// Remove the integer value from the set
mySet.remove(1);

下面的示例演示如何从另一个集合的元素创建集合。

// Define a new set that contains the
// elements of the set created in the previous example
Set<Integer> mySet2 = new Set<Integer>(mySet);
// Assert that the set size equals 1
// Note: The set from the previous example contains only one value
System.assert(mySet2.size() == 1);

有关更多信息(包括所有受支持的 set 系统方法的完整列表),请参见 Set Class。请注意集合的以下限制:

  • 与 Java 不同,Apex 开发人员不需要引用所使用的算法 在其声明中实现集合(例如,或 )。Apex 对所有集合都使用哈希结构。HashSetTreeSet
  • 集合是无序集合 – 您不能在 具体索引。您只能遍历设置的元素。
  • 集合元素的迭代顺序是确定性的,因此您可以依赖 顺序在每次后续执行相同代码时相同。

Map

映射是键值对的集合,其中每个唯一键映射到单个值。 键和值可以是任何数据类型,包括基元类型、集合、sObjects、用户定义 类型和内置 Apex 类型。

下表显示了国家和货币的地图:

国家/地区 (键)“美国”“日本”“法国”“英格兰”“印度”
货币(值)“美元”“日元”“欧元”“磅”“卢比”

映射键和值可以包含任何集合,并且可以包含嵌套集合。为 例如,您可以将整数映射到映射,而映射又将字符串映射到列表。地图 键最多可以包含七级嵌套集合,即最多八级 整体。

要声明映射,请使用关键字后跟 键的数据类型和字符内的值。例如:Map<>

Map<String, String> country_currencies = new Map<String, String>();
Map<ID, Set<String>> m = new Map<ID, Set<String>>();

可以将泛型或特定 sObject 数据类型用于映射。您还可以创建一个 映射的泛型实例。

与列表一样,在使用 curly 声明映射时,可以填充映射键值对 brace () 语法。在大括号内,指定 首先,然后使用 指定该键的值。例如:{}=>

Map<String, String> MyStrings = new Map<String, String>{'a' => 'b', 'c' => 'd'.toUpperCase()};

在第一个示例中,键的值是 ,键的值是 。abcD

若要访问地图中的元素,请使用 Apex 提供的 Map 方法。此示例创建一个地图 整数键和字符串值。它添加两个条目,检查第一个条目是否存在 key,检索第二个条目的值,最后获取所有键的集合。

Map<Integer, String> m = new Map<Integer, String>(); // Define a new map
m.put(1, 'First entry');                  // Insert a new key-value pair in the map
m.put(2, 'Second entry');                  // Insert a new key-value pair in the map
System.assert(m.containsKey(1));  // Assert that the map contains a key
String value = m.get(2);               // Retrieve a value, given a particular key
System.assertEquals('Second entry', value);
Set<Integer> s = m.keySet();       // Return a set that contains all of the keys in the map

有关详细信息(包括所有受支持的 Map 方法的完整列表),请参阅 Map 类。

Map注意事项

  • 与 Java 不同,Apex 开发人员不需要引用用于 在其声明中实现映射(例如,或 )。Apex 使用哈希 所有地图的结构。HashMapTreeMap
  • 映射元素的迭代顺序是确定性的。您可以信赖订单 在每次后续执行相同的代码时都是一样的。但是,我们建议始终 按键访问地图元素。
  • 映射键可以保存该值。null
  • 添加一个映射条目,其键与映射中的现有键匹配,会覆盖 现有条目,该键具有新条目。
  • String 类型的映射键区分大小写。仅因大小写而异的两个键是 被认为是唯一的,并具有相应的不同映射条目。随后,地图 方法,包括 、 、 和 处理这些键 作为不同的。putgetcontainsKeyremove
  • 用户定义类型的映射键的唯一性由您在你的班级。所有其他非基元类型的键的唯一性,例如 sObject 键, 通过比较对象的字段值来确定。
  • 仅当 Map 对象使用以下数据类型之一时,它才可序列化为 JSON 作为钥匙。
    • 布尔
    • 日期
    • 日期时间
    • 十进制
    • 枚举
    • 同上
    • 整数
    • 字符串
    • 时间

参数化类型化

一般来说,Apex 是一种静态类型的编程语言,这意味着用户必须 在使用变量之前,指定该变量的数据类型。这在 Apex 中是合法的:

Integer x = 1;

如果尚未定义,这是不合法的 早些时候:

x

x = 1;

列表、地图和集合在 Apex 中参数化:它们采用任何数据类型 Apex 支持它们作为论据。该数据类型必须替换为实际数据 在构建列表、地图或集时键入。为 例:

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

使用参数化列表进行子类型化

在 Apex 中,如果 type 是 的子类型,则将是 的子类型。例如,以下是 法律:

TUList<T>List<U>

List<String> slst = new List<String> {'alpha', 'beta'};
List<Object> olst = slst;

枚举

枚举是一种抽象数据类型,其值每个值都恰好采用 您指定的有限标识符集。枚举通常用于定义一组 否则没有数字顺序的可能值。典型的例子包括 一张牌的花色,或一年中的特定季节。

尽管每个值对应于一个不同的整数值,但枚举会隐藏此值 实现。隐藏实现可防止任何可能滥用的值 执行算术等。创建枚举、变量、方法参数和 可以声明该类型的返回类型。

注意

与 Java 不同,枚举类型本身没有构造函数语法。

要定义枚举,请在 声明并使用大括号来划分可能值的列表。例如 以下代码创建一个名为 :enumSeason

public enum Season {WINTER, SPRING, SUMMER, FALL}

通过创建枚举,您还可以 创建了一个名为 的新数据类型。您可以 像使用任何其他数据类型一样使用此新数据类型。例如:SeasonSeason

Season southernHemisphereSeason = Season.WINTER;

public Season getSouthernHemisphereSeason(Season northernHemisphereSeason) {

    if (northernHemisphereSeason == Season.SUMMER) return southernHemisphereSeason;
     //...
}

还可以将类定义为枚举。创建枚举类时,请勿在定义中使用关键字。class

public enum MyEnumClass { X, Y }

您可以在任何可以使用其他数据类型名称的地方使用枚举。如果将 类型为枚举的变量,分配给它的任何对象都必须是该枚举的实例 枚举类。

任何方法都可以使用枚举类型作为一部分 他们的签名。在这种情况下,关联的 WSDL 文件包括 枚举及其值,API 客户端可以使用。webservice

Apex 提供以下系统定义的枚举:

  • System.StatusCode这 枚举对应于 WSDL 文档中公开的 API 错误代码 所有 API 操作。为 例:StatusCode.CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY StatusCode.INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY完整的 状态代码列表在组织的 WSDL 文件中可用。为 有关访问组织的 WSDL 文件的更多信息,请参阅下载 Salesforce WSDL 和客户端身份验证 Salesforce 帮助中的证书。
  • System.XmlTag:此枚举返回用于分析方法的结果 XML 的 XML 标记列表。查看更多 信息,请参见 XmlStreamReader 类。webservice
  • System.ApplicationReadWriteMode: 此枚举指示组织在 Salesforce 升级和停机时间。有关更多信息,请参见 System.getApplicationReadWriteMode()。
  • System.LoggingLevel:此枚举与方法一起使用,以指定所有调用的日志级别。有关更多信息,请参见 System 类。system.debugdebug
  • System.RoundingMode:此枚举由执行数学运算的方法用于指定 操作的舍入行为。典型的例子是 Decimal 方法和 Double 方法。欲了解更多信息, 请参阅舍入模式。divideround
  • System.SoapType:此枚举由字段 describe result 方法返回。欲了解更多信息, 请参阅 SOAPType 枚举。getSoapType
  • System.DisplayType:此枚举由字段 describe result 方法返回。有关更多信息,请参见 DisplayType 枚举。getType
  • System.JSONToken:此枚举用于解析 JSON 内容。有关更多信息,请参见 JsonToken 枚举。
  • ApexPages.Severity:此枚举指定 Visualforce 消息的严重性。查看更多 信息,请参阅 ApexPages.Severity 枚举。
  • Dom.XmlNodeType:此枚举指定 DOM 文档中的节点类型。

注意

系统定义的枚举不能在 Web 服务中使用 方法。

所有枚举值(包括系统枚举)都具有与之关联的通用方法。为 有关详细信息,请参阅枚举方法。

不能将用户定义的方法添加到枚举值。

变量

局部变量是用 Java 样式语法声明的。与 Java 一样,多个变量 可以在单个语句中声明和初始化。

局部变量是用 Java 样式语法声明的。例如:

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

与 Java 一样,可以在单个语句中声明和初始化多个变量, 使用逗号分隔。例如:

Integer i, j, k;

Null 变量和初始值

如果您声明一个变量并且不使用值对其进行初始化,则它将是 .从本质上讲,意味着没有值。您还可以分配给使用基元声明的任何变量 类型。例如,这两个语句都会导致变量设置为:

nullnullnullnull

Boolean x = null;
Decimal d;

如果变量为 ,则数据类型上的许多实例方法将失败。在此示例中,第二个语句 生成异常 (

nullNullPointerException)

Date d;
d.addDays(2);

所有变量都初始化为 if 没有为它们分配值。例如,在以下示例中,和 是赋值,而整数变量和布尔变量设置为 ,因为它们未显式初始化。nullikjbnull

Integer i = 0, j, k = 1;
Boolean b;

注意

一个常见的陷阱是假设未初始化的布尔变量是 由系统初始化。这 事实并非如此。与所有其他变量一样,布尔变量如果不是,则为 null 显式分配一个值。false

可变范围

变量可以在块中的任何点定义,并从该点开始 向前。子块无法重新定义已使用的变量名称 在父块中,但并行块可以重用变量名称。例如:

Integer i;
{
   // Integer i;  This declaration is not allowed
}

for (Integer j = 0; j < 10; j++);
for (Integer j = 0; j < 10; j++);

区分大小写

避免与不区分大小写的 SOQL 混淆 和 SOSL 查询,Apex 也不区分大小写。这意味着:

  • 变量和方法名称不区分大小写。为 例:Integer I; //Integer i; This would be an error.
  • 对对象和字段名称的引用不区分大小写。为 例:Account a1; ACCOUNT a2;
  • SOQL 和 SOSL 语句不区分大小写。为 例:Account[] accts = [sELect ID From ACCouNT where nAme = 'fred'];

注意

稍后您将了解有关 sObjects、SOQL 和 SOSL 的更多信息 指导。

另请注意,Apex 使用与 SOQL 相同的过滤语义,即 是 SOAP API 和 Salesforce 用户界面中比较的基础。这 使用这些语义可能会导致一些有趣的行为。例如,如果 最终用户根据筛选器生成报告,以筛选 字母表(即值< ‘m’),结果中返回 null 字段。这 此行为的基本原理是,用户通常会想到没有值的字段 只是一个空格字符,而不是它的实际值。因此,在 Apex 中,以下表达式全部 计算结果为:nulltrue

String s;
System.assert('a' == 'A');
System.assert(s < 'b');
System.assert(!(s > 'b'));

注意

尽管在上面的示例中计算为 ,但会生成错误,因为 您正在尝试将字母与值进行比较。s < ‘b’true‘b.’compareTo(s)null

常数

顶点常量是其值在初始化后不会更改的变量 一次。可以使用关键字定义常量。

final

关键字表示变量可以是 最多分配一次,可以在声明本身中使用,也可以使用静态初始值设定项 方法,如果常量是在类中定义的。此示例声明两个常量。这 首先在声明语句中初始化。第二个在 static 块,通过调用 static 方法。final

public class myCls {
   static final Integer PRIVATE_INT_CONST = 200;
   static final Integer PRIVATE_INT_CONST2; 

   public static Integer calculate() {
       return 2 + 7;
   }

   static {
       PRIVATE_INT_CONST2 = calculate();
   }
}

Apex入门

什么是Apex?

Apex 是一种强类型、面向对象的编程语言,允许开发人员 在 Salesforce 服务器上执行流和事务控制语句,并结合 对 API 的调用。使用看起来像 Java 的语法,并且像存储的数据库一样 过程中,Apex 使开发人员能够将业务逻辑添加到大多数系统事件中,包括 按钮单击、相关记录更新和 Visualforce 页面。Apex 代码可以通过以下方式启动 Web 服务请求和来自对象上的触发器的请求。

作为一种语言,Apex 是:

综合
Apex 提供对常见 Lightning 平台习惯用语的内置支持,包括:
  • 数据操作语言 (DML) 调用,例如 、 和 ,包括内置处理INSERT UPDATE DELETE DmlException
  • 内联 Salesforce 对象查询语言 (SOQL) 和 Salesforce 对象 返回 sObject 记录列表的搜索语言 (SOSL) 查询
  • 允许一次批量处理多条记录的循环
  • 防止记录更新冲突的锁定语法
  • 可从存储的 Apex 方法构建的自定义公共 API 调用
  • 用户尝试编辑或删除自定义项时发出的警告和错误 Apex 引用的对象或字段
简单易用
Apex 基于熟悉的 Java 习语,例如变量和表达式语法, 块和条件语句语法、循环语法、对象和数组表示法。 在 Apex 引入新元素的地方,它使用简单的语法和语义 了解并鼓励有效使用 Lightning 平台。因此 Apex 生成的代码既简洁又易于编写。
以数据为中心
Apex 旨在将多个查询和 DML 语句串联成一个 Salesforce 服务器上的单个工作单元。开发人员使用存储的数据库 将数据库上的多个事务语句串联在一起的过程 服务器以类似的方式。与其他数据库存储过程一样,Apex 不 尝试为在用户中呈现元素提供常规支持 接口。
严格
Apex 是一种强类型语言,它使用对架构对象的直接引用 例如对象和字段名称。如果有的话,它在编译时会很快失败 引用无效。它存储所有自定义字段、对象和类 元数据中的依赖项,以确保它们不会在需要时被删除 活动 Apex 代码。
托管
Apex 完全由闪电解释、执行和控制 平台。
多租户感知
与 Lightning 平台的其他部分一样,Apex 在多租户环境中运行。 因此,Apex 运行时引擎旨在密切防止代码失控, 防止其垄断共享资源。任何违反限制的代码 失败,并显示易于理解的错误消息。
易于测试
Apex 为单元测试的创建和执行提供内置支持。它包括 测试结果,指示覆盖了多少代码,以及 代码可以更有效率。Salesforce 确保所有自定义 Apex 代码都能正常工作 如预期的那样,在任何平台升级之前执行所有单元测试。
版本化
您可以针对不同版本的 API 保存 Apex 代码。这样可以 你保持行为。

了解 Apex 核心概念

Apex 代码通常包含许多您可能熟悉的其他代码 编程语言。
Apex 中的编程元素

本节介绍了 Apex 的基本功能,以及一些核心 概念。

使用版本设置

在 Salesforce 用户界面中,您可以指定 Salesforce API 的版本 保存 Apex 类或触发器的对象。此设置不仅指示 要使用的 SOAP API 版本,以及 Apex 的版本。您可以更改 保存后的版本。每个类或触发器名称都必须是唯一的。你不能 保存相同的类或针对不同版本的触发器。

还可以使用版本设置将类或触发器与特定 组织中安装的托管包的版本 AppExchange。此版本的托管软件包将继续由 类或触发器(如果安装了更高版本的托管包),除非您 手动更新版本设置。要将已安装的托管软件包添加到 设置列表中,从可用包列表中选择一个包。列表是 仅当已安装的托管包尚未安装时显示 与类或触发器相关联。

有关对托管包使用版本设置的更多信息,请参阅 Salesforce 联机帮助中的关于包版本

命名变量、方法和类

在命名变量、方法或 类。这些包括属于 Apex 和 Lightning 平台的单词,例如 作为 、 或 以及保留 关键字。list test account

使用变量和表达式

Apex 是一种强类型语言,也就是说,您必须声明数据 首次引用变量时的类型。Apex 数据类型包括基本类型 例如 Integer、Date 和 Boolean,以及更高级的类型,例如列表、 maps、objects 和 sObjects。

变量使用名称和数据类型进行声明。您可以将值赋给 变量。您也可以稍后分配值。使用以下命令 声明变量时的语法:

datatypevariable_name [ = value];
 

提示

请注意,上述末尾的分号不是可选的。所有语句必须以分号结束。

以下是变量声明的示例:

// The following variable has the data type of Integer with the name Count, 
// and has the value of 0.
Integer Count = 0;
// The following variable has the data type of Decimal with the name Total. Note 
// that no value has been assigned to it.
Decimal Total;
// The following variable is an account, which is also referred to as an sObject.
Account MyAcct = new Account();

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

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

using 语句

语句是执行操作的任何编码指令。

在 Apex 中,语句必须以分号结尾,并且可以是以下类型之一:
  • 赋值,例如为变量赋值
  • 条件 (if-else)
  • 循环:
    • 同时做
  • 锁定
  • 数据操作语言 (DML)
  • 交易控制
  • 方法调用
  • 异常处理

是用 curly 组合在一起的一系列语句 大括号,可以在允许单个语句的任何地方使用。为 例:

if (true) {
    System.debug(1);
    System.debug(2);
} else {
    System.debug(3);
    System.debug(4);
}

如果一个块仅包含一个语句,则可以保留大括号 关闭。例如:

if (true) 
    System.debug(1);
else 
    System.debug(2);

使用集合

Apex 具有以下类型的集合:

  • 列表(数组)
  • 地图

列表是元素的集合,例如整数、字符串、对象或 其他集合。当元素的顺序很重要时,使用列表。你可以有 列表中的重复元素。

列表中的第一个索引位置始终为 0。

要创建列表:

  • 使用关键字new
  • 使用关键字后跟 包含在字符中的元素类型。List<>

使用以下语法创建列表:

List <datatype> list_name
   [= new List<datatype>();] |
   [=new List<datatype>{value [, value2. . .]};] |
   ;

下面的示例创建一个 Integer 列表,并将其分配给变量 。请记住,由于 Apex 是强类型的, 您必须将 的数据类型声明为 整数列表。My_ListMy_List

List<Integer> My_List = new List<Integer>();

有关详细信息,请参阅列表

集合是唯一的、无序元素的集合。它可以包含 基元数据类型,如 String、Integer、Date 等。它还可以包含更多 复杂数据类型,例如 sObjects。

要创建集合,请执行以下操作:

  • 使用关键字new
  • 使用关键字后跟 包含在字符中的原始数据类型Set<>

使用以下语法创建集:

Set<datatype> set_name 
   [= new Set<datatype>();] |
   [= new Set<datatype>{value [, value2. . .] };] |
   ;

下面的示例创建一组 String。集合的值是使用 大括号。{}

Set<String> My_String = new Set<String>{'a', 'b', 'c'};

有关详细信息,请参阅

映射是键值对的集合。键可以是任何原始数据 类型。值可以包括基元数据类型,以及对象和其他集合。 按关键事项查找内容时,请使用地图。地图中可以有重复的值, 但每个键必须是唯一的。

要创建地图:

  • 使用关键字new
  • 使用关键字后跟键值对 对,用逗号分隔并用字符括起来。Map<>

使用以下语法创建映射:

Map<key_datatype, value_datatype> map_name
   [=new map<key_datatype, value_datatype>();] | 
   [=new map<key_datatype, value_datatype>
   {key1_value => value1_value 
   [, key2_value => value2_value. . .]};] |
   ;

以下示例创建一个映射,该映射的数据类型为 Integer 的键和 值的字符串。在此示例中,将传入映射的值 在地图所在的大括号之间 创建。{}

Map<Integer, String> My_Map = new Map<Integer, String>{1 => 'a', 2 => 'b', 3 => 'c'};

有关详细信息,请参阅地图。

使用分支

语句是真假测试, 使应用程序能够根据条件执行不同的操作。基本 语法如下:if

if (Condition){
// Do this if the condition is true
} else {
// Do this if the condition is not true
}

有关更多信息,请参见条件 (If-Else) 语句。

使用循环

虽然该语句使你的 应用程序根据条件来做事,循环告诉你的应用程序做 基于条件一次又一次地做同样的事情。Apex 支持以下类型 循环数:if

  • 同时做

Do-while 循环在代码执行后检查条件。

While 循环在代码开始之前检查条件 执行。

For 循环使您能够更精细地控制 循环。此外,Apex 还支持传统的 For 循环,您可以在其中设置 条件,以及使用列表和 SOQL 查询作为 条件。

什么时候应该使用Apex?

Salesforce 预构建应用程序提供强大的 CRM 功能性。此外,Salesforce 还提供了自定义预构建的功能 适合您组织的应用程序。但是,您的组织可能具有复杂的 现有功能不支持的业务流程。在本例中, Lightning Platform 为高级管理员和开发人员提供了多种方式 构建自定义功能。

Apex

如果您想要执行以下操作,请使用 Apex:
  • 创建 Web 服务。
  • 创建电子邮件服务。
  • 对多个对象执行复杂的验证。
  • 创建工作流不支持的复杂业务流程。
  • 创建自定义事务逻辑(在整个 事务,而不仅仅是单个记录或对象)。
  • 将自定义逻辑附加到另一个操作,例如保存记录,以便 每当执行操作时,它都会发生,而不管它是否 源自用户界面、Visualforce 页面或 SOAP API。

闪电组件

开发 Lightning 组件以自定义 Lightning Experience、Salesforce 移动版 应用程序,或构建自己的独立应用程序。您也可以使用开箱即用的 组件以加快开发速度。

从 Spring ’19(API 版本 45.0)开始,您可以使用两个 编程模型:Lightning Web 组件模型和原始 Aura 组件模型。Lightning Web 组件是使用 HTML 构建的自定义 HTML 元素 和现代 JavaScript。Lightning Web 组件和 Aura 组件可以共存,并且 在页面上进行互操作。将 Lightning Web 组件和 Aura 组件配置为 在 Lightning App Builder 和 Experience Builder 中工作。管理员和最终用户不会 了解用于开发组件的编程模型。对他们来说,他们是 简单的闪电组件。

视觉力

Visualforce 由基于标签的标记语言组成 这为开发人员提供了一种更强大的构建应用程序和自定义方式 Salesforce 用户界面。借助 Visualforce,您可以:
  • 生成向导和其他多步骤过程。
  • 通过应用程序创建自己的自定义流控制。
  • 定义导航模式和特定于数据的规则,以实现最佳、高效 应用程序交互。

SOAP API

如果要将功能添加到 一次只处理一种类型的记录的复合应用程序,并且 不需要任何事务控制(例如设置保存点或回滚) 更改)。

Apex 是如何工作的?

所有 Apex 都完全在 Lightning 平台上按需运行。开发人员编写和保存 Apex 代码到平台,最终用户通过用户触发 Apex 代码的执行 接口。
Apex 完全编译、存储和运行 在闪电平台上

当开发人员编写 Apex 代码并将其保存到平台时,平台应用程序 服务器首先将代码编译成一组抽象的指令,这些指令可以 由 Apex 运行时解释器理解,然后将这些指令另存为 元数据。

当最终用户触发 Apex 的执行时,可能通过单击按钮或 访问 Visualforce 页面时,平台应用程序服务器会检索已编译的 来自元数据的指令,并在之前通过运行时解释器发送它们 返回结果。最终用户观察到执行时间与以下方面没有差异 标准平台请求。

在云中开发代码

Apex 编程语言保存并在云中运行,即多租户 平台。Apex 是为平台上的数据访问和数据操作量身定制的,它 使您能够将自定义业务逻辑添加到系统事件中。虽然它提供了许多好处 对于平台上的业务流程自动化,它不是通用编程 语言。
Apex 不能用于:
  • 在用户界面中呈现错误消息以外的元素
  • 更改标准功能 – Apex 只能阻止该功能 ,或添加其他功能
  • 创建临时文件
  • 生成线程

提示

所有 Apex 代码都在 Lightning 平台上运行,该平台是所有人使用的共享资源 其他组织。为了保证一致的性能和可伸缩性, Apex 的执行受调控器限制的约束,这些限制确保没有单个 Apex 执行 影响 Salesforce 的整体服务。这意味着所有 Apex 代码都受到以下限制 它可以在一个操作中执行的操作数(例如 DML 或 SOQL) 过程。

所有 Apex 请求都返回一个包含 1 到 50,000 条记录的集合。你 不能假定您的代码一次只适用于一条记录。因此,你 必须实现考虑批量处理的编程模式。如果你 不要,您可能会遇到调速器限制。

什么是 Apex 开发流程?

要开发 Apex,请获取一个 Developer Edition 帐户,编写并测试您的代码,然后 部署代码。
我们建议采用以程来开发 Apex:
  1. 获取 Developer Edition 帐户。
  2. 学习 更多关于 Apex.apex
  3. 写下你的 apex。
  4. 在编写 Apex 时,您还应该编写测试。
  5. (可选)将 Apex 部署到沙盒组织并执行最终单元 测试。
  6. 部署 您的 Apex 到 Salesforce 生产组织。

除了部署 Apex 之外,一旦编写和测试,您还可以 同时添加您的类和 触发器 AppExchange 应用程序包。

创建开发人员或沙盒组织

您可以在生产组织、开发人员组织或沙盒组织中运行 Apex。您可以 在开发人员组织或沙盒组织中开发 Apex,但不在生产组织中开发。
  • 生产组织 – 有实时用户访问您的 数据
  • 开发人员组织 – 使用 Developer Edition 创建的组织 帐户
  • 沙盒组织 – 在生产组织上创建的组织,它是 生产组织的副本
 

注意

Apex 触发器在 Salesforce 试用版中可用。但是,它们是 转换为任何其他版本时禁用。如果您新注册的组织包括 Apex,使用其中一种部署方法将代码部署到您的组织。

您无法在 Salesforce 中开发 Apex 生产组织。在开发过程中访问系统的实时用户可以 破坏数据稳定性或损坏应用程序。相反,做你所有的 在沙盒或 Developer Edition 组织中进行开发工作。

如果您还不是开发者社区的成员,请转到 https://developer.salesforce.com/signup 和 按照说明注册 Developer Edition 帐户。开发人员 通过 Edition 帐户,您可以访问免费的 Developer Edition 组织。即使你 已拥有 Professional、Enterprise、Unlimited 或 Performance Edition 组织,以及 用于创建 Apex 的沙盒,我们强烈建议您利用 开发人员社区中提供的资源。

 

注意

您无法使用 Salesforce 用户界面在 Salesforce 生产组织。

要创建沙盒组织,请执行以下操作:

  1. 在“设置”中,在“快速”中输入沙盒 查找框,然后选择沙盒
  2. 单击新建沙盒
  3. 输入沙盒的名称(不超过 10 个字符)和描述。

    我们建议您选择一个符合以下条件的名称:

    • 反映此沙盒的用途,例如 QA。
    • 只有几个字符,因为 Salesforce 将沙盒名称附加到 沙盒环境中用户记录的用户名。字符较少的名称使 沙盒登录更易于键入。
  4. 选择所需的沙盒类型。

    如果您没有看到沙盒选项或需要许可证以获取更多许可证,请联系 Salesforce 为您的组织订购沙盒。

    如果您减少了购买的沙盒数量,则需要匹配该数量 的沙盒数量为购买的数量。例如,如果有两个 Full 沙盒但只购买了一个,则无法创建完整沙盒。相反,将 将完整沙盒更改为较小的沙盒,例如 Developer Pro 或 Developer 沙盒,具体取决于 您有可用的类型。

  5. 选择要包含在“部分副本”或“完整”沙盒中的数据。
    • 对于“部分复制”沙盒,单击“下一步”,然后选择 您创建的模板,用于指定沙盒的数据。如果您尚未创建 模板,请参阅创建或编辑沙盒模板。
    • 对于“完整沙盒”,单击“下一步”,然后确定数据量 包括。
      • 要为完整沙盒包含基于模板的数据,请选择现有沙盒 模板。有关更多信息,请参阅创建或编辑沙盒模板
      • 要将所有数据包含在完整沙盒中,请选择是否字段以及字段大小 跟踪要包含的历史数据,以及是否复制 Chatter 数据。Chatter 数据 包括源、消息和主题,并用于影响 您的沙盒副本。减少复制的数据量可以显著加快速度 沙盒复制时间。
  6. 若要在每次创建和刷新此沙盒后运行脚本,请指定 Apex 类 您之前从 SandboxPostCopy 接口创建。
  7. 对于“沙盒访问”,选择“用户组”以选择公共用户 包含需要访问沙盒的用户的组,或选择“全部” 活跃用户
  8. 单击创建
     

    学习 Apex

    拥有开发者帐户后,有许多资源可供您使用 了解 Apex
    Apex Trailhead 内容
    初级和中级程序员
    多个 Trailhead 模块提供了有关学习 Apex 的教程。使用这些模块 了解 Apex 的基础知识以及如何在 Lightning 上使用它 平台。使用 Apex 通过触发器、单元测试、 异步 Apex、REST Web 服务和 Visualforce 控制器。
    快速入门:Apex
    Apex 基础知识 &数据库
    Apex 触发器
    Apex 集成 服务业
    Apex 测试
    异步 Apex
    Salesforce 开发人员 Apex 页面
    初级和高级程序员
    Salesforce Developers 上的 Apex 页面包含指向多个 资源,包括有关 Apex 编程语言的文章。这些 资源提供了对 Apex 的快速介绍,并包括 Apex开发。
    Lightning 平台代码示例和 SDK
    初级和高级程序员
    开源代码示例和 SDK、参考代码和最佳实践可以是 在代码示例和 软件开发工具包。一个简明、有意义的 Apex 代码示例库,用于 遵循最佳实践的常见用例可以在 Apex-recipes 中找到。
    开发生命周期:Lightning平台上的企业开发
    架构师和高级程序员
    应用程序生命周期和开发 Trailhead 上的模型模块可帮助您了解如何使用该应用程序 闪电平台上的生命周期和开发模型。
    培训课程
    Salesforce Trailhead Academy 还提供培训课程。发展和验证您的 具有 Salesforce 凭据的技能。
    在本书中 (Apex 开发人员指南)
    初级程序员可以查看以下内容:
    • 介绍 Apex,并在 特定:
      • 文档 约定
      • 核心概念
      • 快速上手 教程
    • 类、对象和接口
    • 测试 Apex
    • 执行调控器和限制
    此外,高级程序员可以查看:
    • 触发器和批量请求最佳实践
    • 高级 Apex 编程 例
    • 了解 Apex Describe 信息
    • 异步执行(@future注释)
    • Batch Apex 和 Apex 调度程序

    使用开发环境编写 Apex

    有几种开发环境可用于开发 Apex 代码。开发商 控制台和 Visual Studio Code 的 Salesforce 扩展允许您编写、测试和 调试您的 Apex 代码。用户界面中的代码编辑器仅允许编写代码和 不支持调试。

    开发者控制台

    Developer Console 是一个集成开发环境,其中包含 可用于在 Salesforce 中创建、调试和测试应用程序的工具 组织。

    Developer Console 支持以下任务:
    • 编写代码 – 您可以使用源代码编辑器添加代码。另外,你 可以浏览组织中的包。
    • 编译代码 – 保存触发器或类时,代码为 自动编译。任何编译错误都会被报告。
    • 调试 – 您可以查看调试日志并设置有助于 调试。
    • 测试 – 可以执行特定测试类或所有测试的测试 ,您可以查看测试结果。此外,您可以检查 代码覆盖率。
    • 检查性能 – 您可以检查调试日志以查找性能 瓶颈。
    • SOQL 查询 – 您可以查询组织中的数据并查看 使用查询编辑器的结果。
    • 颜色编码和自动完成 – 源代码编辑器使用颜色 方案,使代码元素更容易阅读,并提供自动完成功能 用于类和方法名称。

    适用于 Visual Studio Code 的 Salesforce 扩展

    适用于 Visual Studio Code 的 Salesforce 扩展包包括用于在 Salesforce 平台上以轻量级、 可扩展的 VS Code 编辑器。这些工具提供了用于开发的功能 orgs(临时组织、沙盒和 DE 组织)、Apex、Aura 组件和 视觉力。

    有关安装和使用的信息,请参阅 web网站。

     

    提示

    如果您想开发自己的 Apex IDE,SOAP API 包括 编译触发器和类以及执行测试方法的方法,而 元数据 API 包括用于将代码部署到生产环境的方法。为 有关更多信息,请参阅部署 Apex 和使用 SOAP API 部署 Apex。

    Salesforce 用户界面中的代码编辑器

    Salesforce 用户 接口。所有类和触发器在保存时都会被编译,任何语法 错误被标记。在代码编译完毕且没有错误之前,无法保存代码。这 Salesforce 用户界面还会对代码中的行进行编号,并使用颜色编码来 区分不同的元素,例如注释、关键字、文本字符串等。
    • 对于对象上的触发器,请从对象的管理设置中转到 触发器,单击“新建”,然后在“正文”文本框中输入代码。
    • 对于课程,请从“设置”中输入“快速查找”框,然后选择“Apex” 类。单击“新建”,然后输入你的 正文文本框中的代码。Apex Classes
     

    注意

    您无法使用 Salesforce 用户界面在 Salesforce 生产组织。

    或者,您可以使用任何文本编辑器(如记事本)来编写 Apex 代码。然后 将代码复制并粘贴到应用程序中,或使用 API 调用之一 部署它。

    编写测试

    测试是成功长期发展的关键,也是 开发过程。我们强烈建议您使用测试驱动的 开发过程,即与 代码开发。

    为了促进健壮、无错误的代码的开发,Apex 支持创建和 执行单元测试。单元测试是验证 特定代码段工作正常。单元测试方法不带任何参数, 不向数据库提交任何数据,也不发送电子邮件。此类方法在方法定义中使用注释进行标记。 单元测试方法必须在测试类中定义,即用 批注的类。@IsTest@IsTest

     

    注意

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

    此外,在部署 Apex 或将其打包之前 AppExchange,则必须满足以下条件。

    • 单元测试必须至少覆盖 75% 的 Apex 代码,并且所有这些测试都必须 成功完成。

      请注意以下事项。

      • 将 Apex 部署到生产组织时,每个单元测试 默认情况下,将执行 Organization 命名空间。
      • 调用不计入 Apex 代码的一部分 覆盖。System.debug
      • 测试方法和测试类不计为 Apex 代码的一部分 覆盖。
      • 虽然只有 75% 的 Apex 代码必须被测试覆盖,但不要专注于 所覆盖代码的百分比。相反,请确保每次使用 涵盖您的申请案例,包括正面和负面案例, 以及批量和单条记录。这种方法可确保 75% 或更多 的代码包含在单元测试中。
    • 每个触发器都必须具有一定的测试覆盖率。
    • 所有类和触发器都必须成功编译。

    有关编写测试的详细信息,请参阅测试 Apex。

    将 Apex 部署到沙盒组织

    沙盒在单独的环境中创建 Salesforce 组织的副本。将它们用于 开发、测试和训练,而不会影响 生产组织。沙盒与您的生产组织隔离,因此您的 在沙盒中执行不会影响生产组织。

    将 Apex 从适用于 Visual Studio Code 的 Salesforce 扩展中的本地项目部署到 Salesforce 组织,请参阅 Salesforce Extensions for Visual Studio 代码。

    您还可以使用元数据 API 调用来 将 Apex 从开发人员组织部署到沙盒组织。deploy()

    一个有用的 API 调用是 。在开发中或 沙盒组织,可以对特定类运行单元测试,列表 类或命名空间。runTests()

    您还可以使用 Salesforce CLI。有关详细信息,请参阅针对任何组织进行开发。

    有关更多信息,请参阅部署 Apex。

    将 Apex 部署到 Salesforce 生产组织

    完成所有单元测试并验证 Apex 代码 正确执行,最后一步是将 Apex 部署到您的 Salesforce 生产中 组织。

    在 Visual Studio Code 编辑器中将 Apex 从本地项目部署到 Salesforce 组织,请参阅适用于 Visual Studio 的 Salesforce 扩展 代码。

    此外,您还可以通过 Salesforce 用户界面中的更改集来部署 Apex。

    有关更多信息和其他部署选项,请参阅部署 Apex 和构建和发布您的应用程序。

    将 Apex 代码添加到 AppExchange 应用程序

    您可以在为其创建的应用中包含 Apex 类或触发器 AppExchange。

    作为包的一部分包含的任何 Apex 必须至少具有 75% 的累积测试覆盖率。每个触发器还必须具有一定的测试覆盖率。当你 将您的软件包上传到 AppExchange,将运行所有测试以确保它们在没有 错误。此外,当包安装在 安装程序的组织。您可以指定在打包期间应运行哪些测试 通过用 注释它们来安装。包必须通过此测试子集 安装成功。@isTest(OnInstall=true)@isTest(OnInstall=true)

    创建自定义对象

    在此步骤中,您将创建一个名为 Book 的自定义对象,其中包含一个名为 价格。
    先决条件:

    沙盒 Professional 中的 Salesforce 帐户, Enterprise、Performance 或 Unlimited Edition 组织,或开发人员中的帐户 组织。

    有关创建沙盒组织的更多信息,请参阅 Salesforce 帮助中的“沙盒类型和模板”。要注册 免费的开发者组织,请参阅 Developer Edition 环境注册页面。

    1. 登录到您的沙盒或开发人员组织。
    2. 从自定义对象的管理设置中(如果您使用的是 Salesforce) “经典”,请单击“新建自定义对象”,或者如果您使用的是 Lightning Experience,选择“创建”|”自定义对象
    3. 输入“书籍”作为标签。
    4. 输入 Books 作为复数标签。
    5. 点击保存
      哒哒!现在,您已经创建了第一个自定义对象。现在让我们创建一个 自定义字段。
    6. “自定义字段和关系”部分中 图书详情页面,点击新建
    7. 选择“数字”作为数据类型,然后单击“下一步”。
    8. 输入字段标签的价格
    9. 在长度文本框中输入 16。
    10. 在小数位文本框中输入 2,然后单击下一步
    11. 单击“下一步”接受字段级的默认值 安全。
    12. 点击保存
    您刚刚创建了一个名为 Book 的自定义对象,并将自定义字段添加到 该自定义对象。自定义对象已经有一些标准字段,例如 Name 和 CreatedBy,并允许您添加更特定于 实现。在本教程中,Price 字段是 Book 对象的一部分,它是 由您将在下一步中编写的 Apex 类访问。

     

    添加 Apex 类

    在此步骤中,您将添加一个 Apex 类,该类包含用于更新书籍的方法 价格。此方法由将在下一个中添加的触发器调用 步。
    先决条件:
    • 沙盒中的 Salesforce 帐户 Professional、Enterprise、Performance 或 Unlimited Edition 组织,或开发人员组织中的帐户。
    • 这本书 自定义对象。
    1. 在“设置”中,在“快速查找”框中输入“Apex Classes”, 然后选择 Apex 类并单击新建
    2. 在类编辑器中,输入以下类定义:
      public class MyHelloWorld {
      
      }
      前面的代码是要向其添加一个的类定义 方法。Apex 代码通常包含在类中。此类定义为 ,其中 表示该类可供其他 Apex 类和触发器使用。查看更多 信息,请参阅类、对象和 接口。public
    3. 在类开始和结束之间添加此方法定义 括弧。
      public static void applyDiscount(Book__c[] books) {
         for (Book__c b :books){
            b.Price__c *= 0.9;
         }
      }

      此方法称为 , 它既是公共的,也是静态的。因为它是一个静态方法,所以你不需要 需要创建类的实例才能访问该方法,您可以 只需使用类的名称后跟点 (.) 和 方法。有关更多信息,请参见静态方法和实例方法。 变量和初始化代码。applyDiscount

      此方法采用一个参数,即 Book 记录列表,该参数分配给 变量 。请注意对象名称中的 。这表示它是您创建的自定义对象。标准对象 在 Salesforce 应用程序中提供的内容(例如帐户)不会以 this 结尾 后缀。books__cBook__c

      代码的下一部分包含方法定义的其余部分:

      for (Book__c b :books){
         b.Price__c *= 0.9;
      }

      请注意字段名称 后面的 。这表明它是 您创建的自定义字段。标准字段 默认情况下,Salesforce 中提供的点使用相同类型的点进行访问 符号,但没有 , for 示例,不以 .该语句采用旧值 的 ,乘以 0.9, 这意味着它的价值将打折 10%,然后存储新的 值添加到字段中。运算符是快捷方式。 写此语句的另一种方法是 。请参阅表达式运算符。__cPrice__c__cName__cBook__c.Nameb.Price__c *= 0.9;b.Price__cb.Price__c*=b.Price__c = b.Price__c * 0.9;

    4. 单击保存”以保存新类。你现在应该有 这个完整的类定义。
       
      public class MyHelloWorld {
         public static void applyDiscount(Book__c[] books) {
            for (Book__c b :books){
               b.Price__c *= 0.9;
            }
         }
      }
    现在,您有一个类,其中包含一些代码,这些代码遍历书籍列表和 更新每本书的“价格”字段。此代码是触发器调用的静态方法的一部分 您将在下一步中创建。applyDiscount

     

    添加 Apex 触发器

    在此步骤中,您将为自定义对象创建一个触发器,该对象调用您在上一步中创建的类的方法。Book__capplyDiscountMyHelloWorld
    先决条件:
    • 沙盒中的 Salesforce 帐户 Professional、Enterprise、Performance 或 Unlimited Edition 组织,或开发人员组织中的帐户。
    • MyHelloWorld(我的你好世界酒店) Apex类。

    触发器是在记录之前或之后执行的一段代码 从 Lightning 平台插入、更新或删除特定类型 数据库。每个触发器都使用一组上下文变量运行,这些变量提供 访问导致触发器触发的记录。所有触发器都批量运行; 也就是说,它们一次处理多条记录。

    1. 从书籍的对象管理设置中,转到“触发器”,然后单击“新建”。
    2. 在触发器编辑器中,删除默认模板代码并输入此触发器 定义:
       
      trigger HelloWorldTrigger on Book__c (before insert) {
      
         Book__c[] books = Trigger.new;
      
         MyHelloWorld.applyDiscount(books);
      }

      第一行代码定义触发器:

      trigger HelloWorldTrigger on Book__c (before insert) {

      它为触发器指定一个名称,指定其操作的对象,以及 定义导致它触发的事件。例如,此触发器是 称为 HelloWorldTrigger,它对对象进行操作,并在将新书籍插入到 数据库。Book__c

      触发器中的下一行将创建一个名为 触发名为 的上下文变量。触发上下文变量,例如在 所有触发器,并提供对导致触发器的记录的访问 火灾。在本例中,包含将要插入的所有新书。booksTrigger.newTrigger.newTrigger.new

       
      Book__c[] books = Trigger.new;

      代码中的下一行调用类中的方法。它传入了一系列新书。applyDiscountMyHelloWorld

      MyHelloWorld.applyDiscount(books);
    现在,您拥有了更新所有书籍价格所需的所有代码。 插入。然而,仍然缺少一块拼图。单元测试是一个 编写代码的重要部分,并且是必需的。在下一步中,您将看到为什么会这样 就是这样,您将能够添加一个测试类。

    添加测试类

    在此步骤中,您将添加一个具有一个测试方法的测试类。您还运行测试并 验证代码覆盖率。测试方法练习并验证触发器中的代码,以及 类。此外,它还使您能够达到触发器的 100% 代码覆盖率,并且 类。
    先决条件:
    • 沙盒中的 Salesforce 帐户 Professional、Enterprise、Performance 或 Unlimited Edition 组织,或开发人员组织中的帐户。
    • 这 HelloWorldTrigger Apex 触发器。
     

    注意

    测试是开发过程的重要组成部分。部署之前 Apex 或将其打包为 AppExchange,则必须满足以下条件。

    • 单元测试必须至少覆盖 75% 的 Apex 代码,并且所有这些测试 必须成功完成。
    • 每个触发器都必须具有一定的测试覆盖率。
    • 所有类和触发器都必须成功编译。

    请注意以下事项。

    • 将 Apex 部署到生产组织时,每个单元测试 默认情况下,将执行组织命名空间。
    • 调用不计算在内 作为 Apex 代码覆盖率的一部分。System.debug
    • 测试方法和测试类不计为 Apex 代码的一部分 覆盖。
    • 虽然只有 75% 的 Apex 代码必须被测试覆盖,但不要 重点关注所覆盖代码的百分比。相反,请确保 涵盖应用程序的每个用例,包括 阳性和阴性病例,以及批量和单条记录。 此方法可确保 75% 或更多的代码被 单元测试。
    1. 在“设置”中,在“快速”中输入 Apex 类 “查找”框,然后选择“Apex 类”和 单击“新建”。
    2. 在类编辑器中,添加此测试类定义,然后单击“保存”。
       
      @IsTest 
      private class HelloWorldTestClass {
          @IsTest
          static void validateHelloWorld() {
             Book__c b = new Book__c(Name='Behind the Cloud', Price__c=100);
             System.debug('Price before inserting new book: ' + b.Price__c);
      
             // Insert book
             insert b;
          
             // Retrieve the new book
             b = [SELECT Price__c FROM Book__c WHERE Id =:b.Id];
             System.debug('Price after trigger fired: ' + b.Price__c);
      
             // Test that the trigger correctly updated the price
             System.assertEquals(90, b.Price__c);
          }
      }

      此类是使用注释定义的。以这种方式定义的类应该只 包含测试方法以及支持这些测试方法所需的任何方法。 为测试创建单独的类的一个优点是类 使用 Don’t Count 定义 违反组织 6 MB 的 Apex 代码限制。您也可以将注释添加到单个 方法。有关更多信息,请参见@IsTest注释和执行 调速器和限制。@IsTest@IsTest@IsTest

      该方法是使用注释定义的。这 注解意味着如果对数据库进行了更改,则会滚动这些更改 执行完成后返回。您不必删除任何测试数据 在测试方法中创建。validateHelloWorld@IsTest

      注意

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

      首先,测试方法创建一本书并将其插入数据库 暂时。该语句在调试中写入价格值 日志。System.debug

      Book__c b = new Book__c(Name='Behind the Cloud', Price__c=100);
      System.debug('Price before inserting new book: ' + b.Price__c);
      
      // Insert book
      insert b;

      插入书籍后,代码将检索新 插入的书籍,使用最初分配给书籍的 ID,当它 已插入。然后,该语句记录触发的新价格 改 性。System.debug

      // Retrieve the new book
      b = [SELECT Price__c FROM Book__c WHERE Id =:b.Id];
      System.debug('Price after trigger fired: ' + b.Price__c);

      当类运行时,它会更新字段并将其值减少 10%.以下测试验证该方法是否运行并生成了 预期 结果。MyHelloWorldPrice__capplyDiscount

      // Test that the trigger correctly updated the price
      System.assertEquals(90, b.Price__c);
    3. 若要运行此测试并查看代码覆盖率信息,请切换到“开发人员” 安慰。
    4. 在开发者控制台中,点击测试 |新运行
    5. 若要选择测试类,请单击“HelloWorldTestClass”。
    6. 若要将 HelloWorldTestClass 类中的所有方法添加到测试运行中,请单击“添加” 已选择
    7. 单击运行
      测试结果将显示在“测试”选项卡中。或者,您可以展开 “测试”选项卡中的测试类,用于查看运行了哪些方法。在本例中, 该类仅包含一个测试方法。
    8. “总体代码覆盖率”窗格显示此测试类的代码覆盖率。自 查看此测试涵盖的触发器中代码行的百分比,其中 为 100%,则双击 HelloWorldTrigger 的代码覆盖率行。因为触发器调用一个方法 来自 MyHelloWorld 类,此类 也有覆盖率(100%)。若要查看类覆盖率,请双击 MyHelloWorld
    9. 若要打开日志文件,请在“日志”选项卡中双击 日志列表。将显示执行日志,包括日志记录信息 关于触发器事件、对 applyDiscount 方法的调用以及 触发。
    到目前为止,您已经完成了使用 在开发环境中运行的测试。在现实世界中,在你 测试了您的代码并对此感到满意,您想要部署代码和任何 生产组织的必备组件。下一步将演示如何执行此操作 部署已创建的代码和自定义对象。

     

    将组件部署到生产环境

    在此步骤中,您将部署 Apex 代码和之前创建的自定义对象 使用更改集添加到您的生产组织。
    先决条件:
    • 沙盒 PerformanceUnlimited 或 Enterprise Edition 组织中的 Salesforce 帐户。
    • 这 HelloWorldTestClass Apex 测试类。
    • 沙盒与生产组织之间的部署连接,该连接 允许生产组织接收入站更改集。看 Salesforce 联机帮助中的“更改集”。
    • “创建和上传变更集”用户创建、编辑、 或上传出站更改集。

    此过程不适用于开发人员组织,因为更改集是 仅在 PerformanceUnlimitedEnterprise 或 Database.com Edition 组织。如果您拥有 Developer Edition 帐户,则可以 使用其他部署方法。有关更多信息,请参阅部署 Apex。

    1. 在“设置”中,在“快速查找”框中输入“出站变更集”,然后选择“出站 变更集
    2. 如果出现启动页面,请单击“继续”。
    3. 在“更改集”列表中,单击“新建”。
    4. 输入更改集的名称(例如 HelloWorldChangeSet)和描述(可选)。 点击保存
    5. 在“更改集组件”部分中,单击“添加”。
    6. 从组件类型下拉列表中选择 Apex Class,然后选择 MyHelloWorld 和列表中的 HelloWorldTestClass 类,然后单击添加到更改集
    7. 单击“查看/添加依赖项”以添加依赖项 组件。
    8. 选中顶部复选框以选择所有组件。单击“添加到” 更改集
    9. 在更改集页面的“更改集详细信息”部分中,单击“上传”。
    10. 选择目标组织(在本例中为生产),然后单击上传
    11. 更改集上传完成后,将其部署到生产环境中 组织。
      1. 登录到您的生产组织。
      2. 在“设置”中,在“快速查找”框中输入“入站更改集”,然后选择“入站” 更改集
      3. 如果出现启动页面,请单击“继续”。
      4. 在等待部署的更改集列表中,单击更改集的 名字。
      5. 单击 Deploy
    在本教程中,您学习了如何创建自定义对象,如何添加 Apex 触发器、类和测试类。最后,您还学习了如何测试代码,以及如何 以使用更改集上传代码和自定义对象。