java中空值处理

举报
Amrf 发表于 2019/12/09 09:58:10 2019/12/09
【摘要】 上周给同事的项目做代码review发现有不少空值处理的问题,由于java里没有像c#里的?.这种写法,很多时候空值处理很容易遗漏,用三目或者条件表达式补上去又会显得比较冗长; 其实常见的判空场景种类其实并不多, 看了一些帖子,感觉下面的做法就不错了:obj.toString() => java.util.Objects.toString(obj,defaultValue);list!=nul...

上周给同事的项目做代码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://stackoverflow.com/questions/12721076/best-practice-to-validate-null-and-empty-collection-in-java

https://stackoverflow.com/questions/5522201/any-simple-way-to-test-null-before-convert-an-object-to-string

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

https://lrwinx.github.io/2018/08/30/java%E5%8C%A0%E4%BA%BA%E6%89%8B%E6%B3%95-%E4%BC%98%E9%9B%85%E7%9A%84%E5%A4%84%E7%90%86%E7%A9%BA%E5%80%BC/

/*------------------- 我目前的观点是,注重编程细节,但是又不能教条的拘泥于细节----------------------------------------------------*/

/*------------------------------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


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。