小Alan最近看到了《Effective Java》这本书,这本书包含的内容非常丰富,这本书我就不多介绍了,只能默默的说一句,作为一名java开发错过了这本书难免会成为一个小遗憾,所以还是建议有时间的小伙伴能够去看看这本书,时间挤挤总还是有的。这本书介绍的很多东西我现在也还看不太明白,很多东西我们在平时的开发中也不见得会用上,所以我不会每个东西都拿来详细解释一遍,只会从中抽取我们平时开发中比较实用的,以及小Alan这个小菜鸟能够看懂的部分,至于一些不实用的以及比较高深的部分那就只能随着小Alan的工作经历和深入理解再慢慢的整理出来给自己也给部分觉得有用的朋友理清思路。

《Effective Java 》第5条:避免创建不必要的对象

我们把原文拆分成几部分来理解,实现一个一个的小目标,最后来完全理解这一块的内容。

第一部分:一般来说,最好能重用对象而不是在每次需要的时候就创建一个相同功能的新对象。重用方式既快速,又流行。如果对象是不可变的,它就始终可以被重用。

反面例子:

String s = new String("啪啪啪");  //Don't do this!

该语句每次被执行的时候都创建一个新的String实例,但是这些创建对象的动作全都是不必要的。传递给String构造器的参数("啪啪啪")本身就是一个String实例,功能方面等同于构造器创建的所有对象。如果这种用法是在一个循环中,或是在一个被频繁调用的方法中,就会创建成千上万不必要的String实例。

改进版本:

String s = "啪啪啪";

这个版本只用了一个String实例,而不是每次执行的时候都创建一个新的String实例。而且,它可以保证,对于所有在同一台虚拟机中运行的代码,只要它们包含相同的字符串字面常量,该对象就会被重用。

扩展思路:①在Java1.7中运行,Java会在方法区运行时常量池中记录首次出现的实例,也就是说会在常量池中保存"啪啪啪",那么当你下次调用String s = "啪啪啪";的时候,Java会直接返回这个对象的引用,而不会去重新创建一个新的对象,这样就节省了内存的开销,也可以放心的在循环中去使用,也不怕在方法中被频繁的调用。String s = new String("啪啪啪");实际上创建了两个对象,一个存放在堆中,一个就是保存在常量池中的"啪啪啪",s只是对象的引用保存在栈中,而String s = "啪啪啪";只会创建一个对象保存在常量池中,然后保存一个对象的引用在栈中就ok了(对Java虚拟机理解不是很深入,理解有误请指出,万分感谢)。

第二部分:对于同时提供了静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法而不是构造器,以避免创建不必要的对象。例如,静态工厂方法Boolean.valueOf(String)几乎总是优先于构造器Boolean(String)。构造器在每次被调用的时候都会创建一个新的对象,而静态工厂方法则从来不要求这样做,实际上也不会这样做。

扩展思路:

 1 package com.czgo.effective;
 2
 3 /**
 4  * 用valueOf()静态工厂方法代替构造器
 5  * @author AlanLee
 6  * @version 2016/12/01
 7  *
 8  */
 9 public class Test {
10
11     public static void main(String[] args) {
12         // 使用带参构造器
13         Integer a1 = new Integer("1");
14         Integer a2 = new Integer("1");
15
16         //使用valueOf()静态工厂方法
17         Integer a3 = Integer.valueOf("1");
18         Integer a4 = Integer.valueOf("1");
19
20         //结果为false,因为创建了不同的对象
21         System.out.println(a1 == a2);
22
23         //结果为true,因为不会新建对象
24         System.out.println(a3 == a4);
25     }
26
27 }

可见,使用静态工厂方法valueOf不会新建一个对象,避免大量不必要的对象被创建,实际上很多类默认的valueOf方法都不会返回一个新的实例,比如原文提到的Boolean类型,不仅仅是Java提供的这些类型,我们在平时的开发中如果也有类似的需求不妨模仿Java给我们提供的静态工厂方法,给我们自己的类也定义这样的静态工厂方法来实现对象的获取,避免对象的重复创建,但是也不要过度迷信使用静态工厂方法的方式,这种方式也有它的弊端(有关静态工厂方法的知识可以看看《Effective Java》第一条),个人很少使用这种方式,平时的类多创建个对象也不会有太大的影响,只要稍微注意下用法就ok了。

第三部分:除了重用不可变的对象之外,也可以重用那些已知不会修改的可变对象。书上写的例子让人非常难以理解,我也没花时间去看了,我给大家想出来一个类似的例子,也不知道是否是这个意思,多多指教!

反面例子:

 1 package com.czgo.effective;
 2
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.sql.SQLException;
 6
 7 public class DBUtilBad {
 8     private static final String URL = "jdbc:mysql://127.0.0.1:3306/imooc";
 9     private static final String UNAME = "root";
10     private static final String PWD = "root";
11
12     public static Connection getConnection() {
13         Connection conn = null;
14         try {
15             // 1.加载驱动程序
16             Class.forName("com.mysql.jdbc.Driver");
17             // 2.获得数据库的连接
18             conn = DriverManager.getConnection(URL, UNAME, PWD);
19         } catch (ClassNotFoundException e) {
20             e.printStackTrace();
21         } catch (SQLException e) {
22             e.printStackTrace();
23         }
24         return conn;
25     }
26 }

该类提供的getConnection方法获取JDBC数据库连接对象,每次调用该方法都会新建一个conn实例,而我们知道在平时的开发中数据库连接对象往往只需要一个,也不会总是去修改它,没必要每次都去新创建一个连接对象,每次都去创建一个实例不知道程序会不会出现什么意外情况,这个我不知道,但有一点是肯定的,这种方式影响程序的运行性能,增加了Java虚拟机垃圾回收器的负担。我们可以对它进行改进。

改进版本:

 1 package com.czgo.effective;
 2
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.sql.SQLException;
 6
 7 public class DBUtil {
 8     private static final String URL = "jdbc:mysql://127.0.0.1:3306/imooc";
 9     private static final String UNAME = "root";
10     private static final String PWD = "root";
11
12     private static Connection conn = null;
13
14     static {
15         try {
16             // 1.加载驱动程序
17             Class.forName("com.mysql.jdbc.Driver");
18             // 2.获得数据库的连接
19             conn = DriverManager.getConnection(URL, UNAME, PWD);
20         } catch (ClassNotFoundException e) {
21             e.printStackTrace();
22         } catch (SQLException e) {
23             e.printStackTrace();
24         }
25     }
26
27     public static Connection getConnection() {
28         return conn;
29     }
30 }

我们使用了静态代码块来创建conn实例,改进后只有在类加载初始化的时候创建了conn实例一次,而不是在每次调用getConnection方法的时候都去创建conn实例。如果getConnection方法被频繁的调用和使用,这种方式将会显著的提高我们程序的性能。除了提高性能之外,代码的含义也更加的清晰了,使得代码更易于理解。

第四部分:Map接口的keySet方法返回该Map对象的Set视图,其中包含该Map中所有的键(key)。粗看起来,好像每次调用keySet都应该创建一个新的Set实例,但是,对于一个给定的Map对象,实际上每次调用keySet都返回同样的Set实例。虽然被返回的Set实例一般是可改变的,但是所有返回的对象在功能上是等同的:当其中一个返回对象发生变化的时候,所有其他返回对象也要发生变化,因为它们是由同一个Map实例支撑的。虽然创建keySet视图对象的多个实例并无害处,却也是没有必要的。这一部分内容我不是特别的理解,贴一段代码给大家分析,如果有对这部分比较理解的朋友,请留下你宝贵的经验,万分感谢!

 1 package com.czgo.effective;
 2
 3 import java.util.HashMap;
 4 import java.util.Iterator;
 5 import java.util.Map;
 6 import java.util.Set;
 7
 8 public class TestKeySet {
 9
10     public static void main(String[] args) {
11
12         Map<String,Object> map = new HashMap<String,Object>();
13         map.put("A", "A");
14         map.put("B", "B");
15         map.put("C", "C");
16
17         Set<String> set = map.keySet();
18         Iterator<String> it = set.iterator();
19         while(it.hasNext()){
20             System.out.println(it.next()+"①");
21         }
22
23         System.out.println("---------------");
24
25         map.put("D", "D");
26         set = map.keySet();
27         it = set.iterator();
28         while(it.hasNext()){
29             System.out.println(it.next()+"②");
30         }
31
32     }
33
34 }

第五部分:有一种创建多余对象的新方法,称作自动装箱(autoboxing),它允许程序员将基本类型和装箱基本类型(Boxed Primitive Type<引用类型>)混用,按需要自动装箱和拆箱。自动装箱使得基本类型和引用类型之间的差别变得模糊起来,但是并没有完全消除。它们在语义上还有着微妙的差别,在性能上也有着比较明显的差别。考虑下面的程序,它计算所有int正值的总和。为此,程序必须使用long变量,因为int不够大,无法容纳所有int正值的总和:

 1 package com.czgo.effective;
 2
 3 public class TestLonglong {
 4
 5     public static void main(String[] args) {
 6         Long sum = 0L;
 7         for(long i = 0; i < Integer.MAX_VALUE; i++){
 8             sum += i;
 9         }
10         System.out.println(sum);
11     }
12
13 }

这段程序算出的结果是正确的,但是比实际情况要慢的多,只因为打错了一个字符。变量sum被声明成Long而不是long,意味着程序构造了大约2的31次方个多余的Long实例(大约每次往Long sum中增加long时构造一个实例)。将sum的声明从Long改成long,速度快了不是一点半点。结论很明显:要优先使用基本类型而不是引用类型,要当心无意识的自动装箱。

最后,不要错误地认为"创建对象的代价非常昂贵,我们应该尽可能地避免创建对象"。相反,由于小对象的构造器只做很少量的显示工作,所以小对象的创建和回收动作是非常廉价的,特别是在现代的JVM实现上更是如此。通过创建附加的对象,提升程序的清晰性、简洁性和功能性,这通常是件好事。

反之,通过维护自己的对象池(Object pool)来避免创建对象并不是一种好的做法,除非池中的对象是非常重量级的。真正正确使用对象池的典型对象示例就是数据库连接池。建立数据库连接的代价是非常昂贵的,因此重用这些对象非常有意义。而如今的JVM(Java虚拟机)具有高度优化的垃圾回收器,如果是轻量的对象池可能还不如垃圾回收器的性能。

这里我们说到“当你应该重用现有对象的时候,请不要创建新的对象”,反之我们也应该考虑一个问题“当你应该创建新对象的时候,请不要重用现有的对象”。有时候重用对象要付出的代价要远远大于因创建重复对象而付出的代价。必要时,如果没能创建新的对象实例将会导致潜在的错误和安全漏洞;而不必要地创建对象则只会影响程序的风格和性能。

结束语:没有目标的人注定不能成功。但如果目标过大,努力很长一段时间你仍达不到目标,就会觉得疲惫,继而容易产生懈怠心理,甚至可能会放弃追求。如果将大目标分解成具体的小目标,分阶段逐一实现,就可以不断尝到成功的喜悦,继而产生更大的动力去实现下一阶段的目标。所以在平时的工作学习中,我们应该有远大的目标,但要学会拆分我们的目标使之成为一个个的小目标,当我的这个小目标无法实现的时候,我就先实现自己能够实现的小目标。慢慢的,那些完不成的小目标最后也能渐渐实现。最后,实现我们的大目标也不是不可能的。不积跬步,无以至千里,不积小流,无以成江河。

可爱博主:AlanLee

博客地址:http://www.cnblogs.com/AlanLee

本文出自博客园,欢迎大家加入博客园。

转载于:https://www.cnblogs.com/AlanLee/p/6122416.html

Java避免创建不必要的对象相关推荐

  1. Java中创建(实例化)对象的五种方式

    Java中创建(实例化)对象的五种方式 1.用new语句创建对象,这是最常见的创建对象的方法. 2.通过工厂方法返回对象,如:String str = String.valueOf(23);  3.运 ...

  2. 创建(实例化)对象的五种方式

    目录 一.Java中创建(实例化)对象的五种方式 1.new关键字创建对象; 2.调用对象的clone()方法创建对象 3.通过反射对对象进行初始化 4.序列化 5.通过工厂方法返回对象 二.Java ...

  3. 【读薄Effective Java】创建和销毁对象

    1. 考虑用静态工厂方法代替构造器 1.1 静态工厂的优点 静态工厂就是通过静态方法来代替构造器.相比构造函数,它有几个优势. 构造器没有名称.而静态工厂能指定名称,当一个类有多组构造函数的时候,可以 ...

  4. Effective Java:创建和销毁对象

    前言: 读这本书第1条规则的时候就感觉到这是一本很好的书,可以把我们的Java功底提升一个档次,我还是比较推荐的.本博客是针对<Effective Java>这本书第2章所写的一篇读书笔记 ...

  5. Effective Java之避免创建不必要的对象

    Effective Java中有很多值得注意的技巧,今天我刚开始看这本书,看到这一章的时候,我发现自己以前并没有理解什么是不必要的对象,所以拿出来跟大家探讨一下,避免以后犯不必要的错误! 首先书中对不 ...

  6. Java黑皮书课后题第10章:*10.1(Time类)设计一个名为Time的类。编写一个测试程序,创建两个Time对象(使用new Time()和new Time(555550000))

    Java黑皮书课后题第10章:*10.1设计一个名为Time的类.编写一个测试程序,创建两个Time对象 题目 程序 代码 Test1.java Test1_Time.java 运行结果 UML 题目 ...

  7. 反射创建对象_如何应用Java反射技术灵活地创建程序类的对象实例

    软件项目实训及课程设计指导--如何应用Java反射技术灵活地创建程序类的对象实例 1.如何应用属性配置文件实现对系统中的配置信息进行读写操作 Java中的属性配置文件主要可以作为软件应用系统及项目的配 ...

  8. java创建一个不可变对象_使用不可变对象创建值对象

    java创建一个不可变对象 在回答我最近的文章中AutoValue:生成的不可变的值类 , 布兰登认为,这可能是有趣的,看看如何AutoValue比较项目Lombok和Immutables和凯文借调这 ...

  9. java string对象创建对象_Java String 创建了几个对象

    我们首先来看一段代码: Java代码 String str=new String("abc"); 紧接着这段代码之后的往往是这个问题,那就是这行代码究竟创建了几个String对象呢 ...

最新文章

  1. Scrapy项目实战
  2. 使用注解打造自己的IOC框架
  3. HokeyPokey — WWDC讲师特供XCode插件高仿版的设计与实现
  4. Python常用模块之configparser
  5. permutations python_为什么Python的itertools.permutations包含重复项? (当原始列表重复时)...
  6. skywalking使用方法_skywalking 6.2配置相关和使用
  7. .NET MVC运行周期
  8. win7宽带已连接但是有感叹号无法上网的解决方法
  9. java webservice ssl_[转贴]Java客户端调用Https Webservice
  10. Python生态概览(二):网络爬虫、web信息提取、网站开发、网络应用开发
  11. Android BottomNavigationBar导航栏
  12. [JSOI2012]玄武密码 题解(AC自动机)
  13. Redis集群原理和总结
  14. 我就是要用MD5!不用不行!那么,怎么防止被拖库后泄露用户密码?
  15. 新手教学,如何快速地画一个PCB板子
  16. 树莓派can总线_RPi 2B: CAN总线通信 - 通过OBD-II接口获取车辆信息
  17. dock接口_回看手机接口发展史:TypeC将实现大一统?
  18. 删除Linux系统中的大文件
  19. 本周大新闻|华为发布BB观影眼镜,Geenee AR试穿加入AI生成玩法
  20. RecyclerView图片错乱复用问题

热门文章

  1. php中单引号和双引号的区别,哪个速度更快?为什么?
  2. Android MVC结构的浅见【转】
  3. XML文件读取数据绑定到DropDownList
  4. jenkins-为什么要持续集成
  5. WWDC上这个神级功能,一言不合又要改变未来购物趋势
  6. 【Extjs】large按钮,图片全部覆盖按钮
  7. [收藏] 王永民先生:自我白描
  8. 对C#下函数,委托,事件的一点理解!
  9. 项目实战之组件化架构
  10. Prometheus监控的最佳实践——关于监控的3项关键指标