这是10个最佳实践的列表,这些最佳实践比您的平均Josh Bloch有效Java规则要微妙得多。 尽管Josh Bloch的列表很容易学习,并且涉及日常情况,但此处的列表包含了涉及API / SPI设计的较不常见的情况,但可能会产生很大的影响。

我在编写和维护jOOQ时遇到了这些问题, jOOQ是Java中的内部DSL建模SQL。 作为内部DSL,jOOQ最大限度地挑战了Java编译器和泛型, 将泛型,可变参数和重载组合在一起,这是Josh Bloch可能不推荐使用的“平均API”。

让我与您分享编码Java时的10个微妙的最佳实践:

1.记住C ++析构函数

还记得C ++析构函数吗? 没有? 然后,您可能会很幸运,因为您无需再调试任何代码,因为删除对象后没有释放分配的内存,因此不会留下内存泄漏。 感谢Sun / Oracle实现垃圾回收!

但是,尽管如此,破坏者还是有一个有趣的特征。 通常以相反的顺序释放内存是有意义的。 在使用类似析构函数的语义进行操作时,也要在Java中记住这一点:

  • 当使用@Before和@After JUnit批注时
  • 分配时,释放JDBC资源
  • 调用超级方法时

还有其他各种用例。 这是一个具体示例,显示了如何实现某些事件侦听器SPI:

@Override
public void beforeEvent(EventContext e) {super.beforeEvent(e);// Super code before my code
}@Override
public void afterEvent(EventContext e) {// Super code after my codesuper.afterEvent(e);
}

另一个臭名昭著的餐饮哲学家问题就是一个很好的例子,说明了为什么这很重要。

餐饮哲学家。 在这里看到: http : //adit.io/posts/2013-05-11-The-Dining-Philosophers-Problem-With-Ron-Swanson.html

规则 :无论何时使用before / after,allocate / free,take / return语义实现逻辑,请考虑after / free / return操作是否应按相反的顺序执行操作。

2.不要相信您早期的SPI发展判断

向消费者提供SPI是允许他们将自定义行为注入您的库/代码中的简便方法。 不过请注意,您的SPI演变判断可能会欺骗您,使您认为(不需要)该附加参数 。 确实, 不应及早添加任何功能。 但是一旦发布了SPI,并决定遵循语义版本控制 ,当您意识到在某些情况下可能还需要另一个参数时,您会后悔自己在SPI中添加了一个愚蠢的单参数方法:

interface EventListener {// Badvoid message(String message);
}

如果还需要消息ID和消息源怎么办? API的发展将阻止您轻松地将该参数添加到上述类型。 使用Java 8,您可以添加防御者方法来“捍卫”您糟糕的早期设计决策:

interface EventListener {// Baddefault void message(String message) {message(message, null, null);}// Better?void message(String message,Integer id,MessageSource source);
}

请注意,不幸的是,防御者方法不能设为final 。

但是,比使用数十种方法污染SPI更好的方法是,仅为此目的使用上下文对象(或参数对象) 。

interface MessageContext {String message();Integer id();MessageSource source();
}interface EventListener {// Awesome!void message(MessageContext context);
}

与EventListener SPI相比,您可以更轻松地开发MessageContext API,因为实施该应用程序的用户将更少。

规则 :无论何时指定SPI,都应考虑使用上下文/参数对象,而不要编写带有固定数量参数的方法。

备注 :通常也可以通过专用的MessageResult类型(可以通过构建器API构造)来传递结果,这是一个好主意。 这将为您的SPI增加更多的SPI演进灵活性。

3.避免返回匿名,本地或内部类

Swing程序员可能有几个键盘快捷键可以为其数百个匿名类生成代码。 在许多情况下,创建它们很不错,因为您可以本地遵守接口,而无需经历思考完整SPI子类型生命周期的“麻烦”。

但是,您不应该过于频繁地使用匿名类,局部类或内部类,原因很简单:它们保留对外部实例的引用。 并且,如果您不小心,它们会将外部实例拖到任何地方,例如,拖到本地类之外的某个范围。 这可能是内存泄漏的主要来源,因为整个对象图会突然以微妙的方式纠缠在一起。

规则 :每当编写匿名,本地或内部类时,请检查是否可以使其成为静态类,甚至是常规顶级类。 避免将匿名,本地或内部类实例从方法返回到外部作用域。

备注 :对于简单对象实例化,围绕双花括号有一些聪明的做法:

new HashMap<String, String>() {{put("1", "a");put("2", "b");
}}

这利用了JLS§8.6中指定的 Java实例初始化程序 。 看起来不错(也许有点奇怪),但确实是个坏主意。 原来是完全独立的HashMap实例现在将保留对外部实例的引用,无论发生什么情况。 此外,您将创建一个其他类供类加载器管理。

4.立即开始编写SAM!

Java 8正在敲门。 随Java 8一起提供lambda ,无论您是否喜欢。 不过,您的API使用者可能会喜欢它们,因此您最好确保他们可以尽可能多地使用它们。 因此,除非您的API接受简单的“标量”类型(例如intlongStringDate ,否则您的API应尽可能多地接受SAM。

什么是SAM? SAM是单一抽象方法[Type]。 也称为功能接口 ,很快将使用@FunctionalInterface注释进行注释 。 这与规则2配合得很好,其中EventListener实际上是SAM。 最好的SAM是具有单个参数的SAM,因为它们将进一步简化lambda的编写。 想象写作

listeners.add(c -> System.out.println(c.message()));

代替

listeners.add(new EventListener() {@Overridepublic void message(MessageContext c) {System.out.println(c.message()));}
});

想象一下通过jOOX进行的 XML处理,它具有几个SAM:

$(document)// Find elements with an ID.find(c -> $(c).id() != null)// Find their  child elements.children(c -> $(c).tag().equals("order"))// Print all matches.each(c -> System.out.println($(c)))

规则 :与您的API使用者友好, 现在已经编写SAM /功能接口。

备注 :有关Java 8 Lambda和改进的Collections API的一些有趣的博客文章可以在这里找到:

  • http://blog.informatech.cr/2013/04/10/java-optional-objects/
  • http://blog.informatech.cr/2013/03/25/java-streams-api-preview/
  • http://blog.informatech.cr/2013/03/24/java-streams-preview-vs-net-linq/
  • http://blog.informatech.cr/2013/03/11/java-infinite-streams/

5.避免从API方法返回null

我曾经写过一两次关于Java的NULL的博客。 我也写了关于Java 8对Optional的介绍的博客。 从学术和实践的角度来看,这些都是有趣的话题。

尽管NULL和NullPointerExceptions在Java中可能会持续一段时间,但是您仍然可以通过设计API来避免用户遇到任何问题。 尽可能避免从API方法返回null。 您的API使用者应能够在适用的情况下链接方法:

initialise(someArgument).calculate(data).dispatch();

在上面的代码段中,所有方法都不应该返回null。 实际上,通常使用null的语义(缺少值)应该是非常例外的。 在诸如jQuery (或jOOX ,其Java端口)之类的库中,由于始终对可迭代对象进行操作 ,因此完全避免了null。 是否匹配某项与下一个方法调用无关。

由于延迟初始化,通常还会出现空值。 在许多情况下,也可以避免延迟初始化,而不会对性能产生重大影响。 实际上,仅应谨慎使用惰性初始化。 如果涉及大型数据结构。

规则 :尽可能避免从方法返回null。 仅将空值用于“未初始化”或“不存在”的语义。

6.切勿从API方法返回空数组或列表

虽然在某些情况下从方法返回null可以,但是绝对没有用过返回null数组或null集合的用例! 让我们考虑一下丑陋的java.io.File.list()方法。 它返回:

在此抽象路径名表示的目录中命名文件和目录的字符串数组。 如果目录为空,则数组为空。 如果此抽象路径名不表示目录,或者发生I / O错误,则返回null。

因此,处理此方法的正确方法是

File directory = // ...if (directory.isDirectory()) {String[] list = directory.list();if (list != null) {for (String file : list) {// ...}}
}

空检查真的必要吗? 大多数I / O操作都会产生IOException,但是此操作返回null。 Null无法保存任何指示为什么发生I / O错误的错误消息。 因此,这在三种方式上是错误的:

  • 空无助于发现错误
  • Null不允许将I / O错误与不是目录的File实例区分开
  • 每个人都会忘记空值

在集合上下文中,“空缺”的概念最好通过空数组或集合来实现。 除了再一次进行延迟初始化外,几乎没有有用的数组或集合。

规则 :数组或集合绝不能为空。

7.避免状态,发挥作用

HTTP的优点在于它是无状态的。 所有相关状态都在每个请求和每个响应中传递。 这对于REST的命名至关重要: 代表性状态转移 。 当用Java完成时,这也很棒。 当方法接收有状态参数对象时,可以根据规则2来考虑它。 如果状态在此类对象中传递,而不是从外部进行操纵,则事情会变得更加简单。 以JDBC为例。 以下示例从存储过程中获取游标:

CallableStatement s =connection.prepareCall("{ ? = ... }");// Verbose manipulation of statement state:
s.registerOutParameter(1, cursor);
s.setString(2, "abc");
s.execute();
ResultSet rs = s.getObject(1);// Verbose manipulation of result set state:
rs.next();
rs.next();

这些使JDBC成为难以处理的API。 每个对象都是难以置信的有状态且难以操纵。 具体来说,有两个主要问题:

  • 在多线程环境中正确处理有状态的API非常困难
  • 很难使有状态资源在全球范围内可用,因为没有记录状态

阿甘正传的戏剧海报, 派拉蒙影业 ( Paramount Pictures)版权所有©1994。 版权所有。 可以相信上述用法满足了所谓的合理使用

规则 :实施更多的功能样式。 通过方法参数传递状态。 操作较少的对象状态。

8.短路equals()

这是一个低落的果实。 在大型对象图中,如果所有对象的equals()方法首先比较便宜地比较身份,则可以显着提高性能:

@Override
public boolean equals(Object other) {if (this == other) return true;// Rest of equality logic...
}

请注意,其他短路检查可能涉及空检查,该检查也应该存在:

@Override
public boolean equals(Object other) {if (this == other) return true;if (other == null) return false;// Rest of equality logic...
}

规则 :短路所有equals()方法以获得性能。

9.尝试使方法默认为final

有些人对此持不同意见,因为默认情况下使事情最终完成与Java开发人员所习惯的相反。 但是,如果您完全控制所有源代码,则默认情况下将方法设为final绝对没有问题,因为:

  • 如果确实需要重写方法(确实吗?),仍然可以删除final关键字
  • 您再也不会意外覆盖任何方法

这特别适用于静态方法,在这些方法中“覆盖”(实际上是阴影)几乎没有任何意义。 最近,我在Apache Tika上遇到了一个非常糟糕的阴影静态方法示例。 考虑:

  • TaggedInputStream.get(InputStream)
  • TikaInputStream.get(InputStream)

TikaInputStream扩展了TaggedInputStream并使用完全不同的实现来隐藏其静态get()方法。

与常规方法不同,静态方法不会互相覆盖,因为调用站点在编译时绑定了静态方法调用。 如果您不走运,您可能会偶然得到错误的方法。

规则 :如果您完全控制自己的API,请尝试在默认情况下尽可能多地使用final方法。

10.避免方法(T…)签名

偶尔接受一个Object...参数的“ accept-all” varargs方法没有任何问题:

void acceptAll(Object... all);

编写这样的方法给Java生态系统带来一点JavaScript的感觉。 当然,您可能希望将实际类型限制为在实际情况下更受限的内容,例如String... 而且由于您不想限制太多,您可能会认为用通用T代替Object是一个好主意:

void acceptAll(T... all);

但事实并非如此。 T总是可以推断为Object。 实际上,您最好不要将泛型与上述方法一起使用。 更重要的是,您可能认为可以重载上述方法,但是您不能:

void acceptAll(T... all);
void acceptAll(String message, T... all);

看起来您可以选择将String消息传递给该方法。 但是这里的电话怎么办?

acceptAll("Message", 123, "abc");

编译器会推断<? extends Serializable & Comparable<?>>T <? extends Serializable & Comparable<?>> ,这使调用变得模棱两可!

因此,每当您拥有“所有人都接受”的签名(即使它是通用的)时,您将永远无法再次安全地重载它。 API使用者可能只是幸运地“偶然地”选择了编译器选择“正确的”最具体的方法。 但是他们也可能被欺骗使用“ accept-all”方法,或者根本无法调用任何方法。

规则 :如果可以,请避免“全部接受”签名。 如果不能,则不要重载这种方法。

结论

Java是野兽。 与其他更高级的语言不同,它已经发展到今天。 那可能是一件好事,因为在Java的发展速度下,已经有数百个警告,这些警告只能通过多年的经验来掌握。

参考:在JAVA,SQL和JOOQ博客上,来自JCG合作伙伴 Lukas Eder的Java编码Java时的10个最佳最佳实践 。

翻译自: https://www.javacodegeeks.com/2013/08/10-subtle-best-practices-when-coding-java.html

编码Java时的10个微妙的最佳实践相关推荐

  1. java微妙_编码Java时的10个微妙的最佳实践

    java微妙 这是10条最佳实践的列表,这些最佳实践比您的平均Josh Bloch有效Java规则要微妙得多. 尽管Josh Bloch的列表很容易学习,并且涉及日常情况,但此处的列表包含了涉及API ...

  2. 最新:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明

    本书一共分为五个部分:走近Java.自动内存管理.虚拟机执行子系统.程序编译与代码优化. 高效并发.各个部分之间基本上是互相独立的,没有必然的前后依赖关系,读者可以从任何一个感兴 趣的专题开始阅读,但 ...

  3. 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)读书笔记

    前言 我在读 深入理解java虚拟机 这本书,把整体其中的关键点标记了,希望自己对它有个不一样的理解,也希望大家能看看这本写的很好的书 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) pd ...

  4. Java异常处理教程(包含示例和最佳实践)

    异常是可能在程序执行期间发生的错误事件,它会破坏其正常流程. Java提供了一种健壮且面向对象的方式来处理异常情况,称为Java异常处理 . 我们将在本教程中研究以下主题. Java异常处理概述 异常 ...

  5. 读书笔记之《深入理解Java虚拟机:JVM高级特性与最佳实践》

    本篇带来的是周志明老师编写的<深入理解Java虚拟机:JVM高级特性与最佳实践>,十分硬核! 全书共分为 5 部分,围绕内存管理.执行子系统.程序编译与优化.高效并发等核心主题对JVM进行 ...

  6. Java笔记018-抽象类、抽象类最佳实践-模板设计模式、接口、内部类

    目录 抽象类 先看一个问题 小结: 抽象类快速入门 抽象类的介绍 抽象类使用的注意事项和细节讨论 抽象类练习题 抽象类最佳实践-模板设计模式 基本介绍 模板设计模式能解决的问题 最佳实践 最佳实践 接 ...

  7. 深入理解java虚拟机:JVM高级特性与最佳实践第一部分走近Java第1章走近Java

    世界上并没有完美的程序,但我们并不因此而沮丧,因为写程序本来就是一个不断追求完美的过程. 1.1 概述 Java不仅仅是一门编程语言,还是一个由一系列计算机软件和规范形成的技术体系,这个技术体系提供了 ...

  8. java中字符串的精确匹配_Java最佳实践–字符串性能和精确字符串匹配

    java中字符串的精确匹配 在使用Java编程语言时,我们将继续讨论与建议的实践有关的系列文章,我们将讨论String性能调优. 我们将专注于如何有效地处理字符串创建, 字符串更改和字符串匹配操作. ...

  9. 《深入理解Java虚拟机:JVM高级特性与最佳实践》书评

    不知不觉做JAVA开发已经两年多了,<深入理解JAVA虚拟机>第二版是我很早就买的书,大多数时间也一直放在我的工作桌上.想要学习写一门语言的话很简单,掌握基本的语法和编程思想就可以上手工作 ...

最新文章

  1. sqlserver 重置自增Id
  2. java ee jms_Java EE6事件:JMS的轻量级替代品
  3. python wait方法_Python条件类| 带有示例的wait()方法
  4. “最强”博士论文答辩阵容:6位院士,副院长任答辩秘书!
  5. C++ opengl 环境光分量
  6. 外媒:Apple面临着印度iPhone的停用
  7. Linux 市场估值将超 70 亿美元,主要原因是安全与开源需求
  8. mybatis映射多对多查询实现
  9. 【Qt串口调试助手】1.8 - 修改Qt应用图标和窗口图标
  10. 计算机网络6版,计算机网络教程(第6版)
  11. 11【matplotlib常用统计图】03绘制多次条形图
  12. vue typeScript get set 用法
  13. 记一个docker网络问题--network=host
  14. js判断域名是否是合法http/https
  15. 信息系统项目管理师核心考点(七)软件架构风格
  16. 2005 最新GIS地理信息系统软件
  17. 汇编语言C什么意思,C和汇编的对应—main之前之后的汇编到底在干什么
  18. FreeRTOS 队列管理
  19. 闲人闲谈PS之三十二——业务工程预算和PS成本计划
  20. RabbitVCS:ubuntu下svn可视化工具的安装和使用

热门文章

  1. asset文件夹路径 unity_我们来捣鼓一下Unity的平台跳跃Microgame
  2. centos Error: Cannot find a valid baseurl for repo: base 解决方法
  3. 学习PL/SQL最好的书籍推荐
  4. MarkdownPad 汉化破解(含下载地址)
  5. 不同特权级间代码段的跳转{ 门 + 跳转(jmp + call) + 返回(ret) }
  6. run spark pi_Spark Run本地设计模式
  7. ubuntu安装jdk语句_JDK 12:实际中的切换语句/表达式
  8. java 8 stream_Java 8 Stream示例
  9. jax-rs jax-ws_您的JAX-RS API并非天生就等于:使用动态功能
  10. rx.observable_在Spring MVC流中使用rx-java Observable