概念
变量就是可以被改变的量,在运行的时候才能知道它的值;常量是被final修饰的不可改变的量,常量命名必须大写(java命名规范),在编译的时候就可以确定它的值。
为了方便知道他们在内存中的位置,首先看看JVM的内存模型:
变量放在哪与它是否被final修饰无关,只与它的位置或是否被static修饰有关。
在JVM中存储的位置
贴一个简单的代码:
class Demo {
private static int w = 9;
private int z = 0;
}
public class Test {
private static int x = 100;
private static Demo demo1 = new Demo();
private int y = 100;
private Demo demo2 = new Demo();
public static void main(String[] args) {
int a = 10;
String s = "abc";
Test test = new Test();
test.fun(s);
}
public void fun(String s) {
s = "lzq";
}
}
将这个类加载到内存中的时候:
然后,当main方法执行之后的时候:
总结:
- static修饰成员变量的放在方法区,类加载的时候就已经初始化好了,并且只初始化一次(因为同一个类虚拟机只会加载一次);
- 非static修饰的成员变量放在对象中,创建对象的时候初始化;
- 方法里面的局部变量放在栈中,属于线程私有;
匿名内部类访问的局部变量为什么需要加final
匿名内部类就是没有名字的局部内部类,还有注意匿名对象和匿名内部类不是一个概念,匿名对象指的是一个没有名字的对象,就是我们没有指定指针指向创建的这个对象,而匿名内部类是一个没有名字的类,一个是对象一个是类;
回归正题啊,我们知道,在一个方法里面传递一个参数给外面另一个类的构造或者是另一个方法,我们传递的都是当前这个方法的参数对应的一个副本,这样做的目的我觉得是为了多线程(就像我们用铁锹挖一座小山,这个小山就是一个对象,铁锹就是指针,传递副本的话,就是每来一个人(新的线程),给它一把新的铁锹(副本就是你们是可以同时拿不同的铁锹进行操作的,互不干扰),他也挖,你自己也挖,然后这个小山很快就被铲平了,但如果传的是实参的话,那就是每来一个人(新的线程),就把自己的铁锹给他,然后自己就没有铁锹了,你就只好歇着了(实参就是实际参数,不能同时拿的,同时拿的话必定有副本),这样就串行化了,还不如你自己一个人挖,因为切换线程也是需要开销的);
但是如果把一个方法里面实参传给它里面的方法(就是匿名内部类里面的方法)那人家拿的可是实参了,当然,不用传的,方法里面的内部类会将当前方法里面的局部变量看作自己的成员变量,既然是在方法中创建的内部类,必然会在某些业务逻辑中出现访问这个方法的局部变量的需求;
这样就会存在几个问题:
- 如果不用final修饰,则原先的局部变量可以发生变化。这里到了问题的核心了,如果局部变量发生变化后,匿名内部类是不知道的(因为他只是拷贝了局部变量的值,并不是直接使用的局部变量)。这里举个例子:原先局部变量指向的是对象A,在创建匿名内部类后,匿名内部类中的成员变量也指向A对象。但过了一段时间局部变量的值指向另外一个B对象,但此时匿名内部类中还是指向原先的A对象。那么程序再接着运行下去,可能就会导致程序运行的结果与预期不同;
所以啊,匿名内部类访问的局部变量需要final修饰的,这样是为了保证数据的一致性;
在JDK8中如果我们在匿名内部类中需要访问局部变量,那么这个局部变量不需要用final修饰符修饰。看似是一种编译机制的改变,实际上就是一个语法糖(底层还是帮你加了final)。但通过反编译没有看到底层为我们加上final,但我们无法改变这个局部变量的引用值,如果改变就会编译报错;