首页 > 学院 > 开发设计 > 正文

表达式中的陷阱

2019-11-14 21:54:21
字体:
来源:转载
供稿:网友
表达式中的陷阱

作者:禅楼望月(http://www.VEVb.com/yaoyinglong/)

字符串的陷阱

java程序创建对象常见的方式有:

  • new;
  • 通过Class对象的newInstance()方法调用构造函数创建Java对象;
  • 反序列化;
  • clone()方法。

此外,基本类型以及基本类的包装类、字符串还可以以直接量的方式来创建Java对象。如:

String str="hello world";Integer in=5;

对于Java程序中的字符直接量,JVM会使用一个字符串池来保护他们:当第一次使用某个字符串直接时,JVM会将它们放入字符串池进行缓存。在一般情况下,字符串缓冲池中字符串对象不会被垃圾回收,当程序再次需要使用该字符串时,无需重新创建一个新的字符串,而是直接让引用变量指向字符串池中已有的字符串:

String str1="hello";String str2="hello";System.out.PRintln(str1==str2);//-->true

虽然String是引用类型。但是由于str1和str2两个字符串的值都是直接量,它们都指向JVM的字符串池里的“hello”字符串,因此判断str1==str2时输出true。

注意:

也可以通过字符串连接表达式来创建字符串对象。如果这个字符串连接表达式的值可以在编译时确定下来,那么JVM会在编译时计算该字符串变量的值,并让它指向字符串池中对应的字符串。但是,如果字符串连接表达式中使用了变量或者调用了方法,那就只能到运行时才可确定该字符串连接表达式的值,也就无法在编译时确定该字符串变量的值,因此无法利用JVM的字符串池。

image

输出的结果为:

image

image

但是也有例外:字符串连接运算中的所有变量都可执行“宏替换”

如果字符串连接运算中的所有变量都可以执行“宏替换”,那么JVM一样可以在编译时就确定字符串连接表达式的值,一样会使字符串变量指向JVM字符串池中的对应字符串。

String str="I love Java";final String str1="I love ";String str2=str1+"Java";System.out.println(str==str2);//-->true

我们用反编译工具看看:

String str = "I love Java";String str1 = "I love ";String str2 = "I love Java";     //这里执行了宏替换。编译时将str1用相应的值代替了。这是因为str1是final的而final变量的值在初始化之后不会在变化了。所以可以执行宏替换。System.out.println(str == str2);

不可变的字符串

String在创建之后是不可变的。只能改变字符串变量的引用,不能改变字符串对象。

String str="I love Java";str="I love C#";

第二行代码并没有将“I love Java”字符串对象变为“I love C#”字符串对象,只是改变str的指向,使其指向“I love C#”字符串对象,但是“I love Java”字符串对象仍然存在内存中。但是该“I love Java”字符串对象以后也许永远也不会被使用了,但是它并不会被垃圾回收器所回收,它将一直存在于字符串池中------>这也是Java内存泄漏的一个原因。字符串比较

使用“==”比较的是两个字符串所引用的是否是同一个字符串对象。但是如果要比较两个字符串序列是否相同则须用String的 equals 方法,如下是String类的equals方法源码

public boolean equals(Object anObject) {        if (this == anObject) {            return true;        }        if (anObject instanceof String) {            String anotherString = (String) anObject;            int n = value.length;            if (n == anotherString.value.length) {                char v1[] = value;                char v2[] = anotherString.value;                int i = 0;                while (n-- != 0) {                    if (v1[i] != v2[i])                            return false;                    i++;                }                return true;            }        }        return false;    }

还有String的compareTo系列方法。

表达式类型的陷阱

表达式类型的自动提升。

当表达式中出现不同的数据类型是,表达式的结果以表达中最高等级操作数的类型为准。

复合赋值运算符的陷阱

诸如:+=、-=、*=……等复合赋值运算符都隐式包含了类型转换。

image

如上图所示,a=a+5出现了编译错误,是因为,5为int类型,所以会将整个表达式的值提升为int,而a又是short不能进行类型的自动转换,所以出现了编译错误。而a+=5没有出现编译错误的原因为,我们看看Java底层怎么处理的:

image

是不是很清楚了,底层默默的进行了隐式的类型转换。怎么转的呢?是以等号左边的类型作为等号后边表达式计算结果的类型。这下可危险了:万一等号右边表达式的计算结果的范围比等号左边的大怎么办,那只能悲催了,如:

image

所以我们在使用复合赋值运算符是要特别小心,特别是:

    1. 将复合赋值运算符运用于byte、short或char等类型的变量;
    2. 将复合赋值运算符运用于int类型的变量,而表达式右边是long、float、double类型的值;
    3. 将复合赋值运算符运用于float类型的变量,而表达式右边是double类型的值。
多线程的陷阱

不要使用Thread类来创建线程。

不要调用run方法。

当同步一段代码块时,程序必须显式的为它指定同步监视器;对于非静态方法同步方法而言,该方法的同步监视器是this—即调用该方法的Java对象;对于静态同步方法而言,该方法的同步监视器不是this而是当前类本身

public class Test implements Runnable{    static boolean staticFLag=true;    public static synchronized void test0(){        for(int i=0; i<100; i++){            System.out.println("test0:"+Thread.currentThread().getName()+" "+i);        }    }    public void test1(){        synchronized (this) {            for(int i=0; i<100; i++){                System.out.println("test1:"+Thread.currentThread().getName()+" "+i);            }        }    }    @Override    public void run() {        if(staticFLag){            staticFLag=false;            test0();        }else {            staticFLag=true;            test1();        }    }    public static void main(String[] args) throws InterruptedException {        Runnable runnable=new Test();        new Thread(runnable).start();        new Thread(runnable).start();    }}

打印的结果为:

test0:Thread-0 0

test1:Thread-1 0

test0:Thread-0 1

test1:Thread-1 1

test0:Thread-0 2

test1:Thread-1 2

test0:Thread-0 3

test1:Thread-1 3

……

我们可以看到,由于两个方法 没有使用相同的同步监视器,所以它们可以同时并发执行,相互之间不会有影响。但是如果我们这样变一下:

public class Test implements Runnable{    static boolean staticFLag=true;    public static synchronized void test0(){        for(int i=0; i<100; i++){            System.out.println("test0:"+Thread.currentThread().getName()+" "+i);        }    }    public void test1(){        synchronized (Test.class) {            for(int i=0; i<100; i++){                System.out.println("test1:"+Thread.currentThread().getName()+" "+i);            }        }    }    @Override    public void run() {        if(staticFLag){            staticFLag=false;            test0();        }else {            staticFLag=true;            test1();        }    }    public static void main(String[] args) throws InterruptedException {        Runnable runnable=new Test();        new Thread(runnable).start();        new Thread(runnable).start();    }}

打印:

test0:Thread-0 0

test0:Thread-0 1

test0:Thread-0 2

……

test0:Thread-0 98

test0:Thread-0 99

test1:Thread-1 0

test1:Thread-1 1

……

test1:Thread-1 99

我们看到,当我们显示的指定test1方法内的代码块的同步监视器为Test.class时,由于test0方法和test1方法是用了相同的同步监视器,所以必须在test0方法释放了该同步监视器的锁定,test1才能使用。

本文链接:http://www.VEVb.com/yaoyinglong/p/java%E8%A1%A8%E8%BE%BE%E5%BC%8F%E4%B8%AD%E7%9A%84%E9%99%B7%E9%98%B1.html


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表