Practical Java

 

实践1、    参数是以by value方式而非by reference方式传递

一个普通存在的误解是:java中的参数是以 by value 方式传递。其实不是这样的,参数是以 by value 方式传递的。请看示例:

class PassByValue {

  public static void modifyPoint(Point pt, int j) {

    pt.setLocation(5,5);                   //1

    j = 15;

    System.out.println("During modifyPoint " + "pt = " + pt +

                       " and j = " + j);

  }

 

  public static void main(String args[]) {

    Point p = new Point(0,0);              //2

    int i = 10;

    System.out.println("Before modifyPoint " + "p = " + p +

                       " and i = " + i);

    modifyPoint(p, i);                     //3

    System.out.println("After modifyPoint " + "p = " + p +

                       " and i = " + i);

  }

}

这显示,modifyPoint()改变了 //2 所建立的Point对象,却没有改变 int i。在main()之中,i被赋值为10。由于参数通过by value方式传递,所以modifyPoint()收到i的一个副本,然后它将这个副本必为15并返回。main()内的原值i并没有受到影响。

 

对于Point对象,其实modifyPoint()是与“Point对象的reference p 的复件”打交道,而不是与“Point 对象的复件”打交道。记住,p是个object reference,并且Javaby value方式传递参数,或者更准确点说,Javaby value方式传递object reference。当pmain()被传入modifyPoint()时,传递的是p(也就是一个reference)的复件。所以modifyPoint()是在与同一个对象打交道,只不过通过别名pt罢了。在进入modifyPoint()之后和执行//1之前,内存中应该是这样的:

image002

如果你并不想modifyPoint()改变传进的Point对象,你可以传递一个克隆对象(见6466)或者将Point设计成不可变的(见65)。

实践2、    对不变的dataobject references使用final

class FinalTest {

    static final int someInt = 10;

    static final StringBuffer objRef = new StringBuffer("sring");

 

    static void prt() {

       System.out.println("someInt=" + someInt + " - objRef=" + objRef);

    }

 

    public static void main(String[] args) {

       prt();

       //不能重新分配 FinalTest.someInt

       //!!someInt = 20;//1

       objRef.append(" other");//2

       //不能重新分配 FinalTest.objRef

       //!!objRef = new StringBuffer(); //3

       prt();

    }

}

输出:

someInt=10 - objRef=sring

someInt=10 - objRef=sring other

 

程序里的//1处理我们应该很清楚了,但//2处又是为什么呢?我们不是已经声明objRef声明成final,为什么还能改变?不,我们确实没有改变objRef的值,我们改变的是objRef所指对象的值,objRef并无变化,仍然指向同一个对象。变量objRef是一个object reference,它指向对象所在的heap位置。而//3处正是我们想的那样,编译出错了,因为你试图改objRef的值,换而言之,它企图令objRe指向其他物体。然而objRef所指对象并不受关键词final的影响,因此所指向的对象本身是可变的。

 

关键词final只能防止变量值的改变。如果被声明为final的变量是个object reference,那么该reference不能被改变,必须永远指向同一个对象,然而被指向的那个对象是可以随意改变的。

实践3、    缺省情况下所有non-privatenon-static函数都可以被覆写

缺省情况下,类中任何non-privatenon-static函数都允许被子类覆写。类的设计者如果希望阻止子类覆写(修改)某个函数,则必须采取明确的动作,也就是将该函数声明为final

 

关键字finalJava中有多重用途,即可被用于变量(不能修改),也可用于类(不能继承)与方法(不能覆写)。

 

声明某个类为final,也就暗暗声明了这个类的所有函数都为final。这种做法可以阻止它派生类,从而禁止任何人覆写此类的所有函数。如果这种设计对你而言过于严苛,也可以考虑只将某些方法声明成final,这种方式允许你派生出类,但不允许你覆写你声明成final的方法。另外,finalnon-final方法的性能要高。

实践4、    arraysArrayList之间慎重选择

在新建一个数组时,每个元素都将依据其自己类型而被赋予缺省值:boolean-falsechar- 'u0000'byteshortintlong-0floatdouble-0.0object reference-null

 

数组的容量是固定的,一旦指定了就不可更改,但ArrayList的容量是可变的,它会随着元素的增加自动的增长。数组即可存放基本类型也可存储引用类型,而ArrayList只能存放引用类型元素(虽然1.5可以,但这是借助于自动装箱特性实现的)。

 

数组比ArrayList拥有更高的性能。

实践5、    多态(polymorphism)优于instanceof

//员工

interface Employee {

    public int salary();//工资计算

}

//经理

class Manager implements Employee {

    private static final int mgrSal = 40000;//工资

 

    public int salary() {

       return mgrSal;

    }

}

//程序员

class Programmer implements Employee {

    private static final int prgSal = 50000;

    private static final int prgBonus = 10000;//奖金

 

    public int salary() {

       return prgSal;

    }

 

    public int bonus() {

       return prgBonus;

    }

}

//薪水册

class Payroll {

    public int calcPayroll(Employee emp) {

       int money = emp.salary();

       if (emp instanceof Programmer)

           //如果是程序员,则计算奖金

           money += ((Programmer) emp).bonus();

       return money;

    }

 

    public static void main(String args[]) {

       Payroll pr = new Payroll();

       Programmer prg = new Programmer();

       Manager mgr = new Manager();

       System.out.println("程序员的薪水 " + pr.calcPayroll(prg));

       System.out.println("经理的薪水 " + pr.calcPayroll(mgr));

    }

}

避免使用instanceof,现重构上面的程序。在Employee中可以增加一个bonus()接口,然后员工都实现他,经理没奖金时直接返回0,这样就不用在calcPayroll方法里使用instanceof了。

 

使用instanceof首先缺乏性能,不够优雅,也不易扩充(如在以后版本中增加另一种Employee,并有另外的福得怎么办?),其次要求程序员写代码去做Java运行期该做的事。而多态则完全可以避免这些问题。

 

如果这里要为使用instanceof找个理由,那就是Employee不是你设计的,你不能去重构它们。

 

instanceof操作符很容易被误用。很多场合都应该以多态来替代instanceof。无论何时当你看见instanceof,都请判断是否可以改进设计以消除它。以多态方式改进设计,会产生更合逻辑、更经得起推敲的设计,以及更易维护的代码。

实践6、    必要时使用instanceof

除了实践5中面对一个设计不当的class程序库时,你可能无法避免使用instanceof。事实上有更多常见情形,使你除了使用instanceof以外另无选择,如当你必须从一个基础类型向下转型为派生类型时。

 

将一个类型强转为另一个不相关的类型时,在编译时就会出错。而将一个基本类型强转为派生类型时在运行时可能出错。

 

使用instanceof可以消除强转型在运行时的错误。

 

1.5以前,如果我们将不同类型的对象放入到集合中后,在取出时都是一个Object类型,这是instanceof就可以排上用场了,因为它可以消除运行期的错误。

实践7、    一旦不再需要object reference,就将它设为null

当不再需要某对象时,你可以将其reference设为null,以协助垃圾回收器取回内存,如果你的程序执行于一个内存受限环境中,这可能很有益处。你可以试着这么做:

public static void main(String args[]){

       LargeObject lo = new LargeObject();//大对象

       // 使用大对象

       //...

       // 大对象不再需要,置为null

       lo = null;

       //  如果确实紧需内存(但不能保证立刻加收,只是建议)

       System.gc();

       // 后面程序还需要运行很长一段时间...

       //...

}

 

为了尽量降低内存乃是,与程序同寿的对象必须尽可能体积小。此外,大块头对象应该尽量“速生速灭”。

 

检阅代码时,请注意大块头对象,尤其是那些存在于完整(或大部分)程序生命中的对象。你得要仔细研究这此对象的建立与运用,以及它们使用多少内存,如果它们引用了大量内存,请确定是否所有那些在对象生命周期内都真的被需要。也许某些大对象可以解甲归田,从而使其后执行的代码能够更高效地运行。

 

任何刻候你都可以通过System.gc()要求垃圾回收器起身运行(注,还是只是建议,当控制从方法调用中返回时,虚拟机已经尽最大努力回收了所有丢弃的对象)。如果想将一个对象解除引用,则可以通过调用System.gc()要求垃圾回收器立刻运转,在代码继承执行前先回收被解除引用的那块内存,但你应当仔细都考虑这种做法为你的程序性能带来的潜在影响。

 

许多垃圾回收算法会在它们运转之前先虚悬其他所有线程,这种做法可以确保一旦垃圾回收器开始运转,就能够拥有heap的完整访问权,可以安全完成任务而不受其他线程的威胁。一旦垃圾回收器完成了任务,便恢复此前被虚悬的所有线程。

 

因此,通过System.gc()显示调用,要求垃圾回收器起而运行,你得冒“因执行回收工作而带来延迟”的风险,延迟程度取决于JVM所采用的垃圾回收算法。

 

大多数JVM的垃圾回收器都会足够的运行,因此你实在不必显式地调用它。然而,如果你的代码有些部分期望在继续进行前先释放所有可能的内存,则可以考虑调用System.gc()

第二章     对象与相等性

实践8、    区别reference类型与primitive类型

int i = 5;//基本类型

Integer j = new Integer(10);//引用类型

这两个变量都存储在局部变量表(即栈,Stack),它们的操作都在Java操作数堆栈(还是栈)中进行,但二者所表述的意义完全不同。不论是基本类型intobject reference,它们都是static中占据32bits空间,但Integer对象在stack中记录的并不是对象本身,而是对象的reference

所有Java对象都是通过Object reference来访问的,那是某种形式的指针,该指针指向heap中的某块区域,heap则为对象的生存提供了真实存储场所。当你声明了一个基本类型后,你就为它声明了一份存储空间。前面两行代码可以这样表示:

image004

 

如果你使用primitive类型,便免除了“调用new以创建包装对象”的需要,这可节省时间和空间。

 

下面看看输出结果是否你是预想的:

class Assign{

  public static void main(String args[]) {

    int a = 1;

    int b = 2;

    Point x = new Point(0,0);

    Point y = new Point(1,1);//1

    System.out.println("a is " + a);

    System.out.println("b is " + b);

    System.out.println("x is " + x);

    System.out.println("y is " + y);

    System.out.println("Performing assignment and " +

                       "setLocation...");

    a = b;

    a++;

    x = y;//2

    x.setLocation(5,5);//3

 

    System.out.println("a is " + a);

    System.out.println("b is " + b);

    System.out.println("x is " + x);

    System.out.println("y is " + y);

  }

}

输出结果如下:

a is 1

b is 2

x is java.awt.Point[x=0,y=0]

y is java.awt.Point[x=1,y=1]

Performing assignment and setLocation...

a is 3

b is 2

x is java.awt.Point[x=5,y=5]

y is java.awt.Point[x=5,y=5]

 

上面代码运行进内存表示如下,经过//1后的情形:

image006

经过//2赋值动作后情形:

image008

//3调用setLocation()时,函数作用于x所指的对象。由于xy指向同一个对象,故而形成:

image010

由于xy指向同一个对象,所有执行于x身上的函数,就好像执行于y身上一样。

 

弄清楚reference类型和primitive类型之间的差异,以及理解references语义到关重要,否则会导致代码的行为和预想不同。

实践9、    区别==equals()

==”用于基本类型时,比较的就是它们所存储值的大小;如果是用于引用类型,它比较的还是所存储值的大小,不过这时这个值是个特殊的值——它们是对象在heap中的地址,所以当它用于对象时比较的是对象地址,如果被比较的对象指向同一对象,则相等,否则不等。

 

Integer ia = new Integer(10);

Float fa = new Float(10.0f);

System.out.println(ia.equals(fa));//false

System.out.println(fa.equals(ia));//false

为什么上面打印的最是false?这不同基本类型的数值彼止可能相等(如果不同类型则会先提升类型后再比),但不同类型的对象则不然。打开类库源就会发现,它们都是先使用instanceof来测试传进被比较对象是否是同一个类型,如果不是则直接返回false。虽然我们可以自己订制一个equals让不现类型的对象也相等,但并不推荐你这么做,这违反equals业界规范。

 

这里再次说明的是:请使用“==”测试两个基本类型是否完全相同,或测试两个object references是否指向同一个对象;请使用equals()比较两个对象是否一致——基准点是其属性(此处是指对象的实值内容,也就是数据值域field)。我们把“根据属性来比较两个对象是否相等”称为“等值测试”,或称为“语义上的相等测试”。

实践10、            不要依赖equals()的缺省实现

如果你设计的类没有重写equals()方法,那么你在使用equals时将会使用Object中的equals默认实现,它们比较的是否指向同一个对象,而不是对象的逻辑值是否相等,源码如下:

public boolean equals(Object obj) {

    return (this == obj);

}

 

String类正确的实现了equals()方法,但StringBuffer类根本就没有重写Object中的equals()方法。如果遇到StringBuffer,则需要将它先转换为String再使用equals相互比较。1.5中的StringBuilderStringBuffer一样。

 

StringStringBufferStringBuilder都是final类。

 

equals()使用准则:

1、  若要比较对象是否相等,其class有责任提供一个正确有equals()

2、  要“想当然地调用equals()”之前,应该先检查并确保你所使用的class的确实现了equals()

3、  如果你所使用的class并未实现equals(),请判断java.lang.Object的缺省函数是否可胜任。

4、  如果无法胜任,就应该在某个外覆类(wrapper class)或subclass中撰写你自己的equals()方法(比如你设计的类中关键域是StringBuffer之类的类型,你得需要继承它重写它的equals方法,但它是final类,所以使用组合的方式设计功能类似的StringBuffer,并提供equals方法;或者不重新设计功能类似StringBuffer的类而是直接在使用StringBuffer的类中提供一个比较方法对StringBuffer进行专门的比较)。

实践11、            不要依赖equals()的缺省实现

 

原文地址:https://www.cnblogs.com/jiangzhengjun/p/4259259.html