Effective java读书笔记

Effective java读书笔记

这周刚刚读完了Effective java这本书,总体感觉这本书写得非常地不错,是一本总结性比较强,比较全面的书,书中罗列了57条建议,但这些建议不是万能的,在有些条件下适合,在有些条件下不适合,它只代表了大部分程序设计的行为,我们只有学会这些基本的原则和建议,才能打破这些规则,写出更符合实际场景下的代码。从书中总结的详细程度可以看出,作者是真的花心思在写这本书,有机会一定要好好读完哦,一定会让你受益匪浅,你会更有效地使用java进行程序设计,读完这本书,你可能会发现,自己以前在写java代码的过程中,是不是也在犯了不少错误呢,甚至有不少的错误在我们大脑中已经形成了对的烙印。

Effective java主要有9章,这9章分别从写java程序的9个主要方面进行总结和对遇到的问题进行分析,分别针对的内容如下:创建和销毁对象、对于所有对象都通用的方法的概括和总结、写类和接口时应该注意的事项、如何用java语言来实现C语言中的结构、写类的方法的应该注意的方面、java通用的程序设计、异常处理方面、多线程方面、最后就是序列化方面的问题,下面分别逐一对这9个方面进行讲解。

1.创建和销毁对象:这一部分主要针对的就是创建和销毁对象:什么时候、如何创建对象;什么时候、如何避免创建对象;如何保证对象能够适时地销毁、对象被销毁之前如何管理各种清理工作,这一部分的重点就是单例模式。它有如下几个小的规则:

A.       考虑用静态工厂方法代替构造函数:

优点:与构造函数不同,静态工厂方法具有名字,通过这个静态工厂方法的名字,可以比较容易判断构造的用途,这样就更易于使用;每次构造的时候,不一定要构造一个新的对象,这有点像单例模式;静态工厂方法可以返回一个对象的了类型,添加了构造的灵活性。

缺点:如何类中用私有的构造方法,则不能被继承;静态工厂方法和其它的静态方法没有什么区别,在API文档中,它很难像普通的构造函数那样进行详细的描述说明。

总体来说:静态工厂方法和公有的构造都有它们各自的用途,我们需要了解它们各自的长处。但是我们要养成这一个好的习惯:不要一上来就使用公有构造函数,有时静态工厂方法会更适合,如果你没有办法进行权衡这两种实现的利弊,请老老实实使用公有的构造方法

B.       使用私有的根构造函数强化Singleton属性

a)  public class Elavis {

public static final Elvis INSTANCE = new Elavis();

privateElavis() {}

}

b)  public class Elavis {

private static final Elvis INSTANCE = new Elavis();

privateElavis() {}

publicstatic Elavis getInstance() {

   return INSTANCE;

}

}

              很明显,第二种实现更灵活,封闭性也更好

C.       通过私有的构造函数强化不可实例化的能力

D.       避免创建重复的对象

B、C、D这三条应该合起来说,说穿了就是一句话,单例模式的逐步实现过程,标准的单例模式实现例子如下:

publicclass Elavis {

private static final Elvis INSTANCE = new Elavis();

private Elavis() {}

public static Elavis getInstance() {

   if (INSTANCE == null) {

       INSTANCE = new Elavis();

   }

   return INSTANCE;

}

}如果在并发环境下,可以改成如下这样:

public class Elavis{

privatestatic final Elvis INSTANCE = new Elavis();

privateElavis() {}

publicstatic Elavis getInstance() {

   synchronized(INSTANCE) {

       if(INSTANCE == null) {

           INSTANCE= new Elavis();

       }

}

return INSTANCE;

}

}

其实真正做到完全单一是非常困难的,如果用反射机制,那不单例了,可以用反射试试

E.       消除过期的对象

这一条主要就是针对java的gc来说明的,我们在编程的时候,有些没用的对象我们应该马上通知gc回收掉,但不幸的是,有些没用的对象很隐蔽,很容易导致内存泄漏,这告诉我们在写代码码是一定要加倍的小心,对无用对象的引用即时的设置为null,书中用的是Stack例子,很好地说明了垃圾对象的存在,这一部分,参加ATA中面向gcjava编程

F.       避免使用终结函数

这一条是告诫我们,除非万不得已,否则不要使用终结函数,因为:虽然执行终结函数是JVM执行垃圾回收一个主要功能,但是在不同的JVM实现中,大相径庭,有些平台运行得非常好,有些平台却无法运行;最重要的是JVM不会保证终结函数会被及时地执行,甚至有时根本就不会执行,书中举例说了System.gc和System.runFinalization方法以及System.runFinalizersOnExit和Runtime.runFinalizersOnExit,前两个方法JVM不一定会执行,后两个方法会带不不安全的因素。如果在万不已的情况下,非要用终结函数,这时有两条建议:1,显示地调用终结函数,如InputStream中的close方法;如果一个非final子类重写了父类的终结方法,那么一定要显示调用父类的终结方法。

2.对于所有对象都通用方法:这一部分主要是针对何时以及如何改写Object对象的非final方法,以及重写这些方法时所遵行的一些原则。

A.       在改写equals方法的时候请遵守通用的约定

a)        自反性,对于任意的引用值x, x.equals(x)一定为true;

b)       对称性,对于任意的引用值x, y, x.equals(y)和y.equals(x)是等价的,要么同时为true,要么同时为false;

c)        传递性,对于任意的引用值x, y,z,x.equals(y)和y.equals(z)都为true,则有x.equals(z)一定返回true;

d)       一致性,对于任意的引用值x,y,如果用equals比较返回的对象信息没有被修改的话,那么,多次调用x.equals(y)要么一致地返回true,要么一致地返回false。

e)        非空性,对于任意非空的x,x.equals(null)一定会返回false;

如果我们在改写某个类的equals方法时遵守这些约定,一般情况下是不会有错的,至少在同类之间,但在了类对象和父类对象进行调用时,有时真不会满足以上这几个约定,但是在那些情况,下也是正确的,就如书中所说的例子,书中还提到了高质量equals方法的一个处方。1.使用==操作符检查“实参是否为指向一个对象的个费用”,2.使用instanceof操作符检查“实参是否为正确的类型“,3.把参数转移到正确的类型;4.对于该类中每一个“关键域”,检查实参中的域与当前对象中对应的域值是否匹配;除了这些以外,还有一些告诫:不要企图让equals方法过于聪明;不要使用equals方法依赖于不可靠的资源;不要将equals声明中Object对象替换为其他的类型,因为这并不是重写

B.       修写equals时总是要改写hashCode方法  

a)        没有改写hashCode是一个很常见的错误,特别是在重写equals方法的前提下更是如此,如果不这样,就会违反Object.hashCode的通用约定,从而导致该类无法与所有基于散列值的集合结合在一起正常动作,这样集合包括HashMap、HashSet和Hashtable。

b)       hashCode的约定如主要有,若一个对象的在运行期间所有属性没有变,则HashCode的值应该不变;如果两个对象equals相同,则它们的HashCode值不一定相同;如果两个对象的equals不相同,则它们的HashCode可以相同,可以不相同,但最好相同;

c)        相等的对象必须有相同的散列码,应该设计好的散列码,使用一个集合中不同的对象均匀地散列,Eclipse中,我们可以很容易地用工具直接生成某个类的hashCode方法

d)       不要试图从散列码计算中排除掉一个对象的关键部分以提高性能。

C.       总是要改写toString方法

这一点就不用多说,如果一个类没有必定toString方法,你用Sytem.out.println会是一些没有用的信息,提供一个好的toString实现,可以使用一个类用起来更加愉快,并且在在实际中,toString方法返回对象中所有令人感兴趣的信息,例如,set集合等,最后还要强调,在重写toString方法时,一定要多写点注释,以便说明你的用意。

D.       谨慎地改写clone方法

因为clone方法在Object类中是一个受保护的方法,对于Clonable接口,它只改变超类中clone方法的行为,clone方法相当于C++中的拷贝构造函数。注意,如果你改写一个非final类的clone方法,则应该返回一个通过调用super.clone而得到对象,在实际的应用中,我们用java中拷贝构造函数代替clone方法,写法和C++类似。

E.       考虑实现Comparable接口

Comparable接口是一个非常有用的接口,它和Object类中的equals方法有些类似,如果一个对象现了Comparable接口,那么就表明这个类的实例具有内的排序关系,这在对象的集合中是非常有用的,这个接口中只一个方法:compareTo,返回值,可以参考C语言中的qSort函数中的cmp方法。

3.类和接口:类和接口是java语言最核心的两个概念,这一部分主要就是列出了一些原则,帮助你设计出更好的类和接口,以便这些接口更加有用、更加健壮,更加灵活。

A.       使用类和成员的可访问能力最小化

这个类主要就是为了进一步说明面向对象的封装性,不到万不得已,每一个类的每一个属性和方法尽量要用private修饰,不能让外界知道它的实现细节,说穿了就是一句话,尽量不用用public修饰一个类的属性和方法,除非万不得已,最后强调一点就是:具有公有的静态final数组域几乎几乎总是错误的。

B.       支持非可变性

类的非可变性是说明类所对应的实例是只能读的,不能去改写的,这样的类的实例具有线程安全性,特别适合在多线程并发的情况, 一个类如何做到是非可变的呢,主要有以下几点:

1.       不要提供任何会修改对象的方法

2.       保证没有可被子类改写的方法

3.       使用所有域都是final的

4.       使所有域都是private

5.       保证对于任何可变组件的互斥访问,不需要同步

6.      缺点:对于每一个不同的值,都要创建一个新的对象,因些不要为每一个get方法写一个相应的set方法,总之限制一个类的可变性是不会错

C.       复合优先于继承

D.       要么专门为继承而设计,并给出文档说明,要么禁止继承

这两条主要是为了说明,如果不是在多态的情况下,复合比继承要好,复合写代码更容易,继承是为了多态而设计的,子类的实体指向父类引用,其它的情况都最好用复合。

E.       接口优于抽象类

F.       接口总是被用于定义类型

a)        说到接口,它的用途比抽象类要广,抽象类可以被单一继承,而接口可以被我实现,这有点像c++中多继承;抽象类作为类型定义有一些不足,已有的类要更新,实现了新的接口,这样就要修改抽象类,不太方便,而用接口就容易多了,只需要添加一个新的接口就ok了;接口是定义混合类型最好理想的选择;接口可以使得我们可以构造出非层次的类型框架

b)       总之要多用接口,少用抽象类,如果非要用抽象类,一定要在接口的基础之上。

G.       优先考虑静态成员类,静态成员类大部分都是像Math类一样,是一种工具类,但这一节重点不是放在静态成员类上,而是放在四不同的嵌套类,每一种都有自己的用途:如果一个嵌套类需要在单个方法这外仍是可见的,或者它太长了,不适合放在一个方法内部,那么应该使用成员类。如果成员类的每一个实例都需要指向一其外围实例的引用,则把成员类做成非静态的;否则就做成静态的。假设一个嵌套类属于一个方法的内部,如果你只需要在一个地方创建它的实例,并且已经有一个预先丰在的类型可以说明这个类的特征,则把它做成匿名类;否则就做成局类。

4.C语言结构的替代:这一部分主要针对java语言针对C语言中所缺少的数据结构进行实现,本章用几点来说明如何用Java语言设计出C语言中数据结构的替代品。

A.       用类代替结构

这里只是说明这样一个实事,当一个java类退化到只包含一些数据域,这样的类与c语言的结构大致是等价的。

B.       用类层次来代替联合

Java中是用子类化类型来代替C语言中联合体,例如

abstract class Shape {

       abstractdouble area();

}

Class Circle extends Shape {

       finaldouble radius;

       Circle(doubleradius) {this.radius = radius;}

       doublearea() {return Math.PI*radius*radius;}

}

Class Rectangle extends Shape {

       Finaldouble length;

Final double width;

Rectangle(double length, double width) {

       this.lenght= length;

       this.width= width;

}

double area() {return length*width;}

}从这个例子可以看出,类层次的代码提供了类型安全,因为是继承吗,其次类层次的代码比较简明、容易扩展、反映了类之间层次关系

C.       用类来代替enum结构,其实书中说得有点勉强,大部分情况下,用java的enum类型也是可以的,如果用和c中的enum一样,刚可以用Class来代替,当然这种场景不多,多说了作用也不大。

D.       用来和接口来代替函数指针      

a)        这一点主要是说明多态,面向接口来编程,这在项目中用得比较我,特别是在spring的配制文件中。

由于时间关系,剩如部分,尽快在这一两天之内补全

你可能感兴趣的:(Effective java读书笔记)