【1】几个概念
① 静态代码块
在java中使用static关键字声明的代码块。每个静态代码块只会执行一次。JVM在加载类时会执行静态代码块,静态代码块先于主方法执行。
static{ System.out.println("这是静态代码块"); }
注意: 静态代码块不能存在于任何方法体内。
② 构造代码块(实例初始化块):
直接在类中定义且没有加static关键字的代码块称为{}构造代码,在创建实例对象的时候先于构造函数被调用执行
。
class Test{ int id; String name; // JVM加载class时执行 static{ System.out.println("这是静态代码块"); } // 新建对象的时候执行,构造代码块会先于构造函数被调用时执行 { this.id= 5; this.name = "测试"; System.out.println("这是构造代码块"); } Test(int id){ this.id = id; } public String toString(){ return "name: "+this.name +" , "+"id: "+ this.id ; } }
③ 普通代码块
在方法或语句内出现的{}
就称为普通代码块。普通代码块和一般的语句执行顺序由他们在代码中出现的次序决定–“先出现先执行”。
public class GeneralCodeBlock01{ public static void main(String[] args){ //如下为普通代码块 { int x=3; System.out.println("1,普通代码块内的变量x="+x); } int x=1; System.out.println("主方法内的变量x="+x); { int y=7; System.out.println("2,普通代码块内的变量y="+y); } } }
属性、方法、构造方法和代码块都是类中的成员,在创建对象时,各成员的执行顺序如下:
- 父类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
- 子类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
- 父类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
- 执行父类构造方法;
- 子类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
- 执行子类构造方法。
【2】父类、子类之间代码块与构造方法
示例代码如下:
package com.web.test2; public class HelloA { static{ System.out.println("static A"); } {System.out.println("I'm A class");} public HelloA(){ System.out.println("HelloA"); } public HelloA(String s){ System.out.println(s+"HelloA"); } public static void main(String[] args) { new HelloB(); } } class HelloB extends HelloA{ public HelloB () { //只能调用一个父类的构造方法 // super(); super("parent"); System.out.println("HelloB"); } {System.out.println("I'm B class");} static{ System.out.println("static B"); } }
执行结果:
static A static B I'm A class parentHelloA I'm B class HelloB
总结如下:
① 代码块于构造方法之前执行,静态于非静态之前;
② 在类第一次被调用时,加载该类,静态代码块只执行一次;
③ 项目启动时,只加载需要加载的类(比如xml中显示配置的bean,或者web.xml中的listener等),并不会将所有的类都加载(这是很可怕的事情);
④ 静态代码块只能调用静态变量;静态方法只能调用静态变量;
⑤ 非静态代码块或非静态方法可以调用任何(静态+非静态)变量。
⑥ 非静态代码块在实例化对象时,于构造方法之前执行。
【3】父类、子类与super()
示例代码如下:
public class People { String name; public People() { System.out.println(1); } public People(String name) { System.out.println(2); this.name = name; } } class Child extends People{ People father; public Child () { //super()系统会默认添加的 System.out.println(4); } public Child (String name) { //super()系统会默认添加的 System.out.println(3); this.name = name; father = new People(name+":F"); } public static void main(String[] args) { new Child("mike"); } }
故执行结果:132
【4】类中添加静态变量
静态变量与静态代码块一样,都是在类被加载的时候赋值/被执行,而且静态变量与静态代码块执行顺序是按照代码上次次序进行执行的。
示例代码如下:
public class ExA { //静态成员 private static ExA a = new ExA(); //静态代码块 static { System.out.println("父类--静态代码块"); } //构造方法 public ExA() { System.out.println("父类--构造函数"); } // 实例化代码块 { System.out.println("父类--非静态代码块"); } public static void main(String[] args) { new ExB(); } } class ExB extends ExA { private static ExB b = new ExB(); static { System.out.println("子类--静态代码块"); } { System.out.println("子类--非静态代码块"); } public ExB() { System.out.println("子类--构造函数"); } }
- result as follows :
// 父类静态成员 父类--非静态代码块 父类--构造函数 //父类静态代码块 父类--静态代码块 父类--非静态代码块 父类--构造函数 子类--非静态代码块 子类--构造函数 子类--静态代码块 父类--非静态代码块 父类--构造函数 子类--非静态代码块 子类--构造函数
分析如下:
① 首先加载父类静态成员和静态代码块
private static ExA a = new ExA(); static { System.out.println("父类--静态代码块"); }
这里静态成员赋值为实例化A对象,故而需要先执行A的实例化代码块和构造方法:
public ExA() { System.out.println("父类--构造函数"); } // 实例化代码块 { System.out.println("父类--非静态代码块"); }
此时输入结果为:
// 父类静态成员 父类--非静态代码块 父类--构造函数 //父类静态代码块 父类--静态代码块
② 其次加载子类静态成员和静态代码块
private static ExB b = new ExB(); static { System.out.println("子类--静态代码块"); }
子类静态成员赋值为实例化B对象,按照博文最上面子类实例化对象过程可知,此时该加载父类实例化块和构造方法,此时结果更新为:
// 父类静态成员 父类--非静态代码块 父类--构造函数 //父类静态代码块 父类--静态代码块 父类--非静态代码块 父类--构造函数
静态变量赋值还没有执行完!!该执行子类实例化代码块和构造方法,此时结果更新为:
# 1. 父类静态成员赋值和静态代码块 // 父类静态成员 父类--非静态代码块 父类--构造函数 //父类静态代码块 父类--静态代码块 # 2. 子类静态成员赋值和静态代码块 // 每次调用父类构造方法前都会调用父类实例化代码块 父类--非静态代码块 父类--构造函数这里写代码片 子类--非静态代码块 子类--构造函数
子类B静态变量赋值完,该执行子类静态代码块,此时结果更新如下:
# 1. 父类静态成员赋值和静态代码块 // 父类静态成员 父类--非静态代码块 父类--构造函数 //父类静态代码块 父类--静态代码块 # 2. 子类静态成员赋值和静态代码块 // 每次调用父类构造方法前都会调用父类实例化代码块 父类--非静态代码块 父类--构造函数这里写代码片 子类--非静态代码块 子类--构造函数 # 3. 子类静态代码块 子类--静态代码块
③ 静态完了,该非静态了
按照如下步骤,该执行第3/4/5/6:
父类静态成员和静态初始化块,按在代码中出现的顺序依次执行; 子类静态成员和静态初始化块,按在代码中出现的顺序依次执行; 父类实例成员和实例初始化块,按在diam中出现的顺序依次执行; 执行父类构造方法; 子类实例成员和实例初始化块,按在代码中出现的顺序依次执行; 执行子类构造方法。
无需多考虑,结果更新如下:
# 1. 父类静态成员赋值和静态代码块 // 父类静态成员 父类--非静态代码块 父类--构造函数 //父类静态代码块 父类--静态代码块 # 2. 子类静态成员赋值和静态代码块 // 每次调用父类构造方法前都会调用父类实例化代码块 父类--非静态代码块 父类--构造函数这里写代码片 子类--非静态代码块 子类--构造函数 # 3. 子类静态代码块 子类--静态代码块 # 4. 父类非静态 父类--非静态代码块 父类--构造函数 # 5. 子类非静态 子类--非静态代码块 子类--构造函数
确实比较难理解,需要特别注意父类和子类静态变量赋值的时候取值为实例化对象,非常量或者null值!!
【5】不仅仅父子,还有祖孙
示例如下:
package com.web.test2; public class Creature { public Creature(){ System.out.println("空的"); } public static void main(String[] args) { new wolf(); } } class Animal extends Creature{ public Animal(String name) { super(); System.out.println("一个参数"+name); } public Animal(String name,int age){ //super();错误 this(name); System.out.println("这个动物带了两个属性"+age); } } class wolf extends Animal{ public wolf(){ super("灰太狼",3); System.out.println("狼带了三个属性"); } }
执行结果如下:
空的 一个参数灰太狼 这个动物带了两个属性3 狼带了三个属性
默认会调用父类无参构造方法,如果父类还有parent,则继续向上搜寻,直到Creature。
this()与super()
在一个方法中只能存在一个。
【6】如果父类没有空参构造器呢?
父类如下所示:
public class Parent { private Integer id; private Integer age; public Parent(Integer id,Integer age){ this.id=id; this.age=age; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } }
子类代码如下所示:
public class Child extends Parent { private Integer id; private String name; @Override public void setId(Integer id) { this.id = id; } public void setName(String name) { this.name = name; } @Override public Integer getId() { return id; } public String getName() { return name; } }
此时,直接报编译错误,提示如下:
There is no default constructor available in 'com.jane.model.Parent'
即,父类没有默认构造器,你需要重写父类构造器。修改代码如下:
public class Child extends Parent { private Integer id; private String name; public Child(Integer id, Integer age) { super(id, age); } public Child(Integer id, Integer age, Integer id1, String name) { super(id, age); this.id = id1; this.name = name; } @Override public void setId(Integer id) { this.id = id; } public void setName(String name) { this.name = name; } @Override public Integer getId() { return id; } public String getName() { return name; } }
此时子类同样不能存在空参构造器!!
【7】子类对象实例化是否会实例化父类对象
答案当然是否定的!!
首先举反例证明
如果父类是个抽象类呢?实例化子类的时候实例化父类,很显然矛盾!故而,实例化子类的时候不会实例化父类对象!!
其次相关理论
① 构造器只是负责对java对象实例变量执行初始化(也就是赋初始值),在执行构造器代码之前,该对象所占的内存已经被分配下来,这些内存里值都默认是空值——对于基本类型的变量,默认的空值就是0或false,对于引用类型的变量默认的空值就是null。
② 在处理java类中的成员变量时,并不是采用运行时绑定,而是一般意义上的静态绑定,即成员变量被关联到了类上。必须明确,运行时(动态)绑定针对的范畴只是对象的方法。(方法的话只有static和final(所有private默认是final的)是静态绑定的.)
如果虚拟机在子类对象中找不到某个属性的时候,就到包含该构造方法中的类去找静态绑定相应的属性。
③ 当创建任何java对象时,程序总会先一次调用每个类父类非静态初始化块、父类构造器(总是从Object开始)执行初始化,最后才调用本类的非静态初始化块、构造器执行初始化。
具体请参考博文上面讲述过程。
此时当程序执行到①时,系统会先为父类中的私有属性在堆内存开辟空间
注意,这里并不会实例化父类对象,仅仅是为父类中的属性在堆中开辟了一段内存空间。
然后再为子类在堆内存空间,此时调用父类的构造方法(不写super()的话会隐式的调用),此时通过准备知识1中我们已经知道构造方法仅仅只是负责对java对象实例变量执行初始化,而不会实例化父类。
参考博文:JVM创建对象的奥秘
【8】子类继承父类什么?
常见的一句话,子类对象只能继承父类非私有的属性及方法;还有另外一句话–子类继承父类,子类拥有父类所有的属性和方法。
是否矛盾?该怎样理解?先看看第【7】部分,然后继续往下。
使用程序去验证,发现父类的私有属性和私有方法,子类是不能访问的,当然一些父类的私有属性可能可以通过相应的方法访问到,但是私有的方法似乎不能简单的访问,这里暂不考虑Java反射机制。
子类不能通过object.field的方式获取父类私有属性,这说明不属于子类成员变量!但是子类可以使用父类的public方法为父类的私有属性赋值并获取该私有属性值!这可以理解为子类拥有父类私有属性。
如下图所示,Child继承自父类Parent: