全新出击!《Java开发手册(嵩山版)》解读手册升级下载
简介: 《〈Java开发手册(嵩山版)〉灵魂15问》重磅来袭!“一线大厂如何用Java”解读再升级,千万阅读量博主深究Java规约背后的原理。规范学好Java还不来看一看?
《Java开发手册(嵩山版)》解读版升级下载
随着《Java开发手册(嵩山版)》的发布,解读再升级!灵魂13问随新版JAVA开发手册重磅回归,一线大厂怎么用JAVA?千万阅读量技术博主15问为你全面剖析。
作者介绍
Hollis,一个对Coding有着独特追求的人,现任阿里巴巴技术专家,个人技术博主,技术文章全网阅读量数千万,《程序员的三门课》联合作者。
或者复制该链接到浏览器完成下载或分享:https://developer.aliyun.com/topic/download?id=811
精彩导读

一、为什么禁止使用Apache Beanutils进行属性的copy?
市面上有很多类似的属性拷贝工具类,比较常用的有
1、Spring BeanUtils 2、Cglib BeanCopier 3、Apache BeanUtils 4、Apache PropertyUtils 5、Dozer
那么,我们到底应该选择哪种工具类更加合适呢?为什么Java开发手册中提到禁止使用Apache BeanUtils呢?

接下来就聚焦于对比这几个类库的性能问题来分析。
为什么阿里巴巴禁止使用Apache Beanutils进行属性的copy?
在日常开发中,我们经常需要给对象进行赋值,通常会调用其set/get方法,有些时候,如果我们要转换的两个对象之间属性大致相同,会考虑使用属性拷贝工具进行。
如我们经常在代码中会对一个数据结构封装成DO、SDO、DTO、VO等,而这些Bean中的大部分属性都是一样的,所以使用属性拷贝类工具可以帮助我们节省大量的set和get操作。
市面上有很多类似的工具类,比较常用的有
1、Spring BeanUtils 2、Cglib BeanCopier 3、Apache BeanUtils 4、Apache PropertyUtils 5、Dozer
那么,我们到底应该选择哪种工具类更加合适呢?为什么阿里巴巴Java开发手册中提到禁止使用Apache BeanUtils呢?
由于篇幅优先,关于这几种工具类的用法及区别,还有到底是什么是浅拷贝和深拷贝不在本文的讨论范围内。
本文主要聚焦于对比这几个类库的性能问题。
性能对比
No Data No BB,我们就来写代码来对比下这几种框架的性能情况。
代码示例如下:
首先定义一个PersonDO类:
public class PersonDO {
private Integer id;
private String name;
private Integer age;
private Date birthday;
//省略setter/getter
}
再定义一个PersonDTO类:
public class PersonDTO {
private String name;
private Integer age;
private Date birthday;
}
然后进行测试类的编写:
使用Spring BeanUtils进行属性拷贝:
private void mappingBySpringBeanUtils(PersonDO personDO, int times) {
StopWatch stopwatch = new StopWatch();
stopwatch.start();
for (int i = 0; i < times; i++) {
PersonDTO personDTO = new PersonDTO();
org.springframework.beans.BeanUtils.copyProperties(personDO, personDTO);
}
stopwatch.stop();
System.out.println("mappingBySpringBeanUtils cost :" + stopwatch.getTotalTimeMillis());
}
其中的StopWatch用于记录代码执行时间,方便进行对比。
使用Cglib BeanCopier进行属性拷贝:
private void mappingByCglibBeanCopier(PersonDO personDO, int times) {
StopWatch stopwatch = new StopWatch();
stopwatch.start();
for (int i = 0; i < times; i++) {
PersonDTO personDTO = new PersonDTO();
BeanCopier copier = BeanCopier.create(PersonDO.class, PersonDTO.class, false);
copier.copy(personDO, personDTO, null);
}
stopwatch.stop();
System.out.println("mappingByCglibBeanCopier cost :" + stopwatch.getTotalTimeMillis());
}
使用Apache BeanUtils进行属性拷贝:
private void mappingByApacheBeanUtils(PersonDO personDO, int times)
throws InvocationTargetException, IllegalAccessException {
StopWatch stopwatch = new StopWatch();
stopwatch.start();
for (int i = 0; i < times; i++) {
PersonDTO personDTO = new PersonDTO();
BeanUtils.copyProperties(personDTO, personDO);
}
stopwatch.stop();
System.out.println("mappingByApacheBeanUtils cost :" + stopwatch.getTotalTimeMillis());
}
使用Apache PropertyUtils进行属性拷贝:
private void mappingByApachePropertyUtils(PersonDO personDO, int times)
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
StopWatch stopwatch = new StopWatch();
stopwatch.start();
for (int i = 0; i < times; i++) {
PersonDTO personDTO = new PersonDTO();
PropertyUtils.copyProperties(personDTO, personDO);
}
stopwatch.stop();
System.out.println("mappingByApachePropertyUtils cost :" + stopwatch.getTotalTimeMillis());
}
然后执行以下代码:
public static void main(String[] args)
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
PersonDO personDO = new PersonDO();
personDO.setName("Hollis");
personDO.setAge(26);
personDO.setBirthday(new Date());
personDO.setId(1);
MapperTest mapperTest = new MapperTest();
mapperTest.mappingBySpringBeanUtils(personDO, 100);
mapperTest.mappingBySpringBeanUtils(personDO, 1000);
mapperTest.mappingBySpringBeanUtils(personDO, 10000);
mapperTest.mappingBySpringBeanUtils(personDO, 100000);
mapperTest.mappingBySpringBeanUtils(personDO, 1000000);
mapperTest.mappingByCglibBeanCopier(personDO, 100);
mapperTest.mappingByCglibBeanCopier(personDO, 1000);
mapperTest.mappingByCglibBeanCopier(personDO, 10000);
mapperTest.mappingByCglibBeanCopier(personDO, 100000);
mapperTest.mappingByCglibBeanCopier(personDO, 1000000);
mapperTest.mappingByApachePropertyUtils(personDO, 100);
mapperTest.mappingByApachePropertyUtils(personDO, 1000);
mapperTest.mappingByApachePropertyUtils(personDO, 10000);
mapperTest.mappingByApachePropertyUtils(personDO, 100000);
mapperTest.mappingByApachePropertyUtils(personDO, 1000000);
mapperTest.mappingByApacheBeanUtils(personDO, 100);
mapperTest.mappingByApacheBeanUtils(personDO, 1000);
mapperTest.mappingByApacheBeanUtils(personDO, 10000);
mapperTest.mappingByApacheBeanUtils(personDO, 100000);
mapperTest.mappingByApacheBeanUtils(personDO, 1000000);
}
得到结果如下:
| 工具类 | 执行1000次耗时 | 执行10000次耗时 | 执行100000次耗时 | 执行1000000次耗时 |
|---|---|---|---|---|
| Spring BeanUtils | 5ms | 10ms | 45ms | 169ms |
| Cglib BeanCopier | 4ms | 18ms | 45ms | 91ms |
| Apache PropertyUtils | 60ms | 265ms | 1444ms | 11492ms |
| Apache BeanUtils | 138ms | 816ms | 4154ms | 36938ms |
| Dozer | 566ms | 2254ms | 11136ms | 102965ms |
画了一张折线图更方便大家进行对比
综上,我们基本可以得出结论,在性能方面,Spring BeanUtils和Cglib BeanCopier表现比较不错,而Apache PropertyUtils、Apache BeanUtils以及Dozer则表现的很不好。
所以,如果考虑性能情况的话,建议大家不要选择Apache PropertyUtils、Apache BeanUtils以及Dozer等工具类。
很多人会不理解,为什么大名鼎鼎的Apache开源出来的的类库性能确不高呢?这不像是Apache的风格呀,这背后导致性能低下的原因又是什么呢?
其实,是因为Apache BeanUtils力求做得完美, 在代码中增加了非常多的校验、兼容、日志打印等代码,过度的包装导致性能下降严重。
总结
本文通过对比几种常见的属性拷贝的类库,分析得出了这些工具类的性能情况,最终也验证了《阿里巴巴Java开发手册》中提到的”Apache BeanUtils 效率低”的事实。
但是本文只是站在性能这一单一角度进行了对比,我们在选择一个工具类的时候还会有其他方面的考虑,比如使用成本、理解难度、兼容性、可扩展性等,对于这种拷贝类工具类,我们还会考虑其功能是否完善等。
就像虽然Dozer性能比较差,但是他可以很好的和Spring结合,可以通过配置文件等进行属性之间的映射等,也受到了很多开发者的喜爱。
本文用到的第三方类库的maven依赖如下:
<!--Apache PropertyUtils、Apache BeanUtils-->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.2</version>
</dependency>
<!--Spring PropertyUtils-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.beans</artifactId>
<version>3.1.1.RELEASE</version>
</dependency>
<!--cglib-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2.2</version>
</dependency>
<!--dozer-->
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.5.1</version>
</dependency>
<!--日志相关-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.7</version>
</dependency>
二、为什么要求日期格式化时必须有使用y表示年,而不能用Y?
在Java中进行日期处理大家一定都不陌生,我们经常会需要在代码中进行日期的转换、日期的格式化等操作。
而一般我们进行日期格式化的时候都会使用SimpleDateFormat工具,之前我们有一篇文章介绍过SimpleDateFormat的线程安全问题,这一篇文章再来介绍一个和SimpleDateFormat有关,很容易被忽视,而一旦忽视可能导致大故障的问题。>>点击查看详情
三、《 Java 开发手册-泰山版》提到的三目运算符的空指针问题到底是个怎么回事?
手册中有一条规约引起了作者的关注,那就是手册中提到在三目运算符使用过程中,需要注意自动拆箱导致的NullPointerException(后文简称:NPE)问题:
具体是怎样的呢?>>点击查看详情
四、为什么建议初始化HashMap的容量大小?
我们之前提到过,《Java 开发手册》中建议我们设置 HashMap 的初始化容量。
那么,为什么要这么建议?>>点击查看详情
五、Java开发手册建议创建HashMap时设置初始化容量, 但是多少合适呢?
HashMap 有扩容机制,就是当达到扩容条件时会进行扩容。HashMap 的扩容条件就是当 HashMap 中的元素个数(size)超过临界值(threshold)时就会自动扩容。在 HashMap 中,threshold = loadFactor * capacity。
所以,如果我们没有设置初始容量大小,随着元素的不断增加,HashMap 会发生多次扩容,而 HashMap 中的扩容机制决定了每次扩容都需要重建 hash 表,是非常影响性能的。>>点击查看创建HashMap时设置初始化容量多少合适
六、为什么禁止使用Executors创建线程池?
为什么说可以通过Executors静态工厂构建线程池,但一般不建议这样使用。
本节我们就来围绕这个问题来分析一下为什么JDK自身提供的构建线程池的方式并不建议使用?到底应该如何创建一个线程池呢?>>点击查看详情
七、为什么要求谨慎使用ArrayList中的subList方法?
集合是Java开发日常开发中经常会使用到的。
关于集合类,《Java开发手册》中其实还有另外一个规定:
本节就来分析一下为什么会有如此建议?其背后的原理是什么?>>点击查看详情
八、为什么不建议在for循环中使用“+”进行字符串拼接?
使用+拼接字符串,其实只是Java提供的一个语法糖,那么他的内部原理到底是如何实现的。>>点击查看详情
语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
九、为什么禁止在for each循环里进行元素的remove/add操作?
在Java开发手册中,有这样一条规定:
本节就来深入分析一下该规定背后的思考。>>点击查看详情
十、为什么禁止工程师直接使用日志系统(Log4j、Log back) 中的API?
作为Java程序员,我想很多人都知道日志对于一个程序的重要性,尤其是Web应用。很多时候,日志可能是我们了解应用程序如何执行的唯一方式。
所以,日志在Java Web应用中至关重要,但是,很多人却以为日志输出只是一件简单的事情,所以会经常忽略和日志相关的问题。>>点击查看详情
十一、为什么禁止把SimpleDateFormat定义成static变量?
在日常开发中,我们经常会用到时间,我们有很多办法在Java代码中获取时间。但是不同的方法获取到的时间的格式都不尽相同,这时候就需要一种格式化工具,把时间显示成我们需要的格式。
最常用的方法就是使用SimpleDateFormat类。这是一个看上去功能比较简单的类,但是,一旦使用不当也有可能导致很大的问题。本节就围绕SimpleDateFormat的用法、原理等来深入分析下如何以正确的姿势使用它。>>点击查看详情
十二、为什么禁止开发人员使用is Success作为变量名?
在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个RPC接口的时候,我们一般会定义一个字段表示本次请求是否成功的。
关于这个”本次请求是否成功”的字段的定义,其实是有很多种讲究和坑的,稍有不慎就会掉入坑里,作者在很久之前就遇到过类似的问题,本节就来围绕这个简单分析一下,到底该如何定一个布尔类型的成员变量。>>点击查看详情
十三、为什么禁止开发人员修改serialVersionUID字段的值?
关于serialVersionUID 。这个字段到底有什么用?如果不设置会怎么样?为什么《Java开发手册》中有以下规定:
本节带你一探究竟。>>点击查看详情
十四、为什么建议开发者谨慎使用继承?
对于很多开发者来说,继承肯定都是不陌生的。但是,继承一定适合所有的场景吗?毫无忌讳的使用继承来做代码扩展真的好吗?
为什么《Java开发手册》中有一条规定:谨慎使用继承的方式进行扩展,优先使用组合的方式实现。>>点击查看详情
十五、为什么禁止使用count(列名) 或count(常量) 来替代count(*)?
除了COUNT(id)和COUNT(*)以外,还可以使用COUNT(常量)(如COUNT(1))来统计行数,那么这三条SQL语句有什么区别呢?到底哪种效率更高呢?为什么《Java开发手册》中强制要求不让使用 COUNT(列名)或 COUNT(常量)来替代 COUNT(*)呢?
本节就这些问题带来解答。>>点击查看详情

浙公网安备 33010602011771号