声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.cnblogs.com/jiangzhengjun/p/4259259.html

实践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,并且Java以by value方式传递参数,或者更准确点说,Java以by value方式传递object reference。当p从main()被传入modifyPoint()时,传递的是p(也就是一个reference)的复件。所以modifyPoint()是在与同一个对象打交道,只不过通过别名pt罢了。在进入modifyPoint()之后和执行//1之前,内存中应该是这样的:

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

实践2、    对不变的data和object 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-private、non-static函数都可以被覆写

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

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

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

实践4、    在arrays和ArrayList之间慎重选择

在新建一个数组时,每个元素都将依据其自己类型而被赋予缺省值:boolean-false,char- '\u0000',byte、short、int、long-0,float、double-0.0,object 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操作数堆栈(还是栈)中进行,但二者所表述的意义完全不同。不论是基本类型int或object reference,它们都是static中占据32bits空间,但Integer对象在stack中记录的并不是对象本身,而是对象的reference。

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

如果你使用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后的情形:

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

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

由于x和y指向同一个对象,所有执行于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中的StringBuilder与StringBuffer一样。

String、StringBuffer、StringBuilder都是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()的缺省实现

Practical Java的更多相关文章

  1. 《practical Java》读书笔记

    题记: 花了一周把Peter Haggar的<practical Java>看了遍,有所感悟,年纪大了, 写下笔记,方便日后查看.也希望有缘之人可以看看,做个渺小的指路人. 不足之处还望指 ...

  2. Practical Java (一)关于reference

    Practice 1, 4, 7, 8 1. 参数传递:by value or by reference 变量型别:reference 和 primitive Java中的变量分为两种:referen ...

  3. Practical JAVA (四)异常处理

    Practice 16~27 一 异常控制流(exceptional control flow)机制: try{ <block> } catch(<ExceptionClass> ...

  4. Practical JAVA(三)关于final

    Practice 2,3 final 作用于by value变量时,一旦赋值不可更改 作用于by reference变量时,一旦不能指向第二个对象,但是可以改变对象的内容.比如不可以第二次=new x ...

  5. Practical JAVA(二)关于对象的类型和equals函数

    Practice5,6,9,10,11,12,13,14,15 ==判断等号两边两个变量储存的值是否相同,如果是两个对象,则判断两个变量储存的对象地址是否相同. 大多数时候,我们需要判断的不是左右两个 ...

  6. java程序性能优化

    一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子: import java.util ...

  7. Java 集合类详解

    集合类说明及区别 Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set Map ├Hashtable ├HashMap └W ...

  8. Java 中Iterator 、Vector、ArrayList、List 使用深入剖析

    标签:Iterator Java List ArrayList Vector 线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构.这些 ...

  9. Java中List,ArrayList、Vector,map,HashTable,HashMap区别用法

    Java中List,ArrayList.Vector,map,HashTable,HashMap区别用法 标签: vectorhashmaplistjavaiteratorinteger ArrayL ...

随机推荐

  1. 试用版SQL Server 2008 R2 提示评估期已过

    解决SQL Server 2008提示评估期已过第一步:进入SQL2008配置工具中的安装中心第二步:再进入维护界面,选择版本升级第三步:进入产品密钥,输入密钥第四步:一直点下一步,直到升级完毕.SQ ...

  2. linux 安装

    分区:/boot swap /这三个顺序分区 mkdir -p|-m cat >> 123.txt<<EOF 123 345 EOF 0.1和2分别表示标准输入.标准输出和标准 ...

  3. href="#"会导致location.replace(location.href);脚本不工作

    我们经常这样:<a onclick="xxx" href="#" 其实这不是一个好习惯,当点下这个连接后,主页面的URL后面会加个#号,这样就会导致很多J ...

  4. 【PHP设计模式 05_DanLi.php】单例模式

    <?php /** * [单例模式] * 总结:防止外部new对象:防止子类继承:防止克隆. */ header("Content-type: text/html; charset=u ...

  5. ecshop简单三部实现导航分类二级菜单

    1.在page_header.lbi对应的位置(你想显示导航的位置)插入 (注意下面的"themes/模板名称/util.php"中的"模板名称"改成你模板文件 ...

  6. 使用ResourceBundle访问资源文件(properties)帮助类

    import java.util.ResourceBundle; /** * 读取properties文件的帮助类 * @author */ public class PropertiesUtil { ...

  7. c#对话框

    OpenFileDialog open = new OpenFileDialog();//创建对话框 open.InitialDirectory = @"C:\Documents and S ...

  8. (翻译)理解Java当中的回调机制

    原文地址:http://cleancodedevelopment-qualityseal.blogspot.com/2012/10/understanding-callbacks-with-java. ...

  9. 20150624_Andriod _web_service_匹配

    using System;using System.Data;using System.Configuration;using System.Linq;using System.Web;using S ...

  10. Python学习笔记-Day2-Python基础之元组操作

    元组的常用操作包括但不限于以下操作: 元组的索引,计数等 这里将对列表的内置操作方法进行总结归纳,重点是以示例的方式进行展示. 使用type获取创建对象的类 type(tuple) 使用dir获取类的 ...