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

这是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。 仅将空值用于“未初始化”或“不存在”的语义。

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

阿甘正传的戏剧海报, 派拉蒙影业 ( 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

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

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

相关文章

Vue 实现微信 jssdk 扫码, 上传图片

流程 1: 配置微信公众号JS域名 2:前端发送URL后台获取JSSDK配置, 后台Service代码如下, 修改2处位置: WeixinUtil.APPID > 当前公众号APPID WeixinUtil.getAccessToken() > 当前公众号access_token public interface IWxJssdkService {Map<String, Object> getJssd…

有意思的前端函数面试题

1:考引用类型在比较运算符时候 隐式转换会调用本类型那个方法 toString和valueOf&#xff1f;(去年过年吵的很火国外的题) if(a 1 && a 2 && a 3){console.log("我走进来了"); }<!--答案1:--> var a {num:0}; a.valueOf function(){retur…

用java写个简单的直播强求_全网最简单易懂的Netty入门示例,再不会用Netty我直播吃翔...

//server引导类ServerBootstrap serverBootstrap new ServerBootstrap();//boss 对应 IOServer.java 中的接受新连接线程,主要负责创建新连接NioEventLoopGroup boss new NioEventLoopGroup();//worker 对应 IOServer.java 中的负责读取数据的线程,主要用于读取数据以及业务逻…

XamarinAndroid组件教程设置自定义子元素动画(一)

XamarinAndroid组件教程设置自定义子元素动画(一) 如果在RecyclerViewAnimators.Animators中没有所需要的动画效果&#xff0c;就可以自定义一个。此时&#xff0c;需要让自定义的动画继承BaseItemAnimator抽象类。 【示例1-2】下面以RecylerViewAnimatorsItemAnimator项目为基…

使用Storm进行可扩展的实时状态更新

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

python关于字典嵌套字典,列表嵌套字典根据值进行排序

python 对于字典嵌套字典&#xff0c; 列表嵌套字典排序 例&#xff1a;列表嵌套自字典&#xff1a;d [{"name": 张三, s: 68}, {name: 李四, s: 97}] 对于列表嵌套字典可以使用python的sorted()方法&#xff0c;也可以使用list的sort()方法&#xff1a; sorted方法…

【干货】十分钟读懂浏览器渲染流程

在之前写过的一篇《"天龙八步"细说浏览器输入URL后发生了什么》一文中&#xff0c;和大家分享了从在浏览器中输入网址URL到最终页面展示的整个过程。部分读者向我反馈对于最后的浏览器渲染布局这块不是很清晰&#xff0c;所以本文就浏览器渲染流程单独开篇讲解&#…

控制台资费管理主菜单java_java毕业设计_springboot框架的高速公路收费管理系统...

今天介绍一个java毕设题目, 题目内容为springboot框架的高速公路收费管理系统, 是一个采用b/s结构的javaweb项目, 采用java语言编写开发工具eclipse, 项目框架jspspringbootmybatis, 高速公路收费管理系统的信息存储于mysql中, 并基于mybatis进行了orm封装, 该高速公路收费管理…

Hibernate框架的搭建和一个简单的实例

Hibernate是一个支持对JDBC进行封装的框架&#xff0c;实现了对底层数据库访问的封装。非常适合使用和开发。首先需要下载Hibernate&#xff0c;可以在这个网站下载最新包。http://www.hibernate.org/然后打开他的目录结构&#xff0c;将lib目录下的required目录下的包全部导入…

在Amazon EMR上运行Hadoop MapReduce作业

不久前&#xff0c;我发布了如何使用CLI设置EMR群集的信息。 在本文中&#xff0c;我将展示如何使用适用于AWS的Java SDK来设置集群。 展示如何使用Java AWS开发工具包执行此操作的最佳方法是展示完整的示例&#xff0c;因此&#xff0c;让我们开始吧。 设置一个新的Maven项目…

在商城系统开发时遇到商品的多级分类,为增强扩展性,子类可以任意添加,此类问题数据库如何设计...

表结构为&#xff1a; id&#xff08;编号&#xff09; name&#xff08;分类名&#xff09; parentID&#xff08;父类编号&#xff09; 简单举例如下&#xff1a; id name parentID 1 饮料 0&#xff08;为0表示第一大类&#xff09; 2 水果 …

[JSConf EU 2018] 大脑控制 Javascript

先解释&#xff0c;本人为前端菜鸟&#xff0c;之前也未参加过类似的活动&#xff0c;没有翻译过什么文章&#xff0c;此次是好奇心使然&#xff0c;也是想尝试下&#xff0c;学习学习&#xff0c;英文很烂&#xff0c;全靠有道&#xff0c;但是视频整个看下来&#xff0c;还是…

init tarray 太大_[NOIP 2001提高组T4]Car的旅行路线

[题目来源]&#xff1a;NOIP2001提高组T4[关键字]&#xff1a;最短路径[题目大意]&#xff1a;给定平面直角若干个矩形&#xff0c;计算(可经过其他矩形)两个矩形任意顶点间的最短路程费用。//[分析]&#xff1a;其实题目本事没有太大的难点&#xff0c;只需要对每两个点进行连…

Caffe Caffe2入门博客存档

caffe2 教程入门&#xff08;python版&#xff09; https://www.jianshu.com/p/5c0fd1c9fef9?fromtimeline caffe入门学习 https://blog.csdn.net/hjimce/article/details/48933813 运行caffe自带的两个简单例子 https://www.linuxidc.com/Linux/2016-11/136774p9.htm 关于caf…

JavaScript中不得不说的断言?

断言主要应用于“调试”与“测试” 一、前端中的断言 仔细地查找一下JavaScript中的API&#xff0c;实际上并没有多少关于断言的方法。唯一一个就是console.assert&#xff1a; // console.assert(condition, message)const a 1console.assert(typeof a number, a should be…

Java EE状态会话Bean(EJB)示例

在本文中&#xff0c;我们将了解如何在简单的Web应用程序中使用状态会话Bean来跟踪客户端会话中的状态。 1.简介 有状态会话Bean通常保存有关特定客户端会话的信息&#xff0c;并在整个会话中保留该信息&#xff08;与无状态会话Bean相对&#xff09;。 有状态EJB实例仅与一个…

计算机科学速成课16:软件工程

概念&#xff1a;建造标准或者大型软件的方法和工具代码打包成函数 面向过程函数打包成对象 面向对象对象层层打包Car.Engine.CruiseControl.SetCruiseSpeed(55)应用程序接口api集成开发环境IDE&#xff1a;code&#xff0c;整理&#xff0c;编译&#xff0c;测试注释和readme文…

牛客网NOIP赛前集训营-提高组(第六场)B-选择题[背包]

题意 题目链接 分析 直接背包之后可以 \(O(n)\) 去除一个物品的影响。注意特判 \([p1]\) 的情况。总时间复杂度为 \(O(n^2)\) 。代码 #include<bits/stdc.h> using namespace std; #define go(u) for(int ihead[u],ve[i].to;i;ie[i].last,ve[i].to) #define rep(i,a,b) f…

起点海外版 Hybrid App-内嵌页优化实践

本文作者&#xff1a;刘文涛 原创声明&#xff1a;本文为阅文前端团队 YFE 成员出品&#xff0c;请尊重原创&#xff0c;转载请联系公众号 (id: yuewen_YFE) 获取授权&#xff0c;并注明作者、出处和链接。 今年年初我司开启了起点品牌的海外之旅&#xff0c;名为「 Webnovel 」…

aix 卸载mysql_AIX 删除数据库及集群软件

一、 删除数据库1、用dbca自动删库在CRT上无法打开dbca图形界面&#xff0c;要安装一个Xmanage软件&#xff0c;用Xstart连接终端&#xff0c;并修改oracle用户的.profile&#xff0c;加上“export DISPLAY192.168.8.120:0.0”Xstart配置信息如下&#xff1a;2、手工删除数据库…