干了接近两年的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 17
| 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 4: aload_0 5: new ...... 54: invokevirtual 57: return
public static void main(java.lang.String[]); Code: 0: new 3: dup 4: invokespecial 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 26
| 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 6
| 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 26
| 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
可以看到在创建静态内部类时,会首先初始化外部类的静态域,也只会初始化外部类的静态域。然后按照静态域->非静态域->调动构造器的顺序初始化内部类。