本文讲下类在初始化加载时,静态代码块、构造代码块、构造函数以及有父类时的执行顺序。
1、无父类时
看个例子就一目了然了。
public class TempTest {
private static final Logger LOGGER = LoggerFactory.getLogger(TempTest.class);
static {
LOGGER.info("静态代码块1");
}
static {
LOGGER.info("静态代码块2");
}
{
LOGGER.info("构造代码块1");
}
{
LOGGER.info("构造代码块2");
}
TempTest() {
LOGGER.info("无参构造器");
}
TempTest(String text) {
LOGGER.info("有参构造器");
}
public static void main(String[] args) {
TempTest tempTest1 = new TempTest();
TempTest tempTest2 = new TempTest("有参");
}
}
输入结果如下:
[main] 静态代码块1
[main] 静态代码块2
[main] 构造代码块1
[main] 构造代码块2
[main] 无参构造器
[main] 构造代码块1
[main] 构造代码块2
[main] 有参构造器
可以看到执行顺序是静态代码块->构造代码块->构造器。静态代码块、构造代码块分别按照编写的顺序依次执行。日志是在第一个声明的,所以后面调用都是正常的,如果你把日志声明放在了后面,直接就会报错。
从日志也可以看到,静态代码块只在类初始化加载时执行一次,而构造代码块只要new了一个对象就会执行一次,不管你调用哪个构造器,都会执行。
反编译看下:
public class TempTest {
private static final Logger LOGGER = LoggerFactory.getLogger(TempTest.class);
TempTest() {
LOGGER.info("构造代码块1");
LOGGER.info("构造代码块2");
LOGGER.info("无参构造器");
}
TempTest(String text) {
LOGGER.info("构造代码块1");
LOGGER.info("构造代码块2");
LOGGER.info("有参构造器");
}
public static void main(String[] args) {
new TempTest();
new TempTest("有参");
}
static {
LOGGER.info("静态代码块1");
LOGGER.info("静态代码块2");
}
}
可以看到,编译之后会把静态代码块合并在一起,顺序是编写的先后顺序,而构造代码块被依次放在了各个构造器中,且位于你自己编写的代码前面。利用这些特性,一般静态块中可以进行一些配置、连接等的初始化,只要执行一次的那种。而构造代码块在每创建一个对象时都会执行一次,可以做创建对象的计数。
2、有父类时
看个例子:
public class BaseTest {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseTest.class);
static {
LOGGER.info("父类静态代码块1");
}
static {
LOGGER.info("父类静态代码块2");
}
{
LOGGER.info("父类构造代码块1");
}
{
LOGGER.info("父类构造代码块2");
}
BaseTest() {
LOGGER.info("父类无参构造器");
}
BaseTest(String text) {
LOGGER.info("父类有参构造器");
}
}
public class TempTest extends BaseTest{
private static final Logger LOGGER = LoggerFactory.getLogger(TempTest.class);
static {
LOGGER.info("子类静态代码块1");
}
static {
LOGGER.info("子类静态代码块2");
}
{
LOGGER.info("子类构造代码块1");
}
{
LOGGER.info("子类构造代码块2");
}
TempTest() {
super();
LOGGER.info("子类无参构造器");
}
TempTest(String text) {
super(text);
LOGGER.info("子类有参构造器");
}
public static void main(String[] args) {
TempTest tempTest1 = new TempTest();
TempTest tempTest2 = new TempTest("有参");
}
}
输出如下:
BaseTest][main] 父类静态代码块1
BaseTest][main] 父类静态代码块2
TempTest][main] 子类静态代码块1
TempTest][main] 子类静态代码块2
BaseTest][main] 父类构造代码块1
BaseTest][main] 父类构造代码块2
BaseTest][main] 父类无参构造器
TempTest][main] 子类构造代码块1
TempTest][main] 子类构造代码块2
TempTest][main] 子类无参构造器
BaseTest][main] 父类构造代码块1
BaseTest][main] 父类构造代码块2
BaseTest][main] 父类有参构造器
TempTest][main] 子类构造代码块1
TempTest][main] 子类构造代码块2
TempTest][main] 子类有参构造器
可以看到执行顺序为父类静态代码块->子类静态代码块->父类构造代码块->父类构造器->子类构造代码块->子类构造器。
从第一部分的反编译知道,构造代码块是被编译进各个构造器的,这就是为什么父类构造代码块执行之后执行的是父类构造器而不是子类构造代码块了。知道了构造代码块是被编译进各个构造器这一点,就不难理解了。