通过一个简单的 demo 我们学习下 java 中成员变量和局部变量的相关知识点。
Demo
说说下面代码执行打印的结果是什么?
public class MainClass {
public static void main(String[] args) {
// 局部变量 m1
Variable m1 = new Variable();
// 局部变量 m2
Variable m2 = new Variable();
m1.add(10);
m1.add(20);
m2.add(30);
System.out.println(m1.i + "," + m1.j + "," + m1.s);
System.out.println(m2.i + "," + m2.j + "," + m2.s);
}
}
class Variable {
// 静态成员变量,类变量
static int s;
// 成员变量,实例变量
int i;
// 成员变量,实例变量
int j;
{
// 局部变量
int i = 1;
// 就近原则,这个 i 指向的是上面这个 i
i++;
j++;
s++;
}
// j 是局部变量
public void add(int j) {
// 就近原则,这个 j 指向的是局部变量 j,形参 j
j++;
i++;
s++;
}
}
结论
2,1,5
1,1,5
解析
我们结合 jvm 来解析下为什么会有这样的结果,jvm 的几块区域:堆、栈、方法区。
- 堆:存储对象
- 栈:方法执行栈
- 方法区:存储 class 文件信息
假设我们现在有一个 jvm 如下图:
我们一行一行的分析代码,代码的入口是 main 方法,
第一行代码
Variable m1 = new Variable();
main 方法开始,因为它是个方法,所以要压栈。
第一句话,创建一个 Variable
对象,存储在了堆区,同时加载了Variable.class 到方法区。
我们知道类的静态成员变量是存储在常量池(方法区),所以此时 s 也划分一个区域。具体看下图:
这里强调一点,jvm 不是一次性加载所有 class 到 jvm 的,是按需加载。可能你整个程序都没有用到某个 class,那么从头到尾就不会去加载它。
- main 方法压栈,每个方法都有自己的栈帧,栈帧中存储局部变量
- 执行第一行代码,new 了一个 Variable 对象
- 首先将 variable 的 class 加载到方法区,划出一块区域,然后里面有一个静态成员变量存储到常量池 s 初始化值为 0。【基本数据类型初始化值为0】s 也被称为类变量。
- 在堆中创建一个 variable 对象区域,里面有 2 个成员变量 i = 0,j = 0,这 2 个也叫实例变量。
- 调用对象的构造方法去实例化一个 variable 对象,此时会执行
Variable.<init>
方法,默然 init 方法是每次构造方法都会调用一次的,init 方法执行的代码包括:非静态代码块、非静态实例变量显式赋值代码、构造器方法。
因为 init 是一个方法,所以也要压栈【init 是 jvm 指令方法】
- init 方法压栈,有一个局部变量 i = 1,然后 i++,i 被修改为了 2
- 然后 j++,因为就近原则,找到了对象的 j,所以堆中的 j 被修改为了 1
- 然后 s++,找到了方法区的 s,改为 1
- 然后在栈中,创建局部变量 m1,存储的是堆中对象的地址。
实际第一行最后的执行结果:
我们可以在第一行后面打印下结果:
0,1,1
第二行代码
又实例化了一个 Variable 对象,大家尝试自己分析一下,自己画图,然后结果和我这里的对比看看。
第二个方法又调用了 Variable.<init>
方法,有一个压栈和出栈的过程,此时对象 m2 创建了,而且方法区的 s = 2。
图片有错误,蓝色框的应该是 m2
第三行代码
m1.add(10);
方法调用,压栈。
- add 方法压栈,局部变量j = 10,然后代码中 j++,j 被修改为 11
- i++,找到最近的就是对象的实例变量 i,被改为 1
- s++,找到方法区的 s = 3
- 然后 add 方法弹出 局部变量 j 消失
后面的代码
后面大家就都应该可以自行分析了吧。
变量名重名,this 的作用
上面的非静态代码块中有一个局部变量 i,和我们的实例变量 int i 重名了,因为就近原则,导致紧跟着的 i++中的 i 使用的是局部变量 i。那么我们怎么调用实例变量呢?使用 this
关键字
// 成员变量,实例变量
int i; <-------------
int j; |
{ |
int i = 1; |
this.i++; ---> //指向的是最上面的 i,如果不加 this,则指向代码块的 i
j++;
s++;
}
生命周期
局部变量
跟随方法,方法入栈,变量产生,方法结束,出栈,局部变量销毁
实例变量
随着实例,实例创建,实例变量被创建初始化,实例用完被回收,变量销毁。
类变量
随着类走,jvm 启动加载了 class 后,初始化类变量,jvm 关闭了,class 信息销毁,类变量销毁。