一.值传递和对象引用传递概念
值传递:方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参 数的值。
引用传递:也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。
[b]1.java中分类两种类型:引用类型和基本类型[/b]
基本类型都是属于值传递。引用类型只是引用对象的值,其实也可以说是属于值传递,我们是把引用对象值的地址传给方法参数中,而不是传递引用对象本身。下面来举例说明。
输出结果为:
0
0
0
20
你算对了吗?
接下来我们一一分析
首先第一个输出零我们是属于基本类型传递,这里不多说了,两个i是不一样的对象,在stack中是有两个对象存在的,分别是指向不同的值。
第二个输出零分析:
[img]http://dl2.iteye.com/upload/attachment/0100/0134/b813a08c-9396-3c25-a278-516297f33472.jpg[/img]
这是我们在执行t = tmp;之前的对象指向情况
[img]http://dl2.iteye.com/upload/attachment/0100/0136/68a34f01-edbd-34cc-866a-85d63e15271a.jpg[/img]
这是我们执行t = tmp;之后的对象指向情况
通过这两个图我们能明显的看出我们main函数中对象只是做了传递了一个对象值的引用路径作用,所以他的值不会做出改变。
接下来第三个零分析
基本上跟第一个是一个道理。
第四个值分析
我们继续用图来说明问题
[img]http://dl2.iteye.com/upload/attachment/0100/0139/3dc43a7c-60e5-35c0-9e71-b3df2c21e2cc.jpg[/img]
[img]http://dl2.iteye.com/upload/attachment/0100/0141/fbc53644-4487-312a-869f-db8a8ea726c8.jpg[/img]
到这里相信你已经明白了差不多了,我们两个对象指向的值地址没有发生改变,所以在方法中改变num的值,就改变了引用对象地址中num的值,而我们main函数中用的值地址也是这个,所以值发生了改变。
[b]2.我们来探讨一下String类的引用[/b]
string对象是个最终类,在创建String对象时jvm在heap中给它分配了一个对象池,如果你创建的对象存在在这个对象池,那么jvm就不会再给你分配空间创建新的值地址。但是这种方式只是要String s = "abc";这样创建,如果是String s = new String("abc");就不能使用了,他创建了一个值对象地址,跟之前的abc地址不是一个。
举例来说明:
输出结果是:
true
true
true
true
false
false
当程序执行该代码的时候,JVM会像普通对象一样生成这个String对象,在heap里面保存,直接返回引用,并不会与String Pool交互,这样一来,String Pool的优势就没有被发挥了,怎么办呢?难道我们就不去使用new的方法创建String了吗?答案是JVM还提供了一个方法:String.intern();来让String Pool管理这种String对象。
intern方法的工作原理是这样的:首先在heap里面创建一个完全一样的String对象,并且将该对象的引用放入String Pool中,最后返回给调用方,看代码:
输出结果:
false
false
true
结论:我们在写JAVA代码的时候要尽量避免使用String s = new String("abc");这种方式,因为这样产生的对象并没有“注册”到String Pool中,无法被重复使用,如果已经存在这种对象了,我们可以通过使用s = s.intern();的方式重新创建对象并“注册”到String Pool中,方便后面的重复使用。
[b]3.深入JVM内存的划分[/b]
由于JVM对与heap和stack内存的使用有其特殊的规则,深入了解JVM是如何使用内存的,非常有助于我们在写程序的时候搞清楚自己的对象到底在什么地方,从而可以帮助我们在多线程程序和性能要求较高的程序中优化自己代码,有兴趣的同学可以参考sun的官方文档(http://java.sun.com/docs/books/jvms/second_edition/html/Overview.doc.html#1732),下面仅就部分知识做简单描述。
a. 每个线程都有自己独占的stack,里面存放的是当前线程执行的method及其局部变量
b. heap中有部分是公共区域,存放的是类实例(class instance)和已分配内存的数组(array)
c. heap中对于每个线程都有各自独立的内存区域,存放以下内容:
运行时常量池(runtime constant pool),上面提到的String Pool就属于其中的一部分
方法代码(method code),即线程要执行的方法代码
静态变量和方法(static variables and method),我们定义的static类型的变量和方法都存放在这里
更详细的描述可以参考图片:
[img]http://dl2.iteye.com/upload/attachment/0100/0146/23d20cfd-b1e9-3afb-a594-3f0b3c3d47cc.jpg[/img]
值传递:方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参 数的值。
引用传递:也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。
[b]1.java中分类两种类型:引用类型和基本类型[/b]
基本类型都是属于值传递。引用类型只是引用对象的值,其实也可以说是属于值传递,我们是把引用对象值的地址传给方法参数中,而不是传递引用对象本身。下面来举例说明。
public final class ParamTest {
// 初始值为0
protected int num = 0;
// 为方法参数重新赋值
public void change(int i) {
i = 5;
}
// 为方法参数重新赋值
public void change(ParamTest t) {
ParamTest tmp = new ParamTest();
tmp.num = 9;
t = tmp;
}
// 改变方法参数的值
public void add(int i) {
i += 10;
}
// 改变方法参数属性的值
public void add(ParamTest pt) {
pt.num += 20;
}
public static void main(String[] args) {
ParamTest t = new ParamTest();
// 为基本类型参数重新赋值
t.change(t.num);
System.out.println(t.num);
// 为引用型参数重新赋值
t.change(t);
System.out.println(t.num);
// 改变基本类型参数的值
t.add(t.num);
System.out.println(t.num);
// 改变引用类型参数所指向对象的属性值
t.add(t);
System.out.println(t.num);
}
}
输出结果为:
0
0
0
20
你算对了吗?
接下来我们一一分析
首先第一个输出零我们是属于基本类型传递,这里不多说了,两个i是不一样的对象,在stack中是有两个对象存在的,分别是指向不同的值。
第二个输出零分析:
[img]http://dl2.iteye.com/upload/attachment/0100/0134/b813a08c-9396-3c25-a278-516297f33472.jpg[/img]
这是我们在执行t = tmp;之前的对象指向情况
[img]http://dl2.iteye.com/upload/attachment/0100/0136/68a34f01-edbd-34cc-866a-85d63e15271a.jpg[/img]
这是我们执行t = tmp;之后的对象指向情况
通过这两个图我们能明显的看出我们main函数中对象只是做了传递了一个对象值的引用路径作用,所以他的值不会做出改变。
接下来第三个零分析
基本上跟第一个是一个道理。
第四个值分析
我们继续用图来说明问题
[img]http://dl2.iteye.com/upload/attachment/0100/0139/3dc43a7c-60e5-35c0-9e71-b3df2c21e2cc.jpg[/img]
[img]http://dl2.iteye.com/upload/attachment/0100/0141/fbc53644-4487-312a-869f-db8a8ea726c8.jpg[/img]
到这里相信你已经明白了差不多了,我们两个对象指向的值地址没有发生改变,所以在方法中改变num的值,就改变了引用对象地址中num的值,而我们main函数中用的值地址也是这个,所以值发生了改变。
[b]2.我们来探讨一下String类的引用[/b]
string对象是个最终类,在创建String对象时jvm在heap中给它分配了一个对象池,如果你创建的对象存在在这个对象池,那么jvm就不会再给你分配空间创建新的值地址。但是这种方式只是要String s = "abc";这样创建,如果是String s = new String("abc");就不能使用了,他创建了一个值对象地址,跟之前的abc地址不是一个。
举例来说明:
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println(s2.equals(s3));
System.out.println(s1==s2);
System.out.println(s1==s3);
System.out.println(s2==s3);
输出结果是:
true
true
true
true
false
false
当程序执行该代码的时候,JVM会像普通对象一样生成这个String对象,在heap里面保存,直接返回引用,并不会与String Pool交互,这样一来,String Pool的优势就没有被发挥了,怎么办呢?难道我们就不去使用new的方法创建String了吗?答案是JVM还提供了一个方法:String.intern();来让String Pool管理这种String对象。
intern方法的工作原理是这样的:首先在heap里面创建一个完全一样的String对象,并且将该对象的引用放入String Pool中,最后返回给调用方,看代码:
String s1 = new String("abc");
String s2 = "abc";
String s3 = s1.intern();
System.out.println(s1==s2);
System.out.println(s1==s3);
System.out.println(s2==s3);
输出结果:
false
false
true
结论:我们在写JAVA代码的时候要尽量避免使用String s = new String("abc");这种方式,因为这样产生的对象并没有“注册”到String Pool中,无法被重复使用,如果已经存在这种对象了,我们可以通过使用s = s.intern();的方式重新创建对象并“注册”到String Pool中,方便后面的重复使用。
[b]3.深入JVM内存的划分[/b]
由于JVM对与heap和stack内存的使用有其特殊的规则,深入了解JVM是如何使用内存的,非常有助于我们在写程序的时候搞清楚自己的对象到底在什么地方,从而可以帮助我们在多线程程序和性能要求较高的程序中优化自己代码,有兴趣的同学可以参考sun的官方文档(http://java.sun.com/docs/books/jvms/second_edition/html/Overview.doc.html#1732),下面仅就部分知识做简单描述。
a. 每个线程都有自己独占的stack,里面存放的是当前线程执行的method及其局部变量
b. heap中有部分是公共区域,存放的是类实例(class instance)和已分配内存的数组(array)
c. heap中对于每个线程都有各自独立的内存区域,存放以下内容:
运行时常量池(runtime constant pool),上面提到的String Pool就属于其中的一部分
方法代码(method code),即线程要执行的方法代码
静态变量和方法(static variables and method),我们定义的static类型的变量和方法都存放在这里
更详细的描述可以参考图片:
[img]http://dl2.iteye.com/upload/attachment/0100/0146/23d20cfd-b1e9-3afb-a594-3f0b3c3d47cc.jpg[/img]