Java必知小Tips

Tips 1: Java里面Override返回值是否必须和父类相同

1234567891011121314151617181920212223242526272829
class Base {    public Base newInstance() {        return new Base();    }    public Base newInstance2() {        return new Base();    }    public Number hello() {        return 0;    }}class Test extends Base {    // 返回值不同    @Override    public Test newInstance() {        return new Test();    }    // 返回值相同    @Override    public Base newInstance2() {        return new Test();    }    // 返回值一定要是父类相同方法返回的值的子类,例如这里的Long是Number的子类.    // Test是Base的子类一样,如果不是子类的话,就会编译报错.    @Override    public Long hello() {        return 1L;    }}

在JDK5.0以前,Override要求参数列表和返回值必须完全相同,否则编译不通过,所以在jdk 1.3、 1.4里面,这个代码是错误的。 Test里面的newInstance 的返回值必须修改为为父类完全相同的Base才可以。
而在JDK1.5以后,系统允许返回值和父类不同了,但必须是其子类才可以。这个问题我也是在实际编程时才注意到的。

Tips 2: try中和finally中return的差别

123456789101112131415161718192021
public class Test {

public int aaa() {        int x = 1;

try {            return ++x;        } catch (Exception e) {

} finally {            ++x;        }        return x;    }

public static void main(String[] args) {        Test t = new Test();        int y = t.aaa();        System.out.println(y);    }}

这段程序返回2,而不是3
官方有详细解释: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.2.5

有一段解释的很清楚:

1234567
If the try clause executes a return, the compiled code does the following:

Saves the return value (if any) in a local variable.

Executes a jsr to the code for the finally clause.

Upon return from the finally clause, returns the value saved in the local variable.

简单翻译为:

1234567
如果try语句里有return,那么代码的行为如下:

1.如果有返回值,就把返回值保存到局部变量中

2.执行jsr指令跳到finally语句里执行

3.执行完finally语句后,返回之前保存在局部变量表里的值
123456789101112131415161718192021
public class Test {

public int aaa() {        int x = 1;

try {            return ++x;        } catch (Exception e) {

} finally {            ++x;            return x;        }    }

public static void main(String[] args) {        Test t = new Test();        int y = t.aaa();        System.out.println(y);    }}

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

Tips 3: 包装类型对象的比较

123456789101112
Integer i1 = 256;Integer i2 = 256;Long l1 = 1000L;Long l2 = 1000L;System.out.println(i1==i2); //falseSystem.out.println(l1==l2); //falseInteger i3 = 100;Integer i4 = 100;Long l3 = 127L;Long l4 = 127L;System.out.println(i3==i4); //trueSystem.out.println(l3==l4); //true

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

Tips 4: 反射破坏单例的私有构造函数保护

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

12345678
public class Elvis {    private static final Elvis INSTANCE = new Elvis();    private Elvis(){}

public static Elvis getInstance() {        return INSTANCE;    }}
123456789101112131415161718
public class Test1 {

public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {        Elvis elvis1 = getSingleInstance();   //实例化一个单例        Elvis elvis2 = getInstanceByReflect();  //通过反射取得特权访问实例化        System.out.println(elvis1 == elvis2);   // false    }

private static Elvis getSingleInstance() {        return Elvis.getInstance();    }

private static Elvis getInstanceByReflect() throws IllegalAccessException, InvocationTargetException, InstantiationException {        Constructor[] declaredConstructors = Elvis.class.getDeclaredConstructors();        declaredConstructors[0].setAccessible(true);    // 访问权限打开        return (Elvis)declaredConstructors[0].newInstance();    }}

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

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

1234567891011121314
public class Elvis {    private static Elvis INSTANCE = null;    private static int cntInstance = 0;    private Elvis() throws Exception {        if (++cntInstance > 1)            throw new Exception("Can't Create another instance");    }

public static Elvis getInstance() throws Exception {        if (INSTANCE == null)            INSTANCE = new Elvis();        return INSTANCE;    }}

另外,在Spring的Bean注入中,即使你私有化构造函数,默认他还是会去调用你的私有构造函数去实例化。(通过BeanFactory来装配Bean,和上面的逻辑如出一辙)
所以,如果我们想保证实例的单一性,就要在定义时加上factory-method=””的属性,并且在私有构造函数中添加防御机制。单例的getInstance()可能会添加一些逻辑,而Spring的默认调用构造函数去创建,就不能保证这份逻辑的准确性,所以会带来隐患。
我们可以通过scope=”prototype”来测试单例是否被多次创建:

1234
<beanid="test"class="com.jscai.spring.demo.Singleton"scope="prototype"></bean>BeanFactory bf = new ClassPathXmlApplicationContext("demoAppTestContext.xml");Singleton test1 = (Singleton)bf.getBean("singleton");Singleton test2 = (Singleton)bf.getBean("singleton");

防御机制生效,抛出 Can't Create another instance 异常,证明Spring能正常调用私有的构造函数来创建Bean,并且创建了多次。
这时候我们要使用factory-method来指定工厂方法,才能达到我们想要的效果

<beanid="test"class="com.jscai.spring.demo.Singleton"scope="prototype"factory-method="getInstance"></bean>

Tips 5: HashCode的写法

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

123456789101112131415161718192021222324252627282930313233343536373839
public class PhoneNumber {

private final short areaCode;    private final short prefix;    private final short lineNumber;

public PhoneNumber(int areaCode, int prefix, int lineNumber) {        rangeCheck(areaCode, 999, "area code");        rangeCheck(prefix, 999, "prefix");        rangeCheck(lineNumber, 999, "line number");        this.areaCode = (short)areaCode;        this.prefix = (short)prefix;        this.lineNumber = (short)lineNumber;    }

private static void rangeCheck(int arg, int max, String name) {        if (arg < 0 || arg > max)            throw new IllegalArgumentException(name + ": " + arg);    }

@Override    public boolean equals(Object o) {        if (o == this)            return true;        if (!(o instanceof PhoneNumber))            return false;        PhoneNumber pn = (PhoneNumber)o;        return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode;    }

@Override    public int hashCode() {        int result = 17;    //随便赋值一个数给result,这里为17        result = 31 * result + areaCode;         result = 31 * result + prefix;        result = 31 * result + lineNumber;        return result;    }}

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

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

12345678910111213
private volatile int hashCode;  //懒加载,缓存hashCode@Overridepublic int hashCode() {    int result = hashCode;    if (result == 0) {    result = 17;    result = 31 * result + areaCode;    result = 31 * result + prefix;    result = 31 * result + lineNumber;    hashCode = result;    }    return result;}

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

Tips 6: 慎用重载方法

1234567891011121314151617181920
public class CollectionClassifier {    public static String classify(Set<?> s) {        return "Set";    }

public static String classify(List<?> lst) {        return "List";    }

public static String classify(Collection<?> c) {        return "Unknown Collection";    }

public static void main(String[] args) {        Collection<?>[] collections = {new HashSet<String>(), new ArrayList<BigInteger>(), new HashMap<String ,String>().values()};        /** 打印结果是三个 Unknown Collection **/        for (Collection<?> c : collections)            System.out.println(classify(c));    }}

你可能希望打出的结果是

1
Set List Unknown Collection

但是确实打印了Unknown Collection三次,这是为什么呢?因为classify方法被重载了,而要调用哪个重载方法是在编译时做出决定的。
这个程序的行为有悖常理,因为对于重载方法的选择是静态的,而对于重写的方法的选择是动态的
下面就是一个重写的例子:

123456789101112131415161718192021222324252627
public class Overriding {    public static void main(String[] args) {        Wine[] wines = {new Wine(), new SParklingWine(), new Champagne()};        for (Wine wine : wines)            System.out.println(wine.name());    }}

class Wine {    String name() {        return "wine";    }}

class SParklingWine extends Wine {    @Override    String name() {        return "sparkling wine";    }}

class Champagne extends Wine {    @Override    String name() {        return "champagne";    }}

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

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

Tips 7: 不要使用float和double来获得精确答案

float和double类型尤其不适用于货币计算,因为要让一个float或者double精确地表示0.1是不可能的。
这时候我们就要使用BigDecimal了。

12345678910
import java.math.*;public class TestBigDecimal {  public static void main(String args[]){    // 10.123    BigDecimal bd = new BigDecimal("10.123");    //10.1229999999999993320898283855058252811431884765625    BigDecimal bd1 = new BigDecimal(10.123);    System.out.println(bd +"/n"+ bd1);  }}

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

12345678
public static void main(String[] args) throws Exception {        BigDecimal volumn = new BigDecimal("0");        for (int i = 0; i < 5 ;i++) {            volumn = volumn.add(new BigDecimal("1"));            volumn = volumn.divide(new BigDecimal("2"));        }        System.out.println(volumn);    }

下面是一个BigDecimal的工具类:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
package Effective;

import java.math.BigDecimal;

/** * Created by piqiu on 2/2/16. */public class Arith {    /**     * 提供精确加法计算的add方法     * @param value1 被加数     * @param value2 加数     * @return 两个参数的和     */    public static double add(double value1,double value2){        BigDecimal b1 = new BigDecimal(Double.valueOf(value1));        BigDecimal b2 = new BigDecimal(Double.valueOf(value2));        return b1.add(b2).doubleValue();    }

/**     * 提供精确减法运算的sub方法     * @param value1 被减数     * @param value2 减数     * @return 两个参数的差     */    public static double sub(double value1,double value2){        BigDecimal b1 = new BigDecimal(Double.valueOf(value1));        BigDecimal b2 = new BigDecimal(Double.valueOf(value2));        return b1.subtract(b2).doubleValue();    }

/**     * 提供精确乘法运算的mul方法     * @param value1 被乘数     * @param value2 乘数     * @return 两个参数的积     */    public static double mul(double value1,double value2){        BigDecimal b1 = new BigDecimal(Double.valueOf(value1));        BigDecimal b2 = new BigDecimal(Double.valueOf(value2));        return b1.multiply(b2).doubleValue();    }

/**     * 提供精确的除法运算方法div     * @param value1 被除数     * @param value2 除数     * @param scale 精确范围     * @return 两个参数的商     * @throws IllegalAccessException     */    public static double div(double value1,double value2,int scale) throws IllegalAccessException{        //如果精确范围小于0,抛出异常信息        if(scale<0){            throw new IllegalAccessException("精确度不能小于0");        }        BigDecimal b1 = new BigDecimal(Double.valueOf(value1));        BigDecimal b2 = new BigDecimal(Double.valueOf(value2));        return b1.divide(b2, scale).doubleValue();    }

/**     * 四舍五入操作     * @param d 被操作数     * @param len 保留小数的长度     * @return     */    public static double round(double d, int len) {        BigDecimal b1 = new BigDecimal(d);        BigDecimal b2 = new BigDecimal(1);        // 任何一个数字除以1都是原数字        // ROUND_HALF_UP是BigDecimal的一个常量,        // 表示进行四舍五入的操作        return b1.divide(b2, len,BigDecimal.ROUND_HALF_UP).doubleValue();    }

/**     * 四舍五入操作     * @param d 被操作数     * @param len 保留小数的长度     * @return     */    public static double round2(double d, int len) {        return new BigDecimal(d).setScale(len, BigDecimal.ROUND_HALF_UP).doubleValue();    }}

Tips 8: 同步访问共享的可变数据

12345678910111213141516
public class StopThread {

private static boolean stopRequested;

public static void main(String[] args) throws InterruptedException {        Thread backgroundThread = new Thread(() -> {            int i = 0;            while (!stopRequested)                i++;        });        backgroundThread.start();

TimeUnit.SECONDS.sleep(1);        stopRequested = true;    }}

上面这个程序被通常用来代替Thread.stop()方法来停止线程。你可能期待这个程序运行一秒就暂停了,确实我们这里1秒后把stopRequested设为true,但是结果并没有暂停,而是永远不会停止。
问题在于:由于没有同步,就不能保证后台贤臣何时看到主线程对stopRequested的值所做的改变。没有同步,虚拟机将这个代码:

12
while (!done)    i++;

转变为:

123
if (!done)    while (true)        i++;

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

1234567891011121314151617181920212223
public class StopThread {

private static boolean stopRequested;

private static synchronized void requestStop() {        stopRequested = true;    }    private static synchronized boolean stopRequested() {        return stopRequested;    }

public static void main(String[] args) throws InterruptedException {        Thread backgroundThread = new Thread(() -> {            int i = 0;            while (!stopRequested())                i++;        });        backgroundThread.start();

TimeUnit.SECONDS.sleep(1);        requestStop();    }}

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

private volatile int value;
1234567891011121314151617
public class StopThread {

private static AtomicBoolean stopRequested = new AtomicBoolean(false);

public static void main(String[] args) throws InterruptedException {        Thread backgroundThread = new Thread(() -> {            int i = 0;            while (!stopRequested.get()) {                i++;            }        });        backgroundThread.start();

TimeUnit.SECONDS.sleep(1);        stopRequested.set(true);    }}

Tips 9: jvm执行流程(static代码块和初始化快和父类子类执行过程)

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

123456789101112131415161718
public class Parent {    public static String p_StaticField = "父类--静态变量";    public String p_Field = "父类--变量";

{        System.out.println(p_Field);        System.out.println("父类--初始化块");    }

static {        System.out.println(p_StaticField);        System.out.println("父类--静态初始化块");    }

public Parent() {        System.out.println("父类--构造器");    }}
12345678910111213141516171819202122232425
public class Children extends Parent {    public static String s_StaticField = "子类--静态变量";    public String s_Field = "子类--变量";

{        System.out.println(s_Field);        System.out.println("子类--初始化块");    }

static {        System.out.println(s_StaticField);        System.out.println("子类--静态初始化块");    }

public Children() {        System.out.println("子类--构造器");    }

public static void main(String[] args) {        new Parent();        System.out.println("--------------");        new Children();        System.out.println("--------------");    }}

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

123456789101112131415
父类--静态变量父类--静态初始化块子类--静态变量子类--静态初始化块父类--变量父类--初始化块父类--构造器--------------父类--变量父类--初始化块父类--构造器子类--变量子类--初始化块子类--构造器--------------

是不是和你们想的都不太一样呢?
下面来给大家解释一下:
1、jvm加载Children的main方法前,要看Children中是否有静态的变量和语句,如果有,先给这些静态的变量分配存储空间和执行静态语句(不是静态方法),且由于Children的父类中也有静态的变量,根据继承的特性,则先执行父类Parent的静态数据的初始化,然后执行子类的静态数据的初始化。
2、执行main方法中的new Children()语句,进行Parent的类的实例化因为Parent的静态数据已经实例化,并且在一个执行过程只实例化一次,所以在执行new Parent()语句时,先执行非静态变量定义的类的非静态语句,之后再执行构造方法,所有有上面的结果。

总结如下:
(1)先静态。具体是 父静态 -> 子静态
(2)先父后子。先父的全部,然后子的全部。
(3)优先级:父类 > 子类。静态代码块 > 非静态代码块 > 构造函数(与位置的前后无关系)

大家都看明白了吗?

Tips 10: For循环的执行过程

123456789101112131415
public class ForTest {

public static void main(String[] args) {        int i = 0;        for (foo('A'); foo('B') && (i < 3); foo('C')) {            i++;            foo('D');        }    }

private static boolean foo(char c) {        System.out.print(c + " ");        return true;    }}

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

A B D C B D C B D C B

为什么这么输出呢?因为for循环的执行步骤是先执行第一个分号前的函数,也就是’A’,再执行第二个分号前的函数,也就是’B’,如果符合条件,就执行for循环中的代码,也就是’D’,接着执行第二个分号后的函数,也就是’C’,接下来,’A’永远不会被执行了,因为它只有第一次加载的时候才会执行,所以直接执行’B’,’D’,’C’的循环,知道不满足第二个分号的函数,程序退出。
看到这里,你是否对Java最基础的for循环有了更深的认识了呢?

时间: 2024-10-28 02:15:44

Java必知小Tips的相关文章

做外链的几个必知小技巧

①论坛签名加链接技巧 这种方式带链接是比较安全的,一般网站是不会删除,不过有的网站如果你做的太过分也会被删,比如添加的链接太多.太明显,在这样的网站上做论坛签名我们可以把他设定为:"欢迎踩踩我的博客www.***.com"."欢迎到我空间www.***.com坐坐",一般来说这种方式比较安全,因为没有太浓的广告味;论坛签名最佳的方式当然就是锚文本链接了,不能点击进入的纯文本连接效果就没那么好,如果你想做某个关键词的排名我们只需要添加这种URL:[url=http:/

乱斗西游81篝火完美阵容推荐及必知小技巧

乱斗西游81篝火几点注意事项 1,阵容的选择的确很重要,比较火的二郎.大鹏.青狮.沙僧,但是你要知道大神们打20多秒是因为他们面板攻击都有8,9万 你同样的阵容,别说20几秒了,杀不死也是正常的. 2,大神们秀篝火成绩,有好多是一次一次拼暴击拼出来的,你同样的面板.阵容,打不出来也是正常的. 3,经测试沙僧法攻速优于法CD(当然,可能很阵容有关,仅供参考). 4,金角前几秒依然是神一般的存在. 5,狮子35CD 可以自动(如果你只求打死的话). 乱斗西游81篝火终极阵容推荐 阵容一:有大鹏无二郎

开发 Electron app 必知的 4 个 tips

本文讲的是开发 Electron app 必知的 4 个 tips, Electron ,是包括 Avocode 在内的众多 app 采用的技术,能让你快速实现并运行一个跨平台桌面应用.有些问题不注意的话,你的 app 很快就会掉到"坑"里.无法从其它 app 中脱颖而出. 这是我 2016 年 5月 在 Amsterdam 的 Electron Meetup 上演讲的手抄版,加入了对 api 变化的考虑.注意,以下内容会很深入细节,并假设你对 Electron有一定了解. 首先,我是

新手建站必知的20条SEO黄金准则

中介交易 SEO诊断 淘宝客 云主机 技术大厅 笔者近日拜读了SEO大师Theresa Sheridan在http://www.seo-news.com发表的头条文章Twenty Basic Tips for SEO Newbies深受感触.发现我们追求多年的所谓高深莫测的SEO其实很简单,知识有时候我们把他想得太复杂了.现翻译出来供大家参考,对新手应该有很大帮助,至少可以避免从开始就误入SEO迷途.原文翻译如下: 如果你有个网站并期望通过这个站来维持生活,从从长远考虑,作为站长就不得不了解一些

JavaScript必知必会(九)function 说起 闭包问题_javascript技巧

function 函数格式 function getPrototyNames(o,/*optional*/ a) { a = a || []; for(var p in o) { a.push(p); } return a; } caller func.caller 返回函数调用者 function callfunc() { if(callfunc.caller) { alert(callfunc.caller.toString()); }else { alert("没有函数调用");

JavaScript必知必会(六) delete in instanceof_javascript技巧

in in 判断 左边 的字符串或者能转换成字符串的是否属于 右边 的属性. var data = { x: , y: };//定义了直接对象 alert("x" in data);//true ,x 是data 的一个属性 alert( in data);//false , 是data的属性值. var arr = [, , ];//定义了直接数组对象 alert( in arr);//true ,arr 数组的index包括,,, 是他的一个[]属性. alert( in arr)

Android开发必知 九种对话框的实现方法_Android

在开发过程中,与用户交互式免不了会用到对话框以实现更好的用户体验,所以掌握几种对话框的实现方法还是非常有必要的.在看具体实例之前先对AlertDialog做一个简单介绍.AlertDialog是功能最丰富.实践应用最广的对话框,它可以生成各种内容的对话框.但实际上AlertDialog生成的对话框总体可分为以下4个区域:图标区.标题区.内容区.按钮区. 这里总结了九种对话框的实现方法,有需要的朋友可以来学习下了   除了popupwindow实现稍微麻烦一点,其他形似都相对简单,熟悉2便即可 直

程序员必知35个jQuery 代码片段_jquery

jQuery如今已经成为Web开发中最流行的JavaScript库,通过jQuery和大量的插件,你可以轻松实现各种绚丽的效果.本文将为你介绍一些jquery实用的技巧,希望可以帮助你更加高效地使用jQuery. 收集的35个 jQuery 小技巧/代码片段,可以帮你快速开发. 1. 禁止右键点击 $(document).ready(function(){ $(document).bind("contextmenu",function(e){ return false; }); });

经典的20道AJAX面试题(必知必会)

1.什么是AJAX,为什么要使用Ajax(请谈一下你对Ajax的认识) 什么是ajax: AJAX是"Asynchronous JavaScript and XML"的缩写.他是指一种创建交互式网页应用的网页开发技术. Ajax包含下列技术: 基于web标准(standards-basedpresentation)XHTML+CSS的表示: 使用 DOM(Document ObjectModel)进行动态显示及交互: 使用 XML 和 XSLT 进行数据交换及相关操作: 使用 XMLH