Java中的内存分布主要是区分堆(Heap)和栈(Stack)的不同存放。
如图
讨论“堆“和”栈”的区别其实际意义就是讨论基本数据类型和对象类型在存储上的区别。
变量比较
在Java程序中利用“==”和“!=”比较变量时,系统使用变量在“栈”所存储的值来作为依据。基本数据类型在“栈”中所存的值就是其内容值,而对象类型在“栈”中存放的值是本身所指向”堆“中对象的地址(对象的引用)。
java.lang包中Object类中public boolean equal()方法它比较两个对象是否相等,仅当被比较的两个引用指向的对象类容相同时,返回ture,否则返回false。
例如,当且仅当参数不为mull并且是个Sting对象,该对象与调用该方法的String对象具有相同的字符顺序时,String类中的cquals0方法才会返回true.
总之.“==”和“!=”比较的是地址。也可以认为。“==” 和“!=” 比较的就是对象引用,面equal() 方法比较的是对象内容。或者说,“==”和“!=”比较的是“栈”中的内容,而equal()比较的是“堆”中的内容。
public class Student {
public String id;//学号
public String name;//姓名
}
当且仅当id和name的属性都相等的时候,equal()才会返回true。
变量的复制
心中肯定有如下疑问:
变量复制不是通过赋值的手段就可以实现吗?这可是最基本的编程常识,为什么还要专门来用述呢?对于基本类型的变量来说的确如此,但是对于对象类型来说。情况却不是那么简单。请看下面的代的片段,
int x=0;
int y=x;
x=1;
System.out.println("y="+y);
输出的结果是“y=0”,可见变量y虽然是通过x赋值而来的但是其却没有根据x的改变而改变,可以说达到了变量赋值的目的。但是下面关于对象赋值的运行效果就不同了。
如下代码
Student student1=new Student();
student1.id="0001";
student1.name="Tom";
Student student2=student1;
student1.id="0002";
student1.name="Jilk";
System.out.println("student2.id= "+student2.id);
System.out.println("student2.name= "+student2.name);
输出的結果是“student2.id= 0002 student2.name= Jilk”对象变量student2 从student1赋值得到,而sudemt1 值发生改变的同时,studem2对象的属性值也相应改变了,看起来起来studenl1和student2指向的是同一个对象(即同一块内存),根本没有到达复制的目的。
这是为什么现? 原来对象类型变量的复制,不能和简单类型变量一般。而必须引用java.lang.Object.clone()方法,否贝赋予的只是对象的引用而已。
为了达到这个目的。我们必须修改Student对象,使其java.lang.Cloneable接口的clone()方法:
实现代码如下:
public class Student implements Cloneable {
public String id;//学号
public String name;//姓名
public Object clone(){
Student o=null;
try{
o=(Student) super.clone();
}
catch(Exception e){
throw new java.lang.IllegalStateException("Clone 出错!"+e.toString());
}
return o;
}
public static void main(String[] args){
Student student1=new Student();
student1.id="0001";
student1.name="Tom";
Student student2=(Student) student1.clone();
student1.id="0002";
student1.name="Jilk";
System.out.println("student2.id= "+student2.id);
System.out.println("student2.name= "+student2.name);
}
}
Student student2=(Student) student1.clone();//克隆一份student1(一个副本),保存在student2中。
这样才真正达到复制的效果,从运行结果中也能看出来。
对于字符串String来说情况有些特殊,虽然String也是继承与java.lang.Object对象,但是对于String对象确实一个新String的对象,而不仅仅是将对象的引用赋值给对方。从这个意义上来说String更加类似于基本的数据类型,
例如
String s1="1";
String s2=s1;
s1="2";
System.out.println("s2= "+s2);
s2是s1赋值给它的,s1变化了,s2没有变化。
引用传递和值传递
还有另外一种变量复制——方法参数的传递,即在java语言中,向方法传递参数的机制。
下面的代码是一个向方法传递基本数据类型的案例:
public class ValuePass {
private int test(int b){
return b=1;
}
public static void main(String[] args){
int a=0;
new ValuePass().test(a);
System.out.println("a= "+a);
}
}
运行结果 a= 0
这是为什么?对于基本数据类型来说,接收方会以“副本”变量来存放参数值,对副本的变更不影响原变量的值。这种参数传递叫做值传递。
接下来就是引用传递的案例
public class ReferecePass {
public static void main(String[] args){
Student student=new Student();
student.id="0001";
student.name="Tom";
new ReferecePass().test(student);
System.out.println("student.id= "+student.id+" "+"student.name= "+student.name);
}
private Student test(Student student){
student.id="0002";
student.name="Jilk";
return student;
}
}
运行结果student.id= 0002 student.name= Jilk。从结果看出,这种对原变量的改变生效了。这种情况传入的是引用不是对象本身。