类变量 :使用static修饰的成员变量是类变量,属于该类自己 java
实例变量:没有使用static修饰的成员变量是实例变量,属于该类的实例 ide
因为同一个JVM内每一个类只对应一个Class对象,所以同一个JVM内的一个类的类变量只需一块内存空间。 函数
对于实例变量而言,该类每建立一次实例,就须要为实例变量分配一块内存空间,因此,程序中有几个实例,实例变量就须要几块内存空间。 this
咱们先看下下面三段代码: spa
1)由于两个实例变量都是在建立变量的时候才开始分配空间,此时num2尚未分配,因此前向引用就会出现编译错误。 code
1 int num = num2 + 3; //非法前向引用,会报错 2 int num2 = 2
2)由于两个类变量在JVM加载类的时候分配空间,此时num2尚未分配,因此前向引用就出现编译错误。 视频
1 static int num = num2 + 3; //非法前向引用,会报错 2 static int num2 = 2
3)由于类变量num2在JVM加载类的时候空间已经分配好,而num在建立实例的时候才分配空间,此时num2已经分配成功了,因此num前向引用成功。 对象
1 int num = num2 + 3; //正确使用 2 static int num2 = 2;
由上面三段代码块就能够验证得:类变量的初始化时机老是处于实例变量的初始化以前 继承
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(); } } }
运行结果:
(通常状况下是不用内部类来验证的,可是都是同样的啦,我偷懒下,因此使用了内部类,你们原谅哈)
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); } }
运行结果:
final变量在编译时就被肯定下来了,至关于一个直接量。
1)final修饰的实例变量赋值时机:
2)final修饰的类变量赋值时机: