天天看点

JAVA高效编程指南

JAVA高效编程指南

1 创建和销毁对象

1.1 考虑用静态工厂方法替代构造函数

如:

public static Boolean valueOf(boolean b){

return (b?Boolean.TRUE:Boolean.False);

}

优势:

它们具有自己的名字

不需要在每次调用时都去创建一个新的对象

可以返回任何子类型的对象

1.2 使用私有构造函数强化singleton属性

singleton类就是一种只能被实例化一次的简单类。这种类型典型地被用来表示那些本性上具有唯一特性的系统组件,如:JNDI的name查找器。

具体例子参见:com.zte.resmaster.helpers.NamingHelper

1.3 用私有构造函数强化不可实例化能力

有些工具类不希望被实例化:对它进行实例化没有意义。如:java.lang.Math。然而在类中缺少显式构造函数的时候,编译器会自动地提供一个公共的无参数的缺省构造函数。在类中包含显式的私有类型构造函数来实现类的不可实例化特性。因为构造函数是私有的,所以它在类的外部不可访问。如果构造函数不会被类自身从内部调用,即能保证类永远不会被实例化。

1.4 避免创建重复对象

String s= new String("guojun");//永远不要这么干!

Strong s = "guojun";//可以接受的改进

1.5 消除对过期对象的引用

一旦一个对象的引用被不小心地保留,不仅这个对象被排除在垃圾回收之外。而且所有被这个对象引用的对象也不会被垃圾收集,并顺延下去。

一旦对象过期,清除资源以及对它们的引用。尤其自己的程序管理某些对象时,尤其如此。如类中使用对象数组,HashMap 等等。

2 对象定义

2.1 重载equals时要遵守的约定

满足下面的条件就不要重载:

 每个类实例本质上是唯一的

 不关心类是否提供了“逻辑意义的等同“测试

 超类已经重载了equals,而超类继承的行为适合该类

 类是私有的或是或是包内私有的,而且可以确定它的equals方法永远不会被调用。

当类有逻辑上的等同意义而不仅仅是对象意义上的等同,而且超类没有重载equals方法以实现期望的行为,这时需要重载。这种情形通常是数值类型的类(Value Object)。这时,equals的重载不仅对满足程序员的需要是必须的,它也能使类实例通过可预知的,期望的行为来做map关键字或set元素,以及Collection中的访问。

2.2 重载equals时永远重载hashCode

在EntityBean中的关键字对象,必须这么做,否则程序会出现编译错误。

2.3 永远重载toString()

推荐所有的子类重载这个方法,当对象传递给println方法/串的连接操作(+)时,toString()方法会自动调用。附加效果:有利于调试。

如:定义一个人井对象,在toString()中返回人井名称或是编码,那么当调用参数或是返回值为人井对象时,你无需做其他编码,就能在Jbuilder中看到EJB调用时的人井的toString()产生的信息。

2.4 谨慎重载clone

3 类和接口

3.1 最小化类和成员的可访问能力

隐藏模块的内部数据和实现细节,仅仅把该暴露的部分暴露给调用者。这也是OO三概念之一:封装。

如果把某些不应该暴露的方法暴露给调用者,一旦某个调用者使用了它,那么为了保证程序兼容,你就得继续提供对该方法的支持。

访问性:

 私有(private) 仅在类内部可访问

 缺省的(default) 对包内的所有类可访问(注意:没有default的关键字,当没有指明任何修饰符时,就是这种类型)

 受保护的(protected) 该类的子类和包内所有类可访问

 公共的(public) 访问不受限制

子类重载父类的方法,子类中方法的访问能力不能低于父类中该方法的访问能力;否则编译器产生编译错误。

3.2 组合优于继承

当确实需要继承时才采用继承,否则使用类组合来完成。

3.3 接口优于抽象类

两种机制最明显的区别是抽象类容许包含某些方法的实现,而接口不行。

如:com.zte.resmaster.helpers.AbstractDAOImpl,就是抽象类,它具有得到数据库连接和清除数据库资源的方法,但是怎么实现具体的DAO操作必须是其子类实现。

如果设计成抽象类,一旦子类要求继承其他类时就没有办法实现,因为Java中不能多重继承(extends),但是可以多重实现(implements)。换句话说就是接口是定义混合类型的理想选择。

接口允许非层次类型框架的构造。

接口通过封装类方式,能够获得安全、强大的功能。

当然抽象类有另一个优势:改进抽象类比改进接口更加容易。一旦某个方法的实现发生了变化,仅仅需要更改一个地方;当新加一个方法时,所有子类都具有了该方法。

3.4 静态成员优于非静态的

因为不需要构建类的实例。例如,QuyuDAOFactory的getDAO()方法。

3.5 尽量使用内部类

如果该类仅仅在某个调用者使用,那么把它设计成调用者的内部类。具体的例子,参见:com.zte.resmaster.system.rescomp.dao.RescompDAOOracle。不会暴露该内部类,避免滥用而导致的维护问题。

4 结构的替代

4.1 用类替代结构

使用private修饰属性,使用get和set来获取和变更属性。值对象模型可以看成这样的例子。

4.2 用类层次替代联合

内部类的使用,或是类的组合。

4.3 用类替代枚举

5 方法

5.1 认真设计方法签名

选好名称

避免长参数列表,类型相同的长参数列表尤其有害。需要很多参数时,用一个class包装起来,就像结构(Struct)做的一样。

在参数类型的使用上,接口优于类。显然使用Collection作为参数比使用ArrayList要好。因为Collection的参数可以使用ArrayList,LinkedList以及Vector来赋值调用;而ArrayList却必须是ArrayList来调用。

5.2 谨慎使用过载(overload)

安全保守的策略:永远不要写两个具有相同参数数目的过载方法。

5.3 返回0长度的数组而不是null

对于Collection的情况尤其如此。

6 通用编程

6.1 最小化局部变量的作用域

最小化局部变量最有效的方法是在它第一次使用时声明。

几乎每个局部变量声明都应该包含一个初始值,否则编译也通不过。

方法小而集中,每个方法仅仅处理一种活动。

6.2 了解和使用库

Java提供了强大而复杂的类库,在完成某个常用功能之前,看看Java类库中是否已经存在类似功能的类库。一方面减少工作量,一方面使得自己的程序更加简洁高效。

例如:向量(Collection,Set,List…)中字符串的排序:Collections.sort(v);

忽略大小写的排序:Collections.sort(v,String.CASE_INSENSITIVE_ORDER)

还有许多实用类库。

6.3 精确数据计算时,不要使用float或是double类型

使用int或是long进行精确数据计算,自己管理小数位。

当数目不超过9位时,使用int类型;当数目不超过18位时,使用long;如果超过了18位,使用BigDecimal(注意它是对象,不是基本数据类型)。

6.4 尽量避免使用串

例如:从JSP或是其他客户端得到某些信息,其中包含int,Date,String类型,尽早把它们转化为自己合适的数据类型。

6.5 了解字符串“+”的性能

当字符串不可变化时,使用String类型;当可变时使用StringBuffer类型。

当需要字符串+时,使用StringBuffer。这些例子大家可以在“动态对象管理”的DAO实现类中找到。

String s = “aaa” + “bbb”;

上面的语句实际上创建了三个String对象,性能受损。

StringBuffer s= new StringBuffer();

s.append(“aaa”);

s.append(“bbb”);

上面仅仅创建一个对象。

6.6 通过接口访问对象

如果存在合适的接口类型,那么参数、返回值、变量和域应该用接口类型声明。

如果没有合适的接口,那么通过类而不是接口访问对象,是完全合适的。

6.7 接口优于反射

失去在编译期间进行类型检查提供的好处。

代码笨拙而冗长。

性能损失。反射比正常的方法调用慢40倍左右(JDK1.3)。

有的时候,付出代价是值得的,因为能够得到更多的益处;一般而言,可以通过反射创建一个实例,然后通过他们的接口或是超类正常访问他们。

例如:DAO的Factory设计模式中的通过配置实现数据库实现类的平滑移植。

6.8 谨慎地使用固有方法

Java Native Interface。不要因为性能这么做,在JDK1.3中Java地速度已经大幅提高,在JDK1.4提高得更多,几乎已经达到了C++的水平。如果你使用JDK1.3以下的版本,你会发现很多方法都是C或是C++实现的。如:BigInteger。但是在JDK1.3中它已经通过Java重新改写了。

当然,你可以使用JNI,但是记住,必须使用有严格保证的代码,如:SUN提交的代码。因为不小心的C/C++的指针操作,会引起JVM崩溃。

如:你可以使用SUN提供的操纵COM(串口/并口)的代码;非常好用,提供Windows和Solaris版本的支持。

6.9 实现功能后在想如何优化是一个比较好的策略。

6.10 遵循普遍接受的命名规则,尤其是团队内的规则。

7 异常

设计良好的API不应迫使用户使用异常做正常的控制流程。

对可恢复的情况使用已检查异常,对程序错使用运行时异常。例如:某个对象在数据库中已经存在,就属于可恢复的异常——不能再加入那个对象了,如果使用关键字约束的话。

数据库连接错,就属于不可恢复的异常,你的程序不可能对它做任何处理,只能捕捉它。

使失败原子化。

为你的系统定义一个统一的异常,系统中的其他异常都继承该异常。

8 串行化

为了分布式应用中能够传递对象,你可能使用串行化。

一般而言,你不需要做其他事情,除了实现java.io.Serializable接口。如果要通过RMI-IIOP远程传递,那么必须实现该接口。否则,会报告解析错。

每一个可串行化的类都要与一个唯一的标识符关联,如果没有显示声明一个private static final long 的serialVersionUID,那么系统会通过一个复杂的计算过程为类自动生成一个唯一的标识符。

任何对类名、实现的接口名称、公共的或是受保护的成员或是函数名称的改变都将自动产生一个新的UID。

新类和旧类的UID不一致时,你的分布式应用就会失败。

可以采用“serialver.exe -show”能够为类产生一个UID。

9 避免内存泄漏

由于Java中有内存垃圾收集的机制,并且没有指针类型,开发者就可以从C/C++的内存噩梦中解脱出来。正因为如此,很多开发者就不再关注内存的使用问题。小心,这里存在陷阱。

请注意,Java的这个机制是内存垃圾收集而非内存收集!所以,你需要把使用的对象变成JVM可以识别的垃圾。一般而言,在方法中声明使用的变量在方法外面时就会成为垃圾。但是有些却不是,例如:

private HashMap dialogMap = new HashMap();

...

{

JDialog dialog = new JDialog(“XXX”);

...

dialogMap.put(dialog.getTitle(),dialog);

}

...

如果你在整个代码期间都没有调用如下语句:

Object ref =dialogMap.remove(key);

...(释放资源,如:((JDialog)ref).dispose())

ref = null;

或者

dialogMap.clear()(有时,仅仅这个语句还不行)

那么你的代码就存在内存泄漏问题!

几乎所有存储对象的结构都要引起注意,看看是否有资源没有释放,没有成为垃圾而无法回收。下面的对象要严格释放:

java.sql.Connection;

java.sql.Statement;

java.sql.PreparedStatement

java.sql.CallableStatement

java.sql.ResultSet

这些对象如果不释放其资源,不仅仅是内存问题;还将引起数据库问题。如果不释放Connection,那么很快就用尽连接或是数据库巨慢(数据库连接数目还受到Licence的限制)。如果不释放后面的对象资源,那么很快就会用尽数据库游标,因为每打开一次后面的资源就要使用一个游标,即使语句中没有使用游标(其实每个DDL语句都使用一个缺省游标)。而数据库的游标一般是有限制的,Oracle8.1.6中缺省为100,Oracle8.1.7中缺省为300。

千万注意!

可能在一段时间内永远不会碰到内存不足的问题,是不是就不用警惕上面提到的内容呢?

不!

JVM中垃圾内存的收集还受到内存容量的影响。当还有可用内存时,常常不会主动去垃圾收集。这就是为什么常常没有碰到内存不足的问题,因为你机器的内存足够大(256M)。

为了Java程序有序的运行,你可以为它设定一个最大最小内存使用量。就像Weblogic中的一样,如:

-hotspot -ms64m -mx64m。

这样有利于更好的利用垃圾收集机制。

10 结束语

这里所说的是一些技巧,这些技巧在大多数场合适用,它们不是真理。您在开发的过程发现自己会了解更多,当然希望您对该文档提出批评意见或是补充。

希望这个文档对大家有所帮助,谢谢大家。