欢迎大家关注 github.com/hsfxuebao/j… ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈
63. 注意字符串连接的性能
字符串连接操作符(+)是将几个字符串组合成一个字符串的便捷的方法。对于生成单行输出或构造一个小的、固定大小的对象的字符串表示形式,它是可以的,但是它不能伸缩。重复使用字符串连接运算符来连接n个字符串需要n的平方级的时间。这是由于字符串是不可变的这一事实导致的结果(条目 17)。当连接两个字符串时,需要复制这两个字符串的内容。
例如,考虑这个方法,它通过为每个项目重复连接一行来构造账单语句的字符串表示:
// Inappropriate use of string concatenation - Performs poorly!
public String statement() {
String result = "";
for (int i = 0; i < numItems(); i++)
result += lineForItem(i); // String concatenation
return result;
}
如果项的数量很大,则该方法的性能非常糟糕。为了达到可接受的性能,使用StringBuilder代替String来存储正在构建的语句:
public String statement() {
StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
for (int i = 0; i < numItems(); i++)
b.append(lineForItem(i));
return b.toString();
}
自Java 6以来,为了使字符串连接更快,已经做了大量工作,但是这两个方法在性能上的差异仍然很大:如果numItems返回100个元素,每个lineForItem返回一个固定长度为80个字符串,那么在我的机器上运行,第二个方法的速度是第一个方法的6.5倍。由于第一种方法在项目数量上是平方级增长的,而第二种方法是线性的,所以随着项目数量的增加,性能差异会变得越来越大。注意,第二个方法预先分配了一个足够大的StringBuilder来保存整个结果,从而消除了自动增长的需要。即使使用默认大小的StringBuilder,它仍然比第一个方法快5.5倍。
道理很简单:除非性能无关紧要,否则不要使用字符串连接操作符组合多个字符串。而是使用StringBuilder的append方法。或者,使用字符数组,或者一次处理一个字符串,而不是把它们组合起来。
64. 通过接口引用对象
条目 51中指出,应该使用接口而不是类作为参数类型。更通常地说,应该更喜欢使用接口而不是类来引用对象。如果存在适当的接口类型,那么应该使用接口类型声明参数、返回值、变量和属性。真正需要引用对象的类的惟一时机是使用构造方法创建它的时候。为了具体说明这一点,考虑LinkedHashSet的情况,它是Set接口的一个实现。养成这样的习惯:
// Good - uses interface as type
Set<Son> sonSet = new LinkedHashSet<>();
不要是下面这个样子:
// Bad - uses class as type!
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
如果养成了使用接口作为类型的习惯,那么你的程序将更加灵活。如果决定要切换实现,只需在构造方法中更改类名(或使用不同的静态工厂)。例如,第一个声明可以改为:
Set<Son> sonSet = new HashSet<>();
所有的代码都会继续工作。周围的代码不知道旧的实现类型,所以它不会注意到这一变化。
有一点需要注意:如果原始实现提供了接口的常规约定不需要的某些特殊功能,并且代码依赖于该功能,那么新实现提供相同的功能至关重要。 例如,如果围绕第一个声明的代码依赖于LinkedHashSet的排序策略,那么在声明中用HashSet替换LinkedHashSet是不正确的,因为HashSet不保证迭代顺序。
那么,为什么要更改实现类型呢?因为第二个实现比原来的实现提供了更好的性能,或者因为它提供了原来的实现所缺乏的理想功能。例如,假设一个属性包含一个HashMap实例。将其更改为EnumMap将提供更好的性能和与键的自然顺序一致的迭代顺序,但是只能在键类型为枚举类型的情况下使用EnumMap。将HashMap更改为LinkedHashMap将提供可预测的迭代顺序,性能与HashMap相当,而不需要对键类型作出任何特殊要求。
你可能认为使用变量的实现类型声明变量是可以的,因为可以同时更改声明类型和实现类型,但是不能保证这种更改能否正常编译。如果客户端代码对原始实现类型使用了替换时不存在的方法,或者客户端代码将实例传递给需要原始实现类型的方法,那么在进行此更改之后,代码将不再编译。使用接口类型声明变量可以保持诚实可靠。
如果不存在适当的接口,则通过类而不是接口引用对象是完全合适的。 例如,考虑值类,例如String和BigInteger。 值类很少用多个实现类来编写。 它们通常是final的,很少有相应的接口。 将这样的值类用作参数,变量,属性或返回类型是完全合适的。
没有适当接口类型的第二种情况是属于框架的对象,其基本类型是类而不是接口。 如果一个对象属于这样一个基于类的框架,最好用相关的基类来引用它,它通常是抽象类,而不是它的实现类。 许多java.io包下的类(如OutputStream)都属于此类。
没有适当接口类型的最后一种情况是实现接口的类,但也提供了在接口中找不到的额外方法——例如,PriorityQueue具有Queue接口上不存在的comparator方法。 只有当程序依赖于额外的方法时,才应该使用这样的类来引用它的实例,这种情况应该是非常少见的。
这三种情况并不是面面俱到的,而仅仅是为了传达通过合适的类引用对象的情况。在实践中,给定对象是否具有适当的接口应该是显而易见的。如果是这样,使用接口引用对象,程序将更加灵活和流行。如果没有合适的接口,就使用类层次结构中提供所需功能的最小的具体类。
65. 接口优于反射
核心反射工具java.lang.reflect提供对任意类的编程访问。 给定一个Class对象,可以获得Constructor,Method和Field实例,这些实例表示由Class实例表示的类的构造方法,方法和属性。 这些对象提供对类的成员名称,属性类型,方法签名等的编程访问。
此外,Constructor,Method和Field实例允许反射性地操作它们的底层对应物:可以通过在Constructor,Method和Field实例上调用方法来构造实例,调用方法和访问底层类的属性。 例如,Method.invoke方法允许在任何类的任何对象上调用任何方法(受通常的安全性约束)。 反射允许一个类使用另一个类,即使在编译前者时后者类并不存在。 然而,这种能力是有代价的:
-
失去了编译时类型检查的所有好处,包括异常检查。如果一个程序试图通过反射调用一个不存在的或不可访问的方法,会在运行时失败,除非采取了特殊的预防措施。
-
执行反射访问所需的代码笨拙而冗长。写起来很乏味,读起来很困难。
-
性能受损。反射方法调用比普通方法调用慢得多。到底慢了多少还很难说,因为有很多因素在起作用。在我的机器上,调用一个没有输入参数和返回int类型的方法时,反射方法执行要比普通方法慢11倍。
有一些复杂的应用程序需要反射。示例包括代码分析工具和依赖注入框架。即使是这样的工具,随着它的缺点变得越来越明显,也在逐渐远离反射。如果你对应用程序是否需要反射有任何疑问,那么它可能是不需要的。
通过以非常有限的形式使用反射,可以获得反射的许多好处,同时花费很少。对于许多必须使用在编译时不可用的类的程序,在编译时存在一个适当的接口或父类来引用该类( 条目64)。如果是这种情况,可以使用反射创建实例,并通过它们的接口或父类正常地访问它们。
例如,这是一个创建Set<String>实例的程序,其实例的类由第一个命令行参数指定。 程序将剩余的命令行参数插入到集合中并打印它。 无论第一个参数如何,程序都会打印剩余的参数,并删除重复项。 但是,打印这些参数的顺序取决于第一个参数中指定的类。 如果指定java.util.HashSet,则它们以明显随机的顺序打印; 如果指定java.util.TreeSet,则它们按字母顺序打印,因为TreeSet中的元素是按顺序排序的:
// Reflective instantiation with interface access
public static void main(String[] args) {
// Translate the class name into a Class object
Class<? extends Set<String>> cl = null;
try {
cl = (Class<? extends Set<String>>) // Unchecked cast!
Class.forName(args[0]);
} catch (ClassNotFoundException e) {
fatalError("Class not found.");
}
// Get the constructor
Constructor<? extends Set<String>> cons = null;
try {
cons = cl.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
fatalError("No parameterless constructor");
}
// Instantiate the set
Set<String> s = null;
try {
s = cons.newInstance();
} catch (IllegalAccessException e) {
fatalError("Constructor not accessible");
} catch (InstantiationException e) {
fatalError("Class not instantiable.");
} catch (InvocationTargetException e) {
fatalError("Constructor threw " + e.getCause());
} catch (ClassCastException e) {
fatalError("Class doesn't implement Set");
}
// Exercise the set
s.addAll(Arrays.asList(args).subList(1, args.length));
System.out.println(s);
}
private static void fatalError(String msg) {
System.err.println(msg);
System.exit(1);
}
虽然这只是一个演示程序,但它演示的技术非常强大的。演示程序可以很容易地变成泛型集合测试程序,通过积极地操纵一个或多个实例并检查它们是否遵守Set约定来验证指定的Set实现。 同样,它可以变成泛型集合性能分析工具。 事实上,这种技术足以实现一个成熟的服务提供者框架(service provider framework)(条目 1)。 通常,这种技术就是你在反射中所需要的。
这个例子说明了反射的两个缺点。 首先,该示例在运行时生成六个不同的异常,如果不使用反射实例化,则所有这些异常都是编译时错误。 (为了好玩,可以通过传入适当的命令行参数使程序生成六个异常中的每一个)。第二个缺点是需要25行繁琐的代码才能从其名称生成类的实例, 而构造函数方法使用一行代码即可。 可以通过捕获ReflectiveOperationException来减少程序的长度,该异常是Java 7中引入的各种反射异常的父类。这两个缺点仅限于实例化对象的程序部分。 实例化后,该集合与任何其他Set实例无法区分。 在真实的程序中,大量的代码因此不会受这种限定的反射使用的影响。
如果编译此程序,会获得未经检查的强制转换警告。 这个警告是合法的, 因此转换Class<? extends Set<String>> 也会成功,即使指定的集合不是Set接口的实现。在这种情况下,程序在实例化类时抛出ClassCastException异常。 要了解有关抑制警告的信息,请阅读条目 27。
反射的合法(如果很少)用途是管理类对运行时可能不存在的其他类、方法或属性的依赖关系。如果你正在编写一个必须针对其他包的多个版本运行的包,这将非常有用。该技术是根据支持包所需的最小环境(通常是最老的版本)编译包,并反射访问任何较新的类或方法。要使此工作正常进行,如果试图访问的新类或方法在运行时不存在,则必须采取适当的操作。适当的行动可能包括使用一些替代方法来完成相同的目标,或者使用简化的功能进行操作。
总之,反射是一种功能强大的工具,对于某些复杂的系统编程任务是必需的,但是它有很多缺点。如果编写的程序必须在编译时处理未知的类,则应该尽可能只使用反射实例化对象,并使用在编译时已知的接口或父类访问对象。
66. 明智谨慎地使用本地方法
Java Native Interface(JNI)允许Java程序调用本地方法(native methods),这些方法是用本地编程语言(如C或C ++)编写的方法。 从历史上看,本地方法有三个主要用途。 它们提供对特定于平台设备(如注册表)的访问。 它们提供对现有本地代码库的访问,包括提供对遗留数据库的数据访问。 最后,本地方法用于以本地语言编写应用程序的性能关键部分,以提高性能。
现在已经不再建议使用本地方法来提高性能。 在早期版本(Java 3之前)中,它通常是必需的,但从那时起JVM就变得更快了。 对于大多数任务,现在可以在Java中获得可比较的性能。 例如,当在Java版本1.1中添加java.math时,BigInteger依赖于用C语言编写的一个快速多精度算术库。在Java 3中,BigInteger在Java中重新实现,并仔细调整到比原始本地实现运行得更快的程度。
这个故事的一个可悲的结尾是,除了在Java 8中对大数进行更快的乘法运算之外,BigInteger此后几乎没有发生什么变化。在此期间,对本地类库的工作继续快速进行,尤其是GNU多精度算术类库(GMP)。如果需要真正高性能多精度算法,Java程序员现在可以通过本地方法使用GMP [Blum14]。
使用本地方法具有严重的缺点。 由于本地语言不安全(条目50),使用本地方法的应用程序不再免受内存损坏错误的影响。 由于本地语言比Java更依赖于平台,因此使用本地方法的程序不太可移植。 它们也更难调试。 如果使用不当,本地方法可能会降低性能,因为垃圾收集器无法自动甚至跟踪本地内存使用情况(条目 8),并且存在进入和退出本地代码相关的成本。 最后,本地方法需要“粘合代码”,难以阅读,编写还繁琐。
总之,在使用本地方法之前要三思而后行。 很少需要使用它们来提高性能。 如果必须使用本地方法来访问地城资源或本地类库,请尽可能少地使用本地代码,并对其进行彻底测试。 本地代码中的单个错误可能会破坏整个应用程序。
67. 明智谨慎地进行优化
关于优化有三个格言,每个人都应该知道:
- 更多的计算上的过失是以效率的名义(不一定实现它)而不是任何其他单一原因——包括盲目做愚蠢的事情。
——William A. Wulf [Wulf72] - 我们应该不去计较小小的效率,大约97%时间里:过早的优化是所有问题的根源。
———Donald E. Knuth [Knuth74]
在优化方面,我们遵循两条规则:
- 规则1。不要优化。
- 规则2(只适用于专家)。先不要优化——也就是说,直到你有了一个完全清晰的还未优化的解决方案之前,不要优化。
所有这些格言都比Java编程语言的出现早二十年。 他们讲述了优化的深层真理:特别是如你过早优化的话,弊大于利。 在此过程中,可能会生成既不快,又不正确,且无法轻松修复的软件。
不要为了性能而牺牲合理的架构原则。努力编写好的程序,而不是快的程序。如果一个好的程序不够快,它的架构允许对其进行优化。好的程序体现了信息隐藏的原则:在可能的情况下,他们设计决策本地化为单个组件,因此可以在不影响系统其余部分的情况下更改单个决策(条目15)。
这并不意味着可以在程序完成之前忽略性能问题。 实现问题可以通过以后的优化来解决,但是如果不重写系统,就无法修复限制性能的普遍存在的架构缺陷。 事后改变设计的基本方面可能导致结构不良的系统难以维护和发展。 因此,必须在设计过程中考虑性能。
尽量避免限制性能的设计决策。设计中最难以更改的组件是那些指定组件之间以及与外部系统的交互的组件。这些设计组件中最主要的是API、线路层(wire-level)协议和持久化数据格式。这些设计组件不仅难以或不可能在事后更改,而且所有这些组件都可能对系统能够达到的性能造成重大限制。
考虑API设计决策的性能影响。 使公共类型可变可能需要大量不必要的防御性拷贝(条目 50)。 类似地,在一个公共类中应该使用复用更为合适,但依旧使用继承会把该类永远绑定到它的父类,这会人为地限制子类的性能(第18项)。最后一个例子是,在API中使用实现类型而不是接口会把你绑定到特定的实现,即使将来可能会编写更快的实现(条目 64)。
API设计对性能的影响是非常真实存在的。 考虑java.awt.Component类中的getSize方法。 这个性能关键方法决定,是返回Dimension实例,而且Dimension实例是可变的,强制此方法的任何实现都在每次调用时分配一个新的Dimension实例。 尽管在现代VM上分配小对象的成本很低,但是不必要地分配数百万个对象会对性能造成实际损害。
存在几种API设计替代方案。 理想情况下,Dimension应该是不可变的(条目 17); 或者,getSize可能已被两个返回Dimension对象的各个基本组件的方法所代替。 实际上,出于性能原因,在Java 2中将两个这样的方法添加到Component类中。 但是,预先存在的客户端代码仍然使用getSize方法,并且仍然会受到原始API设计决策的性能影响。
幸运的是,通常情况下,好的API设计与好的性能是一致的。为了获得良好的性能而包装API是一个非常糟糕的想法。导致包装API的性能问题可能在平台或其他底层软件的未来发型版本中消失,但是包装API和随之而来的支持问题将永远伴随着你。
一旦仔细设计了程序并生成了清晰,简洁且结构良好的实现,那么可能是时候考虑优化,假设你对程序的性能还不是不满意。
回想一下Jackson的两条优化规则是“不要优化”和“(只针对专家)还是先别优化”。他本可以再加上一条:在每次尝试优化之前和优化之后,要测量性能。你可能会对自己发现感到惊讶。通常,尝试的优化对性能没有可测量的影响;有时候,他们让事情变得更糟。主要原因是很难猜测程序将时间花在哪里。程序中你认为很慢的部分可能并没有错,在这种情况下,浪费时间来优化它。一般认为,程序将90%的时间花在10%的代码上。
性能分析工具可以帮助你决定将优化工作的重点放在哪里。这些工具提供了运行时信息,比如每个方法大约花费多少时间以及调用了多少次。除了关注调优工作之外,还可以提醒你需要进行算法更改。如果程序中潜藏着平方级(或更糟)算法,那么再多的调优也无法解决这个问题。必须用一个更有效的算法来代替这个算法。系统中的代码越多,使用分析工具就越重要。这就像大海捞针:大海捞针越大,金属探测器就越有用。另一个值得特别提及的工具是jmh,它不是一个分析工具,而是一个微基准测试框架,提供了非并行的可见对Java代码的详细性能 [JMH]。
与C和C++等更传统的语言相比,Java甚至更需要度量尝试优化的效果,因为Java的性能模型很弱:各种基本操作的相对成本没有得到很好的定义。程序员编写的内容和CPU执行的内容之间的“抽象鸿沟(abstraction gap)”更大,这使得可靠地预测优化的性能结果变得更加困难。有很关于性能的说法流传开来,但最终被证明是半真半假或彻头彻尾的谎言。
Java的性能模型不仅定义不清,而且在不同的实现之间、不同的发布之间、不同的处理器之间都有所不同。如果要在多个实现或多个硬件平台上运行程序,那么度量优化对每个平台的效果是很重要的。有时候,可能会被迫在不同实现或硬件平台上的性能之间进行权衡。
自本条目首次编写以来的近20年里,Java软件堆栈的每个组件都变得越来越复杂,从处理器到不同的虚拟机再到类库,Java运行的各种硬件都有了极大的增长。所有这些加在一起,使得Java程序的性能比2001年更难以预测,而对它进行度量的需求也相应增加。
总而言之,不要努力写出快速的程序——努力写出好的程序; 这样速度将随之而来。 但是在设计系统时要考虑性能,尤其是在设计API,线级协议和持久化数据格式时。 完成系统构建后,请测量其性能。 如果它足够快,你就完成了。 如果没有,请借助分析工具找到问题的根源,然后开始优化系统的相关部分。 第一步是检查算法选择:再多低级优化也不可以弥补差的算法选择。 根据需要重复此过程,在每次更改后测量性能,直到满意为止。
68. 遵守普遍接受的命名约定
Java平台有一组完善的命名约定(naming conventions),其中许多约定包含在Java语言规范[JLS, 6.1]中。宽泛地说,命名约定分为两类:字面(typographical)的和语法的(grammatical)。
只有少量的字面的命名约定,包括包、类、接口、方法、属性和类型变量。你不应该违反它们,而且没有理由去违反。如果API违反了这些约定,那么它可能很难使用。如果实现违反了这些规则,可能很难维护。在这两种情况下,违反约定都有可能使其他使用代码的程序员感到困惑和恼怒,并可能导致他们做出错误的假设,从而导致错误。本条目概述了各个命名约定。
包和模块名称应该是分层的,每个部分以句点分隔。 每个部分应包含小写字母字符,很少包含数字。任何在你的组织外部使用的包的名称都应该以你的组织的Internet域名开头,但包名正好相反,例如,edu.cmu,com.google,org.eff。 名称以java和javax开头的标准类库和可选包是此规则的例外。 用户不得创建名称以java或javax开头的包或模块。 可以在JLS [JLS, 6.1]中找到将Internet域名转换为包名称前缀的详细规则。
包名的其余部分应该由描述包的一个或多个组件构成。组件应该很短,通常为8个或更少的字符。鼓励使用有意义的缩写,例如util而不是utilities。缩写词是可以接受的,例如awt。组件通常应该由一个单词或缩写组成。
除了Internet域名之外,许多包的名称只包含一个组件。 其他组件适用于大型设施,其大小要求将其分解为非正式层次结构。 例如,javax.util包具有丰富的包层次结构,其名称如java.util.concurrent.atomic。 这样的包被称为子包,尽管几乎没有语言对包层次结构提供支持。
类和接口名称(包括枚举和注解类型名称)应由一个或多个单词组成,每个单词的首字母大写,例如List或FutureTask。 除了首字母缩略词和某些常用缩写(如max和min)之外,应避免使用缩写。 关于首字母缩略词是大写还是仅首字母大写,存在一些分歧。 虽然一些程序员仍然使用大写字母,但是可以做出强有力的论证,只支持大写第一个字母:即使多个首字母缩写连续出现,仍然可以知道一个单词从哪里开始,下一个单词从哪里结束。 你更喜欢看哪个类名,HTTPURL或HttpUrl?
方法和属性名遵循与类和接口名相同的字面约定,除了方法或属性名的第一个字母应该是小写,例如remove或ensureCapacity。 如果首字母缩略词作为方法或属性名称的第一个单词出现,则它应该是小写的。
前面规则的唯一例外是“常量属性”,它的名称应该由一个或多个大写单词组成,由下划线分隔,例如VALUES或NEGATIVE_INFINITY。常量属性是一个静态的final属性,其值是不可变的。如果静态final属性具有基本类型或不可变引用类型(条目 17),那么它就是常量属性。例如,枚举常量是常量属性。如果静态final属性有一个可变的引用类型,那么如果所引用的对象是不可变的,那么它仍然可以是一个常量属性。注意,常量属性是唯一推荐的下划线用法。
局部变量名称与成员名称具有相似的字面命名约定,但允许使用缩写除外,单个字符和短字符序列的含义取决于它们出现的上下文,例如i,denom,houseNum。 输入参数是一种特殊的局部变量。 它们的名称应该比普通的局部变量更加仔细,因为它们的名称是其方法文档中不可或缺的一部分。
类型参数名通常由单个字母组成。最常见的是以下五种类型之一:T表示任意类型,E表示集合的元素类型,K和V表示映射的键和值类型,X表示异常。方法的返回类型通常为R。任意类型的序列可以是T、U、V或T1、T2、T3。
为了快速参考,下表列出了字面约定的示例。
标识符类型
示例
包名或模块
org.junit.jupiter.api, com.google.common.collect
类或接口
Stream, FutureTask, LinkedHashMap, HttpClient
方法或属性
remove, groupingBy, getCrc
常量属性
MIN_VALUE, NEGATIVE_INFINITY
局部变量
i, denom, houseNum
类型参数
T, E, K, V, X, R, U, V, T1, T2
语法命名约定比字面约定更灵活,也更有争议。包没有语法命名约定。可实例化的类,包括枚举类型,通常使用一个或多个名词短语来命名,例如Thread、PriorityQueue或ChessPiece。不可实例化的实用程序类(条目 4)通常使用复数名词来命名,例如Collector或Collections。接口的名称类似于类,例如Collection或Comparator,或者以able或ible结尾的形容词,例如Runnable、Iterable或Accessible。因为注解类型有如此多的用途,所以没有哪部分词性占主导地位。名词、动词、介词和形容词都很常见,例如,BindingAnnotation、Inject、ImplementedBy或Singleton。
执行某些操作的方法通常使用动词或动词短语(包括对象)命名,例如append或drawImage。 返回boolean类型的方法通常具有以单词is,或不太常用的has开头的名称,后跟名词,名词短语或任何用作形容词的单词或短语,例如isDigit,isProbablePrime,isEmpty, isEnabled,或hasSiblings。
方法返回被调用对象的非boolean的方法或属性,通常使用以get开头的名词、名词短语或动词短语来命名,例如size、hashCode或getTime。有一种说法是,只有第三种形式(以get开头)才是可接受的,但这种说法几乎没有根据。前两种形式的代码通常可读性更强,例如:
if (car.speed() > 2 * SPEED_LIMIT)
generateAudibleAlert("Watch out for cops!");
以get开头的形式起源于基本过时的Java bean规范,该规范构成了早期可重用组件体系结构的基础。有一些现代工具继续依赖于Beans命名约定,你可以随意在任何与这些工具结合使用的代码中使用它。如果类同时包含相同属性的setter和getter,则遵循这种命名约定也有很好的先例。在本例中,这两个方法通常被命名为getAttribute和setAttribute。
一些方法名称值得特别提及。 转换对象类型,返回不同类型的独立对象的实例方法通常称为toType,例如toString或toArray。 返回类型与接收对象类型不同的视图(条目 6)的方法通常称为asType,例如asList。 返回与调用它们的对象具有相同值的基本类型的方法通常称为typeValue,例如intValue。 静态工厂的常用名称包括from,of,valueOf,instance,getInstance,newInstance,getType和newType(条目 1,第9页)。
属性名称的语法约定不太完善,并且不如类,接口和方法名称那么重要,因为设计良好的API包含很少的暴露属性。 boolean类型的属性通常被命名为boolean 访问器方法,省略了初始的is前缀,例如,initialized,composite。 其他类型的属性通常以名词或名词短语命名,例如height,digits或bodyStyle。 局部变量的语法约定类似于属性,但甚至更弱。
总之,将标准命名约定内在化,并将其作为第二天性来使用。字面约定是直接的,而且在很大程度上是明确的;语法约定更加复杂和松散。引用Java语言规范[JLS, 6.1]中的话说,“如果长期以来的传统用法要求不遵循这些约定,就不应该盲目地遵循这些约定”。使用常识。