Java存储相关

对象引用和指针

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

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

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

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

引用变量指向实际对象示意图

当一个对象被创建出来之后,这个对象被放在堆内存中,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两个局部变量,两个变量在内存中的存储示意图如下所示

main()方法中定义的a,b变量存储示意图

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

程序在swap()方法中交换a,b两个变量的值,实际上是对下图中灰色区域的a,b变量进行交换,交换结束后swap()方法中输出a,b变量的值,看到a的值为9,b的值为6
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.此时的存储示意图如下:

image.png

接着开始在main()方法中调用swap()方法,main()方法并没有结束,系统会为main()swap()开辟出两个栈区,用来存放main()swap()方法的局部变量.调用swap()方法时,dw会作为实参传入swap()方法,同样采取值传递的方式:值得指出的是.main()方法中的dw是一个引用(也就是一个指针),它保存了DataWrap对象的地址,当把dw的值赋给swap()方法的dw形参后,即让swap()方法的形参也保存了这个地址值,即也会引用到DataWrap对象.
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类初始化完成后,系统内存中的存储示意图如下所示:

image.png

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

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

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,608评论 18 399
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,092评论 0 62
  • “1”在前文,上学期这个阶段写的,这是第二次感悟。 现在写这篇东西的时候还没有熄灯,九点半,刚才用新买的卸妆...
    一只小天鹅阅读 331评论 0 0
  • 十月份的时候团队招了新,原本十二三个人的团队,突然变成三十人,倒是热闹了很多。招新的时候心事重重,多半也是因为虫虫...
    叫我大白好了阅读 178评论 0 0
  • 转眼现在就大二了,回想当初刚进校园的那一幕,仿佛就在昨天。曾经我也满怀激情,想要在大学中闯出一番天地。想象着自己...
    爱你是有风阅读 203评论 0 0