美文网首页
Java存储相关

Java存储相关

作者: 小徐andorid | 来源:发表于2018-04-18 10:44 被阅读0次

对象引用和指针

当调用代码
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类变量,它们访问的是同一块内存.

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

相关文章

  • Java存储相关

    对象引用和指针 当调用代码Person p=new Person();这行代码实际上产生了两个东西:一个是p变量(...

  • Java 学习笔记(15)——反射

    Java中的类文件最终会被编译为.class 文件,也就是Java字节码。这个字节码中会存储Java 类的相关信息...

  • 数据结构与算法(四)队列和Java ArrayDeque

    本文主要包括以下内容: 队列基本概念 队列的相关操作 队列的顺序存储 循环队列 队列的链式存储 Java Link...

  • 找工作必备技能

    基础知识Java和JQuery SpringMVC 源码学习-入门 Mysql存储过程,Mysql高级查询相关SQ...

  • 存储相关

    沙盒 Document:存放重要的数据,iTunes同步时会备份该目录Library/Caches:一般存放体积大...

  • 存储相关

    requestLegacyExternalStorage (Android11) https://develope...

  • JavaSE-Collection

    集合 java.util.Collection 集合-用于存储一组元素,提供了用于维护集合的相关操作,它派生了两个...

  • 看动画学算法之:hashtable

    简介 java中和hash相关并且常用的有两个类hashTable和hashMap,两个类的底层存储都是数组,这个...

  • 存储过程相关

    命令行执行多行存储过程DELIMITER //CREATE DEFINER=wxpaybase@% PROCEDU...

  • Unity存储相关

    前言 我们在用Unity开发的过程中经常会遇到从本地加载资源以及保存资源到本地这样的需求,Unity也提供了集中本...

网友评论

      本文标题:Java存储相关

      本文链接:https://www.haomeiwen.com/subject/vtnmkftx.html