DEX455

Apex 类与 Java 类的区别

课程介绍

详细比较 Apex 类与 Java 类的核心差异:内部类(仅一层/类似静态内部类)、访问修饰符(private默认/public/global)、继承(final默认/virtual/override显式)、异常类(必须以Exception结尾)。涵盖类编辑器功能(语法高亮/正则搜索/撤销重做)、命名约定与遮蔽、命名空间(System/Schema/命名空间前缀)、名称解析优先级(局部变量→类名→命名空间)、类型解析顺序、Apex代码版本管理与包版本设置、自定义类型在List排序(Comparable/Comparator/Collator)和Map/Set中的使用(equals/hashCode完整示例)。

课程章节

本课程共有 17 个章节

  • 1

    Differences Between Apex Classes and Java Classes

    第 146 页

    今天我们来看看 Apex 的类,它跟 Java 的类用起来很像,但有一些不一样的地方,这些差异在写代码的时候一定要注意。 首先,Apex 里的内部类跟 Java 的内部类规则不同。在 Java 里,内部类可以访问外部类的私有成员;但在 Apex 里,内部类和外部类更像是两个独立的类,内部类不能直接访问外部类的实例变量或方法,除非你给它传过去。所以不要把 Java 的习惯带进来。 然后是静态方法,Apex 里的静态方法有一些限制。它不能访问任何非静态的成员,包括实例变量和非静态方法。这一点跟 Java 类似,但 Apex 的静态上下文隔离得更彻底,要注意。 接下来是访问修饰符。Apex 的访问修饰符有 public、private、global 和 protected。注意这里的 global 修饰符是 Salesforce 特有的,它用于让一个类或方法在整个 Salesforce 组织的命名空间之外也能被访问,比如在托管包之间共享。而 Java 里没有 global,这是关键差异。protected 在 Apex 里跟 Java 的实现也有点区别,在 Apex 里 protected 只能用于内部类和接口,不像 Java 那样可以用于方法和变量,使用范围小很多。 还有一个容易被忽略的点,Apex 的类默认是 final 的。在 Java 里,类默认是可以被继承的,除非你明确写 final;但在 Apex 里,如果你想让一个类可以被扩展,必须显式加上 virtual 关键字,因为类的默认行为是 final,也就是不可继承。这个跟 Java 完全相反。 关于异常,Apex 自定义异常类必须继承自某个已有的 Exception 类,不能随便丢一个对象,这和 Java 一样,但是 Apex 内置异常类型更少,要建自定义异常就 extend Exception。 在开发环境里,Apex 的类编辑器是 Salesforce 平台自带的,它有一些命名约定:类名通常是大写字母开头,跟 Java 一样,但不允许你用某些保留关键字做类名。还有,所有的 Apex 代码在保存时都会被编译,语法错误会直接提示。 如果你在开发托管包,命名空间前缀是一个重要的概念。你的类在包里会带上命名空间,比如你的命名空间叫 myns,那类名实际上是 myns__MyClass,使用时要注意加上这个前缀。但在您自己的开发组织里,写代码时可以省略命名空间,部署到包环境里,系统会自动处理。 说到版本控制,Salesforce 有向后兼容的 API 代码版本控制功能。你可以为你的 Apex 类指定特定的 API 版本,这样即使平台更新了,代码行为依然保持那个版本下的表现,不会因为平台升级而突然报错。这会通过类文件的元数据 xml 里的版本来设置。 最后,使用集合类型如 List、Map、Set 存放自定义类型的时候,要注意排序和相等性。如果你要把自定义对象作为 Map 的键,或者用 Set 去重,就必须实现正确的 hashCode 和 equals 方法。这两个方法决定了对象如何在集合里比较和存储,不重写的话,默认是用对象的地址,可能不是你想要的结果。要实现 Comparable 接口来自定义排序,就得提供 compareTo 方法。 总的来说,Apex 的类与 Java 很相似,但在很多细节上不一样,开发的时候一定要掌握这些差异,特别是默认 final、静态方法限制、访问修饰符里的 global、还有集合中自定义对象的使用问题,这样才能写出可靠、好维护的代码。 今天就讲到这儿,我们下次再继续深入了解 Apex 的其他特性。

    查看详情
  • 2

    Major Differences — Apex vs. Java

    第 147 页

    同学,我们来看一下这一页的重点:Apex的类和Java的类虽然长得像,但其实有不少关键的差别,你一定要小心。 首先,Apex里可以有内部类和内部接口,但是注意——只能有一层深。什么意思呢?就是你不能像套娃一样,类里面有类,里面的类再套一层类,Apex不允许这样做。 第二,静态方法和静态变量只能放在最外层的顶级类里。内部类里是不能声明静态成员的。 然后,访问修饰符要注意。在Java里,你不写访问修饰符,默认是包级别的访问权限,对吧?但在Apex里,如果你什么都不写,默认是private。而且Apex的public含义和Java不一样。在Apex中,public只表示在同一个应用程序或者命名空间内可以访问,它并不是完全公开的。真正相当于Java里public的是global,只有加了这个,才能让代码在任何地方被访问到。 还有,在Apex里,所有的方法和类默认都是final的,也就是说你不能继承和重写。如果你想让一个类可以被继承,或者让一个方法可以被覆盖,一定要用virtual关键字来显式声明。 关于异常,Apex要求所有的自定义异常类必须扩展自基础的Exception类,而且类名必须以“Exception”结尾,否则编译不通过。 最后一点,你可以在触发器和匿名执行块里面定义类和接口,但这种情况下,这些类和接口只能是local的,作用范围仅限于那个块内。 记住这些差异,对你写好Apex代码会很关键。我们继续看下一页。

    查看详情
  • 3

    Class Definition Creation

    第 148 页

    好的同学们,咱们现在来看看怎么在Salesforce里创建一个Apex类。这个操作其实很简单,你只需要用到Salesforce自带的类编辑器。 首先,打开“设置”,在左侧的快速查找框里输入“Apex类”,然后点进去。 进去之后,你会看到右上角有个“新建”按钮,点它,咱们就进入创建类的界面了。 在这儿,你可以先做一些版本设置。一个是API版本,另一个是托管包版本,这部分是可选的,但建议你了解下,确保你的代码和当前环境兼容。然后,就是核心部分了——在下方的编辑区里,敲入你的Apex代码。 需要注意,单个类的代码量是有上限的,最多可以有100万个字符。不过这个限制只计算你的实际业务逻辑,不包括代码里的注释、测试方法,还有那些标记为@IsTest的测试类。所以放心,空间还是很充裕的。 代码写好之后,直接点击“保存”,这个类就建好了。 另外,还有一个省事儿的小功能。如果你手里有SDF文件,也就是那种可以描述实体结构的文件,你可以在这个界面上直接点击“生成自SDF”按钮,系统就会根据文件内容自动帮你生成一个Apex类,不用自己手敲了。 好,创建类的步骤就这几步,是不是挺容易的?咱们接着看下一部分。

    查看详情
  • 4

    The Apex Class Editor — Features

    第 149 页

    同学们,我们来看一下Apex Class Editor,就是咱们写Apex代码的那个编辑器,它有一些很方便的功能,我先快速过一遍,然后挑几个你可能平时没太留意的点,简单说说。 首先,编辑器支持,语法高亮,,这个大家天天见,不同关键词、字符串、注释都会用不同颜色显示,读代码的时候一目了然。然后,搜索和替换,,这个很强大,它不光能找普通的文本,还支持,正则表达式,,而且你甚至可以在替换的时候用`$1`、`$2`这样的分组变量。比如说你搜了一个带有括号的模式,替换的时候写`$1`就会把第一个括号匹配到的内容原样放进去,做批量修改变得特别快。 再比如,转到行,,你直接按个快捷键,输个行号,光标“唰”就过去了,调试错误的时候特别有用。当然,撤销/重做,是标配。还有,字体大小控制,,你可以放大缩小,视力不好的同学也不用眯着眼睛瞅。编辑器的状态栏会实时告诉你现在光标在第几行、第几列,这就是,行/列位置指示器,。 关于规模,单个Apex类的长度限制是多少呢?答案是,最多100万个字符,。这是什么概念呢?一般一个类写几千行已经很夸张了,100万个字符足够你写一本小说塞进去了,所以基本不用担心写不下。 还有两个存储层面的小细节,非常关键。每个Apex类在系统里保存的时候,都会连带它的,API版本号,,以及一个叫,`isValid`的布尔标志,。这个标志你可以理解成“这个类现在是健康的吗”。如果因为外部元数据发生了变化——比方说你引用的某个自定义字段被删了,或者一个外部对象改了名——那这个类的依赖就被破坏了,系统会自动把`isValid`设为`false`。但你别慌,它不会立刻报错来烦你,而是等到,下次这个类被调用的时候,,Salesforce会默默地把它重新编译一次。如果编译成功了,`isValid`又重新变回`true`,一切照旧;如果编译失败,那你就得去修代码了。所以这套机制就像是给每个类配了一个“自动体检”的标签,能尽量让你的代码在元数据变更后还能自动恢复运行。 好了,关于编辑器本身的这些小功能我们就先聊到这儿,大家在这些细节上稍微留个意,以后写代码能顺手不少。有什么问题随时问。

    查看详情
  • 5

    Naming Conventions

    第 150 页

    好,同学们,我们来看这一页Slide,它讲的是Apex里的命名规范和一些有意思的命名空间规则。 首先,我们通常会建议大家遵循Java的命名标准。那具体怎么用呢?类名,我们要求以大写字母开头,采用驼峰式的写法,比如`AccountTriggerHandler`,这样一眼就能看出它是一个类。方法名呢,Slide这里特别提示要用,大写动词开头,。不过在实际开发中,大部分团队习惯用小写字母开头的动词,比如`calculateTotal()`,让方法名读起来像一个动作。但是,这并不影响我们理解规则本身,重点还是要让你的代码,变量名称有意义,,比如`accountName`、`totalAmount`,绝对不要用`a`、`b`、`c`这种让人摸不着头脑的名字。 接下来,我们说说“同名”的问题。在同一个类里面,你不可以定义一个跟类本身同名的内部类,也不可以定义同名的类和接口。举个例子,如果外部类叫`MyClass`,那么内部类也叫`MyClass`就是不合法的,编译会直接报错。同样,一个叫`MyInterface`的接口,你就不能再写一个同名的类`MyInterface`,因为它们共享同一个“类型”的命名空间。 但有趣的是,方法和变量在类里面有自己单独的命名空间。也就是说,,变量、方法和内部类之间,是可以取相同名字的,,这是完全合法的。比如我可以在一个类里同时拥有一个方法叫做`test`,一个变量叫做`test`,再加一个内部类也叫`test`,编译器完全不会混淆,因为它们分别属于不同的命名空间,互不干扰。 所以大家记住:类名、接口名是属于同一个“大池子”,方法和变量各有各的“小池子”。平时命名的时候,不要故意去起一样的名字让读代码的人头疼,但了解这个规则能帮你更好地读懂Apex的底层逻辑。 好,这一页的重点就这些,大家记住这几个命名习惯和命名空间的区别,对以后写规范代码会很有帮助。

    查看详情
  • 6

    Name Shadowing

    第 151 页

    同学们,我们来看一个在Apex编程中经常会遇到,但可能没仔细琢磨过的概念——,变量遮蔽,,也叫影子变量。 你可以这样想:就像在房间里,你关了大灯,只开了一盏台灯,台灯的光就把大灯的光“遮蔽”了。代码里也一样,当你在一个代码块里声明了一个和外部同名的变量,那么在这个内部范围内,你用的就是内部的这个变量,外部的那个暂时被“藏”起来了。 比如在Apex类里,你有个成员变量叫`s`,然后你写了一个方法,方法参数恰好也叫`s`。那在方法的身体里,你直接用`s`,指的是哪个?是参数,不是成员变量。那如果你需要访问被遮住的成员变量怎么办?就用`this.s`来显式地指代对象自己的那个`s`。这一点完全和Java一样,你经常会看到构造方法里写`this.s = s;`,就是这个作用,把参数的值赋给自己的成员。 这种遮蔽不仅发生在方法和局部变量上,在,父子类的继承,中也有。如果父类定义了一个成员变量,子类又定义了一个同名成员变量,那么子类的变量会遮蔽父类的那个。你用子类对象去访问时,默认拿到的是子类的变量。但父类的那个变量还在,只不过被遮住了,需要用特殊方式才能访问到。 同样,,静态变量,也可以被隐藏。假设父类定义了一个静态变量`S`,子类也可以再声明一个同名的静态变量`S`。这时候你在子类代码里直接写`S`,访问的是子类自己的静态变量。如果想用父类的那个,就得带上父类的名字,写成`ParentClass.S`。要记住,静态变量是属于类的,不是属于实例的。所以你不能通过类的实例去引用静态变量,那样会让代码含义不清。要么直接用变量名(在当前类上下文里),要么就用类名作为前缀,比如`MyClass.myStaticVar`。 简单总结一下:变量遮蔽是一个自然的语言作用域规则,帮你写出更清晰、更安全的代码。只要记得,当同名变量出现时,最近的那个优先级最高。要想拿到被遮住的,就得用`this`、`super`或类名来明确指出。这样,在写Apex的时候你就能准确控制你到底在用哪个变量了。

    查看详情
  • 7

    Namespace Prefix

    第 152 页

    各位同学,我们来看一下这个关于命名空间前缀的知识点。在Salesforce里,如果你要开发一个托管包,发布到AppExchange上去,那就必须要用到一个叫命名空间前缀的东西。 简单说,这个命名空间前缀就像是一个全球唯一的代号。开发者在注册的时候,要选一个独一无二的字符串,之后这个代号就会永远跟你的包绑定在一起了。一旦分配了,就再也不能更改,所以选的时候一定要想好,别回头想改都改不了。 那为什么要用这个东西呢?主要是为了避免名字冲突。你想啊,全球那么多开发者都在建自定义对象、自定义字段,万一大家都起了同一个名字,比如都叫“订单对象”,那就乱了。但加上了你的命名空间前缀,比如你注册的前缀是“myNS”,那你的订单对象全名就变成了“myNS__Order__c”,别人哪怕也有个“Order__c”,但它们带有不同的前缀,系统就能分得清清楚楚。 你看这个格式,记住就是用两个下划线把前缀和对象名连起来,后面再跟两个下划线和“c”。比如说“namespacePrefix__ObjectName__c”,如果是字段也是一样,“namespacePrefix__FieldName__c”。这里幻灯片上写的“names_predict__obj_or_field_list_c”其实就是想表达这个意思,大家在脑子里把它替换成实际的前缀和名称就行了。 在Apex代码里,系统还有个很聪明的默认解析规则:它会假设你当前代码所在的命名空间,就是你所有引用的默认命名空间。什么意思呢?比如你在自己的托管包里写Apex类,这个包已经带上了前缀“myNS”,那你在代码里直接写“Order__c”这样不带前缀的对象名,Apex解析器会自动帮你把它识别成“myNS__Order__c”。也就是说,在包内部引用同一个包里的东西时,你可以省掉前缀不写,代码写起来更清爽。 但要注意,如果你在包外面,或者在一个没有命名空间的环境里,去引用从AppExchange安装的托管包里的对象或者字段,那就必须老老实实把前缀加上。简单记就是:只有引用安装来的托管包里的自定义组件时,才用得到命名空间前缀,自己玩自己内部的东西就不用。 好,这一页的核心就是三点:一是命名空间前缀是什么、为什么要用;二是它的标准格式;三是Apex里的默认解析规则帮你省事,但跨包引用时必须写全。大家理解了吗?我们继续下一张。

    查看详情
  • 8

    Using the System Namespace

    第 153 页

    同学们,今天我们来看一下Apex里一个非常基础、但又很容易让人迷糊的概念——System命名空间。 简单来说,在Apex中,System就是一个默认的命名空间。平时你调用像debug、assert这些方法的时候,即使不写“System.”开头,代码也能正常运行。比如你直接写 `debug('Hello');` ,Apex其实会自动帮你补上 `System.debug('Hello');`。 但是,这里有一个容易踩坑的地方:如果你的组织里自己建了一个Apex类,名称偏偏和系统内置的类一模一样,那Apex运行的时候,就会优先使用你自己定义的那个类,而不是系统的。为了明确告诉平台“我要用的是标准的系统类”,你就必须加上 `System.` 这个前缀。 另外,还要和大家理清一个概念:System既是一个命名空间,也是一个真实的类。它里面有很多静态方法。当你调用 `System.debug()` 的时候,其实就是在使用System类里的debug方法。而有趣的是,你还可以写成 `System.System.debug()` ,这俩写法完全等价。因为外层的System是命名空间指向,内层的System是类名,最终调用的都是同一个方法。 好,这段内容的核心就这些,我们下节课继续。

    查看详情
  • 9

    Using the Schema Namespace

    第 154 页

    同学们好,今天我们来快速消化一个很实用的小知识点,它叫,Schema 命名空间,。 在 Apex 里,我们经常会跟“元数据”打交道,比如想动态拿到一个对象的字段列表、或是查看某个字段的类型,这时候就需要用到 Schema 命名空间里提供的各种类和方法。幸运的是,Schema 命名空间是,隐式导入,的——也就是说,你完全不用写 import 语句,可以直接使用 `Schema.SObjectType`、`Schema.DescribeSObjectResult` 这样的系统类。 不过,这里藏着一个容易踩到的坑。如果你的组织里定义了一个 Apex 类,它的名字,恰好跟某个标准对象或自定义对象同名,——比方说,你写了一个叫 `Account` 的 Apex 类。那么当你在代码里直接打出 `Account` 的时候,Apex 运行时会优先理解为你的自定义类,而不是那个标准的 Account sObject,这就产生了命名冲突。 怎么解决呢?办法很简单:,给对象名前面加上 Schema 命名空间,,写成 `Schema.Account`。这种完全限定的写法,会明确告诉 Apex 运行时:“我要用的是那个 sObject,而不是同名的类。” 这样歧义就立刻消除了。 总结一下一句话:只要遇到类和 sObject 重名的情况,记得用 `Schema.对象名` 的方式来区分,这个小习惯能帮你避开很多莫名其妙的 Bug。 这一页就讲到这里,我们继续往下看下一个话题。

    查看详情
  • 10

    Namespace, Class, and Variable Name Precedence

    第 155 页

    同学们,今天我们来看一个Apex解析器的小秘密:当代码里出现名字冲突时,它到底怎么判断谁是变量、谁是类、谁是命名空间。 大家想想,你写了一个表达式,比如 `myVar.field`,那解析器首先要搞清楚 `myVar` 是什么。问题来了——你完全可以定义一个局部变量叫 `Account`,同时也有个类叫 `Account`,甚至还有个命名空间叫 `Account`。解析器遇到 `Account` 的时候,它不会头疼,因为它有固定的优先级顺序: ,第一优先,局部变量。, ,第二优先,类名称。, ,最后才看命名空间。, 也就是说,只要当前代码块里存在一个叫这个名字的局部变量,解析器立刻就把它认作变量,后面的点号就在这个变量的“域”里继续查找字段。只有在没有这个局部变量时,它才去翻类列表,把它当成类来处理。类也没有?那才轮到命名空间。 这个顺序不仅适用于字段访问,方法调用也同样遵循。比如 `something.method()`,解析器会把 `something` 先当局部变量,把 `method` 当成它的方法;如果不是局部变量,就检查 `something` 是不是类名,那么 `method` 就被解释为静态方法;再不是,才往命名空间去找。 更关键的一点来了:一旦解析器确认了前面这段的身份,比如它已经明确 `sBody` 是一个局部变量,那么整个表达式的上下文就“锁定”在了这个变量的内部。后面无论你打多少个点,都只能在这个变量的结构里找字段,不能再跳回 Apex 的其它元素。换句话说,`sBody` 后面的字段名称,永远不可能再去代表一个类、一个命名空间或者一个独立的Apex表达式。 打个形象的比方:你回到家门一开,解析器认出了你家门牌号是“卧室”,那么进去之后,你看到的衣柜、床铺都只能是这个卧室里的东西,不会突然变成隔壁教室的黑板。代码里,`sBody.Field__c` 的 `Field__c` 就只能是 `sBody` 这个对象的一个字段,绝不会被解释成一个叫 `Field__c` 的类或者命名空间了。 所以,记住三句话: ,先局部,再类,最后命名空间;, ,方法调用同样套路;, ,一旦踩进变量的地盘,后面就乖乖只看字段,别想跨界。, 好,这个要点就讲到这儿,大家可以在练习里故意起几个同名变量感受一下解析的优先级,加深理解。

    查看详情
  • 11

    Type Resolution and System Namespace for Types

    第 156 页

    同学们,今天咱们来看一个Apex里挺重要的概念,就是类型解析的顺序。你写代码的时候,可能会碰到像 `T1.T2` 这样用点号连起来的写法,Apex到底怎么去理解它?是把它当成一个内部类,还是一个命名空间下的类,又或者是sObject的关系字段?这时候就需要一套规则来决定了。 Apex解析器在判断一个类型的时候,会按照一个固定的优先级去查找,这个顺序一共分四步: 第一步,先看它是不是一个,纯量类型,。什么是纯量类型呢?就是那些最基本的数据类型,比如 `Integer`、`String`、`Boolean` 这些,系统直接就能认出来。 那如果不是纯量类型,就走第二步:看看是不是,本地定义的类型,。本地定义的类型指的是什么?比如你在当前这个类里面,又定义了一个内部类,或者在同个文件里、同一个作用域下声明的类型。Apex会优先从这个范围里查找。 如果本地也没有,那就进入第三步:,进行类名称查找,。这是说在整个组织或者命名空间里,去搜索有没有叫这个名字的顶级类,或者是其他类里面可以访问到的内部类。这一步相当于一个更广泛的类路径查找。 最后,如果前面三步都没找到,Apex会走到第四步:检查,系统类型,,其中最典型的就是 ,sObject,。比如像 `Contact`、`Account` 这些标准或自定义的对象,就会被当作系统类型来处理。 那这个顺序在实际中是怎么体现的呢?咱们就拿 `T1.T2` 这种形式来举例。当Apex看到 `T1.T2`,它不是随意猜测,而是严格按照刚才的顺序来决定 `T1` 的身份。 如果当前类的内部,有一个内部类叫 `T1`,那按照“本地定义的类型”优先的原则,`T1.T2` 指的就是这个内部类 `T1` 里的 `T2` 类型。如果没有本地内部类,但是有一个顶级类叫 `T1`,那 `T1.T2` 就被解析成那个顶级类里的内部类 `T2`。如果连顶级类也没有,可能 `

    查看详情
  • 12

    Apex Code Versions

    第 157 页

    现在让我们来聊聊Apex代码的版本设置。这个概念其实非常简单,你可以把它想象成给咱们写的代码拍一张“快照”,记录下它当初是在什么环境下成长的。 具体来说,每个Apex类和触发器都会悄悄存一个版本信息——也就是它当时所使用的Salesforce API版本。这么做的目的,主要是为了向后兼容。什么意思呢?就是哪怕将来Salesforce平台升级了,API的行为有了一些变化,你之前写的代码依然会按照当初那个版本的老规矩来运行,不会突然就崩掉或者出奇怪的问题。这就像你习惯了用某个版本的计算器,即便厂家推出了新款,按键位置变了,你手里的那个老计算器还是按照原来的方式工作,很让人安心,对吧? 另外,如果你的代码里用到了从AppExchange安装的托管包,比如说某个报表工具或者流程组件,那么这个类还会同时记住这些托管包各自的版本号。这样一来,就算那个包以后升级了,加了新功能或者改了内部逻辑,你现有的代码还是会精准地调用它当时依赖的那个包版本。这就等于给你的代码加上了一层保护,让它始终绑定在已知、稳定的行为上。 所以总的来讲,版本设置就像是一个时间胶囊,确保了不管外面的世界怎么变,你的类和触发器都能稳稳地跑在自己熟悉的规则里。这样就最大程度上避免了意外,也让咱们的维护工作轻松很多。

    查看详情
  • 13

    Apex Code Versions — Key Concepts

    第 158 页

    同学们,今天咱们来聊一个对代码维护非常有用的知识点:,包的版本管理,。 简单说,当你安装一个包时,设置的版本会直接影响包里面Apex代码能被公开调用的接口。也就是说,版本一旦锁定,哪怕未来新版本里有方法被标记为不建议使用,你的代码依然可以继续引用,不会突然报错。 这就像你买了一本书的某一版,以后作者更新了内容,你的旧书还是照样能看,不受影响。 那咱们应该选择哪个版本呢?通常,咱们都推荐使用最新的API版本。因为Apex里新增的类和方­法,不管它们是什么时候加进去的,基本上对所有API版本都可用——除了ConnectApi命名空间下有少数方法是跟版本绑定的。 但是,并不是所有人都能随时升级,如果你因为一些原因用不了最新版,Salesforce给的建议是:至少要保持在过去三年以内的版本。太旧的版本,以后可能会不被支持。 另外要记住,尽量把项目中用到的API版本整合到最少的几个版本里,别一个项目里十几个不同的版本混着用。这会大大增加复杂性,调试和维护都让人头疼。 总结一下:优先用最新的,不行就用近三年的,并且尽量统一版本。这样你的代码就能稳定又清爽。 好了,这一小节课就到这里,大家明白了吗?

    查看详情
  • 14

    Setting API & Package Versions

    第 159 页

    我们来看一下这一页,关于 Salesforce API 版本设置,其实很简单。 你在写 Apex 类或者触发器的时候,Salesforce 会用一个特定的 API 版本来编译你的代码。如果你需要调整它,操作步骤是这样的:打开你要编辑的类或触发器,在页面上找到“版本设置”这个按钮,点进去,选择你想要的 API 版本,然后保存就行了。 那这里有个很容易被忽视的点:如果你在不同的类之间传递对象,比如你从 A 类传一个 SObject 给 B 类,而这两个类用的 API 版本不一样,那么这个对象里的字段行为,听谁的?答案是——接收方的版本设置说了算。也就是 B 类用什么版本,这个对象在 B 类里就表现出那个版本的字段特性。所以要特别注意,尽量让关联紧密的类保持在同一个版本,避免出现字段访问不一致的问题。 接下来,我们讲托管包的版本设置。如果你在类或触发器里引用了托管包,你还需要单独设定包的版本。操作方式是:在类或触发器的详细信息页,点编辑,然后切换到“版本设置”选项卡,在这里你能选所用的包版本。选定之后保存。关键点是:就算以后你在 org 里安装了更高版本的同一个包,这个类还是会死死绑在你当初选的这个版本上,除非你手动去更新。这一点在长期维护的包升级后尤其重要,很容易造成依赖的类还是跑在旧版 API 上,所以记得回头检查。 最后一个注意点:一旦你的类里引用了某个包,那在这个类的版本设置里,你就不能删除掉这个包的版本配置了。所以设置的时候要想好,改了就要用到,但这同时也是一种保护,避免你误删导致代码依赖断裂。 总结下来,就是三点:普通类或触发器通过版本设置切换 API 版本;对象行为由接收方版本决定;托管包版本要单独设,而且不会自动升级,引用了就不能删。记住了这些,版本问题就不容易踩坑了。

    查看详情
  • 15

    Custom Types in Collections

    第 160 页

    同学们,咱们在Salesforce里经常会用到集合,比如列表、映射或者集。我们知道这些集合里可以放基础类型,比如字符串、数字。但实际开发中,我们经常需要把自定义的Apex类对象也放到集合里面去。 比如说,您用一个列表来存自己定义的“客户”对象,这是完全没问题的,而且还能排序——只要您实现了Comparable接口。这很简单。 不过呢,当您把自定义类型的对象放到映射里当键,或者放到集里面去重的时候,就需要注意了。因为映射的键不能重复,集里的元素也不能重复,系统怎么判断两个对象是不是一样的呢?它默认是比较内存地址,但很多时候我们需要按业务逻辑来判断,比如两个客户对象,如果ID相同,就应该认为是同一个。 所以,为了让映射和集能正确识别您的自定义类型,您必须在类里提供两个方法:equals和hashCode。equals用来定义两个对象什么时候相等,hashCode返回一个整数值,用来快速在集合里定位。这两个方法必须同时覆盖,而且要遵守约定:如果两个对象相等,它们的hashCode必须相同。如果您不提供,系统会用默认的,那集和映射就可能得出意想不到的结果。 简单说,只要您打算把自己的类用在集里,或者作为映射的键,就别忘了写好equals和hashCode方法。这样集合才能听话,按您想要的方式去重和匹配。好,关于集合里的自定义类型,我们就先说到这儿。

    查看详情
  • 16

    Lists of Custom Types & Sorting

    第 161 页

    同学们,这节课我们来聊聊如何对保存了自定义对象的列表进行排序。你可能会想,这有什么难的?别急,在Apex里,如果你直接把一堆自己定义的类的实例放进列表,然后调用`sort`方法,平台并不知道该按什么规则来比较它们。所以我们需要亲自告诉它:两个对象之间,谁大谁小。 实现这个比较逻辑,通常有两条路可以走。第一,让你的Apex类实现`Comparable`接口,就是在类里定义一个`compareTo`方法。当你写好这个方法后,直接对列表调用`sort()`,它就会自动按你写的规矩排序。第二条路,是单独写一个比较器类,实现`Comparator`接口,里面有个`compare`方法,然后把比较器的实例作为参数传给`list.sort()`。不管你用哪种方式,排序的标准和顺序——是升序还是降序——都取决于你`compareTo`或者`compare`方法的返回值:返回负数表示第一个对象排在前面,返回正数表示第二个排前面,零就是相等。 这里有个特别需要注意的地方:如果你的排序涉及字符串,并且需要照顾不同语言、地区的习惯,比如中文拼音顺序或德语的特殊字符,那可以使用`Collator`类来做区域敏感的比较。但是要小心,因为这种排序结果会随着当前用户的语言和地区设置而变化。在触发器或者那些要求输出顺序绝对固定的代码里,千万不要使用区域敏感排序,否则可能同一个触发操作,张三看到的记录顺序和李四看到的不一样,很容易产生意外bug。所以,区域敏感排序建议只用在界面展示这类灵活的场合。 好,这个知识点就讲到这儿,大家动手试试看吧。

    查看详情
  • 17

    Custom Types in Map Keys & Sets

    第 162 页

    好,我们来看这一节内容。假如你在 Apex 里自定义了一个类,比如一个“学生”类,有姓名和学号。然后你想把这个学生对象放进一个 Set 集合里,或者作为 Map 的键来用。 这时你要注意了,Apex 需要知道怎么判断两个学生对象是不是同一个。默认情况下,它是按内存地址比较的,也就是两个内容完全一样的学生对象,只要不是同一个引用,就会被当成不同的元素。这往往不是我们想要的结果,对吧? 所以,你必须在你自定义的类里,自己写两个特殊的方法:,equals, 和 ,hashCode,。 ,equals 方法,就是用来定义“相等”的规则。通常你会把关键字段的值逐个比较,全都一样才算相等。 ,hashCode 方法,要返回一个整数,而且规则是:如果两个对象 equals 返回 true,那它们的 hashCode 也必须相同。这个哈希值会被集合内部用来快速定位对象,保证唯一性。 举个例子,你的学生类如果只要学号相同就是同一个学生,那么 equals 就只比较学号,hashCode 也基于学号生成。 还有个很重要的警告:,如果你把一个对象放进 Set 或作为 Map 的键之后,又修改了它的某个字段值,那可就糟了。, 因为它的哈希值也变了,集合内部就再也找不到这个对象了,就变成了“幽灵”成员,容易产生 bug。所以,最好保证放进集合的对象在之后不要被修改,或者说让这些关键字段不可变。 最后告诉你一个额外的好处:一旦你正确实现了 equals 和 hashCode,咱们常用的双等号“==”也会变聪明。它不再傻傻地比较内存引用,而是会调用你写的 equals 方法,按值来比较,这样代码就更符合直觉啦。 简单总结一下:自定义类型要放进集合当键或元素,千万别忘了重写 equals 和 hashCode,还要记得对象一旦放进去,就别再动它的关键字段了。明白了吗?

    查看详情