简介
java常见的面试,有一个比较高频就是考察成员变量和局部变量的区别,掌握好这类题的回答,需要对JVM内存划分需要有一定的了解,需要知道什么数据放在JVM内存的哪一个区域里面。
面试题
public class ParameterTest2 {
static int s;
int i;
int j;
{
int i =1;
// 这里就近原则,i计算的上面i=1的值,而不是this.i的值
i++;
// 计算this.j
j++;
// 计算this.s
s++;
}
public void test(int j) {
// 这里就近原则,j计算的上面形参j的值,而不是this.j的值
j++;
// 计算this.i
i++;
// 计算this.s
s++;
}
public static void main(String[] args) {
ParameterTest2 parameterTest2 = new ParameterTest2();
ParameterTest2 parameterTest3 = new ParameterTest2();
parameterTest2.test(10);
parameterTest2.test(20);
parameterTest3.test(30);
System.out.println(parameterTest2.i+","+parameterTest2.j+","+ParameterTest2.s);
System.out.println(parameterTest3.i+","+parameterTest3.j+","+ParameterTest2.s);
}
}
如果想轻松答出上面的面试题,你需要具备一下基本知识:变量的分类
、JVM内存模型
、变量的就近原则
、非静态代码块的执行
。
变量的分类
一个类中变量分为局部变量
和成员变量
,下面介绍两者的区别。
- 声明的位置
局部变量通常指的是方法的形式参数,在一个方法体中定义的一个变量以及在非静态的代码块中。
成员变量是指的类中方法外的定义的,如果有static
修饰指的就是类变量,如果没有就是实例变量。 - 值存储位置
首先看一下JVM的内存区域划分如下图所示:
堆(Heap)
主要存放所有对象的实例,以及数组。
虚拟机栈(VM Stack)
存放局部变量表,对象引用。
方法区(Method Area)
存放被虚拟机栈加载的类信息、常量、以及静态变量。 - 声明周期
- 局部变量:每一个线程,每一次调用执行都是新的生命周期。
- 实例变量:随着对象的创建而创建,随着对象的销毁而消亡,每一个对象的实例变量是相互独立的。
- 类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的对象类变量都是共享的。
就近原则
上面的面试题有两处应用到了就近原则的问题,分别如下所示:
- 非静态代码块出就近原则
- 方法中就近原则
非静态方法代码块
非静态的方法代码块,会随着每一次对象的创建而执行一次。
面试题分析
有了上面的基础知识支撑,下面将一步步分析面试题main方法代码执行的过程。
ParameterTest2 parameterTest2 = new ParameterTest2();
首先创建了ParameterTest2这个对象,此时初始化静态变量s=0,成员变量i=0,j=0,然后执行非静态方法代码块:
{
int i =1;
i++;
j++;
s++;
}
这儿i++
计算的是局部变量i的值(就近原则),j++
计算的是成员变量j,s++
计算的是静态成员变量s。
所以 代码执行后i=0,j=1,s=1
,同理当再次创建ParameterTest2的时候,代码执行后i=0,j=1,s=2
(s存储在方法区,被其他对象共享)。
parameterTest2.test(10);
这个调用了实例方法:
public void test(int j) {
j++;
i++;
s++;
}
parameterTest2对象执行test方法之前:i=0,j=1,s=2
。
这里计算j++
计算的是局部变量j(就近原则),i++
计算的是成员变量i,s++
计算的是静态成员变量s的值。
所以执行方法完后j=1,i=1,s=3
,再次调用 parameterTest2.test(10)
的执行完方法后时候j=1,i=2,s=4
。
然后最重要的最后一步:
parameterTest3.test(30);
parameterTest3对象执行test方法之前:i=0,j=1,s=4
。
然后这里执行方法完后i=1,j=1,s=5。注意s静态变量被parameterTest2与parameterTest3对象所共享的。
所以上面面试题的输出结果为: