Java常见问题(一)

Posted by Terry on September 27, 2016

类型转换

先看下面一段代码:

        byte a=5;
        byte b=6;
        byte c=7;
        a=b+c;
        System.out.println(b);

编译时报错:“java: 不兼容的类型: 从int转换到byte可能会有损失”。

Java中,byte类型占1个字节,int类型占4个字节。

整数常量的默认数据类型是int,当其赋值给byte变量时,系统会先检查其大小是否在byte的取值范围(-128-127)内,如果在范围内,则会自动进行类型转换,把常量的前3个字节删除;如果不在范围,编译时就会报错。

再看上面的代码,a=b+c中b和c都是变量,因此编译时系统不能确定b和c的和是否在byte内,可能会丢失精度,所以报错。而如果将变量初始化为int类型则不会有此问题。

引用变量

先看下面一段代码:

public class Num{

int num =1;

public static void main(String[] args) {
        Num a = new Num();
        System.out.println("a="+a.num);
        Num b = a;
        b.num=2;
        System.out.println("a="+a.num);
    }
}

输出结果是:

a=1
a=2

要注意的是,新建一个Num对象的时候,a是一个类类型的引用变量,储存了该类对象在堆内存中的地址,而Num b = a表示新建一个类类型的引用变量b,并将a储存的地址复制给b,所以此时a和b都指向同一个对象,a和b对对象的操作是等价的。

不可变字符串与限定字符串

我们知道String并不是基本数据类型,而是一个类,JDK1.5以后的autoboxing特性,使得新建一个String对象变得更方便了:

//JDK1.5以前
String a = new String();
//JDK1.5以后
String a = "...";      

注意,第一种是用new关键字来生成对象,后一种属于直接声明,得到字符串直接量

对于字符串直接量,JVM会在内存中开辟一个“String Pool”,用来存放字符串直接量。

String类的对象是不可变的,称为不可变字符串。

看这一段代码:

String a = "abc";
a = "bcd";
System.out.println(a);

输出:

bcd

这说明String对象的内容可以被改动?但实际情况是,a储存的引用被改变了,从“abc”指向了“bcd”所在的地址。

通过获取hashcode可以明显的看出,a储存的地址发生了变化:

String a = "abc";
System.out.println(r.hashCode());
a = "bcd";
System.out.println(a);
System.out.println(r.hashCode());

输出:

96354
bcd
97347

a的指向被改变后,原来的对象“abc”并不会被清除,而是会一直存在于String Pool中,通过intern()方法,可以重新定位到“abc”对象,具体内容后面会讲到。

再看一段代码:

String a = new String("abc");
String b = new String("abc");
String c = "abc";
String d = "abc";
String e = a.intern();
System.out.println(a==b);
System.out.println(c==d);
System.out.println(c==a);
System.out.println(c==b);
System.out.println(c==e);

输出结果为:

false
true
false
false
true

这段代码体现出了String Pool的另一个特点,JVM为了节约资源,会对具有相同字符串的字符串直接量使用同一个实例,这样的实例称为限定的(interned)。

代码中c和d属于字符串直接量,而且其初始化的字符串相同,所以JVM会自动令c和d指向同一个实例对象,其Hashcode是相等的。

a和b不属于字符串直接量,它们在初始化时不会搜索String Pool,而是在堆内存分别创建对象,所以Hashcode不相同。

再看代码中的intern()方法,通过这个方法,可以将对象转化为限定的,系统会检查当前对象在对象池是否存在内容相同的对象,如果有则返回对象,如果没有就将当前对象变成String Pool中的一员。

回到之前的例子:

String a = "abc";
System.out.println(a.hashCode());
a = "bcd";
System.out.println(a);
System.out.println(a.hashCode());
//添加代码
String b = "abc";
String c = new String("abc");
String d = c.intern();
System.out.println(b==d);
System.out.println(b.hashCode());

输出:

96354
bcd
97347
true
96354

从结果可以看出,不论是新建字符串直接量,还是通过intern()方法,都可以重新定位到String Pool中的“ABC”对象。

bug…

重新测试了上一个主题的代码,发现了一个bug,希望高手能帮我来解答。。。

是这样的,看这一段代码:

String a = new String("abc");
String c = "abc";
System.out.println(a==c);
System.out.println(a.hashCode());
System.out.println(c.hashCode());
System.out.println(a.hashCode()==c.hashCode());

输出:

false
96354
96354
true

也就是说,其实a和c具有相同的哈希值,但是用“==”方法却返回false,不知道哪里出了问题…

补充:查了一些资料,原来Hashcode并不反映内存地址,两个对象的HashCode相同,并不一定表示两个对象就相同,java似乎也没有能够返内存地址的方法,但是通过“==”方法还是可以用来比较两个对象的内存地址是否相同的。

Integer autoboxing

和String类一样,Integer也可以实现autoboxing,看下面的例子:

Integer a = new Integer(127);
        Integer b = new Integer(127);

        System.out.println(a==b);
        System.out.println(a.equals(b));

        Integer c = 127;
        Integer d = 127;
        System.out.println(c==d);
        System.out.println(c.equals(d));

        Integer e = 128;
        Integer f = 128;
        System.out.println(e==f);
        System.out.println(e.equals(f));

输出:

false
true
true
true
false
true

从输出可以看出,只有当装箱的对象是1个字节的时候,对象才是限定的,一旦对象超过1个字节,就会分别开辟新的空间。