6 JAVA编程思想初始化和清除

               

 

欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/51040234

随着计算机的进步,‘不安全’的程序设计已成为造成编程代价高昂的罪魁祸首之一。

许多 C程序的错误都是由于程序员忘记初始化一个变量造成的。对于现成的库,若用户不知道如何初始化库的一个组件,就往往会出现这一类的错误。清除是另一个特殊的问题,因为用完一个元素后,由于不再关心,所以很容易把它忘记。这样一来,那个元素占用的资

源会一直保留下去,极易产生资源(主要是内存)用尽的后果。

C++为我们引入了“构建器”的概念。这是一种特殊的方法,在一个对象创建之后自动调用。Java 也沿用了这个概念,但新增了自己的“垃圾收集器”,能在资源不再需要的时候自动释放它们。

 

1      用构建器自动初始化

对于方法的创建,可将其想象成为自己写的每个类都调用一次initialize()。这个名字提醒我们在使用对象之前,应首先进行这样的调用。但不幸的是,这也意味着用户必须记住调用方法。在 Java 中,由于提供了名为“构建器”的一种特殊方法,所以类的设计者可担保每个对象都会得到正确的初始化。若某个类有一个构建器,那么在创建对象时,Java 会自动调用那个构建器——甚至在用户毫不知觉的情况下。

接着的一个问题是如何命名这个方法。存在两方面的问题。第一个是我们使用的任何名字都可能与打算为某个类成员使用的名字冲突。第二是由于编译器的责任是调用构建器,所以它必须知道要调用是哪个方法。C++采取的方案看来是最简单的,且更有逻辑性,所以也在Java 里得到了应用:构建器的名字与类名相同。这样一来,可保证象这样的一个方法会在初始化期间自动调用。

 

示例:

class Rock {

 Rock() { // This is the constructor

   System.out.println("CreatingRock");

 }

}

 

publicclass test {

 publicstaticvoid main(String[] args) {

   for(inti = 0; i < 10;i++)

     new Rock();

 }

} ///:~

一旦创建一个对象:

new Rock();

就会分配相应的存储空间,并调用构建器。这样可保证在我们经手之前,对象得到正确的初始化。请注意所有方法首字母小写的编码规则并不适用于构建器。这是由于构建器的名字必须与类名完全相同! 和其他任何方法一样,构建器也能使用自变量,以便我们指定对象的具体创建方式。

class Rock {

    Rock(inti) {

        System.out.println("Creating Rocknumber " + i);

    }

}

 

publicclass test {

    publicstaticvoid main(String[] args) {

        for (inti = 0; i < 10;i++)

            new Rock(i);

    }

}

构建器属于一种较特殊的方法类型,因为它没有返回值。这与 void 返回值存在着明显的区别。对于void 返回值,尽管方法本身不会自动返回什么,但仍然可以让它返回另一些东西。构建器则不同,它不仅什么也不会自动返回,而且根本不能有任何选择。若存在一个返回值,而且假设我们可以自行选择返回内容,那么编译器多少要知道如何对那个返回值作什么样的处理。

 

2      方法过载

在任何程序设计语言中,一项重要的特性就是名字的运用。我们创建一个对象时,会分配到一个保存区域的名字。方法名代表的是一种具体的行动。通过用名字描述自己的系统,可使自己的程序更易人们理解和修改。

我们用名字引用或描述所有对象与方法。若名字选得好,可使自己及其他人更易理解自己的代码。 将人类语言中存在细致差别的概念“映射”到一种程序设计语言中时,会出现一些特殊的问题。这是由于听众根本不需要对执行的行动作任何明确的区分。人类的大多数语言都具有很强的“冗余”性,所以即使漏掉了几个词,仍然可以推断出含义。我们不需要独一无二的标识符——可从具体的语境中推论出含义。

大多数程序设计语言(特别是C)要求我们为每个函数都设定一个独一无二的标识符。所以绝对不能用一个名为print()的函数来显示整数,再用另一个print()显示浮点数——每个函数都要求具备唯一的名字。

在Java 里,另一项因素强迫方法名出现过载情况:构建器。由于构建器的名字由类名决定,所以只能有一个构建器名称。但假若我们想用多种方式创建一个对象呢?例如,假设我们想创建一个类,令其用标准方式进行初始化,另外从文件里读取信息来初始化。此时,我们需要两个构建器,一个没有自变量(默认构建器),另一个将字串作为自变量——用于初始化对象的那个文件的名字。由于都是构建器,所以它们必须有相同的名字,亦即类名。所以为了让相同的方法名伴随不同的自变量类型使用,“方法过载”是非常关键的一项措施。同时,尽管方法过载是构建器必需的,但它亦可应用于其他任何方法,且用法非常方便。

 

示例如下:

importjava.util.*;

 

class Tree {

 

    intheight;

 

    Tree(){

        prt("Planting aseedling");

        height = 0;

    }

 

    Tree(inti) {

        prt("Creating newTree that is "+i +" feet tall");

        height =i;

    }

 

    void info() {

        prt("Tree is " +height +" feet tall");

    }

 

    void info(Strings) {

        prt(s +": Tree is " +height +" feet tall");

    }

 

    staticvoid prt(Strings) {

        System.out.println(s);

    }

}

 

publicclass test {

    publicstaticvoid main(String[] args) {

        for (inti = 0; i < 5;i++) {

            Treet =new Tree(i);

            t.info();

            t.info("overloaded method");

        }

        // Overloadedconstructor:

        new Tree();

    }

} // /:~

输出如下:

Creatingnew Tree that is 0 feet tall

Treeis 0 feet tall

overloadedmethod: Tree is 0 feet tall

Creatingnew Tree that is 1 feet tall

Treeis 1 feet tall

overloadedmethod: Tree is 1 feet tall

Creatingnew Tree that is 2 feet tall

Treeis 2 feet tall

overloadedmethod: Tree is 2 feet tall

Creatingnew Tree that is 3 feet tall

Treeis 3 feet tall

overloadedmethod: Tree is 3 feet tall

Creatingnew Tree that is 4 feet tall

Treeis 4 feet tall

overloadedmethod: Tree is 4 feet tall

Plantinga seedling

Tree 既可创建成一颗种子,不含任何自变量;亦可创建成生长在苗圃中的植物。为支持这种创建,共使用了两个构建器,一个没有自变量,另一个采用现成的高度。

4.2.1 区分过载方法

若方法有同样的名字,Java 怎样知道我们指的哪一个方法呢?这里有一个简单的规则:每个过载的方法都必须采取独一无二的自变量类型列表。

即使自变量的顺序也足够我们区分两个方法(尽管我们通常不愿意采用这种方法,因为它会产生难以维护的代码)

示例如下:

publicclass test {

 staticvoid print(Strings,inti) {

   System.out.println(

     "String:" +s +

     ",int: " +i);

 }

 staticvoid print(inti, String s) {

   System.out.println(

     "int:" +i +

     ",String: " +s);

 }

 publicstaticvoid main(String[] args) {

   print("String first", 11);

   print(99,"Int first");

 }

} ///:~

 

3      返回值过载

为什么只有类名和方法自变量列出?为什么不根据返回值对方法加以区分?比如对下面这两个方法来说,虽然它们有同样的名字和自变量,但其实是很容易区分的:

void f() {}

int f() {}

若编译器可根据上下文(语境)明确判断出含义,比如在 int x=f()中,那么这样做完全没有问题。然而,我们也可能调用一个方法,同时忽略返回值;我们通常把这称为“为它的副作用去调用一个方法”,因为我们关心的不是返回值,而是方法调用的其他效果。所以假如我们象下面这样调用方法:

f();

Java 怎样判断f()的具体调用方式呢?而且别人如何识别并理解代码呢?由于存在这一类的问题,所以不能根据返回值类型来区分过载的方法。

 

4      默认构建器

默认构建器是没有自变量的。它们的作用是创建一个“空对象”。若创建一个没有构建器的类,则编译程序会帮我们自动创建一个默认构建器。

如下:

class Bird {

 inti;

}

 

publicclass test {

 publicstaticvoid main(String[] args) {

   Bird nc =new Bird();// default!

 }

} ///:~

new Bird(); 它的作用是新建一个对象,并调用默认构建器——即使尚未明确定义一个象这样的构建器。若没有它,就没有方法可以调用,无法构建我们的对象。然而,如果已经定义了一个构建器(无论是否有自变量),编译程序都不会帮我们自动合成一个

 

5      this 关键字

如果有两个同类型的对象,分别叫作a 和b,那么您也许不知道如何为这两个对象同时调用一个f()方法:

 

class Banana { void f(int i) { /* ... */ }}

Banana a = new Banana(), b = new Banana();

a.f(1);

b.f(2);

 

若只有一个名叫f()的方法,它怎样才能知道自己是为 a 还是为b 调用的呢?

为了能用简便的、面向对象的语法来书写代码——亦即“将消息发给对象”,编译器为我们完成了一些幕后工作。其中的秘密就是第一个自变量传递给方法f(),而且那个自变量是准备操作的那个对象的句柄。所以前述的两个方法调用就变成了下面这样的形式:

 Banana.f(a,1);

Banana.f(b,2);

 这是内部的表达形式,我们并不能这样书写表达式,并试图让编译器接受它。但是,通过它可理解幕后到底发生了什么事情。

 

假定我们在一个方法的内部,并希望获得当前对象的句柄。由于那个句柄是由编译器“秘密”传递的,所以没有标识符可用。然而,针对这一目的有个专用的关键字:this。

this 关键字(注意只能在方法内部使用)可为已调用了其方法的那个对象生成相应的句柄。可象对待其他任何对象句柄一样对待这个句柄。但要注意,假若准备从自己某个类的另一个方法内部调用一个类方法,就不必使用this。只需简单地调用那个方法即可。当前的this 句柄会自动应用于其他方法。

this 关键字只能用于那些特殊的类——需明确使用当前对象的句柄。例如,假若您希望将句柄返回给当前对象,那么它经常在return 语句中使用。

 

示例如下:

publicclass test {

 privateinti = 0;

 test increment() {

   i++;

   returnthis;

 }

 void print() {

   System.out.println("i = " + i);

 }

 publicstaticvoid main(String[] args) {

      test x =new test();

   x.increment().increment().increment().print();

 }

} ///:~

由于increment()通过this 关键字返回当前对象的句柄,所以可以方便地对同一个对象执行多项操作。

 

5.1      在构建器里调用构建器(this)

若为一个类写了多个构建器,那么经常都需要在一个构建器里调用另一个构建器,以避免写重复的代码。可用this 关键字做到这一点。

通常,当我们说this 的时候,都是指“这个对象”或者“当前对象”。而且它本身会产生当前对象的一个句柄。在一个构建器中,若为其赋予一个自变量列表,那么 this 关键字会具有不同的含义:它会对与那个自变量列表相符的构建器进行明确的调用。这样一来,我们就可通过一条直接的途径来调用其他构建器。

示例如下:

publicclass Flower {

    privateintpetalCount = 0;

    private Strings =new String("null");

 

    Flower(intpetals ) {

        petalCount =petals;

        System.out.println("Constructor w/int arg only, petalCount= "

                +petalCount);

    }

 

    Flower(Stringss) {

        System.out.println("Constructor w/ String arg only, s=" +ss);

        s =ss;

    }

 

    Flower(Strings,intpetals) {

        this(petals);

        // ! this(s); //Can't call two!

        this.s = s;// Another use of "this"

        System.out.println("String &int args");

    }

 

    Flower(){

        this("hi", 47);

        System.out.println("defaultconstructor (no args)");

    }

 

    void print() {

        // ! this(11); // Notinside non-constructor!

        System.out.println("petalCount =" + petalCount +" s = " +s);

    }

 

    publicstaticvoid main(String[] args) {

        Flowerx =new Flower();

        x.print();

    }

} // /:~

输出如下:

Constructorw/ int arg only, petalCount= 47

String& int args

defaultconstructor (no args)

petalCount= 47 s = hi

尽管可用this 调用一个构建器,但不可调用两个。除此以外,构建器调用必须是我们做的第一件事情,否则会收到编译程序的报错信息。这个例子也向大家展示了this 的另一项用途。由于自变量s 的名字以及成员数据s 的名字是相同的,所以会出现混淆。为解决这个问题,可用 this.s来引用成员数据。经常都会在 Java 代码里看到这种形式的应用。在print()中,我们发现编译器不让我们从除了一个构建器之外的其他任何方法内部调用一个构建器。

 

5.2      static 的含义

理解了this 关键字后,我们可更完整地理解static(静态)方法的含义。它意味着一个特定的方法没有this。我们不可从一个 static方法内部发出对非 static方法的调用,尽管反过来说是可以的。

而且在没有任何对象的前提下,我们可针对类本身发出对一个 static方法的调用。事实上,那正是 static方法最基本的意义。它就好象我们创建一个全局函数的等价物(在C 语言中)。除了全局函数不允许在Java中使用以外,若将一个 static方法置入一个类的内部,它就可以访问其他static 方法以及static 字段。

有些人抱怨 static方法并不是“面向对象”的,因为它们具有全局函数的某些特点;利用static方法,我们不必向对象发送一条消息,因为不存在this。这可能是一个清楚的自变量,若您发现自己使用了大量静态方法,就应重新思考自己的策略。然而,static 的概念是非常实用的,许多时候都需要用到它。所以至于它们是否真的“面向对象”,应该留给理论家去讨论。事实上,即使Smalltalk 在自己的“类方法”里也有类似于static的东西。

 

6      清除:收尾和垃圾收集

程序员都知道“初始化”的重要性,但通常忘记清除的重要性。

但是对于库来说,用完后简单地“释放”一个对象并非总是安全的。当然,Java可用垃圾收集器回收由不再使用的对象占据的内存。现在考虑一种非常特殊且不多见的情况。假定我们的对象分配了一个“特殊”内存区域,没有使用new。

垃圾收集器只知道释放那些由new分配的内存,所以不知道如何释放对象的“特殊”内存。为解决这个问题,Java提供了一个名为finalize()的方法,可为我们的类定义它。在理想情况下,它的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存。所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作。

但也是一个潜在的编程陷阱,因为有些程序员(特别是在C++开发背景的)刚开始可能会错误认为它就是在C++中为“破坏器”(Destructor)使用的finalize()——破坏(清除)一个对象的时候,肯定会调用这个函数。有必要区分一下C++和Java 的区别,因为C++的对象肯定会被清除(排开编程错误的因素),而Java 对象并非肯定能作为垃圾被“收集”去。或者换句话说: 垃圾收集并不等于“破坏”!

 

Java 并未提供“破坏器”或者类似的概念,所以必须创建一个原始的方法,用它来进行这种清除。例如,假设在对象创建过程中,它会将自己描绘到屏幕上。如果不从屏幕明确删除它的图像,那么它可能永远都不会被清除。若在finalize()里置入某种删除机制,那么假设对象被当作垃圾收掉了,图像首先会将自身从屏幕上移去。但若未被收掉,图像就会保留下来。所以要记住:我们的对象可能不会当作垃圾被收掉!

     有时可能发现一个对象的存储空间永远都不会释放,因为自己的程序永远都接近于用光空间的临界点。若程序执行结束,而且垃圾收集器一直都没有释放我们创建的任何对象的存储空间,则随着程序的退出,那些资源会返回给操作系统。这是一件好事情,因为垃圾收集本身也要消耗一些开销。如永远都不用它,那么永远也不用支出这部分开销。

 

6.1      finalize() 用途何在

 

垃圾收集只跟内存有关!

垃圾收集器存在的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾收集有关的任何活动来说,其中最值得注意的是finalize()方法,它们也必须同内存以及它的回收有关。

但这是否意味着假如对象包含了其他对象,finalize()就应该明确释放那些对象呢?答案是否定的——垃圾收集器会负责释放所有对象占据的内存,无论这些对象是如何创建的。它将对finalize()的需求限制到特殊的情况。在这种情况下,我们的对象可采用与创建对象时不同的方法分配一些存储空间。

之所以要使用finalize(),看起来似乎是由于有时需要采取与Java的普通方法不同的一种方法,通过分配内存来做一些具有C 风格的事情。这主要可以通过“固有方法”来进行,它是从Java 里调用非Java 方法的一种方式。C和C++是目前唯一获得固有方法支持的语言。        

但由于它们能调用通过其他语言编写的子程序,所以能够有效地调用任何东西。在非Java 代码内部,也许能调用C 的malloc()系列函数,用它分配存储空间。而且除非调用了free(),否则存储空间不会得到释放,从而造成内存“漏洞”的出现。当然,free()是一个C 和C++函数,所以我们需要在finalize()内部的一个固有方法中调用它。

 

6.2      必须执行清除

为清除一个对象,那个对象的用户必须在希望进行清除的地点调用一个清除方法。这听起来似乎很容易做到,但却与 C++“破坏器”的概念稍有抵触。在C++中,所有对象都会破坏(清除)。或者换句话说,所有对象都“应该”破坏。若将C++对象创建成一个本地对象,比如在堆栈中创建(在 Java 中是不可能的),那么清除或破坏工作就会在“结束花括号”所代表的、创建这个对象的作用域的末尾进行。若对象是用new创建的(类似于 Java),那么当程序员调用 C++的delete 命令时(Java 没有这个命令),就会调用相应的破坏器。若程序员忘记了,那么永远不会调用破坏器,我们最终得到的将是一个内存“漏洞”,另外还包括对象的其他部分永远不会得到清除。

相反,Java 不允许我们创建本地(局部)对象——无论如何都要使用new。但在Java 中,没有“delete”命令来释放对象,因为垃圾收集器会帮助我们自动释放存储空间。所以如果站在比较简化的立场,我们可以说正是由于存在垃圾收集机制,所以 Java 没有破坏器。然而,随着以后学习的深入,就会知道垃圾收集器的存在并不能完全消除对破坏器的需要,或者说不能消除对破坏器代表的那种机制的需要(而且绝对不能直接调用finalize(),所以应尽量避免用它)。若希望执行除释放存储空间之外的其他某种形式的清除工作,仍然必须调用Java 中的一个方法。它等价于C++的破坏器,只是没后者方便。

finalize()最有用处的地方之一是观察垃圾收集的过程。

示例:

class Chair {

    staticbooleangcrun = false;

    staticbooleanf = false;

    staticintcreated = 0;

    staticintfinalized = 0;

    inti;

 

    Chair(){

        i = ++created;

        if (created == 47)

            System.out.println("Created47");

    }

 

    protectedvoid finalize() {

        if (!gcrun) {

            gcrun =true;

            System.out.println("Beginning tofinalize after " + created

                    +"Chairs have been created");

        }

        if (i == 47) {

            System.out.println("FinalizingChair #47, "

                    +"Settingflag to stop Chair creation");

            f =true;

        }

        finalized++;

        if (finalized >= created)

            System.out.println("All " + finalized +" finalized");

    }

}

 

publicclass Flower {

    publicstaticvoid main(String[] args) {

        if (args.length == 0) {

            System.err.println("Usage: \n" + "java Garbage before\n  or:\n"

                    +"javaGarbage after");

            return;

        }

        while (!Chair.f) {

            new Chair();

            new String("To take up space");

        }

        System.out.println("After allChairs have been created:\n"

                +"totalcreated = "+ Chair.created + ", total finalized = "

                +Chair.finalized);

        if (args[0].equals("before")) {

            System.out.println("gc():");

            System.gc();

            System.out.println("runFinalization():");

            System.runFinalization();

        }

        System.out.println("bye!");

        if (args[0].equals("after"))

            System.runFinalizersOnExit(true);

    }

} // /:~

输出如下:

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

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

Created47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

Beginningto finalize after 220 Chairs have been created

221

FinalizingChair #47, Setting flag to stop Chair creation

222

Afterall Chairs have been created:

totalcreated = 222, total finalized = 105

gc():

runFinalization():

All222 finalized

bye!

 

上面这个程序创建了许多Chair 对象,而且在垃圾收集器开始运行后的某些时候,程序会停止创建Chair。

由于垃圾收集器可能在任何时间运行,所以我们不能准确知道它在何时启动。因此,程序用一个名为gcrun的标记来指出垃圾收集器是否已经开始运行。利用第二个标记 f,Chair 可告诉 main()它应停止对象的生成。这两个标记都是在 finalize()内部设置的,它调用于垃圾收集期间。

另两个static 变量——created 以及finalized——分别用于跟踪已创建的对象数量以及垃圾收集器已进行完收尾工作的对象数量。最后,每个Chair 都有它自己的(非static)int i,所以能跟踪了解它具体的编号是多少。编号为47 的Chair 进行完收尾工作后,标记会设为true,最终结束Chair 对象的创建过程。

       运行这个程序的时候,提供了一个命令行自变量“before”或者“after”。其中,“before”自变量会调用System.gc()方法(强制执行垃圾收集器),同时还会调用System.runFinalization()方法,以便进行收尾。

调用的runFinalizersOnExit()方法却只有Java 1.1 及后续版本提供了对它的支持。注意可在程序执行的任何时候调用这个方法,而且收尾程序的执行与垃圾收集器是否运行是无关的。

       若使用一个不是“before”或“after”的自变量(如“none”),那么两个收尾工

作都不会进行,而且我们会得到象下面这样的输出

totalcreated = 322, total finalized = 187

为强制进行收尾工作,可先调用System.gc(),再调用System.runFinalization()。这样可清除到目前为止没有使用的所有对象。这样做一个稍显奇怪的地方是在调用runFinalization()之前调用gc(),这看起来似乎与 Sun公司的文档说明有些抵触,它宣称首先运行收尾模块,再释放存储空间。然而,若在这里首先调用runFinalization(),再调用gc(),收尾模块根本不会执行。

 

7      成员初始化

Java 尽自己的全力保证所有变量都能在使用前得到正确的初始化。若被定义成相对于一个方法的“局部”变量,这一保证就通过编译期的出错提示表现出来。

初始化示例:

class Measurement {

      booleant;

      charc;

      byteb;

      shorts;

      inti;

      longl;

      floatf;

      doubled;

      void print() {

        System.out.println(

          "Data type     Inital value\n" +

          "boolean       " +t +"\n" +

          "char          " +c +"\n" +

          "byte          " +b +"\n" +

          "short         " +s +"\n" +

          "int           " +i +"\n" +

          "long          " +l +"\n" +

          "float         " +f +"\n" +

          "double        " +d);

      }

    }

     

    publicclass Flower {

      publicstaticvoid main(String[] args) {

        Measurementd =new Measurement();

        d.print();

        /* In this case you could also say:

        new Measurement().print();

        */

      }

    }///:~

输出如下:

Data type      Inital value

boolean        false

char          

byte           0

short          0

int            0

long           0

float          0.0

double         0.0

 

其中,Char 值为空(NULL),没有数据打印出来。

在一个类的内部定义一个对象句柄时,如果不将其初始化成新对象,那个句柄就会获得一个空值。

7.1      规定初始化

如果想自己为变量赋予一个初始值,又会发生什么情况呢?为达到这个目的,一个最直接的做法是在类内部定义变量的同时也为其赋值(注意在C++里不能这样做,尽管C++的新手们总“想”这样做)

 

7.2      构建器初始化

7.2.1       初始化顺序

可考虑用构建器执行初始化进程。这样便可在编程时获得更大的灵活程度,因为我们可以在运行期调用方法和采取行动,从而“现场”决定初始化值。但要注意这样一件事情:不可妨碍自动初始化的进行,它在构建器进入之前就会发生。

示例如下:

class Tag {

    Tag(intmarker) {

        System.out.println("Tag(" + marker +")");

    }

}

 

class Card {

    Tagt1 =new Tag(1);// Before constructor

 

    Card(){

        // Indicate we're inthe constructor:

        System.out.println("Card()");

        t3 =new Tag(33);// Re-initialize t3

    }

 

    Tagt2 =new Tag(2);// After constructor

 

    void f() {

        System.out.println("f()");

    }

 

    Tagt3 =new Tag(3);// At end

}

 

publicclass Flower {

    publicstaticvoid main(String[] args) {

        Cardt =new Card();

        t.f();// Shows that construction is done

    }

}

输出如下:

Tag(1)

Tag(2)

Tag(3)

Card()

Tag(33)

f()

t3句柄会被初始化两次,一次在构建器调用前,一次在调用期间(第一个对象会被丢弃,所以它后来可被当作垃圾收掉)。从表面看,这样做似乎效率低下,但它能保证正确的初始化——若定义了一个过载的构建器,它没有初始化 t3;同时在t3 的定义里并没有规定“默认”的初始化方式,那么会产生什么后果呢?

   

7.2.2       静态数据的初始化

若数据是静态的(static),那么同样的事情就会发生;如果它属于一个基本类型(主类型),而且未对其初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的句柄,那么除非新建一个对象,并将句柄同它连接起来,否则就会得到一个空值(NULL)。

如果想在定义的同时进行初始化,采取的方法与非静态值表面看起来是相同的。但由于static 值只有一个存储区域,所以无论创建多少个对象,都必然会遇到何时对那个存储区域进行初始化的问题。

示例如下:

class Bowl {

    Bowl(intmarker) {

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值