1.实例变量和类变量的内存分配
类变量 :使用static修饰的成员变量是类变量,属于该类本身
实例变量:没有使用static修饰的成员变量是实例变量,属于该类的实例
由于同一个JVM内每个累只对应一个Class对象,因此同一个JVM内的一个类的类变量只需一块内存空间。
对于实例变量而言,该类没创建一次实例,就需要为实例变量分配一块内存空间,所以,程序中有几个实例,实例变量就需要几块内存空间。
2.类变量的初始化时机总是出于实例变量的初始化之前
我们先看下下面三段代码:
1)因为两个实例变量都是在创建变量的时候才开始分配空间,此时num2还没有分配,所以前向引用就会出现编译错误。
- int num = num2 + 3; //非法前向引用,会报错
- int num2 = 2
2)因为两个类变量在JVM加载类的时候分配空间,此时num2还没有分配,所以前向引用就出现变异错误。
- static int num = num2 + 3; //非法前向引用,会报错
- tatic int num2 = 2
3)因为类变量num2在JVM加载类的时候空间已经分配好,而num在创建实例的时候踩分配空间,此时num2已经分配成功了,所以num前向引用成功。
- int num = num2 + 3; //正确使用
- static int num2 = 2;
由上面三段代码块就可以验证得:类变量的初始化时机总是出于实例变量的初始化之前
3.Java对象的初始化方式及其执行顺序
Java对象的初始化方式有三种:1)构造器 2)初始化块 3)定义变量时指定初始化值
如果这三种初始化方式同时出现,也要注意,他们也有一个执行顺序的规定:
1)静态初始化块只在类第一次创建对象的时候运行一次,后面就不会再运行,而类在每次创建对象时,非静态初始化块总是会运行一次。
- public class Test{
- static {
- System.out.println("执行---静态初始化代码块.");
- }
- {
- System.out.println("执行---非静态初始化代码块.");
- }
- public static void main(String[] args) {
- for (int i = 1; i <= 2; i++) {
- System.out.println("创建第 " + i + " 个对象");
- new Test();
- System.out.println();
- }
- }
- }
运行结果:
2)构造器每次创建对象时,构造器必然有执行的机会,此时,非静态初始化块必定也将获得机会并且运行在构造器之前
- public class Test{
- {
- System.out.println("执行---非静态初始化代码块.");
- }
- public Test() {
- System.out.println("执行---构造器.");
- }
- public static void main(String[] args) {
- for (int i = 1; i <= 2; i++) {
- System.out.println("创建第 " + i + " 个对象");
- new Test();
- System.out.println();
- }
- }
- }
运行结果:
3)定义变量时指定的初始化值和初始化块中指定的初始值的执行顺序与他们在源程序中的排列顺序相同。
验证代码一:
- public class Test{
- String i = "定义变量时指定的初始化值";
- {
- i = "初始化块中指定的初始值";
- }
- public static void main(String[] args) {
- for (int i = 1; i <= 2; i++) {
- System.out.println("创建第 " + i + " 个对象");
- System.out.println(new Test().i);
- System.out.println();
- }
- }
- }
运行结果
验证代码二 :
- public class Test{
- {
- i = "初始化块中指定的初始值";
- }
- String i = "定义变量时指定的初始化值";
- public static void main(String[] args) {
- for (int i = 1; i <= 2; i++) {
- System.out.println("创建第 " + i + " 个对象");
- System.out.println(new Test().i);
- System.out.println();
- }
- }
- }
运行结果:
4.关于父子实例的内存控制
(一般情况下是不用内部类来验证的,但是都是一样的啦,我偷懒下,所以使用了内部类,大家原谅哈)
1)当子类重写父类方法后,父类表面上只是调用属于自己的被子类重写的方法。
- public class Test{
- class Base {
- Base() {
- this.info();
- }
- public void info() {
- System.out.println("Base");
- }
- public void getInfo() {
- info();
- }
- }
- public class Child extends Base{
- @Override
- public void info() {
- System.out.println("Child");
- }
- }
- public static void main(String[] args) {
- Test test = new Test();
- Base base = test.new Child();
- base.info();
- base.getInfo();
- }
- }
运行结果:
2)上述是属于多态中方法的体现,但是方法有多态,实例变量无多态。
解释下“方法有多态,变量无多态”这句话:意思是,不管怎样,父类表面上只是调用属于自己的被子类重写的方法。而变量不一样,假设父类和子类都有同一个变量名的实例变量,向上转型后,通过父类访问的实例变量得到的值是自身的而非子类的。向下转型后,通过子类访问的实例变量得到的值是自身的而非父类的。
很多书上或教学视频上都讲,创建一个子类对象的时候,Java 会顺着继承结构往上一直找到 Object,然后从 Object 开始往下依次执行构造函数。先执行父类的构造函数,然后在其子类中会创建一个成员变量指向他的父类。其实这个说法是错误的,系统并不会真正的去创建父类对象,只是在子类对象中不仅保存了本身的实例变量,还有它父类的全部实例变量。
- public class Test{
- class Base { //父类
- int i = 2;
- }
- public class Child extends Base{ //子类
- int i = 20;
- }
- public static void main(String[] args) {
- Test test = new Test();
- Child child = test.new Child();
- Base base = child;
- System.out.println(" Base.i : " + base.i);
- System.out.println("Child.i : " + child.i);
- }
- }
运行结果:
5.final修饰符
final变来那个在编译时就被确定下来了,相当于一个直接量。
1)final修饰的实例变量赋值时机:
定义final实例变量时 指定初始值
在非静态初始化模块中为final实例变量指定的初始值
在构造器中为final实例变量指定初始值
2)final修饰的类变量赋值时机:
定义final类变量时指定初始值
在静态初始化模块中为final实例变量指定的初始值