这是一个比乔什布洛赫的有效的Java规则更精妙的10条Java编码实践的列表。和乔什布洛赫的列表容易学习并且关注日常情况相比,这个列表将包含涉及API/SPI设计中不常见的情况,可能有很大影响。
我在编写和维护jOOQ (Java中内部DSL建模的SQL)时遇到过这些。作为一个内部DSL, jOOQ最大限度的挑战了Java的编译器和泛型,把泛型,可变参数和重载结合在一起,乔什·布洛赫可能不会推荐的这种太宽泛的API。
让我与你分享10个微妙的Java编码最佳实践:
<强> 1。牢记c++的析构函数强>
记得c++的析构函数?不记得了?那么你真的很幸运,因为你不必去调试那些由于对象删除后分配的内存没有被释放而导致内存泄露的代码。感谢Sun/Oracle实现的垃圾回收机制吧!
尽管如此,析构函数仍提供了一个有趣的特征。它理解逆分配顺序释放内存。记住在Java中也是这样的,当你操作类析构函数语法:
-
<李>使用JUnit的@Before和@After注释李>
<李>分配,释放JDBC资源李>
<李>调用超级方法李>
还有其他各种用例。这里有一个具体的例子,说明如何实现一些事件侦听器的SPI:
@Override 公共空间beforeEvent (EventContext e) { super.beforeEvent (e);//超级代码在我的代码 } @Override 公共空间afterEvent (EventContext e) {//超级代码在我的代码 super.afterEvent (e); }
臭名昭著的哲学家就餐问题是另一个说明它为什么重要的好例子。关于哲学家用餐的问题,请查看链接:
http://adit.io/posts/2013-05-11-The-Dining-Philosophers-Problem-With-Ron-Swanson.html
<>强规则强>:无论何时使用之前/之后,分配/释放,带/返回语义实现逻辑时,考虑是否逆序执行/自由/回来后操作。
2。不要相信你早期的SPI演进判断
向客户提供SPI可以使他们轻松的向你的库/代码中注入自定义行为的方法。当心你的SPI演进判断可能会迷惑你,使你认为你(不)打算需要附加参数。当然,不应当过早增加功能。但一旦你发布了你的SPI,一旦你决定遵循语义版本控制,当你意识到在某种情况下你可能需要另外一个参数时,你会真的后悔在SPI中增加一个愚蠢的单参数的方法:
EventListener接口{//坏 无效的消息(字符串消息); }
如果你也需要消息ID和消息源,怎么办? API演进将会阻止你向上面的类型添加参数。当然,有了Java8,你可以添加一个后卫方法,“防”御你早期糟糕的设计决策:
EventListener接口{//坏 默认空消息(字符串消息){ 消息(消息,零,零); }//更好的# 63; 无效的消息( 字符串消息, 整数id, MessageSource源 ); }
注意,不幸的是,防守方法不能使用最后修饰符。
但是比起使用许多方法污染你的SPI,使用上下文对象(或者参数对象)会好很多。
接口MessageContext { 字符串消息(); 整数id (); MessageSource源(); } EventListener接口{//太棒了! 无效的消息(MessageContext上下文); }
比起EventListner SPI你可以更容易演进MessageContext API,因为很少用户会实现它。
<>强规则强>:无论何时指定SPI时,考虑使用上下文/参数对象,而不是写带有固定参数的方法。
<>强备注强>:通过专用的MessageResult类型交换结果也是一个好主意,该类型可以使用建设者API构造它。这样将大大增加SPI进化的灵活性。
3。避免返回匿名,本地或者内部类
Swing程序员通常只要按几下快捷键即可生成成百上千的匿名类。在多数情况下,只要遵循接口,不违反SPI子类型的生命周期(SPI亚型生命周期),这样做也无妨。但是不要因为一个简单的原因,它们会保存对外部类的引用,就频繁的使用匿名,局部或者内部类。因为无论它们走到哪,外部类就得跟到哪,例如,在局部类的域外操作不当的话,那么整个对象图就会发生微妙的变化从而可能引起内存泄露。
规则:在编写匿名,局部或内部类前请三思能否将它转化为静态的或普通的顶级类,从而避免方法将它们的对象返回到更外层的域中。
注意:使用双层花括号来初始化简单对象: