商务网站建设流程步骤,阿里云商业网站建设视频,wordpress主页空白页,有趣的网站网址java微妙这是10条最佳实践的列表#xff0c;这些最佳实践比您的平均Josh Bloch有效Java规则要微妙得多。 尽管Josh Bloch的列表很容易学习#xff0c;并且涉及日常情况#xff0c;但此处的列表包含了涉及API / SPI设计的较不常见的情况#xff0c;尽管这些情况可能会产生很… java微妙 这是10条最佳实践的列表这些最佳实践比您的平均Josh Bloch有效Java规则要微妙得多。 尽管Josh Bloch的列表很容易学习并且涉及日常情况但此处的列表包含了涉及API / SPI设计的较不常见的情况尽管这些情况可能会产生很大的影响。 我在编写和维护jOOQ时遇到了这些问题 jOOQ是Java中的内部DSL建模SQL。 作为内部DSLjOOQ最大限度地挑战了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 / afterallocate / freetake / 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 HashMapString, String() {{put(1, a);put(2, b);
}} 这利用了JLS§8.6中指定的 Java实例初始化程序 。 看起来不错也许有点奇怪但确实是个坏主意。 原来是完全独立的HashMap实例现在将保留对外部实例的引用无论发生什么情况。 此外您将创建一个额外的类供类加载器管理。 4.立即开始编写SAM Java 8正在敲门。 随Java 8一起提供lambda 无论您是否喜欢。 不过您的API使用者可能会喜欢它们因此您最好确保他们可以尽可能多地使用它们。 因此除非您的API接受简单的“标量”类型例如int long String Date 否则您的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非常困难 由于没有记录状态因此很难使全局状态资源可用 阿甘正传的戏剧海报版权所有©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的发展速度下已经有数百项警告这些警告只能通过多年的经验来掌握。 参考在JAVASQL和JOOQ博客上来自我们JCG合作伙伴 Lukas Eder的Java编码Java时的10个最佳最佳实践 。 翻译自: https://www.javacodegeeks.com/2013/08/10-subtle-best-practices-when-coding-java.htmljava微妙