天天看点

Java必知小Tips

<a></a>

在jdk5.0以前,override要求参数列表和返回值必须完全相同,否则编译不通过,所以在jdk 1.3、 1.4里面,这个代码是错误的。 test里面的newinstance 的返回值必须修改为为父类完全相同的base才可以。

而在jdk1.5以后,系统允许返回值和父类不同了,但必须是其子类才可以。这个问题我也是在实际编程时才注意到的。

这段程序返回2,而不是3

有一段解释的很清楚:

简单翻译为:

而上面这段输出的则是3,因为如果finally中有return就直接返回。

上面的结果大概很多人都猜错了。-127到128之间的值是不可变的wrapper类型,所以vm确实对i1与i2使用了同样的对象实例(以及内存地址),因此,==运算的结果是true。我们必须要注意此事,因为它会引发一些古怪又非常难以判别的bug。

大家是不是觉得单例函数永远只能返回一个对象呢?其实利用反射可以轻松返回n个不同的对象。java的反射破坏单例的私有构造函数保护,最典型的就是spring的bean注入,我们可以通过改造私有构造函数来防止。在singleton中,我们只对外提供工厂方法(获取单例),而私有化构造函数,来防止多面多余的创建。对于一般的外部调用者来说,私有构造函数已经很安全了。

下面只是一个简单的例子(不考虑线程安全等因素,只是为了简洁)

一般的正常反射也是找不到私有构造函数的,但是我们只要使用declaredconstructors方法和特权setaccessible方法即可实例化。

如果要防御这样的反射侵入,可以修改构造函数,加上第二次实例化的检查。

另外,在spring的bean注入中,即使你私有化构造函数,默认他还是会去调用你的私有构造函数去实例化。(通过beanfactory来装配bean,和上面的逻辑如出一辙)

所以,如果我们想保证实例的单一性,就要在定义时加上factory-method=””的属性,并且在私有构造函数中添加防御机制。单例的getinstance()可能会添加一些逻辑,而spring的默认调用构造函数去创建,就不能保证这份逻辑的准确性,所以会带来隐患。

我们可以通过scope=”prototype”来测试单例是否被多次创建:

防御机制生效,抛出 <code>can't create another instance</code> 异常,证明spring能正常调用私有的构造函数来创建bean,并且创建了多次。

这时候我们要使用factory-method来指定工厂方法,才能达到我们想要的效果

在每个覆盖了equals方法的类中,也必须覆盖hashcode方法。如果不这样做的话,就会违反object.hashcode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括hashmap、hashset和hashtable。

上面是一个完成了hashcode的简单例子,例子当中为什么选择31,因为它是一个奇素数。如果乘数是偶数,并且惩罚溢出的话,信息就会丢失,因为与2相乘等价于移位运算。31有个很好的特性,即用移位和减法来替代乘法,可以得到更好的性能:31*i == (i&lt;&lt;5)-i。现代的vm可以自动完成这种优化。

如果一个雷是不可变的,并且计算散列码的开销也比较大,就应该考虑把散列吗缓存在对象内部,而不是每次请求的时候都重新计算散列码。如果你觉得这种类型的大多数对象会被用作散列键(hash keys),就应该在创建实例的时候计算散列码。否则,可以选择“延迟初始化(lazily initialize)”散列码,一直到hashcode被第一次调用的时候才init.

这种方法能够获得相当好的散列函数,但是它并不能产生最新的散列函数。

你可能希望打出的结果是

但是确实打印了unknown collection三次,这是为什么呢?因为classify方法被重载了,而要调用哪个重载方法是在编译时做出决定的。

这个程序的行为有悖常理,因为对于重载方法的选择是静态的,而对于重写的方法的选择是动态的

下面就是一个重写的例子:

这个程序就是按照我们所想的打印出 wine sparkling wine champagne

到底怎样才算胡乱使用重载机制呢?这个问题仍有争议。安全而保守的策略是,永远不要导出两个具有相同参数数目的重载方法。如果方法使用可变参数,保守的策略是根本不要重载它,如果你遵守这些限制,程序员永远也不会陷入到“对于任何一组实际的参数,哪个重载方法是适用的”这样的疑问中。这项限制并不麻烦,因为你始终可以给方法其不同的名称,而不使用重载机制

float和double类型尤其不适用于货币计算,因为要让一个float或者double精确地表示0.1是不可能的。

这时候我们就要使用bigdecimal了。

关于bigdecimal是如何计算的,我以论坛中一个人的提问帖子为例,来简单的写出bigdecimal的运算方法。题目是:李白无事街上走,提壶去买酒。遇店加一倍,见花喝一斗,五遇花和店,喝光壶中酒,试问李白壶中原有多少斗酒?

下面是一个bigdecimal的工具类:

上面这个程序被通常用来代替thread.stop()方法来停止线程。你可能期待这个程序运行一秒就暂停了,确实我们这里1秒后把stoprequested设为true,但是结果并没有暂停,而是永远不会停止。

问题在于:由于没有同步,就不能保证后台贤臣何时看到主线程对stoprequested的值所做的改变。没有同步,虚拟机将这个代码:

转变为:

这种优化称作提升(hoisting),正式hopspot server vm的工作。结果是个活性失败(liveness failure),修改这个问题使用同步就可以搞定了。

但是我们还有更简洁的写法,给变量加个volatile关键字即可。虽然volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该域的时候都将看到最近刚刚被写入的值:

我们也可以使用atomicboolean来实现,其实它的内部也是使用了volatile关键字:

现在我们有两个类,一个parent,一个children,children继承parent,如下所示:

大家猜想一下结果是什么呢?

是不是和你们想的都不太一样呢?

下面来给大家解释一下:

1、jvm加载children的main方法前,要看children中是否有静态的变量和语句,如果有,先给这些静态的变量分配存储空间和执行静态语句(不是静态方法),且由于children的父类中也有静态的变量,根据继承的特性,则先执行父类parent的静态数据的初始化,然后执行子类的静态数据的初始化。

2、执行main方法中的new children()语句,进行parent的类的实例化因为parent的静态数据已经实例化,并且在一个执行过程只实例化一次,所以在执行new parent()语句时,先执行非静态变量定义的类的非静态语句,之后再执行构造方法,所有有上面的结果。

总结如下:

(1)先静态。具体是 父静态 -&gt; 子静态

(2)先父后子。先父的全部,然后子的全部。

(3)优先级:父类 &gt; 子类。静态代码块 &gt; 非静态代码块 &gt; 构造函数(与位置的前后无关系)

大家都看明白了吗?

这个测试的输出结果大家都写对了吗?

为什么这么输出呢?因为for循环的执行步骤是先执行第一个分号前的函数,也就是’a’,再执行第二个分号前的函数,也就是’b’,如果符合条件,就执行for循环中的代码,也就是’d’,接着执行第二个分号后的函数,也就是’c’,接下来,’a’永远不会被执行了,因为它只有第一次加载的时候才会执行,所以直接执行’b’,’d’,’c’的循环,知道不满足第二个分号的函数,程序退出。

看到这里,你是否对java最基础的for循环有了更深的认识了呢?