在java中,只有值传递,没有引用传递。
对上面的程序进行分析:
首先,在java中,java虚拟机会为每一个方法创建一个栈帧(栈帧简单来说其实就是支持虚拟机进行方法执行和方法调用的一种数据结构,里面存放了局部变量表,操作数栈,动态链接,方法返回地址等信息),然后每当一个方法从调用到执行结束的过程,就对应了一个栈帧在java虚拟机栈的入栈,出栈的过程。对于上面的程序,执行主方法时,主方法入栈,调用add方法时,add方法入栈,如下图:
在main方法中的a 和 add方法中的a不是一个东西,只是main方法中将10传给了add方法中的参数,让add方法中的a = 10,在add方法中的 a+=10 也只是对add方法中的a进行操作,对main方法中的a没有影响。所以上述程序的输出还是10; 下面在来看一个例子:
在这个例子中,String类型是引用类型,但是还是使用的值传递,其实,它是将str的地址传递给了add方法的参数;但是由于String类型的字符串是不可变的(String中的值是被final修饰的)
所以在我们修改String类型的值的时候不可能直接在原字符串上接着修改,而是重新开辟一块空间,将新空间的值设置为我们要修改的值,然后返回新值的地址,如下图:
有了这些做铺垫,就可以分析上面的那段程序了,过程如下图:
字符串改变之后,对原来空间的值 "10" 没有影响。
说这个之前,先看看下面这段代码:
public class Main {
public static void main(String[] args) {
String str1 = "abc";
String str2 = new String("abc");
String str3 = "a";
String str4 = "bc";
String str5 = str3 + str4 ;
String str6 = str3 + str4 ;
String str7 = str3 + "bc" ;
String str8 = "a" + "bc" ;
System.out.println(str1 == str2);
System.out.println(str1 == str5);
System.out.println(str1 == str6);
System.out.println(str1 == str7);
System.out.println(str1 == str8);
System.out.println(str5 == str6);
}
}
当看到这段代码的时候你认为运行结果会是什么呢,这里就不卖关子了,运行结果如下:
分析:
【1】首先,对于str1 和 str2 来说,这个问题并不难,因为有new 就会产生新空间 == 比较的是地址,所以是false
【2】 对于str1 和 str5,这个其实是在底层,只要是String类型的变量执行 + 操作,java虚拟机就会将String的优化为StringBuilder对象,然后调用append操作,最后用toString方法在将其转换为String对象。
StringBuilder类中的toString方法:
为了验证这个问题,我们可以重新写一个简单的代码,在看一下它的反编译结果:
代码:
public class Main {
public static void main(String[] args) {
String str1 = "aaa";
String str2 = "bbb";
String str = str1 + str2 ;
}
}
知道了这,str1 和 str6 和str7 、str5 和 str6 其实也都的这个原因
最后还剩一个str1 和 str8 :我们对以下代码进行反编译:
public class Main {
public static void main(String[] args) {
String str = "aaa" + "bbb" ;
}
}
结果如下:
我们可以看到,当只有字符串常量相加的时候,并没有优化成StringBuilder,因此String类型的字符串常量最后会入池,池子中如果有了同样的常量,就会共用同一个,所以地址是相同的。
public class Test{
public static void main(String[] args) {
System.out.println( exectionTest() );
}
private static int exectionTest() {
int i = 0;
try{
i = 1;
return i;
}catch (Exception e){
i = 5;
return i;
}finally {
i = 10;
return i;
}
}
}
代码的运行结果为:
如果将刚刚的代码改为:
public class Test{
public static void main(String[] args) {
System.out.println( exectionTest() );
}
private static int exectionTest() {
int i = 0;
try{
i = 1/0; // 会抛出异常 0 不能为分母 进入catch块
return i;
}catch (Exception e){
i = 5;
return i;
}finally {
i = 10;
return i;
}
}
}
运行结果还是同样的:
因为finally块中的代码一定会执行,当三个块中都有return语句的时候,一定会执行finally中的return语句。
public class Test{
public static void main(String[] args) {
System.out.println( exectionTest() );
}
private static int exectionTest() {
int i = 0;
try{
i = 1;
return i;
}catch (Exception e){
e.printStackTrace();
}finally {
i = 10;
}
return 0;
}
}
当finally中没有return时,运行结果:
看到这里是不是觉得有点奇怪,不是说finally中的代码一定会执行么,为什么没有吧i的值改成10,接下来,我们调试一下:
当代码执行到try的return语句的时候:
在朝下走一步:就到了finally块中:
在走一步:
我们会发现i的值已经被改成10了,但是刚刚是怎么回事,难道刚刚运行的时候我的编译器傻了一下,当然不是,我们现在在让它直接走完:
我们发现结果任然是1,那么这是怎么回事呢??
我们现在把代码编译一下,看看编译后的代码Test.class里面的内容:
主要看try ... catch ... finally 这一部分:从这里面可以发现,
在try中,它创建了一个临时变量,用来存之前的值,然后执行finally虽然把当前值给改了,但是它返回的是执行finally块之前的值(返回的是临时变量中的值),所以虽然刚刚它通过finally块把i的值改为10,但是在改之前先将i =1给存起来的,最后返回的是1;
public class Test{
int testNum = 0;
public static void main(String[] args) {
System.out.println( exectionTest().testNum );
}
private static Test exectionTest() {
Test test = new Test();
test.testNum = 0;
try{
test.testNum = 1;
return test;
}catch (Exception e){
e.printStackTrace();
}finally {
test.testNum = 10;
}
return null;
}
}
运行结果:
有木有感觉有点不对劲,为什么刚刚的还说有一个临时变量存着,返回值不会被改变,但是为什么刚刚说完就打脸了,现在值就被finally代码块给改变了,其实不是的;刚刚说的是有一个临时变量把try中的值存着,其实这个也是,但是这个是一个引用对象,临时变量和原来的对象其实是指向同一块内存空间的,他们其实就是同一个对象,所以最后值就被改变了。编译后的结果如下:
如果我们将代码稍作改变:
这样的话,finally中的test和try中的test不再是同一个test,值就不会被改变了。
先看下面这段代码:
public class Test{
public static void main(String[] args) {
int j = 0;
for (int i=0; i<100; i++){
j = j++ ;
}
System.out.println(j);
}
}
看到这里你们觉得答案应该是什么呢,看下面的运行结果:
为什么会这样呢,我们可以看一下编译后的代码:
将代码编译为 .class 文件,在IDEA下查看:
从这里我们可以看出,其实在java中,对于 j = j ++ 这种的,使用了缓存中间变量的机制,将j++的值用一个临时编程存着,而j还是原来的值。相当于一下代码:
所以不管循环多少次,结果都不会发生改变,还是0;
先看下面的代码:
public class Test{
public static void main(String[] args) {
int a = 078;
System.out.println(a);
}
}
// 问题:程序的输出是什么 ???
答案是:编译不通过,因为代码中的 a 是一个8进制的数,只能由数字0-7组成,所以编译不通过。
暂时先说到这,后面遇到了在补充。