java中空值处理
上周给同事的项目做代码review发现有不少空值处理的问题,由于java里没有像c#里的?.这种写法,很多时候空值处理很容易遗漏,用三目或者条件表达式补上去又会显得比较冗长;
其实常见的判空场景种类其实并不多, 看了一些帖子,感觉下面的做法就不错了:
obj.toString() => java.util.Objects.toString(obj,defaultValue);
list!=null && list.size()>0 => org.springframework.util.CollectionUtils.isEmpty(list)
//更广泛的可以使用org.springframework.util.ObjectUtils.isEmpty(list)
参考:
https://dzone.com/articles/stringvalueofobject-versus-objectstostringobject
https://juejin.im/post/5bf60e286fb9a049dd7fe9f8
http://shzhangji.com/cnblogs/2018/09/22/how-to-avoid-null-pointer-exception/
https://blog.csdn.net/Charein/article/details/17590523
/*------------------- 我目前的观点是,注重编程细节,但是又不能教条的拘泥于细节----------------------------------------------------*/
/*------------------------------StringBuilder/StringBuffer---------------------------------------------------------------*/
1. 划重点
未优化的字符串 + 操作性能最差。比如for循环中重复 + 操作就不会被优化,应该要避免。
简单的字符串 + 操作会被编译器优化成 StringBuilder.append 。比如: “This is ” + val + "." ,性能
几乎跟显式使用 StringBuilder.append 一样。(详见JHM测试结果)
StringBuffer几乎不会用到它! StringBuffer是线程安全的,有加锁开销,性能比StringBuilder稍差。如果你
用到了StringBuffer,请重新审视这么做的合理性和必要性。
StringBuilder在实际项目中使用最多! StringBuilder后于StringBuffer出现,是StringBuffer的简易替换,取
消了加锁,线程不安全,绝大多数字符串拼接场景都无需线程安全,因此使用最多。各种衍生的拼接类内部都
用到了它。
StringBuilderHelper(衍生自BigDecimal的ThreadLocal版StringBuilder)更适合大数据量字符串拼接,性
能最好,其内部做到了实例和缓冲区重复利用,也更节省资源。
StringBuilderHelper是一个线程握持一个StringBuilder实例,使用时要清楚这一点。如果想要在一个方法中
使用两个sb实例,则不能这么做:
StringBuilder sb1 = StringBuilderHelper.getStringBuilder();
StringBuilder sb2 = StringBuilderHelper.getStringBuilder();
sb1和sb2其实是同一个实例。
不同拼接方式性能对比
1. ThreadLocal版StringBuilder(更适合大数据量的字符串拼接
2. StringBuilder.append
3. 优化的+拼接 (简单字符串拼接)
4. StringBuffer.append
5. 未优化的+拼接(比如for/while循环)
建议1:对于大批量字符串拼接,绝大多数情况下,都建议使用StringBuilder。
建议2:如果真的需要线程安全才使用StringBuffer,但请一定严格审视这么做的必要性。
这里的toString中不存在循环等复杂语句,属于简单字符串拼接,为了代码简洁性和可读性,建议使用+拼接。不用
担心性能受影响,因为java编译器已经帮我们自动转换成StringBuilder了(Oracle JDK 1.8/Huawei OpenJDK 1.8上用
javap验证)。
建议1:简单字符串拼接,直接用+完成。
建议2:简单的toString方法,一样建议使用+进行拼接,使得代码更简洁,更符合阅读习惯。
建议3:对于toString方法,可以进一步使用lombok的@ToString注解简化。
推荐写法1:简单字符串拼接直接用"+"
推荐写法2:大批量字符串拼接(非线程安全)
无需考虑线程安全的场景下,推荐使用StringBuilder字符串拼接
比StringBuffer性能好,大部分场景下都要考虑采用此接口
考虑进一步提升大字符串的拼接性能,降低内存占用,考虑使用下一节的StringBuilderHelper技术。
推荐以局部变量的方式使用来规避多线程竞争问题。
// 每次都以局部变量使用,这样就能保证不会出现多线程同步问题
StringBuilder sb = new StringBuilder();
for (final String s : dataList) {
sb.append(s);
}
return sb.toString();
直面NullPointerException
. 划重点
使用Objects.requireNonNull进行null检查。
使用Optional包装可能为空的返回值。
使用Optional简化编程。
返回零长度的数组或者集合,而不是null。
. Java String适合存储敏感信息明文吗?
修改建议
1. 不要将敏感数据(如密码、密钥)以明文形式存储于内存中,即使是短时间内临时存储。
2. 建议使用专用类存储明文的敏感信息,或者,将明文敏感信息临时存储在可变数据类型(如:Char数组、字节
数组)中,且用完后立即擦除。
使用String Joiner API写出简洁的代码
划重点
String Joiner API是围绕着StringBuilder做的一些便利性封装,使得编码更加简洁。
Java 1.8提供了三个String Joiner API实现:String.join()、StringJoiner和Collectors.joining()。在此之前,
Apache Commons lang3提供了StringUtils.join()来完成类似功能。
StringJoiner和Collectors.joining()除了支持分隔符拼接,还支持前缀、后缀的拼接。
StringJoiner的使用方式跟StringBuilder相似,Collectors.joining()主要结合Stream函数式编程使用。
更简洁的getter/setter/toString
Lombok属性访问注解
1. 为属性提供getter/setter方法 @Getter/@Setter
2. 为所有属性提供getter、setter、toString、equal等功能 @Data
3. 覆写toString方法 @toString
4. hashCode和equal方法 @EqualsAndHashCode
Lombok使用时需要注意的地方
1. 名称aType类似的属性使用@Getter
2. 用了@Data就不要有继承关系
单例(Singleton)你用对了吗
划重点
单例加载的时机可以分为即时加载 和延时加载 两种模式。
即时加载主要有:枚举类单例、静态公有域单例和静态工厂方法单例。
推荐程度: 枚举类单例 > 静态工厂方法单例 > 静态公有域单例。
例外: 如果你的单例类必须要继承某个超类,那么枚举类单例不宜使用。
延时加载主要有:静态内部类单例和 双重校验锁(DCL)单例。
推荐静态内部类单例。
双重校验锁有PMD告警。
如果没有特殊需要,优先使用即时加载模式的单例。
对于一些无状态的具有“唯一”特征的类,比如工具类,建议使用静态方法实现。
更好地申请和关闭资源
划重点
传统的 try-finally 方式存在复杂易出错和异常抑制(Suppressed)等问题。
使用 try-with-resource ,可以更安全、简洁地申请和关闭资源,同时解决了异常抑制问题。
try-with-resource 是语法糖,其最终仍然会被编译成 try-finally 方式并调用 close 方法关闭资源。
为了支持 try-with-resource ,资源类必须要实现 AutoClosable 接口,否则无法使用。
在使用 try-with-resource 之前,请务必确认下资源类是否已经实现了 AutoClosable 接口。
try-with-resource 使用很简单,申请资源的代码写在try后面的()中即可,无需显式调用 close 方法来关闭资
源。
为了使程序更加健壮,在 try-with-resouce 中使用装饰器时,建议显式声明被装饰/包裹对象的引用。
异常你用对了吗?
划重点
对可恢复的情况使用受检异常,对编程错误使用运行时异常。
不要用异常去控制程序的流程
切忌使用空catch块
避免多次在日志信息中记录同一个异常
在finally块中不要使用return、break或continue使finally块非正常结束
不要随意捕获Exception基类和Throwable基类,除非场景真的需要
敏感信息禁止放在异常信息里
Java 8 Stream了解多少?
划重点
Stream API 借助于 Lambda 表达式,为 Collection 操作提供了一个新的选择。如果使用得当,可以极大地提高
编程效率和代码可读性。
Stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。
审慎地使用 Stream
综合代码可读性、业务场景等适度使用。
在很多场景下,选择优雅、可读性好的代码比性能低一点更重要。就比如,我们都知道直接写汇编代码,
运行性能最高,但我们依然选择高级语言编码一样。关于性能问题,多去尝试、多去测试。
使用Stream时注意小心函数的副作用。
使用Stream API实现并发程序变得越来越容易,但编写正确快速的并发程序还像以前一样困难。
在产品中不建议使用并行Stream。
XXE与各种库滥用
1. 划重点
利用XXE漏洞(XML外部实体注入)可能造成以下攻击风险:
机密数据泄露
内部端口扫描
远程代码执行
拒绝服务攻击
预防XXE攻击:通过XML解析器的参数配置进行防护,如果明确不需要DTD时,完全禁止DOCTYPE声明是最好的策略;如果无法避免DTD时,请禁用外部实体。
对于不同的库,设置XXE防护的方式存在差异。
合适的封装公共代码块,是降低维护成本、减少风险的方式。
Effective Java 3
- 点赞
- 收藏
- 关注作者
评论(0)