初始化顺序:父类的静态变量-->父类的静态代码块-->子类的静态变量-->子类的静态代码快-->父类的非静态变量(父类的非静态代码块)-->父类的构造函数-->子类的非静态变量(子类的非静态代码块)-->子类的构造函数
public class Test {
public static void main(String[] args) {
Child c=new Child();
}
}
class Parent {
public static PrintMessage a=new PrintMessage("父类静态成员被初始化");
private PrintMessage b=new PrintMessage("父类非静态成员被初始化");
static{
System.out.println("父类的静态代码块被执行");
}
{
System.out.println("父类的非静态代码块被执行");
}
public Parent(){
System.out.println("父类的构造方法被执行");
}
}
class Child extends Parent{
public static PrintMessage a1=new PrintMessage("子类静态成员被初始化");
private PrintMessage b1=new PrintMessage("子类非静态成员被初始化");
static {
System.out.println("子类的静态代码块被执行");
}
{
System.out.println("子类的非静态代码块被执行");
}
public Child(){
System.out.println("子类的构造函数被执行");
}
}
class PrintMessage{
public PrintMessage(String mes){
System.out.println(mes);
}
}
986140-20170728164542149-1587220315.png
- 如果父类构造器调用了被子类重写的方法,且通过子类构造函数创建子类对象,调用了这个父类构造器(无论显示还是隐式),就会导致父类在构造时实际上调用的是子类覆盖的方法
public abstract class Father {
public Father() {
display();
}
public void display() {
System.out.println("Father's display");
}
}
public class Son extends Father {
public Son() {
}
public void display() {
System.out.println("Son's display");
}
public static void main(String[] args) {
new Son();
}
}
输出为:Son's display
优点:通过继承相同的父类,初始化子类时,父类会调用不同子类的不同复写方法,从而实现多态性。
缺点:如果在父类构造函数中调用被子类重写的方法,会导致子类重写的方法在子类构造器的所有代码之前执行,从而导致子类重写的方法访问不到子类实例变量的值,因为此时这些变量还没有被初始化。
成员变量的初始化过程:默认初始化-》显示初始化(就是赋值,是调用)-》在构造函数的特定初始化(如果有)
关于final
- final在jvm中
- 不加final关键字:
public class demo02 {
public static void main(String[] args) {
System.out.println(demo.test);
}
}
class demo{
public static String test="我是demo类的一个测试字符串";
static {
System.out.println("我是demo的静态代码块!!!");
}
}
1747974-20191229192652819-1589162209.png
- 加final关键字:
public class demo02 {
public static void main(String[] args) {
System.out.println(demo.test);
}
}
class demo{
public static final String test="我是demo类的一个测试字符串";
static {
System.out.println("我是demo的静态代码块!!!");
}
}
1747974-20191229192739854-330549726.png
- 可以看到这两个的运行结果的不同,加了final关键字的java程序并不会去主动加载demo这个class类。
- 分析结果:如果加入了常量关键字,也就是final关键字,JVM会把这个常量放到demo02这个类里面的常量池当中,因此并不会主动加载demo这个类(当然这里是因为采用的final赋值是直接赋值)
- final的四种初始化
public class Main {
private final static int i = 0;//声明时直接初始化
private final int j;//构造函数赋值(在构造代码块之后执行)
private final static int k;//静态代码块中赋值(先于构造代码块执行)
private final int m;//构造代码块中赋值(先于构造函数执行)
public Main(int j) { //正确
this.j = j;
}
public Main() {//正确
this.j = 1;
}
static {//静态代码块
k = 2;
}
{//构造代码块
m = 3;
}
public static void main(String[] args) {
}
}
class Person {
int age;
}
public class LearnHeap {
public static void main(String args[]){
int a=10;
Person person = new Person();
person.age =20;
change(a,person);
System.out.println("a="+ a+",and person.age = "+person.age);
}
static void change(int a1, Person person){
a1 = 11;
person.age= 21;
System.out.println("a1="+ a1+",and age1 = "+person.age);
}
- main()函数是程序入口,JVM先执行,首先将main方法压入栈中,在栈内存中开辟一个空间,存放int类型变量a,同时附值10。在堆中分配一片区域,用来存放和创建Person对象,这片内存区域会有属于自己的内存地址,假设是1001,然后给成员变量赋值,age=20。执行结束后,构造防范弾栈,Person创建完成,将Person的内存地址1001赋值给person(此处person小写,是引用变量类型)。
- JVM执行change()函数,在栈内存中又开辟一个新的空间,存放int类型变量a1和类型为Person的person。此时main空间与change空间并存,同时运行,互不影响。
- change()方法执行,将a1赋值为11,person对象的堆中age赋值为21。
- change()执行完毕,变量a1立即释放,空间消失。
网友评论