Java工程师面试1000题145-成员变量和局部变量

145、程序执行结果?

答案:

2 1 5
1 1 5

解析:这道题的考点主要有四个:变量的变量的分类(成员变量、局部变量)、就近原则、非静态代码块的执行:每次创建实例都会执行、方法的调用:调用一次执行一次。

先看一下变量的分类:Java中变量可以分为局部变量和成员变量。局部变量一般是在方法体{}中、形参、代码块{}中。成员变量是在类中方法外。成员变量中又可细分为两类,一类是被static修饰的类变量,一类是没有被static修饰的实例变量。局部变量都只能使用final修饰,成员变量的修饰符可以是:public、protected、private、final、static、volatile、transient。局部变量是存储在栈里面的,实例变量是存储在堆里面的,类变量是存储在方法区里面。

我们在这里再简单介绍一下JVM运行时数据区:

  1. 堆:此内存区域存在的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java虚拟机规范中的描述原文是:所有的对象实例以及数组都要在堆上分配。
  2. 虚拟机栈:我们通常所说的栈就是指虚拟机栈,虚拟机栈用于存储局部变量表等。局部变量表里存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象的引用(reference类型,它不等同于对象本身,是对象在堆内存中的首地址)。方法执行完,虚拟机栈自动释放。
  3. 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译期编译后的代码等数据。

本地方法栈和程序计数器在这里就不介绍了,用的很少,考的更少。

根据以上分类,我们把程序中出现的变量都给归一下类,如下图:

再看一下作用域:局部变量是从声明处开始,到所属的}处结束,成员变量一般不谈作用域,没有意义。关于生命周期:局部变量:每一个线程,每一次调用执行的都是新的生命周期。实例变量:伴随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量都是独立的。类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的。

下面就开始分析程序的执行过程,从主函数的前两行开始:Exam5 obj1 = new Exam5();Exam5 obj2 = new Exam5();

程序在将要执行main主方法的时候,会首先在栈中开辟出一块内存区域,用来存储主方法main()执行所需要用到的数据。注意:栈中是以方法为单位来开辟内存空间,一个方法开辟一块区域,用来存储本方法所用到的数据,我们称为栈帧,方法执行完该栈帧就销毁了。执行前两行的时候,程序会在主方法的栈帧中创建两个局部变量obj1和obj2,这两个局部变量指向的是什么呢?肯定是对象在堆里面的内存地址啦。执行new Exam5();的时候,会在堆内存中创建Exam5对象的实例,实例里面存放的是该对象的实例变量,int i;  int j; 局部变量没有默认值,但是这里成员变量是由默认值的int i=0;  int j=0; 。注意:这里面没有类变量s噢,s是类变量,要存放在方法区中的。

但是我们这里还涉及到一块代码:

{
    int i = 1;  //非静态代码块中的局部变量i
    i++;
    j++;
    s++;
}

非静态代码块会随着实例创建的时候而执行。对象的实例化其实执行的就是一个叫<init>()的实例化方法,<init>()方法是由非静态实例变量显式赋值代码和非静态代码块、对应的构造器代码组成。既然是执行<init>()方法,是方法就会开栈里面给它开辟一块内存空间,里面存放一个局部变量 int i=1,然后遇到i++;这个时候i的值就变为了2;紧接着执行j++,根据就近原则,实际上操作的是堆里面obj1里面j的值,此时堆里面obj1里面 j 的值就变为了1;然后执行s++,s是我们的类变量,类变量存放在方法区,初始化值也是0,执行完s++就变为了1,注意:这个s是类变量,所有实例共享,每次加都会影响它的值。

执行完第一行 Exam5 obj1 = new Exam5(); 之后,在栈里面,给<init>()方法开辟的栈帧就会被销毁了。再开始执行第二句,Exam5 obj2 = new Exam5();又涉及到一个实例对象的创建,这个和obj1对象的创建过程差不多,也是在堆里面开辟一块区域,存储obj2的int i = 0,int j = 0;然后再执行创建obj2时候的<init>()方法,同样的,需要在栈中创建一个栈帧,里面有i 然后i再增1变为2。紧接着执行j++,根据就近原则,实际上操作的是堆里面obj2里面 j 的值,执行完堆里面obj2里面 j 的值就变为了1;然后执行s++,s是我们的类变量,类变量存放在方法区,s是类变量,所有实例共享,每次加都会影响它的值,经过上一次创建obj1操作后,s的值已经变为了1,再进行一次自增,此时s的值变为了2。同样的,执行完第二行 Exam5 obj2 = new Exam5(); 之后,在栈里面,给<init>()方法开辟的栈帧也会被销毁。

接下来,代码执行到  obj1.test(10); 只要涉及到方法调用,就会在栈里面开辟一块空间(栈帧),用于执行这个方法的所有操作,方法执行完,空间销毁。test方法涉及到局部变量(在这里就是形参) int j ,这里就是涉及到传参的过程,把10传给int j , 因此在栈帧中,j的初始值就是10,紧接着执行 j++ ,变为11,接下来,执行i++,根据就近原则,这个i操作的是成员变量,于是堆里面obj1的 i 值就变为了1。下面执行s++, s是类变量,所有实例共享,每次加都会影响它的值,经过上一次创建obj2操作后,s的值已经变为了2,再进行一次自增,此时s的值变为了3。执行完成之后,该栈帧就被销毁了。

接着又调用一次obj1.test(20)方法,只要涉及到方法调用,就会在栈里面开辟一块空间(栈帧),用于执行这个方法的所有操作,方法执行完,空间销毁。(重要的事情说三遍,不要嫌烦,我说的次数越多,说明越重要噢)test方法涉及到局部变量(在这里就是形参) int j ,这里就是涉及到传参的过程,把20传给int j , 因此在栈帧中,j的初始值就是20,紧接着执行 j++ ,变为21,接下来,执行i++,经过上面一次执行test(10)之后,堆里面obj1的 i 的值已经变为了1,再执行一次自增,此时堆里面obj1的 i 的值就变为2。 下面执行s++, s是类变量,所有实例共享,每次加都会影响它的值,经过上一次执行test(10)操作后,s的值已经变为了3,再进行一次自增,此时s的值变为了4。同样的,执行完此test方法后,栈帧销毁。

最后,调用obj2的test方法,和上面过程一样,栈帧中局部变量j 的初始值就是30,紧接着执行 j++ ,变为31,接下来,执行i++,根据就近原则,这个i操作的是成员变量,于是堆里面obj2的 i 值就变为了1。下面执行s++, s是类变量,所有实例共享,每次加都会影响它的值,经过上一次执行test(20)操作后,s的值已经变为了4,再进行一次自增,此时s的值变为了5。同样的,执行完此test方法后,栈帧销毁。

(栈帧销毁之后,局部变量 j 就没有了,不管是10,20还是30,都不存在了,并没有影响到堆里面实例里的j的值)

所以程序的执行结果就是:

2 1 5
1 1 5

猜你喜欢

转载自blog.csdn.net/qq_21583077/article/details/88635139
今日推荐