Java存储相关

对象引用和指针

当调用代码
Person p=new Person();这行代码实际上产生了两个东西:一个是p变量(引用变量);一个是Person对象.这行代码的含义是:创建Person实例,并把这个Person实例赋值给一个引用变量p.
当我们创建了一个Person对象时,系统会自动为这个Person对象开辟一块堆内存用来存储这个Person对象,

Java存储相关_第1张图片
Person对象的存储示意图
当把这个 Person对象赋值给一个引用变量 p时,系统不会把这个 Person对象在内存中重新复制一份, Java让引用变量指向这个对象即可.也就是说:这个引用变量 p里存放的仅仅是一个 引用( 地址, 指针),它指向实际的对象

程序中定义的Person类型的变量Person p实际上是一个引用,它被存放在栈内存里,指向实际的Person对象;而真正的Person对象则存放在堆内存中.

下图显示了将Person对象赋给一个引用变量的示意图

Java存储相关_第2张图片
引用变量指向实际对象示意图

当一个对象被创建出来之后,这个对象被放在堆内存中, Java程序不允许直接访问堆内存中的对象,只能通过该对象的引用来操纵该对象,不管是数组还是对象都只能通过引用来访问它们.

堆内存中的对象可以有多个引用,即多个引用变量指向同一个对象.

方法的参数传递机制

java中方法的参数传递的方式只有一种:值传递.所谓值传递:就是将实际参数的副本(复制品)传入方法内,而参数的本身不会受到任何的影响.

基本类型的参数传递

public class PrimitiveTransferTest
{
    public static void swap(int a , int b)
    {
        // 下面三行代码实现a、b变量的值交换。
        // 定义一个临时变量来保存a变量的值
        int tmp = a;
        // 把b的值赋给a
        a = b;
        // 把临时变量tmp的值赋给a
        b = tmp;
        System.out.println("swap方法里,a的值是"
            + a + ";b的值是" + b);
    }
    public static void main(String[] args)
    {
        int a = 6;
        int b = 9;
        swap(a , b);
        System.out.println("交换结束后,变量a的值是"
            + a + ";变量b的值是" + b);
    }
}

结果如下:

swap方法里,a的值是9;b的值是6
交换结束后,变量a的值是6;变量b的值是9

swap()方法里的ab只是main()方法里变量ab的复制品.java程序总是先从main()方法开始执行,main()方法里开始定义了a,b两个局部变量,两个变量在内存中的存储示意图如下所示

Java存储相关_第3张图片
main()方法中定义的a,b变量存储示意图

当程序开始执行 swap()方法时,系统开始进入 swap()方法,并将 main()方法中的 a,b变量作为参数值传入 swap()方法,传入 swap()方法的只是 a,b的副本,而不是 a,b本身,进入 swap()方法后系统产生了4个变量,这4个变量在内存中的存储示意图如下所示
Java存储相关_第4张图片
main()方法中变量作为参数传入swap()方法存储示意图

程序在 swap()方法中交换 a, b两个变量的值,实际上是对下图中灰色区域的 a, b变量进行交换,交换结束后 swap()方法中输出 a, b变量的值,看到 a的值为9, b的值为6
Java存储相关_第5张图片
image.png

引用类型的参数传递

Java对引用类型的参数传递一样采取值传递的方式

class DataWrap
{
    int a;
    int b;
}
public class ReferenceTransferTest
{
    public static void swap(DataWrap dw)
    {
        // 下面三行代码实现dw的a、b两个成员变量的值交换。
        // 定义一个临时变量来保存dw对象的a成员变量的值
        int tmp = dw.a;
        // 把dw对象的b成员变量值赋给a成员变量
        dw.a = dw.b;
        // 把临时变量tmp的值赋给dw对象的b成员变量
        dw.b = tmp;
        System.out.println("swap方法里,a成员变量的值是"
            + dw.a + ";b成员变量的值是" + dw.b);
        // 把dw直接赋为null,让它不再指向任何有效地址。
        dw = null;
    }
    public static void main(String[] args)
    {
        DataWrap dw = new DataWrap();
        dw.a = 6;
        dw.b = 9;
        swap(dw);
        System.out.println("交换结束后,a成员变量的值是"
            + dw.a + ";b成员变量的值是" + dw.b);
    }
}

结果如下:

swap方法里,a成员变量的值是9;b成员变量的值是6
交换结束后,a成员变量的值是9;b成员变量的值是6

很容易引起一个幻觉:调用swap()方法时,传入的是dw对象的本身而不是它的复制品,但这只是一种幻觉.
首先程序从main()方法开始执行,main()方法中开始创建了一个DataWrap对象,并定义了一个dw引用变量指向DataWrap对象.创建一个对象时,系统内存中有两个东西:一个是堆内存中存储了对象本身,栈内存中保存了引用该对象的引用变量.接下来程序通过引用来操作DataWrap对象,把该对象的a,b两个成员变量分别赋值为6和9.此时的存储示意图如下:

Java存储相关_第6张图片
image.png

接着开始在 main()方法中调用 swap()方法, main()方法并没有结束,系统会为 main()swap()开辟出两个栈区,用来存放 main()swap()方法的局部变量.调用 swap()方法时, dw会作为实参传入 swap()方法,同样采取值传递的方式:值得指出的是. main()方法中的 dw是一个引用(也就是一个指针),它保存了 DataWrap对象的地址,当把 dw的值赋给 swap()方法的 dw形参后,即让 swap()方法的形参也保存了这个地址值,即也会引用到 DataWrap对象.
Java存储相关_第7张图片
image.png

当程序在 swap()方法中操作 dw形参时,由于 dw是一个引用变量,故实际操作的还是堆内存中的 DataWrap对象.不管操作 main()方法中的 dw变量,还是操作 swap()方法中的 dw参数,其实都是操作它们所引用的 DataWrap对象,它们引用的是同一个对象.因此当 swap()方法中交换 dw参数所引用 DataWrap对象的 a,b两个成员变量的值后,可以看到 main()方法中 dw变量所引用 DataWrap对象的 a,b两个成员变量的值也被交换了.
总结:不管是基本类型的参数传递还是引用类型的参数传递都是 先复制然后值传递,只不过后者传递的是一个 引用变量(地址)

成员变量的初始化和内存中的运行机制

class Person
{
    // 定义一个实例变量
    public String name;
    // 定义一个类变量
    public static int eyeNum;
}
public class PersonTest
{
    public static void main(String[] args)
    {
        // 第一次主动使用Person类,该类自动初始化,则eyeNum变量开始起作用,输出0
        System.out.println("Person的eyeNum类变量值:"
            + Person.eyeNum);
        // 创建Person对象
        Person p = new Person();
        // 通过Person对象的引用p来访问Person对象name实例变量
        // 并通过实例访问eyeNum类变量
        System.out.println("p变量的name变量值是:" + p.name
            + " p对象的eyeNum变量值是:" + p.eyeNum);
        // 直接为name实例变量赋值
        p.name = "孙悟空";
        // 通过p访问eyeNum类变量,依然是访问Person的eyeNum类变量
        p.eyeNum = 2;
        // 再次通过Person对象来访问name实例变量和eyeNum类变量
        System.out.println("p变量的name变量值是:" + p.name
            + " p对象的eyeNum变量值是:" + p.eyeNum);
        // 前面通过p修改了Person的eyeNum,此处的Person.eyeNum将输出2
        System.out.println("Person的eyeNum类变量值:" + Person.eyeNum);
        Person p2 = new Person();
        // p2访问的eyeNum类变量依然引用Person类的,因此依然输出2
        System.out.println("p2对象的eyeNum类变量值:" + p2.eyeNum);
    }
}
//创建第一个Person对象
Person p1=new Person();
//创建第二个Person对象
Person p2=new Person();
//分别为两个Person对象的name实例变量赋值
p1.name="张三";
p2.name="孙悟空";
//分别为两个Person对象的eyeNum类变量赋值
p1.eyeNum=2;
p2.eyeNum=3;

当程序执行第一行代码Person p1=new Person();时,如果这行代码是第一次使用Person类,则系统通常会在第一次使用Person类时加载这个类,并初始化这个类.在该类的准备阶段,系统会为该类的类变量分配内存空间,并指定默认初始值.当Person类初始化完成后,系统内存中的存储示意图如下所示:

Java存储相关_第8张图片
image.png

Person类初始化完成后,系统将在堆内存中为 Person类分配一块内存区( Person类初始化完成后,系统会为Person类创建一个类对象), 在这块内存区里包含了保存eyeNum类变量的内存,并设置eyeNum的默认初始值:0.
系统接着创建了一个 Person对象,并把这个 Person对象赋给 p1变量, Person对象里包含了名为 name的实例变量, 实例变量是在创建实例时分配内存空间并指定初始值.当创建了第一个 Person对象后,系统内存中存储状态示意图如下所示:
Java存储相关_第9张图片
image.png

从图中可以看出 eyeNum变量并不属于Person对象,而是属于Person类的,所以创建一个Person对象时并不需要为eyeNum类变量分配内存,系统只是为了name实例变量分配了内存空间,并指定默认初始值:null
接着执行 Person p2=new Person();代码创建第二个 Person对象,此时 Person类已经存在于堆内存中了,所以不需要再对 Person类进行初始化了.创建第二个 Person对象与创建第一个 Person对象并没有什么不同.
当程序执行 p1.name="张三";代码时,将为 p1name实例变量赋值,也就是让堆内存中的 name指向"张三"字符串
Java存储相关_第10张图片
image.png

从上图可以看出, name实例变量是属于单个 Person实例的,因此修改任何一个 Person对象的 name实例仅仅与该对象有关,与 Person类和其他的 Person对象没有任何关系.
直到执行 p1.eyeNum=2;代码时,此时 通过Person对象来修改Person类的类变量,从上图中不难看出 Person对象根本就没有 eyeNum这个变量,通过 p1访问 eyeNum类变量,其实是 Person类的 eyeNum类变量.因此此时修改的是 Person类的 eyeNum类变量.修改之后的内存图
Java存储相关_第11张图片
image.png

事实上,所有的 Person实例访问 eyeNum类变量都是访问的是 Person类的 eyeNum类变量,换句话来说: 不管通过哪个Person实例来访问eyeNum类变量,本质上其实还是通过Person类来访问eyeNum类变量,它们访问的是同一块内存.

注意:当程序需要访问类变量时,尽量使用类作为主调,而不要使用对象作为主调,这样可以避免程序产生歧义,提高程序的可读性.

你可能感兴趣的:(Java存储相关)