获得一个对象实例最常见的方式是通过构造函数. 有另外一个方法, 应该成为每一个程序员工具箱中重要的一个工具 : 使用静态方法获得对象的实例. 一个类, 可以向外暴露一个简单的静态方法, 这个方法返回值是这个类的一个实例. 这样的例子很多, 例如Boolean这个类当中的valueOf方法, 通过静态方法,将一个布尔值转换为Boolean对象本身:
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
需要注意的是,这里所说的静态方法, 跟我们所说的静态工厂设计模式不是一回事.
一个类可以通过静态方法, 替代或者是作为一种补充形式, 实现得到其实例的目的. 当然,这样做,既有好处, 也有弊端.
一个好处是, 通过合理的命名静态方法, 可以更好的做到见名知义, 而不像是构造函数, 仅仅能使用类名.这样直接导致代码更加直观,提高了代码可读性. 例如:
Integer类的构造方法:BigInteger(int, int, Random), 返回的是一个规定位数的最大可能素数, 但显然构造函数并没有指明返回值的意义, 那么如果替换成静态方法: BigInteger.probablePrime就会更好些, 实际上, 在1.4版本之后, 这个方法被加上了.
一个类只能有一个特定签名的构造函数, 程序员们可以通过改变构造函数的参数列表来获取多个构造函数. 但是这样做并不好, 使用这样API的用户, 如果不参照JavaDoc, 永远记不清楚那个构造参数代表什么意义, 最终导致调用构造函数时出错.
相对而言, 通过见名知义的静态方法得到类的实例, 就不存在上述缺点. 因此,当一个类中, 存在构造方法参数个数相同, 仅仅是参数类型不同的构造函数时, 考虑使用静态工厂方法, 通过方法名称区分各个方法之间的区别,是一个非常赞的方式.
第二个好处是:
不同于构造函数, 静态方法获取类的实例, 不会每次调用的时候, 都去创建一个新的对象. 这样, 对于不可变类, 就可以使用它的预加载实例,或者反复的使用创建它时缓存的实例, 而不是每次都创建一个新的对象. 这个技术在布尔类中的Boolean.valueOf(boolean)方法中有着淋漓尽致的体现, 这个类从来不会创建新的对象, 实际上, 你可以看JDK中是这么定义布尔这个类的”public final class Boolean”. 这个技术, 很类似于”享元设计模式”, 他能在程序需要频繁创建相同对象, 且对象创建成本较大时候, 显著提高性能.
静态方法返回相同的对象, 能够严格的控制在某个时刻, 那个对象的实例存在, 这称作实例受控”instance-controlled”. 编写实例受控类有几个方面的必要:实例受控类可以控制一个类是否是单例模式还是不可实例化的类. 它还可以确保不可变类不存在两个equals的实例, 有了这样前提, 我们就可以调用==代替equals来提高程序的运行效率.
第三个好处是:
使用静态方法得到类的实例, 可以返回类的任意子类对象, 这样给了程序员很大的自由, 去选择方法返回的类型.
一个典型的应用是在类非public的情况下, 通过API来得到类的实例, 这样会使得代码变得非常简洁.
第四个好处是:
在创建参数化类型的实例的时候, 能让代码变得更简洁.
当让它也有他的缺点:
一是:若果一个类不含有共有的或者protected权限的构造函数, 它就不能被继承.
二是:他们与其他的静态方法没有任何区别.
注: 不可变类
从字面意思来理解就是不会发生变化的类,那么是什么不会发生变化呢,其实就是类的状态,也就是不变类的实例一旦被创建,其状态就不会发生变化,举个例子:如果人是一个class,那么我们中的每一个都是人这个类的具体的instance,如果人这个类只有一个状态就是生身父母,那么它就是一个不变类,因为每一个人在出生的那一刹那,生身父母就已经被设置了值,而且终生都不会发生变化。
不变类有什么好处呢?
1) 不变类是线程安全的,由于不变类的状态在创建以后不再发生变化,所以它可以在线程之间共享,而不需要同步。
2) 不变类的instance可以被reuse
创建类的实例需要耗费CPU的时间,当这个实例不再被引用时,将会被垃圾回收掉,这时候,又需要耗费CPU的时间。对于不变类而言,一个好处就是可以将常用的实例进行缓存,从而减少了对象的创建。举个例子,对于布尔型,最常用的便是true and false。JDK中的Boolean类就是一个不变类,并且对这两个实例进行了缓冲。
public final class Boolean implements java.io.Serializable{
/**
* The Boolean
object corresponding to the primitive
* value true
.
*/
public static final Boolean TRUE = new Boolean(true);
/**
* The Boolean
object corresponding to the primitive
* value false
.
*/
public static final Boolean FALSE = new Boolean(false);
// 这个方法不会创建新的对象,而是重用已经创建好的instance
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
}
3) 不变类的某些方法可以缓存计算的结果
hashCode这个方法来自于Object这个类,这个方法用来返回对象的hashCode,主要用于将对象放置到hashtable中时,来确定这个对象的存储位置。对于一个不变类的实例,它的hashCode也是不变的,所以就可以缓存这个计算的结果,来提高性能,避免不必要的运算,JDK中的String类就是一个例子。
public final class String{
/** Cache the hash code for the string */
private int hash; // Default to 0
public int hashCode() {
int h = hash;
if (h == 0) {
// compute the value
hash = h; // cache the value
}
return h;
}
}
在JDK中, String, the primitive wrapper classes, and BigInteger and BigDecimal都是不变类。
如果一个类是不变类,这个类是不是就不能有改变状态的方法呢?
答案当然是否定的,String是一个不变类,仍然有replace,replaceAll这样的方法,而String仍然是一个不变类,那是因为在这些改变状态的方法中,每次都是新创建一个String对象。
如果大家理解了不变类,那也就不难理解为什么在做String的concatenate时,应当用StringBuffer而不是用+的操作符。
如何正确使用String呢?
1) 不要用new去创建String对象。
如果使用new去创建String,那么每次都会创建一个新对象。
public static void main(String[] args) {
String A1 = "A";
String A2 = "A"; // It won't create a new object
checkInstance(A1, A2); // Result: They are same instances
String B1 = new String("A"); // create a new object
String B2 = new String("A"); // creat a new object
checkInstance(B1, B2); // Result: They are different instances
}
private static void checkInstance(String a1, String a2) {
if (a1 == a2) {
System.out.println("They are same instances");
} else {
System.out.println("They are different instances");
}
}
2) 应当用StringBuffer来做连接操作
因为String是一个不变类,那么在做连接操作时,就会创建临时对象来保存中间的运算结果,而StringBuffer是一个mutable class,这样就不需要创建临时的对象来保存结果,从而提高了性能。