Java中成员变量的初始化
- 类变量与成员变量
在java类的初始化中,一边只初始化一次,类的初始化主要是用以初始化类变量,即静态变量
在初始化的过程中存在着静态变量与静态块两部分,初始化的顺序为先加载静态变量,后加载静态块的内容,如下例:
public class Person{
public static String name="张三";
public static int age;
static{
age=20;
System.out.println("初始化age");
}
public static String address;
static{
address="北京市";
age=34;
}
public static void main(String[] args) {
System.out.println(name);
System.out.println(age);
System.out.println(address);
}
}
其初始化的顺序如下述代码所示:
public class Person{
public static String name;
public static int age;
public static String address;
static{
name="张三";
age=20;
System.out.println("初始化age");
address="北京市";
age=34;
}
public static void main(String[] args) {
System.out.println(name);
System.out.println(age);
System.out.println(address);
}
}
即先加载静态变量,后加载代码块的内容,对于类变量而言在声明处进行初始化与在静态代码块中进行初始化效果是一致的。所有的类变量都是先声明,后赋值的,并且其赋值的顺序与其声明顺序一致。
也就是说在代码顺序中即便静态代码块在前,类变量的声明在后,也是不会出现报错的,因为在加载的时候是先加载的类变量,后加载的静态代码块中的内容,同时在静态代码块中只能对之前声明的类变量进行赋值,而不可以使用之前声明的类变量,因为此时并没有声明,如果声明在前方可使用
也就是说最先加载的是类变量与静态代码块中的内容,首先会加载类变量的声明,之后再在静态代码块中按照代码顺序进行依次加载,
static{
a = 4;
System.out.println("lalal");
}
private static int a=6;
public static void main(String[] args) {
System.out.println(a);
}
会输出
lalala
6
这时的加载顺序如下:
static int a;
static{
a=4
System.out.println("lalal");
a=6
}
如下:
public class Person {
static{
a = 4;
//System.out.println(a);//这里会报出错误。
}
private static int a;
public static void main(String[] args){
System.out.println(a);
}
}
此时的输出结果为4
如果出现子类与父类都具有类变量的情况下
如下:
class Parent {
// 静态变量
public static String p_StaticField = "父类--静态变量";
// 变量
public String p_Field = "父类--变量";
protected int i = 9;
protected int j = 0;
// 静态初始化块
static {
System.out.println(p_StaticField);
System.out.println("父类--静态初始化块");
}
// 初始化块
{
System.out.println(p_Field);
System.out.println("父类--初始化块");
}
// 构造器
public Parent() {
System.out.println("父类--构造器");
System.out.println("i=" + i + ", j=" + j);
j = 20;
}
}
public class SubClass extends Parent {
// 静态变量
public static String s_StaticField = "子类--静态变量";
// 变量
public String s_Field = "子类--变量";
// 静态初始化块
static {
System.out.println(s_StaticField);
System.out.println("子类--静态初始化块");
}
// 初始化块
{
System.out.println(s_Field);
System.out.println("子类--初始化块");
}
// 构造器
public SubClass() {
System.out.println("子类--构造器");
System.out.println("i=" + i + ",j=" + j);
}
// 程序入口
public static void main(String[] args) {
System.out.println("子类main方法");
new SubClass();
}
}
在输出 “子类main方法”即在进入程序入口之前首先会输出
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
当加载完类之后,进入到类对象的构造阶段
对于对象的构造与类构造有相似之处,同时多出了构造函数这一部分
1 public class Person{
2 {
3 name="李四";
4 age=56;
5 System.out.println("初始化age");
6 address="上海";
7 }
8 public String name="张三";
9 public int age=29;
10 public String address="北京市";
11 public Person(){
12 name="赵六";
13 age=23;
14 address="上海市";
15 }
16 }
如上的代码初始化顺序如下所示:
1 public class Person{
2 public String name;
3 public int age;
4 public String address;
5 public Person(){
6 name="李四";
7 age=56;
8 System.out.println("初始化age");
9 address="上海";
10 name="张三";
11 age=29;
12 address="北京市";
13 name="赵六";
14 age=23;
15 address="上海市";
16 }
17 }
对于类中成员变量的初始化的过程相当于全部挪到了构造函数中,同时初始化的顺序与之前声明的顺序一致,对同一个变量,后者的值覆盖前者的值,所以最好name值等于赵六,age=23,address=上海
静态变量、静态初始化块,变量、初始化块初始化了顺序取决于它们在类中出现的先后顺序。
对比以下代码
public class Test1 {
{
a = 4;
}
private static int a;
public static void main(String[] args){
Test3 test3 = new Test3();//注意:这里开始new了一个对象
System.out.println(test3.a);
}
}
该代码的输出结果为4,因为调用的是test3这个对象的a成员变量
public class Test2 {
{
a = 4;
System.out.println(a);//这里不会报错,但是这条语句并不会执行
}
private static int a;
public static void main(String[] args){
System.out.println(a);
}
}
输出0,因为调用Test2该类的类成员变量 且该类成员变量无初始值,默认为0
public class Test3 {
static{
a = 4;
}
private static int a;
public static void main(String[] args){
System.out.println(a);
}
}
输出为4,通过静态代码块进行了初始化
final关键字的影响
一个类一旦被设定为final,则表明该类是无法被修改,也无法被继承的。
变量被定义为final,则表明该变量有且仅有一次被赋值的机会,同时该变量必须被初始化
public class Test1 {
{
a = 4;
}
private static final int a;
public static void main(String[] args){
System.out.println(a);
}
}
对于Test1来说会出现编译错误,因为在定义static final int a时没有附初始值
public class Test2 {
static{
a = 5;
}
private static final int a;
public static void main(String[] args){
System.out.println(a);
}
}
此时a值为5,因为对于类变量在声明时赋初值与在static代码块中赋初值是一样的效果
static{
a=4;
}
private static final int a=3;
public static void main(String[] args) {
System.out.println(a);
}
}
此时同样会出现编译错误,因为已经将声明的a值赋值为4,不可以更改其赋值为3(注意赋值的顺序)
最终在此基础上引用下述代码清晰展现初始化顺序
public class InitialOrderTest {
// 静态变量
public static String staticField = "静态变量";
// 变量
public String field = "变量";
// 静态初始化块
static {
System.out.println(staticField);
System.out.println("静态初始化块");
}
// 初始化块
{
System.out.println(field);
System.out.println("初始化块");
}
// 构造器
public InitialOrderTest() {
System.out.println("构造器");
}
public static void main(String[] args) {
new InitialOrderTest();
}
}
运行以上代码,我们会得到如下的输出结果:
1. 静态变量
2. 静态初始化块
3. 变量
4. 初始化块
5. 构造器
这与上文中说的完全符合。那么对于继承情况下又会怎样呢?我们仍然以一段测试代码来获取最终结果:
class Parent {
// 静态变量
public static String p_StaticField = "父类--静态变量";
// 变量
public String p_Field = "父类--变量";
protected int i = 9;
protected int j = 0;
// 静态初始化块
static {
System.out.println(p_StaticField);
System.out.println("父类--静态初始化块");
}
// 初始化块
{
System.out.println(p_Field);
System.out.println("父类--初始化块");
}
// 构造器
public Parent() {
System.out.println("父类--构造器");
System.out.println("i=" + i + ", j=" + j);
j = 20;
}
}
public class SubClass extends Parent {
// 静态变量
public static String s_StaticField = "子类--静态变量";
// 变量
public String s_Field = "子类--变量";
// 静态初始化块
static {
System.out.println(s_StaticField);
System.out.println("子类--静态初始化块");
}
// 初始化块
{
System.out.println(s_Field);
System.out.println("子类--初始化块");
}
// 构造器
public SubClass() {
System.out.println("子类--构造器");
System.out.println("i=" + i + ",j=" + j);
}
// 程序入口
public static void main(String[] args) {
System.out.println("子类main方法");
new SubClass();
}
}
运行一下上面的代码,结果马上呈现在我们的眼前:
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
子类main方法
父类--变量
父类--初始化块
父类--构造器
i=9, j=0
子�%2