当我们拿到一个有超类、有子类、包括有主程序的入口类的程序去分析它运行步骤时:
1.程序应当从入口类类(含有public的类,即就是1个.java文件中只能包含一个public类,它就是入口类)开始;
2.你一定要明确的是在入口类中含有的这个静态主函数public static void main(String args){}仅仅是一个普通的静态函数(类函数);
3.在入口类中比静态主函数public static void main(String args){}的运行优先级高的有:
3.1 入口类的静态变量(第一)static
3.2 入口类的静态代码块(第二)static {}
4.在入口类中可以定义的内容如下:
4.1 入口类实例变量 修饰符 数据类型 变量名;
4.2 入口类静态变量 修饰符 static 数据类型 变量名;
4.3 入口类静态方法 static 返回值 方法名() {}
4.4 入口类静态代码块 static {}
4.5 入口类初始化代码块 {}
4.6 入口类构造函数 修饰符 类名(){}
4.7 入口类实例方法 修饰符 返回值 方法名(){}
4.8 public static void main(String args)
{ 1. 入口类的对象创建 入口类类名 引用名 = new 入口类类名;
2.并列的超类 超类类名 引用名 = new 超类类名;
3.并列的子类 子类类名 引用名 = new 子类类名;
4.并列的超类引用子类的对象创建 并列超类类名 引用名 = new 并列子类类名;
并列超类类名 引用名
解释: 可以被当作超类的引用变量(类的变量可以分为:基本数据类型变量和引用类型的变量)
new 并列子类类名
解释: 创建1个子类对象
并列超类类名 引用名 = new 并列子类类名;
解释: 该语句的执行步骤是:
1.开辟子类对象的数据空间;
2.将超类的引用变量分配一个保存引用的空间;
3.将开辟子类对象的数据空间的首地址赋给引用变量;
注意:引用类型变量在声明后必须通过实例化开辟数据空间,才能对变量所指向的对象进行访问,即必须初始化这个对象之后才能将引用指向它
该语句的过程类似于基本数据类型的转换:这些类型由"小"到"大"分别为 (byte,short,char)--int--long--float—double(这里我们所说 的"大"与"小",并不是指占用字节的多少,而是指表示值的范围的大小)。
将"小"数据转换为"大"数据时 :
byte b;int i=b; long l=b; float f=b; double d=b;
将"大"数据转换为"小"数据时:
你可以使用强制类型转换。即你必须采用下面这种语句格式: int n=(int)3.14159/2;可以想象,这种转换 肯定可能会导致溢出或精度的下降。
相反道理: 将子类对象转化为超类:(将"大"数据转换为"小"数据时,主动删除部分特性)
并列超类类名 引用名 = new 并列子类类名;
将超类对象强制转化为子类:( 将"小"数据转换为"大"数据时需要调用强制转换,主动补充部分特性)
注意:(1)如果父类对象与引用指向的实际是一个子类对象,那么这个父类对象的引用可以用强制类型转换转化成子类对象的引用。
(2)类型转换必须在继承层次类转换,即超类与子类之间.
(3)兄弟类之间不存在继承,因此也不存在类型转换.
(4)决不能在不相关的任何类之间执行类的赋值或者类型转换。
强制转化语句方法:
1. Child a = new Child();
2. Parent b = a;
3. Child c = (Child) b;
.......;
.......;
}
4.9 public static void main(String args)
{
使用new关键字实例化对象时。
调用某个类的静态方法时。
读取或设置类的静态字段时(被final修饰、已在编译期把结果放入常量池的除外)。
使用java.lang.reflect包的方法对类进行反射调用。
初始化某个类的子类时。
虚拟机启动时被标明为启动类(包含main方法的类)。
}
4.10 在以上4.8和4.9 的描述中列举出在主函数public static void main(String args){}的{}中可能出现的并且可以作为入口类、超类、子类的初始化契机的触发语句。
需要注意的是:
(1)入口类初始化契机:(入口类对象new创建、或者访问入口类的静态方法、访问类的静态变量):
入口类类名 引用名 = new 入口类类名;
入口类类名.方法;
入口类类名.变量;
入口类首次初始化只和自己本类相关。
(2)超类初始化契机:(超类对象new创建、或者访问超类的静态方法、访问类的静态变量):
超类类名 引用名 = new 超类类名;
超类类名.方法;
超类类名.变量;
超类首次初始化只和自己本类相关。
(3)子类初始化契机:(子类对象new创建、或者访问子类的静态方法、访问类的静态变量):
子类类名 引用名 = new 子类类名;
子类类名.方法;
子类类名.变量;
子类首次初始化不仅和自己本类相关,还与超类初始化相关。
(4)超类引用子类对象
超类类名 引用名 = new 子类类名;
子类首次初始化不仅和自己本类相关,还与超类初始化相关。
(5)引用了子类对象的超类强制转换为子类对象;
1. Child a = new Child();
2. Parent b = a;
3. Child c = (Child) b;
依据语句的顺序,完成如下步骤
1.创建1个子类对象
2.用超类引用该子类对象
3.另外1个子类引用将该超类引用强制的转换。
因此, 首次初始化不仅和自己本类相关,还与超类初始化相关。
现在以第(3)、(4)、(5)种为例 :三种的初始化步骤一致。
如下代码证明:
第(4)种代码如下:
1 class BaseTest 2 { 3 // 父类变量 4 private String baseName = "base"; 5 // 父类静态变量 6 public static String staticField = "父类静态变量"; 7 // 父类静态方法 8 public static void Order() 9 { 10 System.out.println("父类静态方法-"); 11 System.out.println("staticField:" + staticField); 12 } 13 // 父类静态初始代码块 14 static 15 { 16 System.out.println("父类静态初始化代码块-"); 17 System.out.println("staticField:" + staticField); 18 } 19 // 初始化代码块 20 { 21 System.out.println("父类非静态初始化代码块-"); 22 System.out.println("baseName:" + baseName); 23 } 24 // 构造函数 25 public BaseTest() 26 { 27 System.out.println("父类构造方法"); 28 callName(); 29 } 30 // 成员方法 31 public void callName() 32 { 33 System.out.println("父类callName方法-"); 34 System.out.println("baseName:" + baseName); 35 } 36 } 37 38 // 子类 39 class Sub extends BaseTest 40 { 41 // 子类变量 42 private String baseName = "sub"; 43 // 子类 静态变量 44 public static String staticField = "子类静态变量"; 45 46 // 子类静态方法 47 public static void Order() 48 { 49 System.out.println("子类静态方法-"); 50 System.out.println("staticField:" + staticField); 51 } 52 53 // 子类静态初始化代码块 54 static 55 { 56 System.out.println("子类静态初始化代码块-"); 57 System.out.println("staticField:" + staticField); 58 } 59 // 子类非静态初始化代码块 60 { 61 System.out.println("子类非静态初始化代码块-"); 62 System.out.println("baseName:" + baseName); 63 } 64 65 public Sub() 66 { 67 System.out.println("子类构造方法"); 68 callName(); 69 } 70 71 // 成员方法 72 public void callName() 73 { 74 System.out.println("子类重写父类callName方法-"); 75 System.out.println("baseName:" + baseName); 76 } 77 } 78 public class zhuChenXu 79 { 80 81 // 主程序类变量 82 private String zhuChenXuName = "zhuChenXu"; 83 // 主程序类静态变量 84 private static String zhuChenXuField = "主程序类静态变量"; 85 // 主程序类静态方法 86 public static void Order() 87 { 88 System.out.println("主程序类静态方法-"); 89 System.out.println("zhuChenXuField :" + zhuChenXuField); 90 } 91 // 主程序类静态初始代码块 92 static 93 { 94 System.out.println("主程序类静态初始化代码块-"); 95 System.out.println("zhuChenXuField:" + zhuChenXuField); 96 } 97 // 初始化代码块 98 { 99 System.out.println("主程序类非静态初始化代码块-"); 100 System.out.println("zhuChenXuName:" + zhuChenXuName); 101 } 102 // 构造函数 103 public zhuChenXu() 104 { 105 System.out.println("主程序类构造方法"); 106 callName(); 107 } 108 // 成员方法 109 public void callName() 110 { 111 System.out.println("主程序类callName方法-"); 112 System.out.println("zhuChenXuName:" + zhuChenXuName); 113 } 114 115 116 117 public static void main(String[] args) 118 119 { 120 zhuChenXu s = new zhuChenXu(); 121 BaseTest b = new Sub(); 122 System.out.println("-----[[-------"); 123 Sub.Order(); 124 System.out.println(Sub.staticField); 125 System.out.println(BaseTest.staticField); 126 BaseTest.Order(); 127 System.out.println("------]]------"); 128 129 } 130 }
第(3)种代码变更如下:
其中可以将上述程序代码变更:
120行代码不变: zhuChenXu s = new zhuChenXu();
121行代码可以改为: Sub b = new Sub();
第(5)种代码变更如下:
120行代码不变: zhuChenXu s = new zhuChenXu();
121行代码可以改为: Sub b = new Sub();
122行增加: BaseTest a = b;
Sub c = (Sub) a;
第(3)种的前两行、
第(4)种的前两行、
第(5)种的前三行的
以上三种代码中的;执行的初始化操作步骤是一致的,如下图的运行结果显示的一致。
但是问题来了,第(5)种的第四行 Sub c = (Sub) a; 的语句出现并没有增加程序的初始化步骤,这就说明:强制类型转化而来的子类对象(Sub c)与被超类引用的子类(Sub b) 是共享内存空间的。
现在以第(3)、(4)、(5)种为例
运行结果一样均如下:
程序运行的步骤是:
1.主程序类静态变量
2.主程序类静态初始化代码块
主函数public static void main(String args){}
3.主程序类实例变量
4.主程序类非静态初始化代码块
5.主程序构造函数(其中调用主程序实例方法)
7.超类静态变量
8.超类静态初始化代码块
9.子类静态变量
10.子类静态初始化代码块
11.超类实例变量
12.超类非静态初始化代码块
13.超类构造函数(其中调用超类实例方法因为超类实例方法被子类覆盖,实际调用的是子类的实例方法,而此时子类实例方法中的实例变量还没有创建,所以子类实例变量显示为null)
15.子类的实例变量
16.子类非静态初始化代码块
17.子类构造函数(其中调用子类实例方法)
-----------[[--------------
19.在对象创建完成以后,依据主函数下的语句情况调用超类、子类、主程序类的静态方法(即类方法),在调用后才能购被创建初始化。
因为类方法是对该类的所有对象所共享的,它的初始化时机滞后。