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

java微妙

这是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);
}

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

at_the_table

餐饮哲学家。 在这里看到: 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。 仅对“未初始化”或“缺少”的语义使用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非常困难
  • 由于没有记录状态,因此很难使全局状态资源可用

http://en.wikipedia.org/wiki/File:Forrest_Gump_poster.jpg

阿甘正传的戏剧海报,版权所有©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微妙

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/346983.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GraphQL在Wildfly群上

“ GraphQL是API的查询语言&#xff0c;是用于使用现有数据完成这些查询的运行时。 GraphQL为您的API中的数据提供了一个完整且易于理解的描述&#xff0c;使客户能够准确地询问他们所需的内容&#xff0c;仅此而已&#xff0c;使随着时间的推移更容易开发API并启用强大的开发人…

javafx 示例_示例介绍:JavaFX 8打印

javafx 示例我有一段时间没有写博客了&#xff0c;我想与其他人分享有关JavaFX的所有信息&#xff08;我的日常工作和家庭可能是借口&#xff09;。 对于那些对此博客不熟悉的人 &#xff0c;我是JavaFX 2 Introduction by Example&#xff08;JIBE&#xff09;的作者&#xff…

Spring Data JPA教程

在Java类或对象与关系数据库之间管理数据是一项非常繁琐且棘手的任务。 DAO层通常包含许多样板代码&#xff0c;应简化这些样板代码&#xff0c;以减少代码行数并使代码可重复使用。 在本教程中&#xff0c;我们将讨论Spring数据的JPA实现。 1.简介 1.1什么是JPA&#xff1f;…

使用storm 实时计算_使用Storm进行可扩展的实时状态更新

使用storm 实时计算在本文中&#xff0c;我将说明如何借助Storm框架以可扩展且无锁定的方式在数据库中维护实时事件驱动流程的当前状态。 Storm是基于事件的数据处理引擎。 它的模型依赖于基本原语&#xff0c;例如事件转换&#xff0c;过滤&#xff0c;聚合……&#xff0c;我…

【多元域乘法】多项式乘法电路原理及MATLAB详解

关注公号【逆向通信猿】更精彩!!! 关于二元域上的两个元素的乘法、多项式除法,在之前的博客 【有限域除法】二元多项式除法电路原理及MATLAB详解 子程序:sub_poly_div.m 【有限域元素加法和乘法】有限域元素加法和乘法的原理及MATLAB实现 子程序:sub_gf_add.m、sub_gf_…

my CSAPP Attack lab堆栈详解

关注公号【逆向通信猿】更精彩!!! 这个实验时学习了简书上的一篇文章后,自己根据课程例子进行的一次小测试,phase 4和5的堆栈图解还没有画,等后续有时间会进行补充。 本人转载的简书原文: https://blog.csdn.net/wlwdecs_dn/article/details/121249364#comments_19237…

Spring MVC教程

1.简介 作为企业Java开发人员&#xff0c;这项工作的主要重点之一是开发Web应用程序。 对于Web应用程序&#xff0c;后果还包括许多挑战。 具体来说&#xff0c;其中一些是状态管理&#xff0c;工作流和验证。 HTTP协议的无状态性质只会使事情变得更加复杂。 Spring的Web框架旨…

ubuntu22.04 下载路径

ftp下载路径 csdn下载 ubuntu22.04下载路径ubuntu-22.04-desktop-amd64.7z.001资源-CSDN文库 ubuntu22.04下载路径ubuntu-22.04-desktop-amd64.7z.002资源-CSDN文库 【免费】ubuntu-22.04-desktop-amd64.7z.003资源-CSDN文库 【免费】ubuntu-22.04-desktop-amd64.7z.004资源-…

Spring Reactor教程

在RESTful服务的世界中&#xff0c;实际上实际上是在幕后进行许多工作&#xff0c;我们通常必须在应用程序中进行很多处理&#xff0c;而实际上并不会影响需要发送给真实用户的响应。 可以被动地做出这些业务决策&#xff0c;以便它们对与应用程序交互的用户没有任何影响。 Spr…

api签名_使用签名保护基于HTTP的API

api签名我在EMC上的一个平台上可以构建SaaS解决方案。 与越来越多的其他应用程序一样&#xff0c;该平台具有基于RESTful HTTP的API。 使用JAX-RS之类的开发框架&#xff0c;构建这样的API相对容易。 但是&#xff0c; 正确构建它们并不容易。 建立基于HTTP的API的问题 问…

【多元域除法】多项式除法电路原理及MATLAB详解

关注公号【逆向通信猿】更精彩!!! 关于二元域上的两个元素的加法和乘法、多项式除法,在之前的博客 【有限域除法】二元多项式除法电路原理及MATLAB详解 子程序:sub_poly_div.m 【有限域元素加法和乘法】有限域元素加法和乘法的原理及MATLAB实现 子程序:sub_gf_add.m、s…

win10高分辨率下修改字体显示大小(不是缩放百分比)

问题 不通过修改设置缩放百分比来增大win10的字体显示大小&#xff0c;缩放百分比调大后会导致很多问题出现&#xff01;&#xff01;&#xff01; 修改 打开设置&#xff0c;或者右键个性化&#xff0c;在搜索栏输入&#xff1a;“放大文本大小”&#xff0c;搜索框下面会自…

应用程序无法正常启动 0xc0150002

Visual Studio 2017在debug下运行程序报错 应用程序无法正常启动 0xc0150002 分析原因 可能是&#xff1a;原程序是低版本的VS所编写的&#xff0c;缺少低版本的运行库&#xff0c;所以报错 解决 安装了VS2010后即可正常运行 error LNK2019: 无法解析的外部符号 __vsnwprin…

Excel之抽奖器实现

Excel实现一个抽奖器&#xff0c;关键在于学会几个Excel中的函数即可轻松实现。 单人抽奖 RANDBETWEEN 例&#xff1a; INDEX(A2:A61,RANDBETWEEN(1,60))缺点&#xff1a;这种方式生成的抽奖器&#xff0c;在多人情况下&#xff0c;由于RANDBETWEEN函数的返回值有可能是相同…

【RS码1】系统RS码编码原理及MATLAB实现(不使用MATLAB库函数)

关注公号【逆向通信猿】更精彩!!! 基础知识 要想搞懂本节知识,需要先熟悉掌握以下前几篇博客 【多元域乘法】多项式乘法电路原理及MATLAB详解 【多元域除法】多项式除法电路原理及MATLAB详解 RS码编码原理 RS码的编码与BCH码类似,区别在于RS码为多进制的 生成多项式…

如何用Java编写类似C的Sizeof函数

如果您刚开始学习Java并且是C语言背景&#xff0c;那么您可能已经注意到Java和C编程语言之间存在一些差异&#xff0c;例如String是Java中的对象&#xff0c;而不是NULL终止的字符数组。 同样&#xff0c;Java中没有sizeof&#xff08;&#xff09;运算符。 所有原始值都有预定…

Spring启动教程

1.简介 如果您一直想使用一个Web框架&#xff0c;它使您能够快速开始API开发&#xff0c;而无须设置Web服务器&#xff0c;收集所有有线依赖项&#xff0c;安装各种工具的麻烦&#xff0c;那么您将拥有一个出色的框架&#xff0c;即Spring开机 Spring Boot的主要座右铭是约定优…

【LDPC系列1】基于MATLAB中LDPC编译码器对象的图像传输通信系统仿真

关注公号【逆向通信猿】更精彩!!! 1. 构造编码器对象 采用MATLAB内置的comm.LDPCEncoder构造编码器对象,其中使用默认的校验矩阵,信息位长32400比特,码长64800比特,该校验矩阵中除第一行中1的个数为6个外,其余行中1的个数均为7;前12960列中1的个数为8,后32400列构成…

【LDPC系列2】基于MATLAB中LDPC编译码器对象的图像传输通信系统仿真(IEEE 802.16e标准协议基础矩阵)

关注公号【逆向通信猿】更精彩!!! 1. 构造校验矩阵H,并保存为mat文件 采用IEEE 802.16e标准协议中的基础校验矩阵 通过构造QC-LDPC校验矩阵,码长n=2304,信息长k=1152,码率r=1/2,基础矩阵维数为1224: Hb = [-1 94 73 -1

VS2010附加进程调试DLL时断点无法断下的解决方法

系统版本&#xff1a;Win10 x64 1809 VS版本&#xff1a;VS2017 企业版 问题一 在动态链接库(DLL)附加到进程调试时&#xff0c;用VS2017附加后单步调试&#xff0c;结果发现总是在调试过程中卡死&#xff0c;VS2017无响应&#xff1b; 解决办法是&#xff1a;强制结束VS2017…