Effective Java : 通用程序设计

45.将局部变量的作用域最小化

简介

13 条,使类和成员的可访问性最小化,是一个道理,可以采取如下几种办法:

  1. 在第一次使用它的地方声明.
  2. 几乎每个局部变量的声明都应该包含一个初始化的表达式,否则(没有足够的信息来对一个变量进行有意义的初始化),就应该延迟这个声明.
  3. for循环优于while循环,而且防止了“剪切-粘贴”错误,且更加简短可读
  4. 最后一种方法是将局部变量的作用域最小化的方法是使方法小而集中

46.for-each优于for循环

简介

利用 fro-each循环,对数组的索引边界值只会计算一次.
并且 看起来很 简洁

下面是一个示例:

for(Iterator<Suit> i = suits.iterator();i.hasNext()){
    for(Iterator<Rank> j = ranks.iterator();j.hasNext()){
        deck.add(i.next(),j.next());
    }
}

这段代码,看似没问题,其实在内层循环中,会多次调用 i.next(),有可能抛出NoSuchElementException,正确的用法应该是;

for(Iterator<Suit> i = suits.iterator();i.hasNext()){
    Suit suit = i.next();
    for(Iterator<Rank> j = ranks.iterator();j.hasNext()){
        deck.add(suit,j.next());
    }
}

而用for-each,就会变成这样

for(Suit suit:suits){
    for(Rank rank: ranks){
        deck.add(suit,rank);
    }
}

小结

建议如果 编写的类型表示的是一组元素, 应该让它实现 Iterable

不能用for-each 的场景

在需要获取 数组或者迭代器索引的时候.

47.了解和使用类库

不正确实例

产生位于 0某个上界之间的随机整数.常见代码,如下:

private static final Random rnd = new Random();
static int random(int n){
    return Math.abs(rnd.nexInt())%n;
}

这个方法有三个缺点:

  1. 如果n 是一个比较小的 2 的盛放,经过一段周期,随机数序列会重复
  2. 如果n 不是2 的平方,平均起来,有些数比其他数出现的更为频繁
  3. 极少数情况下,会失败

幸运的是,在Java标注库中已经有了相关实现,nextInt(int),因此可以充分利用这些标准类库.

小结

总而言之,不要重新发明轮子,如果要做的事情十分常见,有肯能标准类库中某个类已经完成了这样的工作.

48.如果需要精确的答案,避免使用float和double

简介

float 和 double 是为了提供较为精确的 快速近似计算而精心设计的.
不适合用于货币计算

解决办法:

  1. 使用BigDecimal,与使用基本运算类型相比,BigDecimal不是很方便,而且很慢.
  2. 除了使用BigDecimal,还用一种办法是使用 int或者 long,者取决于所涉及的数据的大小

小结

  1. 精确计算避免使用floatdouble,要使用BigDecimal,这样可以控制舍入,
  2. 如果不介意自己记录十进制小数点,可以使用 int或者 long
  3. 如果数值超过 18位数字,则必须使用 BigDecimal

49.基本类型优先于装箱基本类型

简介

Java 类型系统包含 基本类型引用类型,每一种基本类型都有一个对应的引用类型,称为装箱基本类型,Java1.5 版本提供了自动装箱自动拆箱

基本类型与装箱类型的区别

  1. 基本类型只有值,而装箱基本类型则具有与他们的值不同的同一性
  2. 基本类型只有功能完备的值,而装箱类型除了功能值之外,还有非功能值null
  3. 基本类型通常比装箱基本类型更节省时间和空间.

警醒

  • 装箱基本类型运用== 操作符 几乎总是错误的.
        Comparator<Integer> naturalOrder = new Comparator<Integer>() {
            public int compare(Integer first, Integer second) {
                return first < second ? -1 : (first == second ? 0 : 1);
            }
        };

        naturalOrder.compare(new Integer(42),new Integer(42));//返回1
  • 基本类型和装箱类型混合使用,会自动拆箱,而null的拆箱操作会抛出NullPointException
public class Unbelievable {
    static Integer i;

    public static void main(String[] args) {
        if (i == 42)//抛出异常
            System.out.println("Unbelievable");
    }
}
  • 包装类型,会创建过多的对象,造成性能问题
//这是之前第5个item中的代码
public static void main(String[] args) {
    Long sum = 0L;//这里每次都会构造一个实例,多创建了2^31个实例. 
    for (long i = 0; i < Integer.MAX_VALUE; i++) { 
        sum += i; 
    } 
System.out.println(sum); }

小结

  1. 基本类型优先于 装箱类型,基本类型更加简单快速
  2. 使用装箱类型,要考虑其自动拆箱的问题
  3. 警惕 ==

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

简介

字符串 常被用来表示文本 .因为其通用性,就出现了如下一些不适合的场景

字符串不适合代替其他的值类型.

  1. 当一段数据从网络,文件或者输入设备进入到程序之后,就应该转化为其本来的类型.而不是继续使用String
  2. 字符串不适合代替枚举类型(枚举类型比字符串更加适合用来表示枚举常量)
  3. 字符串不适合代替聚集类型(拼接字符串场景)
  4. 字符串不适合代替能力表(ThreadLocal的例子)

小结

  1. 如果可以使用更加合适的数据类型,就 应该避免使用字符串`来表示
  2. 字符串如果使用不当,比其他类型更加笨拙,速度慢.

51.当心字符串连接的性能

简介

字符串连接符 是将多个字符串合并为一个字符串的遍历途径.
为连接 n 个字符串而重复地使用连接符,需要n 的平方级的时间(这是由于字符串不可变导致的)
为了获得可以接受的性能,请使用StringBuilder

考虑如下两种情况:

 public String statement() {
    String result = "";
    for (int i = 0; i < numItems(); ++i) {
      result += lineForItem(i);
    }
    return result;
  }
  public String statement() {
    StringBuilder stringBuilder = new StringBuilder(numItems * LINE_WIDTH);
    for (int i = 0; i < numItems(); ++i) {
      stringBuilder.append(lineForItem(i));
    }
    return stringBuilder.toString();
  }

后者前者85 倍之多.

原因:
第一种随项目数量而呈平方增加,第二种则是线性增加
第二种,默认分配一个足够容纳结果的 StringBuilder,即使使用默认大小,也比第一种快50倍之多.

52.通过接口引用对象

简介

40 条中,建议使用 接口 作为 参数类型
其实,应该是 优先使用接口, 而不是类引用对象.
如果有合适的接口类型存在,对于 参数,返回值,变量,都应该使用接口类型来声明.

示例

// 使用接口引用指向子类对象,good
 List<SubscriptSpan> mSubscriptSpen = new Vector<SubscriptSpan>();
// 子类引用指向子类对象,bad
 Vector<SubscriptSpan> mSubscriptSpen = new Vector<SubscriptSpan>();
//使用接口的好处之一,就是可以方便的更换实现类
 List<SubscriptSpan> mSubscriptSpen = new ArrayList<SubscriptSpan>();
  • 如果没有合适的接口存在,完全可以用类而不是接口来引用对象.

不存在相应接口的情况:

  1. 值类(很少会有多个实现),final
  2. 对象属于一个框架,而框架的基本类型是类.
  3. 类实现了接口,但是提供了接口中不存在的额外方法.

53.接口优先于反射机制

简介

反射提供了一种能力: 通过程序来访问已装载的类的信息的能力,即使被编译的时候后者还不存在,然而,

反射的代价:

  • 丧失了编译时类型检查的好处(包括异常检查). 如果程序企图 用反射调用不存在或者不可访问的方法,将会运行失败.
  • 执行反射访问所需要的代码非常笨拙和冗长: 编写乏味,阅读也困难
  • 性能损失: 反射的 执行比普通方法调用要 慢一些.

反射的场景

  • 反射通常只在设计时被用到,普通应用在运行时不应该以反射方式访问对象.
  • 以反射的形式创建实例,然后通过它们的接口或者超类,已正常方式访问这些实例.

小结

反射机制是一种功能强大的机制,但也有一些缺点.
如果编写的程序必须要与编译时 未知的类 一起工作,如 有可能,应该仅仅使用反射机制实例化对象,访问对象时则使用 编译时 已知的接口或者超类

54.谨慎的使用本地方法

简介

Java Native Interface(JNI) 允许 java程序调用 本地方法.
本地方法主要有 三种用途:

  1. 提供了访问特定于平台的机制
  2. 访问老的C/C++版本的库,访问遗留代码的能力
  3. 通过本地方法,编写应用中注重性能的部分

JNI的劣势

  1. 不安全,内存管理不受JVM控制了
  2. 与平台相关,自由移植变得困难
  3. 难以调试
  4. Java和native层的交互是有开销的,如果本地代码只做少量工作,则会降低性能

小结

总而言之,使用本地代码要三思,

  1. 极少数情况下需要使用本地方法来提高性能.
  2. 如果必须要使用,也要尽可能全面的测试.

55.谨慎的进行优化

简介

很多计算上的过失都被归咎于效率(没有必要达到的效率),而不是任何其他的原因-- 甚至包括盲目的做啥事                                       
                                --- 

不要去计较效率上的一些小小得失,在 `97%`的情况下,不成熟的优化才是一切问题的根源
        ---

在优化方面,应该遵循两条规则

        规则1 : 不要进行 优化.
        规则2 (仅针对专家) : 还是不要进行优化 -- 也就是说,在你还没有绝对清晰的未优化方案之前,请不要进行优化. 

这些格言,比Java 出现早了 20年,他们讲述了优化的深刻真理:

1. 优化的弊大于利,特别是不成熟的优化
2. 要努力表写好的程序,而不是快的程序
3. 努力避免那些限制性能的设计决策
4. 要考虑`API`设计决策的性能后果
5. 为了获得好的性能对`API`进行包装是一个不好的想法
6. 在每次试图做优化之前和之后,豆芽对性能进行测量.

小结

总而言之.,不要费力去编写快速的程序 – 应该努力编写好的程序,速度自然会随之而来. 当 构建完系统后,要测量它的性能.

56.遵守普遍的命名规则

简介

Java 平台建立了一 整套很好的命名惯例.分为两大类: 字面的语法的.

  • 包名要体现出组件的层次结构,全小写
  • 公布到外部的,包名以公司/组织的域名开头,如 com.xxx.xxx
  • 执行某个动作的方法长用动词或者动词短语来命名,如 append
  • 转换对象类型的方法,

小结

把命名规则当做一种内在的机制来看待,并且学者用他们作为第二特征.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章