3.1 面向对象及基础类型
3.1.1 采用Clone()方式创建对象
Java语言里面的所有类都默认继承自java.lang.Object类,在java.lang.Object类里面有一个clone()方法,JDK API的说明文档里面解释了这个方法会返回Object对象的一个拷贝。我们需要说明两点:一是拷贝对象返回的是一个新对象,而不是一个对象的引用地址;二是拷贝对象与用new关键字操作符返回的新对象的区别是,这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息,即每次拷贝动作不是一个针对全新对象的创建。
当我们使用new关键字创建类的一个实例时,构造函数中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,那么我们可以通过调用它的clone()方法,注意,clone()方法不会调用任何构造函数。
代码3-1所示是工厂模式的一个典型实现,工厂模式是采用工厂方法代替new操作的一种模式,所以工厂模式的作用就相当于创建实例对象的new操作符。
代码清单3-1 创建新对象
public static CreditgetNewCredit()
{
return new Credit();//创建一个新的Credit对象
}
如果我们采用clone()方法的方式创建对象,那么原有的信息可以被保留,因此创建速度会加快。如清单3-2所示,改进后的代码使用了clone()方法。
代码清单3-2 使用了clone()方法
private static CreditBaseCredit = new Credit();
public static CreditgetNewCredit()
{
return (Credit)BaseCredit.clone();
}
3.1.2 避免对boolean判断
Java里的boolean数据类型被定义为存储8位(1个字节)的数值形式,但只能是true或是false。
有些时候我们出于写代码的习惯,经常容易导致习惯性思维,这里指的习惯性思维是想要对生成的数据进行判别,这样感觉可以在该变量进入业务逻辑之前有一层检查、判定。对于大多数的数据类型来说,这是正确的做法,但是对于boolean变量,我们应该尽量避免不必要的等于判定。如果尝试去掉boolean与true的比较判断代码,大体上来说,我们会有2个好处。
n 代码执行的更快(生成的字节码少了5个字节);
n 代码整体显得更加干净。
例如代码清单3-3和3-4所示,我们针对这个判定进行了代码解释,这两个类只有一个差距,即是否调用了等号表达式进行了一致性判定,如代码string.endswith ("a") == true。
代码清单3-3 boolean示例1
boolean method (stringstring) {
return string.endswith ("a") ==true;//判断是否以a结尾
}
代码清单3-4 boolean示例2
boolean method (stringstring) {
return string.endswith ("a");
}
3.1.3 多用条件操作符
我们在编写代码的过程中很喜欢使用if-else用于判定,这种思维来源于C语言学习的经历。大多数中国学生都是从谭老师的C语言书籍[1]了解计算机领域知识的,我们在高级语言程序设计过程中,如果有可能,尽量使用条件操作符"if (cond) return; else return;"这样的顺序判断结构,主要原因还是因为条件操作符更加简捷,代码看起来会少一点。其实JVM会帮助我们优化代码,但是个人感觉能省就省吧,代码过多让人看着不爽。代码清单3-5和3-6所示是示例代码,对比了两者的区别。
代码清单3-5 if示例1
//采用if-else的方式
public intmethod(boolean isdone){
if (isdone) {
return 0;
} else {
return 1;
}
}
代码清单3-6 if示例
public intmethod(boolean isdone) {
return (isdone ? 0 : 1);
}
上面两个例子,我们可以看到有一定差距,代码行数缩短了50%。其实现代JVM已经在编译时做了类似的处理,但是从代码整洁度考虑,作者觉得还是推荐多采用代码清单3-6的方式实现。
3.1.4 静态方法替代实例方法
在Java中,使用static关键字描述的方法是静态方法。与静态方法相比,实例方法的调用需要消耗更多的系统资源,这是因为实例方法需要维护一张类似虚拟函数导向表的结构,这样可以方便地实现对多态的支持。对于一些常用的工具类方法,我们没有必要对其进行重载,那么我们可以尝试将它们声明为static,即静态方法,这样有利于加速方法的调用。
如代码清单3-7所示,我们分别定义了两个方法,一个是静态方法,一个是实例方法,然后在main函数进程里分别调用10亿次两个方法,计算两个方法的调用总计时间。
代码清单3-7 静态方法示例
public static voidstaticMethod(){
}
//实例方法
public voidinstanceMethod(){
}
@Test
public static voidmain(String[] args){
long start = System.currentTimeMillis();
//循环10亿次,创建静态方法
for(inti=0;i<1000000000;i++){
staticVSinstance.staticMethod();
}
System.out.println(System.currentTimeMillis()- start);
start = System.currentTimeMillis();
staticVSinstance si1 =new staticVSinstance();
//循环10亿次,创建实例方法
for(intj=0;j<1000000000;j++){
si1.instanceMethod();
}
System.out.println(System.currentTimeMillis()- start);
}
清单3-7代码中申明了一个静态方法staticMethod()和一个实例方法instanceMethod(),运行程序,统计了两个方法调用若干次后的耗时,程序输出如下,单位是毫秒,方法内部没有实现任何代码。请读者注意,由于机器差别,所以运行的结果可能也会有所不同。
代码清单3-8 程序运行输出
733 764
总的来说,静态方法和实例方法的区别主要体现在两个方面:
n 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
n 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。
从上面的例子我们可以这么总结,如果你没有必要去访问对象的外部,那么就让你的方法成为静态方法。静态方法会被更快地调用,因为它不需要一个虚拟函数导向表,该表用来告诉你如何区分方法的性质,调用这个方法不会改变对象的状态。
3.1.5 有条件地使用final关键字
在Java中,final关键字可以被用来修饰类、方法和变量(包括成员变量和局部变量)。我们在使用匿名内部类的时候可能会经常用到final关键字,例如Java中的String类就是一个final类。
如代码清单3-9所示,由于final关键字会告诉编译器,这个方法不会被重载,所以我们可以让访问实例内变量的getter/setter方法变成“final”。
代码清单3-9 非final类
public void setsize(int size) {
_size = size;
}
private int _size;
代码清单3-10 final类
//告诉编译器该方法不会被重载
final public voidsetsize (int size) {
_size = size;
}
private int _size;
总的来说,使用final方法的原因有两个[2]。第一个原因是把方法锁定,以防任何继承类修改它的含义。第二个原因是提高效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。JDK6以后的Java版本已经不再需要使用final方法进行这些优化了。
3.1.6 避免不需要的instanceof操作
instanceof关键字是Java的一个二元操作符,和==、>、<是属于同一类表达式。由于instanceof是由字母组成的,所以它也是Java的保留关键字。instanceof的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据,即如果左边的对象的静态类型等于右边的,我们使用的instanceof表达式的返回值会返回true。
代码清单3-11 instanceof示例
void method (dog dog,faClass faclass) {
dog d = dog;
if (d instanceof faClass) // 这里永远都返回true.
system.out.println("dog is a faClass");
faClass faclass = faclass;
if (faclass instanceof object) // alwaystrue.
system.out.println("uiso is anobject");
}
上述代码里面对dog类型的变量都做了判定,由于已经确定类继承自基类,所以我们可以删除不需要的instanceof操作。当然,这样的操作修改还是需要基于实际的业务逻辑,有些时候为了保证数据准确性、安全性,还是需要层层检查的。如代码清单3-12所示,代码可以被精简成这样。
代码清单3-12 instanceof示例
void method () {
dog d;
system.out.println ("dog is an faclass");
system.out.println ("uiso is an faclass");
}
另外,绝大多数情况下都不推荐使用instanceof方法,还是好好利用多态特性吧,这是面向对象的基本功能。
3.1.7 避免子类中存在父类转换
我们知道在Java语言里所有的类都是直接或者间接继承自Object类。我们可以说,Object类是所有Java类的祖先,因此每个类都使用Object作为超类,所有对象(包括数组)都实现这个类的方法。在不明确是否提供了超类的情况下,Java会自动把Object作为被定义类的超类。
我们可以使用类型为Object的变量指向任意类型的对象。同样,所有的子类也都隐含的“等于”其父类。那么,程序代码中就没有必要再把子类对象转换为它的父类了。
代码清单3-13 避免父类转换示例
class oriClass {
string _id = "unc";
}
class dog extendsoriClass{
void method () {
dog dog = new dog();
oriClass animal = (oriClass)dog; //已经确定继承自oriClass类了,因此没有必要再转对象类型
object o = (object)dog;
}
}
代码清单3-14 避免父类转换示例
class dog extendsoriClass {
//去掉了转换父类操作
void method () {
dog dog = new dog();
unc animal = dog;
object o = dog;
}
}
3.1.8 建议多使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都被保存在栈(