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,并且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的更多相关文章
- 《practical Java》读书笔记
题记: 花了一周把Peter Haggar的<practical Java>看了遍,有所感悟,年纪大了, 写下笔记,方便日后查看.也希望有缘之人可以看看,做个渺小的指路人. 不足之处还望指 ...
- Practical Java (一)关于reference
Practice 1, 4, 7, 8 1. 参数传递:by value or by reference 变量型别:reference 和 primitive Java中的变量分为两种:referen ...
- Practical JAVA (四)异常处理
Practice 16~27 一 异常控制流(exceptional control flow)机制: try{ <block> } catch(<ExceptionClass> ...
- Practical JAVA(三)关于final
Practice 2,3 final 作用于by value变量时,一旦赋值不可更改 作用于by reference变量时,一旦不能指向第二个对象,但是可以改变对象的内容.比如不可以第二次=new x ...
- Practical JAVA(二)关于对象的类型和equals函数
Practice5,6,9,10,11,12,13,14,15 ==判断等号两边两个变量储存的值是否相同,如果是两个对象,则判断两个变量储存的对象地址是否相同. 大多数时候,我们需要判断的不是左右两个 ...
- java程序性能优化
一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子: import java.util ...
- Java 集合类详解
集合类说明及区别 Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set Map ├Hashtable ├HashMap └W ...
- Java 中Iterator 、Vector、ArrayList、List 使用深入剖析
标签:Iterator Java List ArrayList Vector 线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构.这些 ...
- Java中List,ArrayList、Vector,map,HashTable,HashMap区别用法
Java中List,ArrayList.Vector,map,HashTable,HashMap区别用法 标签: vectorhashmaplistjavaiteratorinteger ArrayL ...
随机推荐
- 试用版SQL Server 2008 R2 提示评估期已过
解决SQL Server 2008提示评估期已过第一步:进入SQL2008配置工具中的安装中心第二步:再进入维护界面,选择版本升级第三步:进入产品密钥,输入密钥第四步:一直点下一步,直到升级完毕.SQ ...
- linux 安装
分区:/boot swap /这三个顺序分区 mkdir -p|-m cat >> 123.txt<<EOF 123 345 EOF 0.1和2分别表示标准输入.标准输出和标准 ...
- href="#"会导致location.replace(location.href);脚本不工作
我们经常这样:<a onclick="xxx" href="#" 其实这不是一个好习惯,当点下这个连接后,主页面的URL后面会加个#号,这样就会导致很多J ...
- 【PHP设计模式 05_DanLi.php】单例模式
<?php /** * [单例模式] * 总结:防止外部new对象:防止子类继承:防止克隆. */ header("Content-type: text/html; charset=u ...
- ecshop简单三部实现导航分类二级菜单
1.在page_header.lbi对应的位置(你想显示导航的位置)插入 (注意下面的"themes/模板名称/util.php"中的"模板名称"改成你模板文件 ...
- 使用ResourceBundle访问资源文件(properties)帮助类
import java.util.ResourceBundle; /** * 读取properties文件的帮助类 * @author */ public class PropertiesUtil { ...
- c#对话框
OpenFileDialog open = new OpenFileDialog();//创建对话框 open.InitialDirectory = @"C:\Documents and S ...
- (翻译)理解Java当中的回调机制
原文地址:http://cleancodedevelopment-qualityseal.blogspot.com/2012/10/understanding-callbacks-with-java. ...
- 20150624_Andriod _web_service_匹配
using System;using System.Data;using System.Configuration;using System.Linq;using System.Web;using S ...
- Python学习笔记-Day2-Python基础之元组操作
元组的常用操作包括但不限于以下操作: 元组的索引,计数等 这里将对列表的内置操作方法进行总结归纳,重点是以示例的方式进行展示. 使用type获取创建对象的类 type(tuple) 使用dir获取类的 ...