类、对象与接口
Apex 类以 Java 类为模型。你将定义、实例化和扩展类,并使用接口、Apex 类版本、属性以及其他相关类概念。类是创建对象的模板或蓝图,对象是类的实例。例如,PurchaseOrder 类描述了一个完整的采购订单及其所有可执行操作。所有对象都有状态(对象知道关于自己的信息)和行为(对象可以做的事情)。类可以包含变量(成员变量)和方法,还可以包含其他类、异常类型和初始化代码。
接口类似于所有方法都未实现的类——方法签名存在,但每个方法体为空。要使用接口,另一个类必须通过实现所有方法体来实现它。
Apex 类定义
在 Apex 中,你可以定义顶级类(外部类)及内部类(定义在另一个类内部的类)。内部类只能有一层深。类定义语法:
private | public | global
[virtual | abstract | protected | with sharing | without sharing | inherited sharing]
class ClassName [implements InterfaceNameList] [extends ClassName]
{
// 类的主体
}
访问修饰符说明:
private(内部类默认):只被当前代码段知晓。只能用于内部类或标记了 @IsTest 的顶级测试类public:类在你的应用程序或命名空间中可见global:类对所有 Apex 代码可见。所有包含webservice方法的类必须声明为 global。如果方法或内部类声明为 global,外部顶级类也必须定义为 globalwith sharing/without sharing:指定此类的共享模式virtual:声明此类允许扩展和重写。除非类被定义为 virtual,否则不能用override关键字重写方法abstract:声明此类包含抽象方法(只声明签名、未定义方法体)
注意:避免使用标准对象名称作为类名——会导致意外结果。一个类可以实现多个接口,但只能扩展一个现有类。Apex 不支持多重继承。
版本化行为变更(API 65.0+):抽象或重写方法需要 protected、public 或 global 访问修饰符。如果不包含,方法访问默认变为 private,这将导致编译错误。API 61.0+ 中,私有方法不再被子类中具有相同签名的实例方法重写。
类变量与方法
声明变量
[public | private | protected | global] [final] [static] data_type variable_name [= value]
// 示例
private static final Integer MY_INT;
private final Integer i = 1;
定义方法
Apex 方法由以下元素组成:可选的修饰符;必需的返回值数据类型(无返回值使用 void);必需的输入参数列表(逗号分隔,最多 32 个参数);必需的方法体(花括号包围)。
[public | private | protected | global] [override] [static] data_type method_name(input params) {
// 方法体
}
用户定义方法的特点:
- 可在任何使用系统方法的地方使用
- 可以递归调用
- 可以有副作用(如 DML insert 初始化 sObject 记录 ID)
- 可引用自身或同一类/匿名块中后面定义的方法(Apex 分两阶段解析,无需前向声明)
- 可以重载:同名方法可以有不同的参数列表。解析器选择最匹配的实现,无精确匹配时使用类型强制规则寻找近似匹配——如果找到多个近似匹配则生成解析时异常
- 所有用户定义类型支持
clone方法
方法参数传递:值传递 vs 引用传递
原始类型参数(如 Integer、String)按值传递:方法内对参数的更改仅限于方法作用域内,方法返回后更改丢失。
public class PassPrimitiveTypeExample {
public static void debugStatusMessage() {
String msg = 'Original value';
processString(msg);
// msg 的值未改变,仍然是旧值
System.assertEquals(msg, 'Original value');
}
public static void processString(String s) {
s = 'Modified value'; // 不影响原始变量
}
}
非原始类型参数(如 List、sObject)按引用传递:方法内可以修改对象的字段值,但不能改变引用指向另一个对象。
public class PassNonPrimitiveTypeExample {
public static void createTemperatureHistory() {
List<Integer> fillMe = new List<Integer>();
reference(fillMe);
// fillMe 被修改,包含 5 个温度值
System.assertEquals(fillMe.size(), 5);
List<Integer> createMe = new List<Integer>();
referenceNew(createMe);
// createMe 仍指向原始的空列表,未被修改
System.assertEquals(createMe.size(), 0);
}
public static void reference(List<Integer> m) {
m.add(70); m.add(68); m.add(75); m.add(80); m.add(82);
}
public static void referenceNew(List<Integer> m) {
m = new List<Integer>{55, 59, 62, 60, 63}; // 只改变局部引用
}
}
使用构造函数
构造函数是从类蓝图创建对象时调用的代码。不需要为每个类编写构造函数——没有自定义构造函数时,系统会生成一个与包含类可见性相同的默认无参构造函数。构造函数的语法类似于方法,但没有显式返回类型且不被对象继承。
编写了带参构造函数后,如果需要无参构造函数,必须自己创建。编写构造函数后就不再拥有默认的公共无参构造函数。
构造函数可以重载——一个类可以有多个构造函数,每个参数列表不同。使用 this(...) 语法进行构造函数链式调用:
public class TestObject2 {
private static final Integer DEFAULT_SIZE = 10;
Integer size;
// 无参构造函数——通过 this(...) 链式调用带参构造函数
public TestObject2() {
this(DEFAULT_SIZE);
}
// 带参构造函数
public TestObject2(Integer ObjectSize) {
size = ObjectSize;
}
}
TestObject2 myObject1 = new TestObject2(42);
TestObject2 myObject2 = new TestObject2();
每个构造函数必须有不同的参数列表。参数顺序不同也算不同的构造函数签名。
访问修饰符
Apex 允许在定义方法和变量时使用 private、protected、public 和 global 访问修饰符:
private(默认):方法或变量仅在其定义的 Apex 类内部可访问。如果不指定,默认就是 privateprotected:方法或变量对定义类中的任何内部类以及扩展该定义类的类可见。只能用于实例方法和成员变量。比默认设置更宽松public:方法或变量可被特定包中的所有 Apex 访问。注意:Apex 中的public与 Java 不同——在 Apex 中,如果想要类似 Java 的 public 效果,需要使用globalglobal:方法或变量可被任何有权限访问该类的 Apex 代码使用,不限于同一应用程序。必须在 SOAP API 或其他 Apex 代码中引用时使用。如果声明为 global,包含它的类也必须声明为 global。建议尽量少用 global——跨应用程序依赖难以维护
注意:接口中定义的方法具有与接口相同的访问修饰符(public 或 global)。
静态与实例成员
在 Apex 中,你可以有静态方法、变量和初始化代码(但 Apex 类不能是静态的),以及实例成员和局部变量。
静态成员特点
- 与类关联,只能在外部类中使用
- 仅在类加载时初始化一次
- 不作为 Visualforce 页面的视图状态传输
- 静态变量只在 Apex 事务范围内是静态的——不是跨服务器或整个组织的。值在单个事务上下文中持久存在,跨事务边界会重置。如果一个 Apex DML 请求导致触发器多次触发,静态变量在这些触发器调用间持久存在
- 静态方法作为工具方法使用,从不依赖实例成员变量的值
- 静态变量不能被类实例访问——必须通过类名访问
跨触发器通信:单个事务生成的所有触发器可以通过查看和更新相关类中的静态变量来相互通信。递归触发器可以使用类变量的值来确定何时退出递归。
public with sharing 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;
}
}
}
}
重要:触发器中定义的静态变量不会在同一事务的不同触发器上下文之间保留其值(如 before insert 和 after insert 之间)。应在类中定义静态变量。
实例成员与初始化代码
实例初始化代码块在每次从该类实例化对象时执行,在构造函数之前运行:
{
// 实例初始化代码
}
静态初始化代码块只在类首次使用时执行一次:
static {
// 静态初始化代码——用于初始化静态 final 变量、填充静态 Map 等
colorMap.put('red', new RGB(255, 0, 0));
colorMap.put('cyan', new RGB(0, 255, 255));
}
一个类可以有任意数量的静态或实例初始化代码块,按文件中出现的顺序执行。
Apex 属性
Apex 属性类似于变量,但可以在代码中对属性值进行额外的操作。属性可以用于:验证更改前的数据、在数据更改时触发操作(如修改其他成员变量)、暴露从其他来源获取的数据。属性定义包含一个或两个代码块:
- get 访问器:在读取属性时执行
- set 访问器:在属性被赋新值时执行。系统传递一个与属性同类型的隐式参数
value
只读属性:只有 get。只写属性:只有 set。读写属性:两者都有。属性不能在接口上定义。
public class BasicProperty {
public integer prop {
get { return prop; }
set { prop = value; }
}
}
BasicProperty bp = new BasicProperty();
bp.prop = 5; // 调用 set 访问器
System.assertEquals(5, bp.prop); // 调用 get 访问器
自动属性
属性不需要在 get 或 set 访问器代码块中包含额外代码——留空即可定义自动属性,代码更紧凑、更易维护:
public class AutomaticProperty {
public integer MyReadOnlyProp { get; } // 只读
public double MyReadWriteProp { get; set; } // 读写
public string MyWriteOnlyProp { set; } // 只写
}
静态属性与访问器修饰符
声明为 static 的属性在静态上下文中执行,无法访问非静态成员变量。属性访问器可以有自己的访问修饰符——该修饰符覆盖属性的访问修饰符,但必须比属性本身更严格:
global virtual class PropertyVisibility {
public integer X { private get; set; } // 私有读、公共写
global integer Y { get; public set; } // 全局读、类内写
public integer Z { get; protected set; } // 类内读、子类写
}
扩展类(继承)
扩展另一个类的类继承该扩展类的所有方法和属性。扩展类可以使用 override 关键字重写现有的 virtual 方法,提供不同的实现——这称为多态。
// 基类
public virtual class Marker {
public virtual void write() {
System.debug('Writing some text.');
}
public virtual Double discount() {
return .05;
}
}
// 扩展类
public class YellowMarker extends Marker {
public override void write() {
System.debug('Writing some text using the yellow marker.');
}
}
// 多态示例
Marker obj1 = new Marker();
Marker obj2 = new YellowMarker();
obj1.write(); // 'Writing some text.'
obj2.write(); // 'Writing some text using the yellow marker.'
Double d = obj2.discount(); // 继承的方法也可用
扩展类可以有基类没有的额外方法。接口也可以扩展另一个接口——扩展接口继承被扩展接口的所有方法和属性。
完整的类示例
以下示例展示了 Apex 类的所有关键特性——顶级类、静态成员、实例成员、内部类、接口、接口扩展、抽象类、具体实现、异常内部类、构造函数链式调用等:
public class OuterClass {
// 静态 final 变量(常量)
private static final Integer MY_INT;
public static String sharedState;
public static Integer getInt() { return MY_INT; }
static { MY_INT = 2; }
// 实例成员
private final String m;
{ m = 'a'; } // 实例初始化块
// 内部接口
public virtual interface MyInterface { void myMethod(); }
interface MySecondInterface extends MyInterface { Integer method2(Integer i); }
// 内部类——实现接口
public virtual class InnerClass implements MySecondInterface {
private final String s;
private final String s2;
{ this.s = 'x'; }
private final Integer i = s.length();
InnerClass() { this('none'); }
public InnerClass(String s2) { this.s2 = s2; }
public virtual void myMethod() { }
public Integer method2(Integer i) { return this.i + s.length(); }
}
// 抽象子类
public abstract class AbstractChildClass extends InnerClass {
public override void myMethod() { }
public abstract Integer abstractMethod();
}
// 具体实现
public class ConcreteChildClass extends AbstractChildClass {
public override Integer abstractMethod() { return 5; }
}
// 异常内部类
public virtual class MyException extends Exception {
public Double d;
MyException(Double d) { this.d = d; }
}
}
接口
接口为代码提供抽象层——它将方法的具体实现与声明分离。这样你可以根据具体应用有不同的方法实现。定义接口类似于定义新类——你定义了一个新的数据类型。接口名称可以在任何使用其他数据类型名称的地方使用。
// 接口定义——采购订单的通用原型
public interface PurchaseOrder {
Double discount();
}
// 客户采购订单实现
public class CustomerPurchaseOrder implements PurchaseOrder {
public Double discount() { return .05; } // 5% 折扣
}
// 员工采购订单实现
public class EmployeePurchaseOrder implements PurchaseOrder {
public Double discount() { return .10; } // 10% 折扣
}
注意:在 Managed - Released 包版本中上传类后,不能向全局接口添加方法。任何实现接口的类必须定义接口中包含的所有方法。
自定义迭代器
要使用自定义迭代器,必须创建一个实现 Iterator 接口的 Apex 类。Iterator 接口有两个方法:hasNext()(返回 Boolean——如果集合中还有下一项则返回 true)和 next()(返回集合中的下一项)。
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.');
}
}
}
// 使用 Iterable 接口
public class CustomIterable implements Iterable<Account> {
public Iterator<Account> iterator() {
List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 10];
return new CustomIterator(accounts);
}
}
实现 Iterable 接口的对象可以在 for-each 循环和批处理 Apex 中使用。
关键字
final 关键字
- final 变量只能被赋值一次。静态 final 变量可在静态初始化代码块中或定义处初始化。成员 final 变量可在初始化代码块、构造函数或定义处初始化
- 定义常量:标记为
static和final - 方法和类默认是 final 的——不能使用 final 关键字声明类或方法。如需重写,使用
virtual关键字 - 不能对属性使用 final 关键字
instanceof 关键字
在运行时验证对象是否是特定类的实例:
if (Reports.get(0) instanceof CustomReport) {
CustomReport c = (CustomReport) Reports.get(0);
} else {
// 处理非 CustomReport 对象
}
版本化行为:API 32.0+ 中,如果左操作数为 null,instanceof 返回 false(31.0 及更早版本中返回 true)。API 60.0+ 中,如果声明类型始终是目标类型的实例,编译会失败。
super 关键字
被扩展自 virtual 或 abstract 类的类使用。可以重写父类的构造函数和方法:
public virtual class SuperClass {
public String mySalutation, myFirstName, myLastName;
public SuperClass() { mySalutation = 'Mr.'; myFirstName = 'Carl'; myLastName = 'Vonderburg'; }
public SuperClass(String salutation, String firstName, String lastName) { ... }
public virtual void printName() { System.debug('My name is ' + mySalutation + myLastName); }
}
public class Subclass extends Superclass {
public override void printName() {
super.printName(); // 调用父类方法
System.debug('But you can call me ' + super.getFirstName());
}
public Subclass() {
super('Madam', 'Brenda', 'Clapentrap'); // 调用父类构造函数
}
}
this 关键字
两种用法:(1) 点符号形式表示当前实例; (2) 括号形式用于构造函数链式调用(必须是构造函数中的第一条语句)。
transient 关键字
声明不能被保存、不应作为 Visualforce 页面视图状态传输的实例变量。常用于仅在页面请求期间需要的字段,减小视图状态大小。常见自动视为 transient 的类型包括 PageReferences、XmlStream 类、Savepoints 集合。
共享关键字
使用 with sharing、without sharing 和 inherited sharing 关键字指定是否强制执行共享规则。默认共享模式为 with sharing。
with sharing
强制执行当前用户的共享规则。Salesforce 建议始终显式设置此关键字以确保 Apex 代码在当前用户上下文中运行。
without sharing
不强制执行当前用户的共享规则。谨慎使用——此类可以访问当前用户原本无权访问的记录。仅对需要系统级访问的类使用。
inherited sharing
以调用类的共享模式运行。这是一种高级技术,在运行时确定共享模式。使用 inherited sharing 的类在作为入口点(Aura 组件控制器、LWC 的 @AuraEnabled 方法、Visualforce 控制器、Apex REST 服务、异步 Apex)时以 with sharing 模式运行。仅在从已建立的 without sharing 上下文显式调用时才以 without sharing 模式运行。
触发器中的共享:Apex 触发器不能有显式共享声明——它们始终在 without sharing 上下文中隐式运行。然而,触发器体内的数据库操作(SOQL、DML)默认以 USER_MODE 运行——用户模式重新应用当前用户的记录共享规则,有效覆盖触发器的 without sharing 上下文。
| 共享模式 | 使用场景 |
|---|---|
| with sharing | 作为默认模式使用,除非有特殊需求 |
| without sharing | 谨慎使用。确保不暴露敏感数据。适用于需要向当前用户授予目标性权限提升的场景 |
| inherited sharing | 用于需要灵活支持不同共享模式的服务类 |
重要:建议始终在包含数据库操作或 SOQL 查询的 Apex 类中包含显式的共享声明。API 67.0+ 中,没有显式共享声明的类以 with sharing 模式运行。
注解概述
Apex 注解修改方法或类的使用方式,类似于 Java 注解。注解以 @ 符号开头。可以对同一类或方法使用多个注解,每个注解单独一行。Apex 支持的注解包括:
- @AuraEnabled
- @Deprecated
- @Future
- @InvocableMethod
- @InvocableVariable
- @IsTest
- @JsonAccess
- @NamespaceAccessible
- @ReadOnly
- @RemoteAction
- @SuppressWarnings
- @TestSetup
- @TestVisible
- Apex REST 注解:@RestResource、@HttpDelete、@HttpGet、@HttpPatch、@HttpPost、@HttpPut
@AuraEnabled 与 @Deprecated
@AuraEnabled:使客户端和服务器端可以访问 Apex 控制器方法。只有带此注解的方法才会暴露给 Lightning 组件(LWC 和 Aura)。API 44.0+ 中,可使用 @AuraEnabled(cacheable=true) 在客户端缓存方法结果(仅限只读数据)。API 55.0+ 支持 scope='global' 全局缓存。API 55.0+ 中不允许对 @AuraEnabled 方法使用重载。
@Deprecated:标记后续版本中不再引用的方法、类、异常、枚举、接口或变量。新订阅者看不到已弃用的元素,现有订阅者和 API 集成继续正常工作。webservice 方法和变量不能被弃用。弃用后不能撤销。
@Future 与 @InvocableMethod
@Future:标记异步运行的方法。Salesforce 现在推荐使用 Queueable Apex 替代 future 方法(Queueable 提供额外的优势:Job ID、支持非原始类型和作业链)。Future 方法必须是 static void,参数只能是原始类型及其集合。不允许接受 sObject 或对象参数。使用 @Future(callout=true) 允许外呼。
public with sharing class MyFutureClass {
@Future
static void myMethod(String a, Integer i) {
System.debug('Method called with: ' + a + ' and ' + i);
}
}
@InvocableMethod:标记可从 REST、Apex、Flow、Agentforce Agent 调用为可调用动作的方法。支持动态输入输出值和 describe 调用。
public with sharing class AccountQueryAction {
@InvocableMethod(
label='Get Account Names'
description='Returns account names for specified IDs.'
category='Account'
)
public static List<String> getAccountNames(List<ID> ids) {
// 实现
}
}
@IsTest 与 @JsonAccess
@IsTest:定义仅包含测试代码的类和方法。@IsTest 类不计入组织的 6MB 代码限制。支持 @IsTest(SeeAllData=true) 访问组织数据、@IsTest(OnInstall=true) 在包安装时运行测试、@IsTest(isParallel=true) 并行运行。
@JsonAccess:在 Apex 类级别控制实例的序列化和反序列化。如果限制 JSON/XML 操作,运行时抛出 JSONException。支持 Serializable 和 Deserializable 枚举值。
@NamespaceAccessible、@ReadOnly、@RemoteAction
- @NamespaceAccessible:使共享命名空间的 2GP 包可访问方法和变量
- @ReadOnly:标记 Visualforce 控制器方法为只读——放宽查询限制
- @RemoteAction:使方法可从 Visualforce 页面的 JavaScript 远程调用
@TestSetup、@TestVisible 与 Apex REST 注解
- @TestSetup:创建所有测试方法可用的通用测试记录——在每个测试类执行前运行一次
- @TestVisible:使 private/protected 成员在测试代码中可见
- @SuppressWarnings:不执行操作,但可向第三方工具提供信息
- Apex REST 注解:
@RestResource(urlMapping='/yourUrl')将类暴露为 REST 资源;@HttpGet、@HttpPost、@HttpPut、@HttpDelete、@HttpPatch标记 HTTP 方法处理程序
类与类型转换
Apex 支持向上转型(隐式——子类对象赋值给父类变量,始终安全)和向下转型(显式——使用括号转换,运行时检查)。使用 instanceof 在转换前检查是安全转换的最佳实践:
// 向上转型(隐式)
Animal a = new Dog(); // 安全
// 向下转型(显式)
if (a instanceof Dog) {
Dog d = (Dog) a; // 安全
}
集合转型与自定义类型
集合子类型:如果 T 是 U 的子类型,则 List<T> 可以赋值给 List<U>。自定义类型可在 List 中排序——实现 Comparable 或 Comparator 接口。要执行基于语言环境的排序,使用 Collator 类(避免在触发器中使用)。
自定义类型作为 Map 键与 Set 元素:必须在类中提供 equals() 和 hashCode() 方法。Apex 使用这两个方法来确定对象键的相等性和唯一性。添加到集合后如果对象字段值改变,该对象将无法再被找到。
public class PairNumbers {
Integer x, y;
public PairNumbers(Integer a, Integer b) { x = a; y = b; }
public Boolean equals(Object obj) {
if (obj instanceof PairNumbers) {
PairNumbers p = (PairNumbers) obj;
return ((x == p.x) && (y == p.y));
}
return false;
}
public Integer hashCode() { return (31 * x) ^ y; }
}
// 验证 Map 键唯一性和 Set 去重
Map<PairNumbers, String> m = new Map<PairNumbers, String>();
PairNumbers p1 = new PairNumbers(1, 2);
PairNumbers p3 = new PairNumbers(1, 2); // 与 p1 重复
m.put(p1, 'first');
m.put(p3, 'third'); // 覆盖 first
System.assertEquals(2, m.size()); // Map 只有 2 个条目
Set<PairNumbers> s1 = new Set<PairNumbers>();
s1.add(p1); s1.add(p2); s1.add(p3);
System.assertEquals(2, s1.size()); // Set 只有 2 个唯一条目
本章全面涵盖了 Apex 中类、对象和接口的所有核心概念。从基本类定义到继承、接口、自定义迭代器、注解系统、共享模式以及自定义类型在集合中的使用,这些知识构成了 Apex 面向对象开发的完整基础。