Effective Java读书笔记(1)—— 创建和销毁对象

很久以前买的一本《Effective Java》,最近开始读啦!从第二章开始看(第一章是引言),每一章都有若干条建议,翻了一下感觉对日常编码有很强的指导意义呀,勤劳的我决定写个读书笔记记录一下。

笔记里面会记录每一章的所有条建议,略过我觉得不太重要的细节,加入一些小例子。如果有书里讲的不太清楚的,会贴一些其他的资料。

第一条:考虑用静态工厂方法代替构造器

说到静态工厂方法,书里给的例子是包装类型,也就是平时常会用到的Integer.valueOf(),Integer.parseInt()想必也算,返回的都是Integer类型的对象。对于自定义的类而言,我写了个简单的例子:

class Student{
	private int id;
	private String name;
	
	private Student(int id, String name){
		this.id = id;
		this.name = name;
	}
	
	public static Student newInstance(int id, String name){
		return new Student(id, name);
	}
}

当我们想创建一个Student对象时,最常见的一种方法就是用构造器方法,也就是:

Student s = new Student(1, "ZhangSan");

但是在上面的代码示例里,将构造方法私有化,对外只提供一个静态方法,创建对象时需要这么写:

Student s = Student.newInstance(1, "ZhangSan")

那么这样有什么好处呢?

  • 静态工厂方法可以自定义函数名。构造器方法的方法名就是类名,如果我们想实现两种不同功能的函数(比如一个随机数类,我们可以生成一个随机偶数,或一个随机奇数),两个函数的参数是一样的,想重载只能打乱参数顺序。由于函数名相同,必须严格参照文档,即使参照文档一不小心还是很容易调用错误的函数。但如果使用静态工厂方法,只需要创建RandomNum.newEvenNumber() 和 RandomNum.newOddNumber() 即可。
  • 每次调用的时候不一定要创建新对象:每次使用new关键字的时候肯定是会创建一个新对象的。但在一些场景下,比如单例模式,我们希望每次获取这个类的实例获取的都是同一个,就需要用静态方法去获取类的唯一实例(单例模式在前面的博客里写过了)。
  • 可以返回原类型的任何子类型对象
  • 返回对象的类可以根据输入参数的不同而不同:同上一条,根据参数不同,可以是声明返回类型的任何子类。

缺点:

  • 构造方法如果用private修饰,就不能作为父类被其他类继承了(因为子类的构造方法需要调用父类的改造方法)
  • 不像构造方法那样,一下子就知道这是个创建对象的方法。通过命名、注释、文档来弥补。

第二条: 构造方法参数过多时用建造者(builder)模式

关于建造者模式,在前面的博客里已经详细说过了,这里不再赘述

第三条: 使用私有构造方法或枚举类实现单例模式

同上,这个在前面的博客(单例模式)里也详细说了。

第四条: 使用私有构造器执行非实例化

对于一些类来说,实例化是没有意义的。比如在我写的一个商城项目里封装了一些工具类,比如PropertiesUtil、MD5Util、JedisUtil,我只需要调用这些类的静态方法,不需要实例化对象。

这个时候就可以把构造函数私有化,如果要防止在类的内部显示调用构造方法,可以在私有的构造方法里加入这样的方法体:

throw new AssertionError();

第五条:依赖注入优于硬链接资源

依赖注入这个概念在看设计模式和spring的时候都接触过。所谓依赖就是A类有个B类的成员变量,那么A依赖于B;比如车里有车轮,那么车这个类可以这么写:

class Car{
	private Wheel wheel;
	
	public Car(){
		this.wheel = new Wheel();
	}
}

上面的代码里,Car类在初始化的时候要负责wheel对象的创建。那么问题来了,当Wheel的构造方法变了,比如要指定一个形参size代表车轮尺寸,那么不仅要改Wheel类的代码,Car类的代码(以及其他所有依赖于Wheel类的代码)都要更改,不符合开闭原则和单一职责原则。如果车依赖于车轮,车轮依赖于螺丝,那么螺丝的构造方法更改了,所有直接或间接依赖于螺丝的都要更改,是非常难以维护的。

为了解耦,把一个类(称为A)所依赖的其他类的对象创建过程与A的实现解耦,就要用到依赖注入。听上去挺高级的,其实用起来很简单:

class Car{
	private Wheel wheel;
	
	public Car(Wheel wheel){
		this.wheel = wheel;
	}
}

上面的代码就是依赖注入中非常常见的一种——构造器注入,以前没听过依赖注入这个概念,但是平时其实都是这么写的。

第六条:避免创建不必要的对象

对于不可变的对象,以及创建开销非常大的对象,可以采用缓存、对象池这样的方法进行重用。

缓存最常见的例子就是Java提供的字符串缓存池了(基本类型也是有缓存池的,比如int类型会缓存-128至127),如果写这么一句代码:

String s = "abc";

那么s这个引用指向的不是堆里产生的新对象,而是去字符串常量池里找“abc”这个字面量,找得到就指向这个字面量,找不到就生成一个并且指向他。如此一来所有值为“abc”的字符串引用指向的是同一块内存地址,重用的前提是String是final类型不用担心数据安全问题。

至于创建开销特别大的对象,数据库的连接算是比较典型的例子,可以用连接池进行联结的重用。

第七条:消除过期的对象引用

这一条针对的主要是内存泄漏问题,书中提到的例子是栈(top指针后面的元素都属于内存泄漏)、缓存(解决方法一般是lru)、监听器和其他回调(弱引用解决)。

除此之外,我记得Threadlocal对象也是有内存泄漏问题的,解决方法是弱引用+手动调用remove()。

第八条:避免使用Finalizer和Cleaner机制

finalize()方法在看JVM垃圾回收的时候看到过,对象被标记为垃圾对象后不会立即被回收,会检查一下是否需要调用finalize()方法,但是这个方法不是很稳定,不能确保执行。

从Java9开始,Finalizer机制被弃用,被Cleaner机制代替,仍然是不可预测、运行缓慢、通常不必要的。

这两个我都没用过,他们的问题主要在于:

  • 不能保证及时运行(如果依靠finalize来实现连接的关闭,很可能会延迟很久,浪费资源),甚至不保证能够运行。
  • 性能损失:垃圾不能及时被回收,要等待finalize方法调用。
  • finalizer机制攻击

但是这两个机制也是有好处的,用途主要有二:

  • 安全网:在一些资源没有手动释放的情况下作为最后一道防线。
  • 终止非关键的本地资源。
    (这边没有深究和细看,先记住一般来说尽可能避免使用finalize)

你可能感兴趣的:(Effective,Java)