Java编程思想---第五章 初始化与清理(下)

第五章 初始化与清理(下)

5.7 构造器初始化

 

  可以使用构造器来进行初始化,在运行时可以调用方法或执行某些动作来确定初值,但是我们无法阻止自动初始化的进行,它将在构造器被调用之前发生。例如:

public class Counter {
  int i;
  Counter() { i = 7; }
}

 

  那么i首先被置为0,然后变成7.编译器不会强制你一定要在构造器的某个地方或在使用它们之前对元素进行初始化,因为初始化早已得到了保证。

 

5.7.1 初始化顺序

 

  在类的内部,变量定义的先后顺序决定了初始化的顺序,即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)之前得到初始化。例如:

 

package com.example.demo;

class Window {
    Window(int marker){
        System.out.print("Window(" + marker + ")\n");
    }
}

class House{
    Window w1 = new Window(1);

    House(){
        System.out.print("House()\n");
        w3 = new Window(33);
    }

    Window w2 = new Window(2);

    void f(){
        System.out.print("f()\n");
    }

    Window w3 = new Window(3);
}
public class test { public static void main(String[] args) { House h = new House(); h.f(); } }

 

 

 

  输出结果为:

 

 

  由输出可见,w3这个引用会被初始化两次,一次是在调用构造器前,一次是在调用期间,第一次引用的对象将被丢弃并作为垃圾回收。

 

5.7.2 静态数据的初始化

 

  无论创建多少个对象,静态数据都只占用一份存储区域,static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型域,且没有对它进行初始化,那么它就会获得基本类型的标准初值,如果他是一个对象引用,那么他的默认初始化值及时null。

  看看下面这个例子:

  

package com.example.demo;

class Bowl {
    Bowl(int marker) {
        System.out.print("Bowl(" + marker + ")\n");
    }

    void f1(int marker) {
        System.out.print("f1(" + marker + ")\n");
    }

}

class Table {
    static Bowl bowl1 = new Bowl(1);

    Table() {
        System.out.print("Table()\n");
        bowl2.f1(1);
    }

    void f2(int marker) {
        System.out.print("f2(" + marker + ")\n");
    }

    static Bowl bowl2 = new Bowl(2);
}

class Cupboard {
    Bowl bowl3 = new Bowl(3);
    static Bowl bowl4 = new Bowl(4);

    Cupboard() {
        System.out.print("Cupboard()\n");
        bowl4.f1(2);
    }

    void f3(int marker) {
        System.out.print("f3(" + marker + ")\n");
    }

    static Bowl bowl5 = new Bowl(5);
}

public class StaticInitialization {
    public static void main(String[] args){
        System.out.print("Creating new Cupboard() in main\n");
        new Cupboard();
        System.out.print("Creating new Cupboard() in main\n");
        new Cupboard();
        table.f2(1);
        cupboard.f3(1);
    }

    static Table table = new Table();
    static Cupboard cupboard = new Cupboard();

}

 

  输出结果如下:

Bowl(1)

Bowl(2)

Table()

f1(1)

Bowl(4)

Bowl(5)

Bowl(3)

Cupboard()

f1(2)

Creating new Cupboard() in main

Bowl(3)

Cupboard()

f1(2)

Creating new Cupboard() in main

Bowl(3)

Cupboard()

f1(2)

f2(1)

f3(1)

 

  由输出可见,静态初始化只有在必要时才会进行,此后,静态对象不会再次被初始化。

  初始化的顺序是先静态对象,而后是非静态对象。从输出结果可以观察到这一点,要执行main()就必须加载StaticInitialization类,然后其静态域table和cupboard被初始化,浙江导致他们对应的类也被加载,并且由于他们也都包含静态的Bowl对象,因此Bowl随后也被加载,这样,在这份特殊的程序中的所有类在main()开始之前就都被加载了。但实际情况通常并非如此,不会像这个例子一样把所有的事物都通过static联系起来。

 

  总结:假设有一个名为Dog的类,

1、即使没有显示使用static关键字,构造器实际上也是静态方法,当首次创建Dog对象时,或者Dog类的静态方法/静态域被首次访问时,Java解释器必须查找类路径,以此定位Dog.class文件。

2、然后载入Dog.class,有关静态初始化的所有动作都会执行,因此静态初始化只在Class对象首次加载的时候进行一次。

3、当用new Dog()创建对象时,首相将在堆上为Dog对象分配足够的存储空间。

4、这块存储空间会被清零,这就自动地将Dog对象中所有基本类型数据都设置成了默认值,而引用则被设置为null。

5、执行所有出现于字段定义处的初始化动作。

6、执行构造器。

 

5.7.3 显式的静态初始化

 

  Java允许将多个静态初始化动作组织成一个特殊的静态字句(静态块),例如:

public class Spoon {
  static int i;

  static {
    i = 47;
  }
}

 

  尽管上面的方法看起来像个方法,但它实际只是一段跟在static关键词后的代码,与其他静态初始化动作一样,这段代码仅执行一次。

 

5.7.4 非静态实例初始化

 

  Java中也有被称为实例初始化的类似语法,用来初始化每一个对象的非静态变量。看起来与静态初始化子句一模一样只不过少了static关键字。这种语法对于支持匿名内部类的初始化是必须的,但它也是的你可以保证无论调用了哪个显式构造器,某些操作都会发生。实例化子句是在构造器之前执行的。

 

5.8 数组初始化

 

  数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。数组是通过方括号下标操作符[]来定义和使用的,要定义一个数组只需要在类型后加上一对空的方括号即可:

int[] a1;

int a1[];

 

  两种格式的含义是一样的,后一种更符合C和C++程序员的习惯,不过前一种格式或许更合理。编译器不允许指定数组的大小。

  在Java中可以将一个数字赋值给另一个数组: a2 = a1,但是真正做的只是复制了一个数组赋值给另一个数组,示例:

package com.example.demo;

public class ArrayOfPrimitives {
    public static void main(String[] args) {
        int[] a1 = { 1, 2, 3, 4, 5};
        int[] a2;
        a2 = a1;

        for(int i = 0; i < a2.length; i++) {
            a2[i] = a2[i] + 1;
        }

        for(int i = 0; i < a1.length; i++) {
            System.out.print("a1[" + i + "] = " + a1[i] + "\n");
        }
    }
}

 

  输出结果为:

a1[0] = 2

a1[1] = 3

a1[2] = 4

a1[3] = 5

a1[4] = 6

 

  可以看出,代码中给出了a1的初值但a2却没有,在上述代码中,a2是在后面被赋给另一个数组,由于a2和a1是相同数组的别名,因此通过a2所做的修改可以在a1中可以看到。

  所有数组都有一个固定成员,也就是length,可以通过他获取数组包含元素个数,且不可以修改。Java数组和C、C++类似,从0开始计数,最大下标为length-1,要是超出这个边界,Java会出现运行异常。

 

5.8.1 可变参数列表

 

  第二种形式提供了一张方便的语法来创建对象并调用方法,以获得与C的可变参数列表一样的效果,这可以应用于参数个数或类型未知的场合,由于所有的类都直接或间接继承于Object类,所以可以创建Object数组为参数的方法,像下面这样调用:

  

package com.example.demo;

class A {}

public class VarArgs {
    static void printArray(Object[] args) {
        for(Object obj : args)
            System.out.print(obj + " ");
        System.out.println();
    }

    public static void main(String[] args) {
        printArray(new Object[]{
                new Integer(47), new Float(3.14),new Double(11.11)
        });

        printArray(new Object[]{"one", "two", "three"});

        printArray(new Object[]{new A(), new A(), new A()});
    }
}

 

  输出结果如下:

47 3.14 11.11

one two three

com.example.demo.A@649d209a com.example.demo.A@6adca536 com.example.demo.A@357246de

 

  可以看到print方法使用Object数组作为参数,使用foreach语法进行遍历,打印每个对象,打印出的内容只是类的名称已经后面跟一个@符号以及多个十六进制数字,于是默认就是打印类的名字和对象地址。有了可变参数,就再也不用显示编写数组语法了,当你指定参数时编译器会为你去填充数组。

 

5.9 枚举类型

 

  在Java SE5中添加了一个看似很小的特性,即enum关键字,它是的我们在需要群组合并使用枚举类型集时,可以很方便地处理,在此之前你需要创建一个整数常量集,但是这些枚举值并不会必然的将其自身的取值限制在这个常量集的范围之内,因此他们显得更有风险,并且难以使用。一个简单的例子:

public enum Spiciness {
  NOT, MILD, MEDIUM, HOT, FLAMING
}

 

  这里创建了一个名为Spiciness的枚举类型,它具有五个具名值,由于枚举类型的实例是常量,因此按照命名惯例它们都适用大写字母表示。

  为了适用enum,需要创建一个该类型的引用,并将其赋值给某个实例:

public class SimpleEnumUse {
  public static void main(String[] args) {
    Spiciness howHot = Spiciness.MEDIUM;
    System.out.println(howHot);
  }
}

 

  输出结果为:MEDIUM

 

  在创建enum时,编译器会自动添加一些有用的特性,例如toString(),你可以很方便地显示某个enum实例的名字;ordinal(),用来表示某个特定enum常量的声明顺序;static values(),用来按照enum常量的声明顺序,产生由这些常量值构成的数组:

public class VarArgs {
    public static void main(String[] args) {
        for(Spiciness s : Spiciness.values())
            System.out.println(s + ".ordinal " + s.ordinal());
    }
}

 

  输出结果为:

NOT.ordinal 0

MILD.ordinal 1

MEDIUM.ordinal 2

HOT.ordinal 3

FLAMING.ordinal 4

 

  由于switch是要在有限的可能值集合中进行选择,因此它与enum是绝佳的组合。大体上你可以将enum用作另外一种创建数据类型的方式,然后直接将所得到的类型拿来使用。

posted @ 2019-09-02 08:30  寓言i  阅读(126)  评论(0编辑  收藏  举报