Spring 系列还在进行,又开了新坑(苦笑)。这一系列基于《Effective Java》——相比《Spring in Action》,标题就友好很多,大部分是 Java 的一些编程思想,探讨如何写出简洁、高效和健壮的代码,写起来会很轻松(毕竟就是抄嘛)。这本书买了有一年多,反复翻阅了几次。记忆最深的是,有一次从成都到武汉,因为赶高铁不及,只能转坐绿皮火车。在车上的二十多个小时,大部分时间花在这本书上。算是一本质量很高的指导手册。
用静态工厂方法代替构造器
比如下面的代码,来自 Boolean 的简单示例
public static Boolean valueOf(boolean b) { |
通过静态工厂方法(区别于设计模式中的工厂方法),代替传统的公有构造器,向客户端提供实例,这么做的优势在于
静态工厂方法具有名称:这是显而易见的。另一方面,拥有多个参数的类很可能提供多个构造器,如何区分这些构造器就是问题。
静态工厂方法可以提供单例:从这个角度看,相当于实现了简单的单例模式。
静态工厂方法可以返回任何子类型的对象:典型的应用为
Java.util.Collections
类,其中定义了很多静态内部类比如EmptyList
,并通过静态方法emptyList
返回实现静态工厂方法可以提供更简洁的实例化代码:实际上在较新的 JDK 版本已经去掉了多余的类型参数,但是静态工程方法确实可以做到更简洁
List< String> strings = new ArrayList<>();
List< String> emptyList = Collections.emptyList();
静态工厂方法的缺点在于,第一如果类不包含公有构造器,则外部无法继承它——勉强算一个缺点吧——第二它与其他静态方法没有任何区别,只是返回的是自身的一个实例。这就造成如何类没有提供公有构造方法,那么外部调用者将苦恼于它的实例化。下面是一些静态工厂方法的惯用名称:
valueOf
:实际上属于类型转换方法of
:上面名称的简洁形式getInstance
:返回的实例通过方法参数描述。在单例模式中保证返回唯一的实例newInstance
:跟上面的相似,但保证返回的实例与所有其他实例不同getType
:不了解,Character
提供了该工厂静态方法返回CharacterData
的不同实现newType
:不了解
多个构造器时考虑使用构建器
实际上就是使用简单的建造者模式实例化对象,还是通过代码说明
public class Human { |
这种方式广泛运用于 Java 开发中,优点显而易见,唯一的缺点可能为了创建对象需要先创建其建造器(不值一提)
使用私有构造器、枚举强化单例
其实就是单例模式,简单贴一下代码吧,忘记了可以回去看设计模式的笔记:Java设计模式:创建型模式
public class Singleton { |
下面是枚举类型实现单例
public enum Singleton { |
通过私有构造器强化不可实例化的能力
用得最多的地方就是各种Util
类,一般只有一个静态方法,实现一个简单的功能。最典型的例子就是java.lang.Math
划水而过
避免创建不必要的对象
最常见的例子就是 Java 的基本数据类型,直接使用int
、char
而不是它们的包装类型Integer
、Character
,因为基本数据类型直接保存在内存模型中的栈中,而且具有复用性,比对象类型更加节省内存
Java 的经典面试题中经常牵扯到String
的实例化
String s0 = "9527"; |
(第一个比较是多余的,因为对于基本类型来说,==
会直接比较他们的值)。一定范围内的基本数据类型会保存在栈内的常量池中。第一行代码,虚拟机首先创建一个引用s0
,然后到常量池中寻找是否存在字面量为9527
的值,如果有则直接返回栈地址,没有则新建一个常量9527
并将s0
指向它,然后返回。到了s1
被创建的时候,根据上面的步骤,s1
将直接指向s0
所指向的栈地址
第二个比较结果显然是false
。如果使用构造器构造一个String
类型的变量,则虚拟机会先在栈中创建引用s2
,在堆中开辟内存新建String
对象,保存传入的参数9527
,然后将堆地址指向栈内的s2
对于String
来说,直接使用构造器更糟糕的一点还在于,其被声明为final
,每new
一次就创建一个对象。可以想象如果在循环中使用了构造器来创建字符串,将会造成多么大的内存浪费
消除过期的对象引用
通过一个栈的实现说明
public class Stack { |
该实现的问题出现在,pop
方法中弹出的对象将不会被系统回收,因为栈内部仍然维护着它们的过期引用,解决办法如下
public Object pop(){ |
避免使用终结方法
终结方法其实就是Object
内的finalize
方法,其声明为
protected void finalize() throws Throwable { } |
该方法是当 JVM 发现不可达对象时,将其回收之前调用的方法。问题在于,从发现该回收对象到真正回收这之间的时间是不确定的。JVM 不但不保证终结方法会被及时执行,甚至不保证其会被执行,如果重写终结方法,并期望在其中添加资源回收业务,将造成巨大隐患——资源可能永远都不会被回收。
使用终结方法的另一个弊端在于,如果子类重写了父类的finalize
,但是却没有在其中调用父类的finalize
,那么父类(非 Object)永远不会被回收。
好了,这就是第一章的划水内容。可以看到大都是一些有用的编程经验和技巧,用来时刻提醒自己不要犯低级的错误。可能这本书提及到的内容有些开发者一辈子都碰不到,但是要想成为一个卓越的工程师,注重细节和原理永远是不可获取的。这也算是对自己的勉励吧。
不知道是不是前面写 Dart、Gradle、Spring 的东西太多,突然这么轻松地写博客,竟然感到“周身血气运转通畅、心情愉悦”,简直好惨一博主。那下一篇也继续划水吧~