Thinking In Java笔记(第五章 初始化与清理(三))

第五章 初始化与清理

5.6 成员初始化

Java尽力保证:所有变量在使用前都能得到恰当的初始化。对于方法的局部变量,Java以编译错误的形式来保证。如下:

void f() {
    int i;
    i++; //Error:i not initialized
}

会得到一条错误的消息,提示i可能没有初始化。编译器可以给i赋初值,但是并没有这么做,因为没有初始化是程序员的疏忽,为了保证程序的稳定性,能帮助程序员找出程序的缺陷所在。

但如果是类的数据成员是基本类型,类的每个基本类型成员变量都会保证有一个初值。如下:

public class Test {
    boolean t;
    char c;
    byte b;
    short s;
    int i;
    long l;
    float f;
    double d;
    InitialValues reference;
}

上面的一系列数值分别为:false, (空白), 0, 0, 0, 0, 0.0, 0.0, null。char值为0,所以显示为空白。

5.6.1 指定初始化

有一种很直接的方法给某个变量赋初值,就是在定义类成员变量的地方直接为其赋值,C++中不允许。对初始化非基本类型的对象也可以,例如下面的A类的对象,这样类Test的每个对象都会具有相同的初始值。如下:

class A{
}
public class Test {
    boolean bool = true;
    char ch = ‘x‘;
    int i = 99;
    A a = new A();
}

5.7 构造器初始化

还可以用构造器来进行初始化。在运行时刻,可以调用方法或执行某些动作来确定初值,这给编程带来了更大的灵活性。但是要记住,无法组织自动初始化的进行,也就是前面提到的编译器自动赋值,这个工作在构造器被调用之前就发生。例如:

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

i的值首先被置为0,再被赋值为7。

5.7.1 初始化顺序

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

class Window {
    Window(int maker) {
        System.out.println("Window(" + maker + ")");
    }
}

class House {
    Window w1 = new Window(1);
    House() {
        System.out.println("House");
        w3 = new Window(33);
    }
    Window w2 = new WIndow(2);
    void f() {
        System.out.println("f()");
    Window w3 = new Window(3);
    }
}

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

结果为Window(1) Window(2) Window(3) House() Window(33) f()

5.7.2 静态数据的初始化

无论创建多少个对象,静态数据都至占用一份存储区域。static关键字不能应用于局部变量。看下面的例子:

class Bowl {
    Bowl(int marker) {
        System.out.println("Bowl(" + marker + ")");
    }
    void f1(int marker) {
        System.out.println("f1(" + marker + ")");
    }
}
class Table {
    static Bowl bowl1 = new Bowl(1);
    Table() {
        System.out.println("Table()");
        bowl2.f1(1);
    void f2(int marker) {
        System.out.println("f2(" + marker + ")");
    }
    static Bowl bowl2 = new Bowl(2);
    }

class Cupboard {
    Bowl bowl3 = new Bowl(3);
    static Bowl bowl4 = new Bowl(4);
    Cupboard() {
        System.out.println("Cupboard()");
        bowl4.f1(2);
    }
    void f3(int marker) {
        System.out.println("f3(" + marker + ")");
    }
    static Bowl bowl5 = new Bowl(5);
}
}

public class Test {
    public static void main(String[] args){
        System.out.println("Creating new Cupboard() in main");
        new Cupboard();
        Syste.out.println("Creating new Cupboard() in main");
        new Cupboard();
        table.f2(1);
        cupboard.f3(1);
    }
    static Table table = new Table();
    static Cupboard cupboard = new Cupbpard();
}

输出的结果依次为:

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)

静态初始化只有在必要的时候才会进行,只有在第一个类对象被创建(或第一次访问静态数据)的时候,静态数据才会被初始化。之后,无论怎么创建对象,都不会再次被初始化。

初始化的顺序是先静态对象(如果它们尚未被初始化),而后是”非静态对象”。

总结一下对象的创建过程,假设有个名为Dog的类:

  1. 即使没有显式的使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog对象时(构造器可以看成静态方法),或者Dog类的静态方法、静态域首次被访问的时候,Java解释器必须查找类的路径,以定位Dog.class。
  2. 然后载入Dog.class,有关静态初始化的所有动作都会被执行,因此,静态初始化只在Class对象首次加载的时候进行一次。
  3. 当用new创建Dog的对象时候,首先在堆上给Dog对象分配足够的存储空间。
  4. 清零所分配的存储空间,这就自动的将Dog对象中所有的基本数据类型设置成了默认值(对数字来说就是0,对boolean和char类型的来说也类似),而引用则都被设置成了null。
  5. 执行所有出现于字段定时的初始化动作。
  6. 执行构造器方法。

5.7.3 显示的静态初始化

Java允许多个静态初始化动作组织成一个特殊的”静态子句”(有时也叫做”静态块”)。就像下面这样:

public class Spoon {
    static int i;
    static {
        i = 47;
    }
}

和其他静态初始化动作一样,这段代码只执行一次:当首次生成这个类的对象时,或首次访问属于这个类的静态成员数据的时候执行。例如:

class Cup {
    Cup(int marker) {
        System.out.println("Cup(" + marker + ")");
    }
    void f(int marker) {
        System.out.println("f(" + marker + ")");
    }
}

class Cups {
    static Cup cup1;
    static Cup cup2;
    static {
        cup1 = new Cup(1);
        cup2 = new Cup(2);
    }

    Cups() {
        System.out.println("Cups()");
    }
}

public class Test {
    public static void main(String[] args) {
        System.out.println("Inside main()");
        Cups.cup1.f(99);  //  (1)
    }

    //static Cups cups1 = new Cups(); //  (2)
    //static Cups cup2 = new Cups();  //  (2)
}

上面的结果为:Inside main() Cup(1) Cup(2) f(99)

5.7.4 非静态实例初始化

Java中也有被成为实例初始化的类似语法,用来初始化每一个对象的非静态变量。和静态语句块一样的,只不过少了static。如果不创建类的实例,非静态语句块是不会被执行,只会触碰static变量和语句块。

下面用一个例子来总结下上述的顺序:

class Cup {
{
    System.out.println("Block - Cup");
}

static int c;
static {
    c = 1;
    System.out.println("Static Bolck - Cup");
}

    Cup(int marker) {
        System.out.println("Construct - Cup(" + marker + ")");
    }
    void f(int marker) {
        System.out.println("Function - f(" + marker + ")");
    }
}

class Cups {

    static {
        cup1 = new Cup(1);
        cup2 = new Cup(2);
    }
    static Cup cup1;
    static Cup cup2;
    {
        System.out.println("Block - Cups");
    }

    Cups() {
        System.out.println("Construct - Cups()");
    }
}
public class JavaTest {
    public static void main(String[] args) {
        System.out.println("Inside main()");
        Cups.cup1.f(99);  //  (1)
    }
}

输出的结果为:

Inside main()
Static Bolck - Cup
Block - Cup
Construct - Cup(1)
Block - Cup
Construct - Cup(2)
Function - f(99)

从上面的结果可以看出,没有新建Cups类的对象时,不会执行非静态语句块,也就是被{}包括起来的语句块。在第一次创建类对象或者使用到类的静态变量的时候,就会将.class文件加载进来,初始化static变量,执行static{}语句块。

5.8 数组初始化

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

两种格式的含义是一样的,后面一种格式符合C和C++程序员的习惯。前面一种格式能更直观的看出,其表明的类型是”一个int型数组”。

编译器不允许指定数组的大小,现在拥有的只是对数组的一个引用(你已经为该引用分配了足够的存储空间),而且也没给数组对象本身分配任何空间。为了给数组创建相应的存储空间,需要对数组进行初始化。数组的初始化代码可以出现在代码的任何地方,但也可以使用一种特殊的初始化表达式,必须在创建数组的地方出现。这种特殊的初始化是由一对花括号括起来的,存储空间的分配(等价于使用new)将由编译器负责。例如:

int[] a1 = {1,2,3,4,5};

那么为什么还要在没有数组的时候定义一个数组的引用呢?Java中可以将数组赋值给另一个数组,int[] a2;,在Java中可以将一个数组赋值给另一个数组,所以可以这样:a2 = a1;直接复制一个引用。下面的例子:

public class ArrayTest {
    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.println("a1[" + i + "]" + a[i]);
    }
}

输出为a1[0]=1 a1[1]=2 a1[2]=3 a1[3]=4 a1[4]=5

所有数组(无论元素始对象还是基本类型)都有一个固有成员length,可以通过它获得数组长度,但其不能直接被更改。和C与C++类似,Java数组计数从0开始,数组越界,C和C++默默接受,但Java直接出现运行时错误。

可以在编程时,通过new再数组里面创建元素。尽管创建的是基本类型数组,new仍然可以工作(不能用new创建单个的基本类型数据)。

public class ArrayNew {
    public static void main(String[] args) {
        int[] a;
        Random rand = new Random(47);
        a = new int[rand.nextInt(20)];
    }
}

如果创建了一个非基本类型的数组,那么就是一个引用数组。以整型的包装器类Integer为例:

public class Test {
    public static void main(String[] args) {
        Random rand = new Random(47);
        Integer[] a = new Integer[rand.nextInt(20)];
    }
}

这里即便用new创建了数组之后,也只是一个引用数组,并且直到通过创建新的Integer对象,并把对象和引用连接起来,初始化才算结束。如果忘记了创建对象,并链接对象和引用,那数组中就全是空引用,运行时会产生异常。

5.8.1 可变参数列表

可变参数列表可用于参数个数或者类型未知的场合。例如void f(Object[] args)函数里的参数。这种在Java SE5之前出现,然而再Java SE5中,添加入了新特性,可以使用新特性来定义可变参数列表了,下面的例子:

public class NewVarArgs {
    static void printArray(Object... args) {
        for(Object obj : args) {
            System.out.println(obj + " ");
        }
    }
    public static void main(String[] args) {
        printArray(new Integer(47), new Float(3.14), new Double(11.11));
        printArray(47, 3.14F, 11.11);
        printArray("one", "two", "three");
    }
}

有了可变参数,就不用显示的编写数组语法了,当指定参数的时候,编译器实际上会去填充数组,最后得到的仍然是一个数组。

5.9 枚举类型

Java SE5中添加了一个看似很小的特性,即enum关键字,它使得我们在需要群组并使用枚举集时可以很方便的处理。C和C++以及以其他很多语言已经拥有枚举类型了,Java中枚举类型功能比C/C++的功能更加完备。下面是简单的示例:

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

这里创建了一个名为A的枚举类型,它具有5种值,由于枚举类型的实例是常量,因此按照命名习惯通常用大写字母表示(有多个字母用下划线隔开)。

为了使用enum,需要创建一个该类型的引用,并和某个实例连接起来。

public class Test {
    public static void main(String[] args) {
        A a = A.MEDUIM;
        System.out.println(a);
    }
}

在创建枚举类型的时候,编译器会自动添加一些特性。例如:

  • 会创建toString()方法,一边可以很方便的显示某个enum实例的名字。
  • 创建ordinal()方法,用来表示某个特定enum常量的声明顺序。
  • static values()方法,用来按照enum常量的声明顺序,产生由这些常量值构成的数组。

例子如下:

public class EnumOrder {
    public static void main(String[] args){
        for(A a : A.values) {
            System.out.println(s + ", oridinal " + a.ordinal());
        }
    }
}

输出结果为:
NOT, oridinal 0
MILD, oridinal 1
MEDIUM, oridinal 2
HOT, oridinal 3
FLAMING, oridinal 4

enum的另一个特别实用的特性是能和switch语句一起使用。看下面的例子:

    enum Pet {
    Cat,
    Dog,
    Bird
}
public class JavaTest {
    Pet pet;
    public JavaTest(Pet p) {
        pet = p;
    }
    public void describe() {
        switch(pet) {
        case Cat :
            System.out.println("The pet is Cat");
            break;
        case Dog :
            System.out.println("The pet is Dog");
            break;
        case Bird :
            System.out.println("The pet is Bird");
            break;

        }
    }
    public static void main(String[] args) {
        Pet p1 = Pet.Bird;
        JavaTest test = new JavaTest(p1);
        test.describe();
    }
}

结果为:The pet is Bird
时间: 2024-10-11 13:10:43

Thinking In Java笔记(第五章 初始化与清理(三))的相关文章

Thinking in JAVA笔记——第五章 初始化与清理

5.1用构造器确保初始化 为了确保安全性,强制在使用前进行初始化 Java构造器与类名相同,无参数构造器有叫做默认构造器. 5.2 方法重载 method overloading:重载,同名不同参 method overriding:重写/覆盖,子类覆盖父类 5.2.1区分重载方法 独一无二的参数类型表,否则编译不通过! 5.2.2涉及基本类型的重载 如果参数类型是int,short,byte,long等,自动定位到int而不会执行short,byte 因为默认常数就是int型,如果没有int,

《Java编程思想》笔记 第五章 初始化与清理

1.构造器 因为创建一个类的对象构造器就会自动执行,故初始化某些东西特好 2.方法重载 方法名相同,参数列表不同. 2.1 区分重载方法 方法重载后区别不同方法的就是方法签名 -->参数类型和个数(参数顺序不同也能区分但一般不考虑顺序) 2.2 涉及基本数据类型的重载 范围由小到大:byte > short > char > int > long > float > double 自动提升: 传入类型小于声明类型,传入值提升至与其最近的类型,该类型参数的方法被调用

Thinking In Java笔记(第五章 初始化与清理(二))

第五章 初始化与清理(二) 5.5 清理:终结处理和垃圾回收 清理的工作常常被忽略,Java有垃圾回收器负责回收无用对象占据的内存资源.但也有特殊情况:假定对象(并非使用new)获得了一块"特殊"的内存区域,由于垃圾回收器只知道释放那些由new分配的内存,所以不知道如何释放特殊内存.Java允许在类中定义一个名为finalize()的方法,工作原理"假定"是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,首先调用其finalize()方法,并且在下一次垃圾回收动

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

第五章 初始化与清理(下) 5.7 构造器初始化 可以使用构造器来进行初始化,在运行时可以调用方法或执行某些动作来确定初值,但是我们无法阻止自动初始化的进行,它将在构造器被调用之前发生.例如: public class Counter { int i; Counter() { i = 7; } } 那么i首先被置为0,然后变成7.编译器不会强制你一定要在构造器的某个地方或在使用它们之前对元素进行初始化,因为初始化早已得到了保证. 5.7.1 初始化顺序 在类的内部,变量定义的先后顺序决定了初始化

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

从今天开始每天一小时的java 编程思想的阅读和编码,其实就是把书上的代码抄下来. 5.5 清理:终结处理和垃圾回收 初始化和清理工作同等重要,但是清理工作却被常常忘记,但是在使用对象之后,对对象弃之不顾的做法并不是很安全.Java有自己的垃圾回收器负责回收无用的对象占据的内存资源.但也有特殊情况:假定你的内存区域不是用new获得的,这是无法用垃圾回收器释放所以java中允许在类中定义一个名为 finalize()的方法.       工作原理: 一旦垃圾回收器准备好释放对象占用的存储空间,将首

Java 编程思想 第五章 初始化与清理 上

休整几天,闲了蛋疼也没写文章,这开学了坚持每天写// 必须的天天写.不敢再松懈了.羡慕一好朋友能坚持的静下心来学习. 5.1 用构造器确保初始化 在Java中,通过提供构造器,类的设计者可确保每个对象都会得到初始化.  创建对象时,如果其类具有构造器,Java就会在用户有能力操作兑现之前自动调用相应的构造器,从而保证了初始化的进行.  当然了 接下来的我们需要的问题是: 1.所取的任何名字都可能与类的某个成员名称相冲突: 2.调用股早期是编译器的责任,所以必须让编译器知道应该调用哪个方法: 采取

java笔记 第五章

循环结构(一) 1  whilc循环 whilc(条件){ //循环语句1 } 条件:布尔类型  变量或表达式.  顺序: 当条件为真,则继续运行循环语句1直到条件为假 在执行后面的语句. 2  do-whilc do{ //循环语句1 }whilc(条件) 条件:布尔类型  变量或表达式. 特点:不管条件为真还是假都会先执行1遍循环语句1. 顺序:先执行一次循环语句1,再去判断条件.如果条件为真则回到do执行循环语句1直到条件为假,跳出循环.

&lt;Thinking in java 第五章&gt; 初始化与清理

P86--构造器中可以用this(arg);来调用另一个构造器,但是却不能调用两个.此外,必须将构造器调用置于最起始处,否则编译器会报错. 关于finalize方法的流程: 当对象变成(GC roots)不可达时候,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收.否则,如对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法.执行finalize方法完毕后,GC会判断该对象是否可达,若不可达,则进行回收,否则

第五章 初始化与清理

1.初始化,变量初始化优先于方法,静态类型与非静态类型初始化的差别在于,前者发生在类加载阶段,而后者发生在创建对象的阶段. 2.数组初始化三种方法: (1)int[] a = {1, 2, 3}; 只能用于定义时初始化,这种方法不够灵活 (2)int[] a = new int[]{ 1, 2 ,3}; 数组大小由{}中元素个数决定 (3)int[] a = new a[10]; 数组大小可以指定. 3.实际上可变参数列表的实现就是数组,当你指定参数,编译器会为你填充数组 原文地址:https: