Java Effective 节选

本文内容来源于《Effective Java》 第二版(中文翻译版);
一门语言(english java c++ chinese等是一门语言)我们主要是学习它的语法、词汇、和用法;前两者是容易说明了,而对于用于则需要在使用过程中自己摸索!
Java中重要的包有
  • java.lang:该包定义的是一些基本数据结构(String Integer Long等)、一些错误异常的定义、注解的定义(@override等)
  • java.util:该包定义的则是一些集合,Arrays ArrayList Map等类
  • java.util.concurrent:该包主要是定义的一些并行处理所需要的一些工具
  • java.io:该包定义的是基本的输入输出流
Part1(第2章:对象的创建和销毁)(keys:构造器、参数、名字、数组、过期引用、单例)
  • 对于构造器的参数:P9 Line2
    • 一般的构造器都习惯采用重叠构造器模式;即最终实际上只有一个构造器具体完成对类中的域进行赋值
    • 上面的缺点在于
      • 一旦我们的域的个数发生变化,则需要变更的代码比较多
      • 当我们需要传输很多参数的时候,传输的参数将会是一长串数字,容易输错造成意想不到的错误;
    • 下面介绍的方法将解决上面的问题;如果参数超过四个以上建议采用构建器;
    • 构建器定义:
      • 提供一个build方法,用于返回待创建的类对象;内部实现是调用外部类的私有构造器
      • 由构建器去接受输入的参数值,此处类似一个javaBean;
      • 简单的说构建器算是一个有build方法的JavaBean
      • 如下定义:
      • public class Person{ 
          private int xx; 
          private String yy;
          public static class Builder{ 
            private int zz; 
            private String bb; 
            public Builder(); 
            public Person build(){ return new Person(this)} 
          }  
          private Person(Builder builder){...}
        }
    • 构建器使用:
      • 构建器一般定义在待构建的类的内部;
      • 客户端使用类似: new Person.Buider("evan").sex("boy").data("1991").build();的语句生成目标对象
  • 对于构造器名字:P4 Line1
    • 默认的构造器名字都是固定的跟类名保持一致,当客户要使用该构造器的时候并不十分清楚自己最后构造的对象有什么特点;它们之间的区分往往只是参数个数的区分;
    • 通过静态工厂方法代替构造器,我们可以任意定制名字;而且还可以通过不同的方法来返回究竟是一个新的实例还是一个已经存在的实例;
    • 缺点:
      • 上面的静态工厂方法跟一般的静态方法没有任何区别;
      • 使用静态工厂方法往往导致构造器为私有的或者保护的,这样该类就无法进行扩展或被继承;(但有时候为了避免被继承,估计将构造器设为私有)
  • 避免创建不必要的类:P17 Line5
    • 尽量避免创建不必要的对象,尤其是比较大的类在创建和销毁的时候往往耗费比较多的资源,对于这样的对象,最好是能够复用,单例模式在此就比较适合。
    • 此外除了静态工厂的方法,还可以通过延迟初始化来达到上面的目的;
      • 这在以前是不行的,以前的规则是必须将声明放在方法的开头,C语言也是这样,现在的版本已经没有这样的限制了,所以最好是延迟初始化;(注意延迟初始化在并发操作中慎用!因为这可能导致一些问题,问题类似单例模式的问题,单例模式最后使用双重检查的方式解决这个问题;具体内容可以参见P249 Line71
    • 对象池也能在一定程度上达到上述的目的,但是它的适用条件是对象必须足够的大,因为池的维护会耗费比较多的资源!比较常用的有线程池、数据库描述符等;
  • 消除过期的引用:P21 Line6
    • 此处主要针对的是引用数组(集合)的情况;
    • 帮助垃圾回收机制回收不再被使用的对象,就是不要胡乱的引用
    • 如果一个对象的引用存在于一个数组之中,则该引用将一直有效,所以导致引用的对象永远不会被收集走,久而久之将会出现OutOfMemoryerror错误;参见P21
    • 对于上面的数组的引用的情况解决方法就是在对象不再被需要的时候主动的将引用赋值为null,即清空引用;
    • 对于一般的情况,只需要在使用的时候才声明并初始化变量,即局部变量的作用域最小化(注意它跟延迟初始化是有区别的);对于这一点for循环优于while循环,因为for(;;)中的变量只是在当前循环体中有效!能避免复制粘贴错误;P181 Line45
  • 避免使用终结方法:P24 Line24
    • 该方法就是指finalizer方法;
    • 设该方法的目的估计是跟C++中的析构器设计初衷一样,用于资源的回收;
    • 但是java并不保证它们一定会被执行;所以感觉整个就是一个鸡肋,在此就不再多讲;
    • 当需要在析构器中执行的一些方法,比如回收资源,关闭描述符等,完全可以放在终止方法中进行,即我们经常使用的conn.clsoe();在配置try{}finally{};注意finally中的return会覆盖掉try中的return;
Part2(第3章:所有对象都默认的规则)(keys:Object、equals、hashCode、compareTo、toString)
  • 对于Java来说所有的对象的父类都是Obejct,当我们覆写它的一些方法的时候必须遵守一定的规则,否则将导致意想不到的错误;
  • 需要注意的几个方法有:equals()、hashCode()、toString; Comparable.compareTo()接口;clone方法同上面的finalizer方法一样都属于鸡肋,在此就不再介绍;
    • equals&&hashCode:
      • 两个方法在Map集合中被使用到;
      • equals满足自反性、对称性、传递性、一致性、x.equals(NULL) return false;
      • hashCode()满足相等的对象必须具有相同的散列码,即x.equals(y) return true; x.hashCode==y.hashCode();
      • 如果自己要复写一个hashCode方法,一般采用的是将所有在equals中使用的域对应的int数据相加的结果作为该对象的散列码;
    • toString方法:
      • 该方法没有上面的要求那么严格,如果不重写,问题也不大;
      • 但是重写将会对客户大有好处,客户通过toString能够知道该对象的一些信息;默认的是返回ClassName@散列码;
    •  Comparable.compareTo():
      • 跟泛型算法配套协作完成一些特定的功能;
      • 当对象小于、等于或大于指定对象的时候,分别返回负整数(-1)、零(0)或者正整数(1);
Part3(第10章:并发)(keys:java.util.concurrent.*  线程 线程池 并发集合 同步器
  • 当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步;volatile是轻量级的同步机制,synchronized是重量级的锁;前者使用的时候要注意一些细节,它只能保证读取该变量时读取到的都是最新的值,其它则无法保证;后者花费的代价是最高的,不过使用起来最直观,最好用;
  • jdk1.5个版本提供了java.util.concurrent包,里面的工具大致分为三类:P241 Line69
    • Executor FrameWork:(负责处理线程和线程池)
      • executor和task优于线程;
      • 该部分对应的内容主要对应的是java.util.concurrent.Executors类
      • 使用方法:
        1. ExecutorService executor = Executors.newSingleThreadExecutor();
        2. executor.execute(runnable);
        3. executor.shutdown();
    • 并发集合(Concurrent Collection);(负责处理集合数据的阻塞)
      • 并发工具优于wait和notify
      • 为标准的List、Map、Queue集合接口提供了高性能的并发实现(volley使用该集合),内部自己管理同步;
      • 如:concurrentHashMap、BlockingQueue、Vector;
    • 同步器(Synchronizer):(同步锁机制)
      • 定义:是一些使线程能够等待另一个线程的对象,允许它们协调动作;
      • 常用同步器有:CountDownLatch、Semaphore;
      • CountDownLatch支持所有在等待的线程被处理之前,必须在锁存器上调用countDown方法的次数;具体例子在官方的java api就有
Part4(第8章:通用程序设计)(keys:foreach 装箱 字符串 BigDecimal 类库 接口)
  • for-each优于传统for循环:P184 Line46
    • 用法:for(Element e : elements){ doSomthing}
    • 短板:for each主要应用于读取数据,而如果需要修改数据则不适用于for each,应该使用传统的for循环
  • 基本数据类型优于装箱基本类型:P192 Line49
    • 在可以选择的情况下都是使用基本数据类型;
  • 其它数据类型优于字符串:P195 Line50-51
    • 在可以选择的情况下,能不用String就不用String;
    • Stirng的'+'操作,是一种效率很低的操作,该操作会复制符号两边的字符串;
      • 适用于较短的,对效率要求不高的场景;
      • 如果比较在意效率那就使用StringBuilder 来完成上面的操作;
  • 为了获得精确的计算结果避免使用double、float:P190 Line48
    • double、float只能提供浮点运算的简单结果,如1.03-0.42的结果是0.610000001;所以不适用于计算货币
    • 替代的应该使用int long BigDecimal;
    • 数值范围没有超过9位的十进制数采用int、数值范围没有超过18位的十进制数采用long、数字超过18使用BigDecimal;
    • 短板:但是BigDecimal效率低,操作同普通的基本数据类型操作差距较大可能感觉不太适应;
  • 了解和使用类库:P187 Line47
    • 对于每次jdk的更新,应该重点去关心java.lang; java.util; java.io的更新
    • 遇到一个新功能先去查看是否有对应的api可以直接使用,没有再自己去实现
    • 如:Random.nextInt(n);返回一个不大于n的随意数据
  • 通过接口引用对象:P199 Line52
    • 有面向接口编程的感觉;
    • 如果实在找不到接口来引用对象,则使用类层次结构中提供必要功能的基类所声明的变量进行引用;
Part5(第6章:枚举和注解)(keys:enum @Override)
  • 用enum代替int枚举模式和String枚举模式,即static final String形式的变量:P128 Line30
    • 枚举的本质是int值;枚举天生就是不可变得,具有final的特性;
    • 用法:public enum Apple{ red,blue;}  //enum应该是跟class public一样属于关键字
    • 好处:每个enum就是一个命名空间,很安全;反观上面的xx枚举模式就不太安全,没有自己的命名空间,只能依靠程序猿自己判断,编译器无法做出相关的判断;
      • 上面的安全指的是形如ClassA.name == ClassB.name; 可能会return true; 但是EnumA.name == EnumB.name;绝对返回false;
    • java枚举的新用法:
      • enum是个类,继承自Object,因此具有Object的所有方法;
      • 可以看成是个特殊的类,既可以给它声明一些方法,构造器等;
      • 于普通的类的区别在于,它就像生成了固定的几个对象,你在写完enum的时候,就已经可以知道有几个enum对象了;每个对象的值不再发生任何变化;
  • 坚持使用@Override注解:P152 Line36
    • 它表示被注解的方法声明覆盖了超类型中的一个声明;
    • 好处就是,强制编译器去检查被注解的方法是否达到了我们预期的目的!
    • 因为如果超类方法为 equals(Object obj); 而子类写的方法为equals(ClassA a);原本子类是想重写父类的方法,但是这样一写就类似对方法重载了!!如果采用@Override标注,则编译器会帮助我们找到这个问题,没有标注则很难发现,因为编译能够通过,而且执行过程也不会出错,这样的问题是很难发现的;
Part5(第11章:序列化)(keys:Serializable  灵活性 兼容性)
  • 谨慎实现Serializable接口:P255 Line74
    • 实现很简单只需要在类声明的时候加入implements Serializable即可,系统将采用默认的序列化方案对该对象创建序列化;
    • 但是进行上面的声明前需要考虑如下几点:
      • 灵活性:
        • 使用Serializable声明意味着,这个类你不能随意的改动,否则会造成不兼容的情况出现;
        • 最简单的例子就是私有域long serialVeisionID;该域如果系统没有自己显示的声明,则系统会自动为该类生成由个long数据,改数据跟类中的接口、方法名、域名等都有关系,一旦我们对类中的某个域修改,或者添加删除一个域,都会造成serialVersionID的值发生变化!
      • 兼容性:
        • 一旦我们发布了一个实现了Serializable接口的类,则后期一旦要发布新版本的类,必须要考虑到兼容问题,序列化和反序列化在新旧版本,旧旧,新新版本之间都能得到正确的结果;
    • 适于实现Serializable接口的类有,值类如javaBean;
    • 不适于实现Serializable接口的类比如,为了继承而设计的类,因为这将丧失灵活性;
    • 所以要不要实现Serializable接口,不是一个容易做出的决定!需要认真考虑;总之实现了Serializable意味着以后将最好不再修改该类;






































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值