改善Java程序的151个建议--记录二(持续更新)

来源:互联网 发布:vb 双色球 全部组合 编辑:程序博客网 时间:2024/04/29 13:07

建议29: 优先选择基本类型

包装类型是一个类,它提供了诸如构造方法、类型转换、比较等非常实用的功能,而且在Java 5 之后又实现了与基本类型之间的自动转换,这使包装类型如虎添翼,更是应用广泛了,在开发中包装类型已经随处可见,但无论是从安全性、性能方面来说,还是从稳定性方面来说,基本类型都是首选方案。我们来看一段代码:

public class BaseTypeClient {public static void main(String[] args) {BaseTypeClient cilent = new BaseTypeClient();int i = 140;// 分别传递int 类型和Integer 类型cilent.f(i);cilent.f(Integer.valueOf(i));}public void f(long a) {System.out.println(" 基本类型的方法被调用");}public void f(Long a) {System.out.println(" 包装类型的方法被调用");}}

在上面的程序中首先声明了一个int 变量i,然后加宽转变成long 型,再调用f() 方法,分别传递int 和long 的基本类型和包装类型,诸位想想该程序是否能够编译?如果能编译输出结果又是什么呢?
这段程序绝对是能够编译的。cilent.f(i)调用f(long)这个是很正常的,但为什么cilent.f(Integer.valueOf(i))也能够编译成功并且调用f(long)这个方法呢?
这是因为自动装箱有一个重要的原则:基本类型可以先加宽,再转变成宽类型的包装类型,但不能直接转变成宽类型的包装类型。简单地说就是,int 可以加宽转变成long,然后再转变成Long 对象,但不能直接转变成包装类型,注意这里指的都是自动转换,不是通过构造函数生成。为了解释这个原则,我们再来看一个例子:

public class BaseTypeClient2 {public static void main(String[] args) {int i=100;f(i);}public static void f(Long l){}}

这段程序编译是通不过的,因为i 是一个int 类型,不能自动转变为Long 型。但是修改成以下代码就可以编译通过了:

public class BaseTypeClient2 {public static void main(String[] args) {long i=(long)100;f(i);}public static void f(Long l){}}

这就是int 先加宽转变为long 型,然后自动转换成Long 型。规则说明白了,我们继续来看f(Integer.valueOf(i)) 是如何调用的,Integer.valueOf(i) 返回的是一个Integer 对象, 这没错,但是Integer 和int 是可以互相转换的。没有f(Integer i) 方法?没关系,编译器会尝试转换成int 类型的实参调用,OK,这次成功了,与f(i) 相同了,于是乎被加宽转变成long型—结果也很明显了。
整个f(Integer.valueOf(i)) 的执行过程是这样的:
1、i 通过valueOf 方法包装成一个Integer 对象。
2、由于没有f(Integer i) 方法,编译器“聪明”地把Integer 对象转换成int。

3、int 自动拓宽为long,编译结束。

重申,基本类型优先考虑。


建议30: 不要随便设置随机种子

随机数在太多的地方使用了,比如加密、混淆数据等,我们使用随机数是期望获得一个唯一的、不可仿造的数字,以避免产生相同的业务数据造成混乱。在Java 项目中通常是通过Math.random 方法和Random 类来获得随机数的,我们来看一段代码:

public class Client {public static void main(String[] args) {Random r = new Random(1000);for(int i=1;i<4;i++){System.out.println(" 第"+i+" 次:"+r.nextInt());}}}
在同一台机器上得到的结果永远是:

 第1 次:-1244746321 第2 次:1060493871 第3 次:-1826063944

计算机不同输出的随机数也不同,但是有一点是相同的:在同一台机器上,甭管运行多少次,所打印的随机数都是相同的,也就是说第一次运行,会打印出这三个随机数,第二次运行还是打印出这三个随机数,只要是在同一台硬件机器上,就永远都会打印出相同的随机数,似乎随机数不随机了,问题何在?
1、种子不同,产生不同的随机数。
2、种子相同,即使实例不同也产生相同的随机数。

看完上面两个规则,我们再来看这个例子,会发现问题就出在有参构造上,Random类的默认种子(无参构造)是System.nanoTime() 的返回值(JDK 1.5 版本以前默认种子是System. currentTimeMillis() 的返回值),注意这个值是距离某一个固定时间点的纳秒数,不同的操作系统和硬件有不同的固定时间点,也就是说不同的操作系统其纳秒值是不同的,而同一个操作系统纳秒值也会不同,随机数自然也就不同了。


建议34: 构造函数尽量简化
建议35: 避免在构造函数中初始化其他类

这两个建议的主要原因是在于子类在通过构造方法初始化的时候会调用父类的构造方法。


建议39: 使用匿名类的构造函数

阅读如下代码,看看是否可以编译:
public static void main(String[] args) {List l1 = new ArrayList();List l2 = new ArrayList(){};List l3 = new ArrayList(){{}};System.out.println(l1.getClass() == l2.getClass());System.out.println(l2.getClass() == l3.getClass());System.out.println(l1.getClass() == l3.getClass());}

注意ArrayList 后面的不同点:l1 变量后面什么都没有,l2 后面有一对{},l3 后面有2对嵌套的{},这段程序能不能编译呢?若能编译,那输出是多少呢?
答案是能编译,输出的是3 个false。l1 很容易解释,就是声明了ArrayList 的实例对象,那l2 和l3 代表的是什么呢?
1、l2=new ArrayList(){}
l2 代表的是一个匿名类的声明和赋值,它定义了一个继承于ArrayList 的匿名类,只是没有任何的覆写方法而已,其代码类似于:
// 定义一个继承ArrayList 的内部类class Sub extends ArrayList{}// 声明和赋值List l2 = new Sub();

2、l3=new ArrayList(){{}}

这个语句就有点怪了,还带了两对大括号,我们分开来解释就会明白了,这也是一个匿名类的定义,它的代码类似于:

// 定义一个继承ArrayList 的内部类class Sub extends ArrayList{{// 初始化块}}// 声明和赋值List l3 = new Sub();

就是多了一个初始化块而已,起到构造函数的功能。我们知道一个类肯定有一个构造函数,且构造函数的名称和类名相同,那问题来了:匿名类的构造函数是什么呢?
它没有名字呀!很显然,初始化块就是它的构造函数。当然,一个类中的构造函数块可以是多个,也就是说可以出现如下代码:

List l3 = new ArrayList(){{}{}{}{}{}};

上面的代码是正确无误,没有任何问题的。现在清楚了:匿名函数虽然没有名字,但也是可以有构造函数的,它用构造函数块来代替,那上面的3 个输出就很清楚了:虽然父类相同,但是类还是不同的。

0 0
原创粉丝点击