java基础回顾——浅谈java初始化

      干了接近两年的ETL和数据库运维,开发相关的东西已经忘了很多了,学了的东西还是不能丢。java基础回顾系列作为对以前学习知识的重温、记录以及分享。
      作为科班出生的,第一门编程语言就是C,曾经产生了很多由于未正确初始化导致的错误。java尽力保证所有变量在使用之前都会得到恰当的初始化(对于方法的局部变量,Java会以编译时错误的形式来提醒程序员进行初始化),但是并不意味着我们可以忽略初始化这个问题。

局部变量与类变量

      对于局部变量,如果未进行初始化就对其进行操作,会以编译时错误来提示你该变量未初始化。
      对于类的成员变量则有所不同,对于基本类型,java会给其一个默认值,对于引用类型会赋予一个null。

类型 默认值
boolean false
char ‘\u0000’
byte (byte)0
short (short)0
int 0
long 0l
float 0.0f
double 0.0d

char的默认值为unicode中的一个空字符。

初始化顺序

      在类的内部,初始化的顺序会按照变量定义的先后顺序进行初始化,之后才是构造器的调用。例如:

1
2
3
4
5
public class InitOrder {
InitOrder(int order){
System.out.println("class var "+order);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OrderOfInit {
InitOrder io1 = new InitOrder(1);
OrderOfInit(){
System.out.println("OrderOfInit constructor");
io2 = new InitOrder(21);
System.out.println("construction is done!");
}
public static void main(String[] args) {
new OrderOfInit();
}
InitOrder io2 = new InitOrder(2);
}

OUTPUT:
class var 1
class var 2
OrderOfInit constructor
class var 21
construction is done!

      可以看到我们虽然将类变量的定义与构造器顺序打乱,但是,构造器的调用都在类变量的初始化完成之后再执行的。还注意到io的定义是在构造器定义之后的,却编译通过了。我们可以使用javac -p OrderOfInit.class查看其字节码,可以帮助我们更好的理解其中过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Compiled from "OrderOfInit.java"
public class initorder.OrderOfInit {
initorder.InitOrder io1;
initorder.InitOrder io2;
initorder.OrderOfInit();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class initorder/InitOrder
......
54: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
57: return
public static void main(java.lang.String[]);
Code:
0: new #10 // class initorder/OrderOfInit
3: dup
4: invokespecial #11 // Method "<init>":()V
7: pop
8: return
}

实例初始化

      java中也有称为实例初始化的语法,用于初始化每一个对象的非静态变量。其初始化顺序与类变量定义处于同级,在构造器调用之前执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class OrderOfInit {
InitOrder io1 = new InitOrder(1);
// 实例初始化块
{
InitOrder ioBlock = new InitOrder(3);
System.out.println("block init");
}
OrderOfInit(){
System.out.println("OrderOfInit constructor");
io2 = new InitOrder(21);
System.out.println("construction is done!");
}
public static void main(String[] args) {
new OrderOfInit();
}
InitOrder io2 = new InitOrder(2);
}

OUTPUT:
class var 1
class var 3
block init
class var 2
OrderOfInit constructor
class var 21
construction is done!

静态成员初始化与显示静态初始化

      静态数据与类相关与对象无关,无论创建多少对象都只占用一份存储区域。首先看看以下代码的初始化顺序。

1
2
3
4
5
6
public class StaticInit {
public InitOrder ios4 = new InitOrder(4);
public static InitOrder ios5 = new InitOrder(5);
}

1
2
3
public class StaticInit2 {
public static InitOrder ios6 = new InitOrder(6);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class OrderOfInit {
public InitOrder io1 = new InitOrder(1);
public static InitOrder io2 = new InitOrder(2);
public static InitOrder io3;
static{
io3 = new InitOrder(3);
}
public InitOrder io6;
OrderOfInit(){
new StaticInit();
new StaticInit();
io6 = StaticInit2.ios6;
}
public static void main(String[] args) {
new OrderOfInit();
}
}

OUTPUT:
class var 2
class var 3
class var 1
class var 5
class var 4
class var 4
class var 6

      静态成员的初始化只有在必要的时刻才会进行,从代码中可以看到只有在创建StaticInit对象或者通过StaticInit2.ios6调用静态成员才会使其执行初始化动作。OrderOfInit()构造器中创建了两次StaticInit的对象,可以从结果看到其中的静态成员只初始化了一次。
      初始化的顺序是先初始化静态对象,而后是非静态对象。

继承关系中的初始化顺序

      在继承体系中,对象的构建过程是从基类向下扩散的,基类会在导出类构造器访问他之前就会完成初始化。

1
2
3
4
5
public class Car {
public Car() {
System.out.println("car constructor");
}
}

1
2
3
4
5
6
7
8
9
public class SUV extends Car {
private Window win = new Window("suv");
private static Engine engine = new Engine("SUV");
public SUV() {
System.out.println("suv constructor");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class RangeRover extends SUV{
private Window win = new Window("RangeRover");
private static Engine engine = new Engine("RangeRover");
public RangeRover() {
System.out.println("Range Rover");
}
public static void main(String[] args) {
new RangeRover();
}
}
1
2
3
4
5
public class Engine {
public Engine(String ids) {
System.out.println(ids + " Init engine");
}
}
1
2
3
4
5
public class Window {
public Window(String ids) {
System.out.println(ids + " create windows");
}
}

OUTPUT:
SUV Init engine
RangeRover Init engine
car constructor
suv create windows
suv constructor
RangeRover create windows
Range Rover

      在继承关系中,首先基于类的继承层次一次初始化静态域。然后,根据类的继承层次向外扩散,一次初始化类中的非静态域以及调用构造器完成对象的初始化。默认子类会调用父类的无参构造器,如果父类定义了带参数的构造器,则需要在子类中显示调用父类的构造器。

初始化遇到内部类

      内部类是定义在一个类内部的类,一方面内部类可以作为一种代码的组织和隐藏机制,另一方面使得java中的多重继承机制更加完善。
      内部类在事件驱动类编程中应用比较广泛。

非静态内部类

      非静态内部类中不能包含静态域或者方法。非静态内部类的创建需要通过一个外部类的引用来创建,即首先需要获取一个外部类的对象,才能创建内部类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class OuterClassInitOrder {
private InitOrder io1 = new InitOrder(1);
private static InitOrder io2 = new InitOrder(2);
public OuterClassInitOrder() {
System.out.println("Outer Class constructor");
}
private class InnerClass{
private InitOrder ioi1 = new InitOrder(11);
public InnerClass() {
System.out.println("Inner Class constructor");
}
}
public InnerClass f(){
return new InnerClass();
}
public static void main(String[] args) {
OuterClassInitOrder oci = new OuterClassInitOrder();
oci.f();
}
}

OUTPUT:
class var 2
class var 1
Outer Class constructor
class var 11
Inner Class constructor

静态内部类

      静态内部类即定义为static的内部类,其中可以包含静态或非静态的成员或方法。创建静态内部类不需要对外部类对象的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class OuterClassInitOrder {
private InitOrder io1 = new InitOrder(1);
private static InitOrder io2 = new InitOrder(2);
public OuterClassInitOrder() {
System.out.println("Outer Class constructor");
}
public static class InnerClass{
private InitOrder ioi1 = new InitOrder(11);
private static InitOrder ioi2 = new InitOrder(22);
public InnerClass() {
System.out.println("Inner Class constructor");
}
}
public InnerClass f(){
return new InnerClass();
}
public static void main(String[] args) {
new OuterClassInitOrder.InnerClass();
}
}

OUTPUT
class var 2
class var 22
class var 11
Inner Class constructor

      可以看到在创建静态内部类时,会首先初始化外部类的静态域,也只会初始化外部类的静态域。然后按照静态域->非静态域->调动构造器的顺序初始化内部类。