第45条: 将局部变量的作用域最小化

该条目与第13条(使类和成员的可访问性最小)本质上是类似的。要使局部变量的作用域最小化,最有利的方法就是在第一次使用它的地方声明。在每个局部变量的声明处都应该包含一个初始化表达式。还要使方法小而集中。

第46条: for-each循环优于传统的for循环

传统的for循环的迭代器和索引变量在每个循环中会出现三次,这很容易出错。考虑下面的例子:

  1. public class Suits {
  2.  
  3. public static void main(String[] args) {
  4. // TODO Auto-generated method stub
  5. Collection<Suit> suits = Arrays.asList(Suit.values());
  6. Collection<Rank> ranks = Arrays.asList(Rank.values());
  7. List<Card> deck = new ArrayList<>();
  8. for(Iterator<Suit> i = suits.iterator(); i.hasNext(); ){
  9. Suit suit = i.next();
  10. for(Iterator<Rank> j = ranks.iterator(); j.hasNext(); ){
  11. // deck.add(new Card(i.next(), j.next()));
  12. System.out.println(suit + " " + j.next());
  13. }
  14. }
  15. System.out.println("-----------更好的方法---------------");
  16. for(Suit suit : suits){
  17. for(Rank rank : ranks){
  18. System.out.println(suit + " " + rank);
  19. }
  20. }
  21.  
  22. }
  23.  
  24. }
  25. enum Suit {CLUB, DIAMOND, HEART, SPADE}
  26. enum Rank {ACE, DEUCE, THREE , FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING}
  27. class Card {
  28. private Suit suit;
  29. private Rank rank;
  30. Card(Suit suit, Rank rank){
  31. this.suit = suit;
  32. this.rank = rank;
  33. }
  34. }

如果打开代码中的注释,我们会发现一个bug,迭代器对外部的集合(suits)调用了太多次的next方法,导致程序结果不是我们想要的。修复这个bug可以在外层添加 Suit suit = i.next();这段代码。但也有更好的方式,使用for-each循环,如上面代码中所示。

总之,for-each循环在简洁性和预防bug方面有着传统for循环无法比拟的优势。但下面三种情况无法使用for-each循环:

1.需要遍历集合,并删除选定的元素

2.需要遍历数组或列表,并取代它部分或全部的值

3.需要并行遍历多个集合

第47条:了解和使用类库

JDK中内置了大量的工具类库,但很多“不为人知”。这实际上考研的是编程人员对Java基础的掌握程度,例如输出数组的方法:Arrays.toString等等,再比如判断是否字符串为空是实际上有isEmpty方法的。书中建议每个程序员都应该熟悉java.lang、java.util

在进行工程项目类的开发时,不应重复造轮子,利用现有的已成熟的技术能避免很多bug和其他问题。除非自己业余爱好研究,重复造轮子我认为就很能提高编程水平了。

第48条:如果需要精确答案,请避免使用float和double

float和double类型不适合用于货币计算。因为要让它们精确地表示0.1(或10的任何其他负数次方值)是不肯能的。

看下面的例子:

  1. public class FloatTest {
  2.  
  3. public static void main(String[] args) {
  4. // TODO Auto-generated method stub
  5. System.out.println(1.03 - .42);
  6. double funds = 1.00;
  7. int itemsBought = 0;
  8. for(double price = .10; funds >= price; price += .10){
  9. funds -= price;
  10. itemsBought++;
  11. }
  12. System.out.println("itemsBought: " + itemsBought); //
  13. System.out.println("Change: " + funds); //0.3999999999999
  14. }
  15.  
  16. }

程序的结果不是我们想要的,按理第一条输出语句应返回4,第二条返回0.可以用BigDecimal类型代替double。

  1. public class BigDecimalTest {
  2. public static void main(String[] args) {
  3. final BigDecimal TEN_CENTS = new BigDecimal(".10");
  4. int itemsBought = 0;
  5. BigDecimal funds = new BigDecimal("1.00");
  6. for(BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)){
  7. itemsBought++;
  8. funds = funds.subtract(price);
  9. }
  10. System.out.println(itemsBought + " items bought");
  11. System.out.println("Money left over: $" + funds);
  12. }
  13.  
  14. }

这里结果就没问题了。当然如果不介意记录十进制的小数点,我们可以以分为单位,而不是以元为单位。

总而言之,如果需要精确答案,不要使用float或double。可以使用BigDecimal,如果性能关键,可以使用int或long,9位十进制数以内用int,不超过18位可以用long,超过18位就必须使用BigDecimal。

第49条:基本类型优先于装箱基本类型

基本类型和装箱基本类型有三个主要区别:

1.基本类型只有值,而装箱基本类型具有它们值不同的同一性,换句话说,两个装箱基本类型可以具有相同的值,但其对象引用不同。

2.基本类型有功能完备的值,而后者有非功能值-null

3.前者比后者更节省空间

来看一个有严重缺陷的例子:

  1. public class IntegerTest {
  2. public static void main(String[] args) {
  3. Comparator<Integer> naturalOrder = new Comparator<Integer>() {
  4. @Override
  5. public int compare(Integer first, Integer second) {
  6. return first < second ? -1 : (first == second ? 0 : 1);
  7. // 修正方案
  8. // int f = first;
  9. // int s = second;
  10. // return f < s ? -1 : (f == s ? 0 : 1);
  11. }
  12. };
  13. int result = naturalOrder.compare(new Integer(42), new Integer(42));
  14. System.out.println(result); //返回1 对装箱基本类型做==操作,执行对象同一性比较,导致结果与预期不一致
  15. }
  16. }

那什么时候该用装箱基本类型呢?第一是作为集合中的元素、键和值。第二在参数化类型中用装箱基本类型作为类型参数。最后,在进行反射的方法调用时,必须使用装箱基本类型。总之,可以选择的时候,基本类型要优先于装箱基本类型。自动装箱减少了使用装箱基本类型的繁琐性,但并没有减少它的风险。

第50条:如果其他类型更合适,则尽量避免使用字符串

在开发过程中我们不应该为了省事,所有类型都定义为String类型。应该编写更加适当的数据类型,避免使用字符串来表示对象,若使用不当,字符串类型比其它类型更加笨拙、更加不灵活、速度更慢、也更容易出错。经常被错误地用字符串来代替的类型包括基本类型、枚举类型和聚集类型。

第51条:当心字符串连接的性能

String字符串是不可变的,每次对一个字符串变量的赋值实际上都在内存中开辟了新的空间。如果要经常对字符串做修改应该使用StringBuilder(线程不安全)或者StringgBuffer(线程安全),其中StringBuilder由于不考虑线程安全,它的速度更快。

第52条:通过接口引用对象

应该优先使用接口而不是类来引用对象,例如:

  1. List<String> list = new ArrayList<String>();

这样带来的好处就是可以更换list的具体实现只需一行代码,之前有谈到将接口作为参数的类型,这两者配合使用就能最大限度实现程序的灵活性。

但如果是类实现了接口,但是它提供了接口中不存在的额外方法,且程序依赖这些额外方法,这个时候用接口来代替类引用对象就不合适了。

第53条:通过接口引用对象

反射机制能在运行时获取已装载类的信息,比如Constructor、method、field。但这种方式是有影响的:

丧失了编译时类型检查

执行反射访问所需要的代码非常笨拙和冗长(这需要一定的编码能力)

性能损失

所以要慎用反射机制,但如果以非常有限的形式使用反射机制,可以获得许多好处,还是值得的。比如可以用反射方式创建实例,然后通过它们的接口或超类,以正常的方式返回这些实例。下面程序创建了一个Set<String>实例。

  1. public class Reflective {
  2. public static void main(String[] args) {
  3. Class<?> c1 = null;
  4. String className = args[0];
  5. try {
  6. c1 = Class.forName(className);
  7. } catch (ClassNotFoundException e) {
  8. System.out.println("class not found!");
  9. System.exit(1);
  10. }
  11. //instantiate the class
  12. // @SuppressWarnings("unchecked")
  13. Set<String> s = null;
  14. try {
  15. s = (Set<String>) c1.newInstance();
  16. } catch (InstantiationException e) {
  17. System.out.println("class not accessible!");
  18. System.exit(1);
  19. } catch (IllegalAccessException e) {
  20. System.out.println("class not instantiable!");
  21. System.exit(1);
  22. }
  23. //exercise the set
  24. s.addAll(Arrays.asList(args).subList(1, args.length));
  25. System.out.println(s);
  26. try {
  27. test();
  28. }catch (Exception e){
  29. System.out.println("catch a exception!");
  30. }
  31.  
  32. }
  33. public static void test(){
  34. throw new RuntimeException();
  35. }
  36. }

简而言之,反射机制是一种功能强大的机制,对于特定的复杂的系统编程任务,它是非常必要的。如有可能,应该使用反射机制来实例化类,而访问对象则用编译时已知的某个接口或超类。

第54条:谨慎地使用本地方法

所谓的本地方法就是在JDK源码中你所看到在有的方法中会有“native”关键字的方法,这种方法表示用C或者C++等本地程序设计语言编写的特殊方法。之所以会存在本地方法的原因主要有:访问特定平台的接口、提高性能。

实际上估计很少很少在代码中使用本地方法,就算是在设计比较底层的库时也不会使用到,除非要访问很底层的资源。当使用到本地方法时唯一的要求就是全面再全面地测试,以确保万无一失。

第55条:谨慎地使用本地方法

我在实际编码过程中,常常听到别人说,这么实现性能可能会好一点,少了个什么什么性能会好一点,甚至是少了个局部变量也会提到这么性能要好一点,能提高一点是一点。

但实际上是在编码中如果你没有考虑清楚就冒然想当然的去做优化,常常可能是得不偿失,就像我开头提到的那样,甚至为了优化性能而去减少一个局部变量。正确的做法应该是,写出结构优美、设计良好的代码,不是写出快的程序。性能的问题应该有数据做支撑,也就是有性能测试软件对程序测试来评判出性能问题出现在哪个地方,从而做针对性的修改。

第56条:遵守普遍接受的命名惯例

我们在编码时要把标准的命名惯例当做一种内在机制来看待,遵守普遍接受的命名惯例。

7.通用程序设计_EJ的更多相关文章

  1. [Effective Java]第八章 通用程序设计

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  2. Effective Java 学习笔记----第7章 通用程序设计

    第7章 通用程序设计 第29条 将局部变量的作用域最小化     使一个局部变量的作用域最小化,最有力的技术室在第一次使用它的地方声明.   第30条 了解和使用库      效率提高.如果你不知道库 ...

  3. 《Effective Java》笔记45-56:通用程序设计

    将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性. 要使用局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方才声明,不要过早的声明. 局部变量的作用域从它被声明的 ...

  4. 《Effective Java》第8章 通用程序设计

    第47条:了解和使用类库 Top 100 Java Libraries on Github 2016 Library Number of Projects Type % of projects jun ...

  5. Effective java笔记(七),通用程序设计

    45.将局部变量的作用域最小化 将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能性. Java允许在任何可以出现语句的地方声明变量(C语言中局部变量要在代码块开头声明),要使 ...

  6. 【Java基础】通用程序设计

    Num1:for-each循环优先于传统的for循环 java1.5版本发布之前的做法: for(int i=0;i<a.length;i++){ doSomething(a[i]); } ja ...

  7. Effective Java 读书笔记之七 通用程序设计

    一.将局部变量的作用域最小化 1.在第一次使用变量的地方声明 2.几乎每个变量的声明都应该包含一个初始化表达式:try-catch语句是一个例外 3.使方法小而集中是一个好的策略 二.for-each ...

  8. 《Effective Java》学习笔记 —— 通用程序设计

    本章主要讨论局部变量.控制结构.类库.反射.本地方法的用法及代码优化和命名惯例. 第45条 将局部变量的作用域最小化 * 在第一次使用的它的地方声明局部变量(就近原则). * 几乎每个局部变量的声明都 ...

  9. 《Effective Java》读书笔记七(通用程序设计)

    No45 将局部变量的作用域最小化 要使局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方声明. 几乎每个局部变量的声明都应该包含一个初始化表达式.如果你还没有足够的信息来对一个变量进行有意 ...

随机推荐

  1. 「ZJOI2016」解题报告

    「ZJOI2016」解题报告 我大浙的省选题真是超级神仙--这套已经算是比较可做的了. 「ZJOI2016」旅行者 神仙分治题. 对于一个矩形,每次我们从最长边切开,最短边不会超过 \(\sqrt{n ...

  2. [Swift]两种线程休眠的方式

    一.线程休眠方式1 [Swift]与[C#]的比较: //C#:需要添加using System.Threading; //线程休眠3000毫秒,即3秒 Thread.Sleep(); //Swift ...

  3. scws简单中文分词

    demo如下: /** * 中文分词 * @param $keyword * @param $getTop * @param $limit * @return array */ function sp ...

  4. git gc和fsck的用法

    一.保证git良好的性能 在大的仓库中, git靠压缩历史信息来节约磁盘和内存空间. 压缩操作并不是自动进行的, 你需要手动执行 git gc: $ git gc 压缩操作比较耗时, 你运行git g ...

  5. 使用SAX解析xml文档

    1.首先,在main方法中解析xml文档,具体代码如下: import org.w3c.dom.*; import javax.xml.parsers.DocumentBuilder; import ...

  6. visual Studio 2017 扩展开发(三)《绑定快捷键到菜单项》

    如何将键盘快捷方式映射到自定义按钮,怎么使用快捷键启动自己创建的菜单,刚开始做的时候迷糊了,找了很久.可能也是因为刚开始做不是很明白,后面慢慢就懂了.其实非常简单的. 很多快捷键已经在Visual s ...

  7. Linux安全配置

    注释掉系统不需要的用户和用户组 vi /etc/passwd #adm:x:3:4:adm:/var/adm:/sbin/nologin #lp:x:4:7:lp:/var/spool/lpd:/sb ...

  8. 线程池ThreadPool及Task调度死锁分析

    近1年,偶尔发生应用系统启动时某些操作超时的问题,特别在使用4核心Surface以后.笔记本和台式机比较少遇到,服务器则基本上没有遇到过. 这些年,我写的应用都有一个习惯,就是启动时异步做很多准备工作 ...

  9. 将Long类型字节大小数据转换成标准的视频大小格式

    很多时候针对视频信息,数据库中存储的视频大小是字节类型,然后我们在页面中显示则需要使用的是标准的视频大小显示格式,我这里工具类最多显示的是Mb,如果需求要显示G的话可自行参照修改. 直接上工具类和测试 ...

  10. 【原创】Python第二章——字符串

    字符串是一个字符序列,(提醒:序列是Python的一个重要的关键词),其中存放UNICODE字符.Python中的字符串是不可变的(immutable),即对字符串执行操作时,总是产生一个新的字符串而 ...