首页 JAVA编程思想第四版中文版

JAVA编程思想第四版中文版

举报
开通vip

JAVA编程思想第四版中文版 Thinking in Java Fourth Edition Bruce Eckel 初始化和清理 随着计算机革命的演进,不安全的程序设计方式已经是程序昂贵的罪魁祸 首。 初始化和清理问题便是这类不安全问题中的两个。 许多C语言程序的bugs就是因为程序员忘记 了初始化一个变量。 当使用library component的用户不知道该如何初始化或者他们必须初始化 library component时,初始化的...

JAVA编程思想第四版中文版
Thinking in Java Fourth Edition Bruce Eckel 初始化和清理 随着计算机革命的演进,不安全的程序设计方式已经是程序昂贵的罪魁祸 首。 初始化和清理问题便是这类不安全问题中的两个。 许多C语言程序的bugs就是因为程序员忘记 了初始化一个变量。 当使用library component的用户不知道该如何初始化或者他们必须初始化 library component时,初始化的问题 关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf 现的尤为明显。 清理工作之所以是一个特别的问题就是因 为当你用完某个代码元素后,它就不再引起你的注意了,你就容易忘记使用过它。 结果是代码元素 占用的资源未被清理,你就容易耗尽资源,最明显的就是内存。 C++引入了constructor的概念,它是一类当对象创建时自动被调用的方法。 Java也采用了 constructor,而且Java拥有garbage collector,garbage collector在内存资源不再被使用时自动 释放那些资源。 这一章阐述了初始化和清理问题以及Java如何配合他们的。 使用constructor保证初始化 想象一下你为每一个你写的类都创建了一个称为initialize( )的方法。 这个方法的名字暗示 着在使用类的对象前都应该调用此方法。 不幸的是,这就意味着类的使用者必须记住要调用那个方 法。 而在Java里,类的设计者能够通过提供constructor确保每个创建的对象都初始化。 如果一个 类定义拥有一个constructor,那么在类的使用者进一步操纵类之前,Java就会在该类的对象被创建 的时候调用那个constructor。 因此初始化得到了保证。 接下来的难题是该为constructor方法起个什么样的名字。 有两个问题需要解决。 第一个问题 是你使用的任何名称都有可能与类成员的名称冲突。 第二个问题是编译器必须一直知道哪一个方法 被用做constructor来调用,因为编译器负责调用constructor。 C++的解决方案似乎最简单也最富 逻辑性,因此这种解决方案也在Java里被采用。 也就是constructor的名字和对应类的名字保持相 同。 采用这样的解决方案很有意义,因为它使得初始化的时候constructor将被自动调用。 以下是一个含有constructor的简单的类: //: initialization/SimpleConstructor.java // Demonstration of a simple constructor. class Rock { Rock() { // This is the constructor System.out.print("Rock "); } } public class SimpleConstructor { public static void main(String[] args) { for(int i = 0; i < 10; i++) new Rock(); } } /* Output: Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock *///:~ 现在,当对象被创建的时候: new Rock(); 内存被分配给对象,然后constructor被自动调用。 这样就确保了你在进一步操纵对象之前对 象被适当地初始化。 注意,方法名称的第一个字母是小写的代码编写风格并不适用于constructor,因为constructor 的名字必须和类的名字完全匹配。 没有参数的constructor被称为default constructor。 Java文档对default constructor的特 色称呼是 no-arg constructor,但是在Java问世以前,“default constructor”的叫法已经使用 了很多年,所以我倾向于使用后者。 像一般的方法一样,constructor也可以拥有参数,以便允许 你指定对象如何创建。 前面的例子可以容易地改变成constructor采用一个参数的形式: //: initialization/SimpleConstructor2.java // Constructors can have arguments. class Rock2 { Rock2(int i) { System.out.print("Rock " + i + " "); } } public class SimpleConstructor2 { public static void main(String[] args) { for(int i = 0; i < 8; i++) new Rock2(i); } } /* Output: Rock 0 Rock 1 Rock 2 Rock 3 Rock 4 Rock 5 Rock 6 Rock 7 *///:~ constructor参数为你提供了一种对象初始化的途径。 举个例子,如果类Tree拥有一个 constructor,它采用了单个interger参数来表示要创建tree的高度,你可以像这样来创建一个Tree 对象: Tree t = new Tree(12); // 12-foot tree。 如果Tree(int)是唯一的constructor,那么编译器不会允许你以别的方式创建Tree对象。 constructor消除了类代码多的问题,使代码更容易阅读。 举个例子,在前面的代码片段中, 你并没有看到一个某种概念化的和对象创建分离开的initialize()方法被明确调用。 在Java里, 对象创建和对象初始化是合二为一的概念——你不能将它们分离开来使用。 constructor是一类特殊类型的方法,因为它没有返回值。 没有返回值和void返回值是截然不 同的,含有void返回值的方法不返回任何东西,但是你仍然有让该方法返回别的什么的选择余地。 constructor不返回任何东西并且你也没有要返回别的什么的选择余地(使用 new 表达式确实返回 了一个对新创建对象的引用,但是constructor自身没有返回值)。 假如有返回值,你也因此能够 按照自己的意愿指定返回值,编译器就有必要知道如何处置那个返回值。 练习1: (1)创建一个类,该类包含有一个未初始化的String引用。 用该类来证明引用被Java 初试化为null。 练习2: 创建一个类,该类包含一个在定义时就初始化的String field和另一个通过constructor 来初始化的String field。 这两种初始化的途径有什么不同? Method overloading 名称的使用是任何编程语言的一个重要特征。 当你创建了一个对象,你就为对应的内存区域指 定了一个名称。 方法的名称暗示着方法对应的行为。 你通过使用名称引用任何对象和方法。 经过 选择的合适的名称会构成一个人们易于阅读和改变的名称体系。 这非常像写散文——目标就是和你 的读者交流。 当把人类语言所能表达的具有细微差别的概念转换成编程语言时,会产生一个问题。 在人类语 言中,同样一个词经常可以表达一些不同的意思——这叫做overloaded。 这种表达方式是有用的, 尤其是当表达细微的区别时。 你会说,“洗衬衫”,“洗汽车”,“洗狗”。 但是你仅仅为了让 听者不必区别具体做了什么事情而强迫自己说,“用洗衬衫的方式洗衬衫”,“用洗汽车的方式洗 汽车”,“用洗狗的方式洗狗”,那就太无聊了。 大多数人类语言所表达的意思都是丰富的,所以 即便你没用一些词,你仍然能够表达清楚意思。 你不需要独特的标志符——你能从上下文推断言语 的意思。 但是大多数编程语言(尤其是C)要求你为每一个方法都设置独特的标志符(在那些编程语言里 方法通常被称为函数)。 因此不允许你拥有一个打印integers的print()函数,同时拥有另一个 打印floats的print()函数——每一个函数要求有一个有别于其它函数的名字。 在Java(和C++)里,另一个因素促成了方法名称的overloading: the constructor。 因为 constructor的名称由类的名称决定,所以只能有一个预定的名字。 但是如果你想以多种方式初始 化对象,该怎么办呢? 举个例子,设想你创建了一个类,那个类既能以通常的方式初始化自己也可 以通过读取文件里的信息来初始化。 那你就需要两个constructor,一个是default constructor, 另一个是将String作为参数的constructor,String就是用来初始化对象的文件的名字。 由于两个 都是constructor,所以他们必须使用同样的名称——类的名称。 由此,method overloading本质 上是用来允许不同的方法使用同样的名称以应对不同类型的参数。并且 method overloading 虽然 对于constructor来说是必然,但是它通常还是方便的,能够用于任何方法。 下边这个例子说明了overloaded constructor和overloaded methods。 //: initialization/Overloading.java // Demonstration of both constructor // and ordinary method overloading. import static net.mindview.util.Print.*; class Tree { int height; Tree() { print("Planting a seedling"); height = 0; } Tree(int initialHeight) { height = initialHeight; print("Creating new Tree that is " + height + " feet tall"); } void info() { print("Tree is " + height + " feet tall"); } void info(String s) { print(s + ": Tree is " + height + " feet tall"); } } public class Overloading { public static void main(String[] args) { for(int i = 0; i < 5; i++) { Tree t = new Tree(i); t.info(); t.info("overloaded method"); } // Overloaded constructor: new Tree(); } } /* Output: Creating new Tree that is 0 feet tall Tree is 0 feet tall overloaded method: Tree is 0 feet tall Creating new Tree that is 1 feet tall Tree is 1 feet tall overloaded method: Tree is 1 feet tall Creating new Tree that is 2 feet tall Tree is 2 feet tall overloaded method: Tree is 2 feet tall Creating new Tree that is 3 feet tall Tree is 3 feet tall overloaded method: Tree is 3 feet tall Creating new Tree that is 4 feet tall Tree is 4 feet tall overloaded method: Tree is 4 feet tall Planting a seedling *///:~ 使用Tree类的default constructor,Tree对象可以创建为一棵育苗,为constructor输入高度 参数,Tree对象可以创建成为苗圃中的一棵树。 为了实现上述两种对象初始化的方式,类定义里要 有一个default constructor和一个把height作为参数的constructor。 或许你也想用多种方式调用info()。 举个例子,如果你有个特别的消息想输出在屏幕上,你 就可以使用info(String)方法,如果没什么想告知,就可以使用info()。 为明显在执行同一类 任务的两个方法起不同的名字似乎不合常理。 幸运的是,method overloading允许你为那两个方法 使用同样的名称。 区分overloaded methods 如果若干方法有同样的名称,Java如何知道你选择使用的是哪一个方法呢? 有一个简单的规 则: 每一个overloaded methods必定具备一组区别于其它的参数。 如果你仔细想想这个规则,它是有道理的。 除了通过参数的类型,程序员又如何能通过别的方 式区分两个同名称的方法呢? 即便参数的顺序不同,也足以用来区别两个overloaded methods,但是通常你不想在overloaded methods中采用这种参数方式,因为它给维护代码带来麻烦。 //: initialization/OverloadingOrder.java // Overloading based on the order of the arguments. import static net.mindview.util.Print.*; public class OverloadingOrder { static void f(String s, int i) { print("String: " + s + ", int: " + i); } static void f(int i, String s) { print("int: " + i + ", String: " + s); } public static void main(String[] args) { f("String first", 11); f(99, "Int first"); } } /* Output: String: String first, int: 11 int: 99, String: Int first *///:~ 两个f()方法的参数一样,但是由于顺序不同导致这两个方法区别于对方。 Overloading with primitives primitive可以被自动的从占用较小存贮空间的数据类型转换到较大的,这一点在遇到 overloading的时候有一些令人迷惑。 下面的例子说明了primitive遇到overloaded method时会发 生些什么: //: initialization/PrimitiveOverloading.java // Promotion of primitives and overloading. import static net.mindview.util.Print.*; public class PrimitiveOverloading { void f1(char x) { printnb("f1(char) "); } void f1(byte x) { printnb("f1(byte) "); } void f1(short x) { printnb("f1(short) "); } void f1(int x) { printnb("f1(int) "); } void f1(long x) { printnb("f1(long) "); } void f1(float x) { printnb("f1(float) "); } void f1(double x) { printnb("f1(double) "); } void f2(byte x) { printnb("f2(byte) "); } void f2(short x) { printnb("f2(short) "); } void f2(int x) { printnb("f2(int) "); } void f2(long x) { printnb("f2(long) "); } void f2(float x) { printnb("f2(float) "); } void f2(double x) { printnb("f2(double) "); } void f3(short x) { printnb("f3(short) "); } void f3(int x) { printnb("f3(int) "); } void f3(long x) { printnb("f3(long) "); } void f3(float x) { printnb("f3(float) "); } void f3(double x) { printnb("f3(double) "); } void f4(int x) { printnb("f4(int) "); } void f4(long x) { printnb("f4(long) "); } void f4(float x) { printnb("f4(float) "); } void f4(double x) { printnb("f4(double) "); } void f5(long x) { printnb("f5(long) "); } void f5(float x) { printnb("f5(float) "); } void f5(double x) { printnb("f5(double) "); } void f6(float x) { printnb("f6(float) "); } void f6(double x) { printnb("f6(double) "); } void f7(double x) { printnb("f7(double) "); } void testConstVal() { printnb("5: "); f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5); print(); } void testChar() { char x = 'x'; printnb("char: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); print(); } void testByte() { byte x = 0; printnb("byte: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); print(); } void testShort() { short x = 0; printnb("short: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); print(); } void testInt() { int x = 0; printnb("int: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); print(); } void testLong() { long x = 0; printnb("long: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); print(); } void testFloat() { float x = 0; printnb("float: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); print(); } void testDouble() { double x = 0; printnb("double: "); f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); print(); } public static void main(String[] args) { PrimitiveOverloading p = new PrimitiveOverloading(); p.testConstVal(); p.testChar(); p.testByte(); p.testShort(); p.testInt(); p.testLong(); p.testFloat(); p.testDouble(); } } /* Output: 5: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) char: f1(char) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) byte: f1(byte) f2(byte) f3(short) f4(int) f5(long) f6(float) f7(double) short: f1(short) f2(short) f3(short) f4(int) f5(long) f6(float) f7(double) int: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) long: f1(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(double) float: f1(float) f2(float) f3(float) f4(float) f5(float) f6(float) f7(double) double: f1(double) f2(double) f3(double) f4(double) f5(double) f6(double) f7(double) *///:~ 你可以看到常量 5 被当作 int 来对待,所以如果在类定义中存在一个 overloaded method,它 的参数为 int 类型,这个方法就被调用了。 在所有别的类似情形中,如果你要输入的数据类型比方 法定义中的参数数据类型小,要输入的数据类型会被转换。Char 数据类型有些稍微不同,因为找不 到和它匹配的相应 char,它被转换成 int。 如果你要输入的参数数据类型比overloaded method定义的参数数据类型大,会出现什么情况 呢? 修改一下前面的程序片段就会得到答案: //: initialization/Demotion.java // Demotion of primitives and overloading. import static net.mindview.util.Print.*; public class Demotion { void f1(char x) { print("f1(char)"); } void f1(byte x) { print("f1(byte)"); } void f1(short x) { print("f1(short)"); } void f1(int x) { print("f1(int)"); } void f1(long x) { print("f1(long)"); } void f1(float x) { print("f1(float)"); } void f1(double x) { print("f1(double)"); } void f2(char x) { print("f2(char)"); } void f2(byte x) { print("f2(byte)"); } void f2(short x) { print("f2(short)"); } void f2(int x) { print("f2(int)"); } void f2(long x) { print("f2(long)"); } void f2(float x) { print("f2(float)"); } void f3(char x) { print("f3(char)"); } void f3(byte x) { print("f3(byte)"); } void f3(short x) { print("f3(short)"); } void f3(int x) { print("f3(int)"); } void f3(long x) { print("f3(long)"); } void f4(char x) { print("f4(char)"); } void f4(byte x) { print("f4(byte)"); } void f4(short x) { print("f4(short)"); } void f4(int x) { print("f4(int)"); } void f5(char x) { print("f5(char)"); } void f5(byte x) { print("f5(byte)"); } void f5(short x) { print("f5(short)"); } void f6(char x) { print("f6(char)"); } void f6(byte x) { print("f6(byte)"); } void f7(char x) { print("f7(char)"); } void testDouble() { double x = 0; print("double argument:"); f1(x);f2((float)x);f3((long)x);f4((int)x); f5((short)x);f6((byte)x);f7((char)x); } public static void main(String[] args) { Demotion p = new Demotion(); p.testDouble(); } }/* Output: double argument: f1(double) f2(float) f3(long) f4(int) f5(short) f6(byte) f7(char) *///:~ 这里,方法都采用了较小的primitive数据类型。 假如你要输入的参数数据类型又较大,那么 你必须使用cast执行转换。 如果你不这么做,编译器会 报告 软件系统测试报告下载sgs报告如何下载关于路面塌陷情况报告535n,sgs报告怎么下载竣工报告下载 出现错误的消息。 Overloading on return values 你会很自然疑问,“为什么只用类名称和方法参数列表来区分overloaded method呢?为什么不 从返回值的角度来区分呢?” 举个例子,下面这两个overloaded method定义,他们有同样的名字 和参数,很容易彼此区分。 void f() {} int f() { return 1; } 像int x=f(),只要编译器能够从上下代码行文中毫无含糊的明白编程人员的意图,从返回值 的角度区分overloaded method似乎能够行得通。 不过,在调用一个方法的时候,你也可以只管调 用它而忽视它的返回值。 这种做法通常被称作为方法地副作用而调用那个方法,因为此时你不在意 它的返回值,而是想利用这个方法别的作用。 所以如果你以副作用的方式调用上面所定义的方法: f(); Java怎么决定应当是哪一个f()被调用呢? 并且阅读这段代码的人该如何理解呢? 因为这类问 题的出现,Java不允许使用返回值来区分overloaded methods。 Default constructors 前面已经提及过,default constructor(a.ka.a“no-arg”constructor)是没有参数的 constructor,用来创建“default object。” 如果你创建了一个没有 constructors 的类,编 译器会自动为你创建一个 default constructor。 举个例子: / / : i n i t i a l i z a t i on /D e fa u l tC ons t r u c to r. j a va c l a s s B i r d { } p u b l i c c l a s s D e f a u l t C o n s t r u c t o r { p u b l i c s t a t i c v o i d m a i n ( St r i n g [ ] a r g s ) { B i r d b = n e w B i r d ( ) ; / / D e f a u l t ! } } / / / : ~ 表达式 n e w B i r d ( ) 即便是没有明确定义 constructor,它也创建了一个新的对象并且调用了 default constructor。 没有 default constructor,你就没有方法可以调用来创建对象。 不过,如 果你定义了 constructor(无论是有还是没参数),编译器也不会为你合成一个 default constructor: //: initialization/NoSynthesis.java class Bird2 { Bird2(int i) {} Bird2(double d) {} } public class NoSynthesis { public static void main(String[] args) { //! Bird2 b = new Bird2(); // No default Bird2 b2 = new Bird2(1); Bird2 b3 = new Bird2(1.0); } } ///:~ 如果你告诉编译器: n e w B i r d 2 ( ) 编译器会报错说它找不到匹配 Bird2 的 constructor。 当你编写类的时候,如果没有定 义任何 constructors,对于编译器来讲,它会认为,“你肯定需要 constructor,所以让我为 你添加一个。” 但是假如你为类写了 constructor,编译器会认为,“因为你已经写了 constructor,所以你应当知道如何处理类的初始化;你没有利用 default constructor 是因 为你有意识地避开了它。 练习 3:(1) 创建一个类,该类包含输出打印一条消息的 default constructor(没有 参数)。 创建一个该类的对象。 练习4:(1) 给上一个练习题添加一个overloaded constructor,该constructor以String 为参数并且它将 String 和你自定义的消息一同输出打印。和你自定义的消息一同输出打印。 练习 5:(2)创建一个名为 Dog 的类,在该类中定义一个 bark()overloaded method。 这 个 overloaded method 以不同的 primitive 数据类型作为参数,并且根据调用不同 overloaded method 的版本输出打印不同的叫声,比如咆哮的叫声,哭嚎的叫声。 在 Dod 类里写一个 main ()方法调用不同版本的 bark()。 练习 6:(1) 修改前面的练习题,使其中的两个 overloaded methods 各自具备两个不同 类型的参数,这两个 overloaded methods 的参数组成相同但是彼此顺序不同。 验证参数顺序 不同适用于 overloaded methods。 练习 7: (1)创建一个没有 constructor 的类,然后在 main()方里创建一个该类的对 象以验证编译器自动合成 defult constructor。 this 关键字 假如你有两个同类型的称为 a 和 b 的对象,或许你想知道怎么为这两个对象调用 peel() 方法: //: initialization/BananaPeel.java class Banana { void peel(int i) { /* ... */ } } public class BananaPeel { public static void main(String[] args) { Banana a = new Banana(), b = new Banana(); a.peel(1); b.peel(2); } } ///:~ 如果类定义里仅有一个叫做 peel()的方法,这个方法怎么知道它为对象 a 还是 b 调用 的呢? 为了让你使用方便的面向对象的语法编写代码,让你只管为对象发送消息,编译器在暗地 里为你做了一些工作。 编译器把正在调用的对象的引用当作第一个秘密参数传递给方法 peel ()。 因此两个 peel()方法的调用变成了这样: Banana.pee l (a ,1 ) ; Banana .pe e l ( b , 2 ) ; 这是编译器内部处理方式,你不可以编写像上面这样的代码,编译器也不会接受它们,但 是上面的代码却告诉你发生了什么。 设想你正在一个方法里编写代码,你想得到当前对象的引用。 既然 a 和 b 对象引用是被 编译器暗地里发送的,那么就没法识别这些暗地里使用的引用。 不过,为了得到对当前对象 的引用,java 编程语言中存在一个关键字: this。 this 关键字——只能在非 static 方法里 使用——生成方法所需要的对象引用。 你可以像看待其它对象引用一样看待 this。 记住, 如果在你的类里,你正在一个方法里调用同一个类里的另一个方法,就没有必要使用 this。 简 单地调用那个方法就是了。 this 引用会被自动地使用在那个方法的调用语句中。 因此你可 以这样写: / / : i n i t i a l i z a t i o n / A p r i c o t . j a v a public class Apricot { void pick() { /* ... */ } void pit() { pick(); /* ... */ } } ///:~ 在pit()方法里,你可以写this.pick(),但是没有必要1。编译器会替你自动加上this。 this关键字只在你需要明确使用当前对象引用的特殊情况下派上用场。 举个例子,当你想返 回当前对象的引用时,this就经常在return语句中使用。 / / : i n i t i a l i z a t i o n / L e a f . j a v a / / S i m p l e u s e o f t h e " t h i s " k e y w o r d . p u b l i c c l a s s L e a f { i n t i = 0 ; L e a f i n c r e m e n t ( ) { i + + ; r e t u r n t h i s ; } v o i d p r i n t ( ) { S y s t e m . o u t . p r i n t l n ( " i = " + i ) ; } p u b l i c s t a t i c v o i d m a i n ( St r i n g [ ] a r g s ) { L e a f x = n e w L e a f ( ) ; x . i n c r e m e n t ( ) . i n c r e m e n t ( ) . i n c r e m e n t ( ) . p r i n t ( ) ; } } / * O u t p u t : i = 3 * / / / : ~ 因为 increment()通过 this 关键字返回了当前对象的引用,所以同一个对象的多次操 作能够容易执行。 1 一些人会强迫性地将 this 写在每一个方法和 field 引用的前面,并辩解说这样写使代码更加清楚和明确。 别这样 做。 我们使用高级编程语言的一个理由就是: 高级编程语言替我们做了一些事情。 如果你在没有必要的情况下使用 this,你会迷惑和惹恼每一个阅读你代码的人,因为他们读的别的代码不会在每个地方都使用 this。 人们期待this 在必要的时候才被使用。 遵守一致和简明的代码风格会为你节省时间和金钱。 this 关键字也可用于将当前对象传递给另一个类的某个方法: / / : i n i t i a l i z a t i o n / P a s s i n g T h i s . j a v a c l a s s P e r s o n { p u b l i c v o i d e a t ( A p p l e a p p l e ) { App le pee led = app le .ge tPee le d ( ) ; S y s t e m . o u t . p r i n t l n ( " Yu m m y " ) ; } } c l a s s P e e l e r { s t a t i c A p p l e p e e l ( A p p l e a p p l e ) { / / . . . r e m o v e p e e l r e t u r n a p p l e ; / / P e e l e d } } c l a s s A p p l e { A p p l e g e t P e e l e d ( ) { r e t u r n P e e l e r . p e e l ( t h i s ) ; } } p u b l i c c l a s s P a s s i n g T h i s { p u b l i c s t a t i c v o i d m a i n ( St r i n g [ ] a r g s ) { n e w P e r s o n ( ) . e a t ( n e w A p p l e ( ) ) ; } } / * O u t p u t : Yu m m y * / / / : ~ Apple 需要调用 Peeler.peel(),peel()是一个外部实用方法,由于某种原因它有必要 是不属于 Apple 类的外部方法(或许很多不同的类都要完成这个外部方法所执行的任务,而你 又不想重复写那些代码。 传递对象自身给外部方法,相应的类必须使用 this。 练习 8: (1)创建一个定义了两个方法的类。 在第一个方法内部,调用第二个方法两 次: 第一次不使用 this,第二次使用 this——仅为了明白这样写代码它也是可以工作地;你 不应该在实践中使用这种形式。 从 constructors 中调用 constructors 当你为某个类写了好几个 constructors 时,有时候为了避免重复代码你愿意从一个 constructor 内调用另一个 constructor。 使用 this 关键字你能够实现这样的调用。 正常情况下,当提到 this 时,它的意思是“这个对象”或者“当前对象,”通过它自己 this 生成了对当前对象的引用。 但是在 constructor 内部,当你为 this 关键字输入参数组 时,this 的意思会有所不同。 这时 this 明确调用了参数组匹配的 constructor。 因此你有 了调用别的 constructors 的简明方式。 //: initialization/Flower.java // Calling constructors with "this" import static net.mindview.util.Print.*; public class Flower { int petalCount = 0; String s = "initial value"; Flower(int petals) { petalCount = petals; print("Constructor w/ int arg only, petalCount= " + petalCount); } Flower(String ss) { print("Constructor w/ String arg only, s = " + ss); s = ss; } Flower(String s, int petals) { this(petals); //! this(s); // Can't call two! this.s = s; // Another use of "this" print("String & int args"); } Flower() { this("hi", 47); print("default constructor (no args)"); } void printPetalCount() { //! this(11); // Not inside non-constructor! print("petalCount = " + petalCount + " s = "+ s); } public static void main(String[] args) { Flower x = new Flower(); x.printPetalCount(); } } /* Output: Constructor w/ int arg only, petalCount= 47 String & int args default constructor (no args) petalCount = 47 s = hi *///:~ constructor Flower(String s,int petals)表明当你使用 this 调用某个 constructor 的时候,你不可以使用两次 this。 另外,在某个 constructor 内部调用另一个 constructor 必须是你要做的第一件事情,你不这样做就会得到一个编译错误消息。 等一下你就会明白这个例子也说明了 this 的另一个用途。 因为参数的名字 s 和 member data 的名字 s 相同,存在歧义的情况。 你可以使用 this.s 解决这个问题,意思是说你在引 用 member data。 在 Java 代码里你会经常看到这种形式的应用,本书中也大量使用了。 在 printPetalCount()里,你能明白除了 constructor 编译器不会让你在任何方法内部 调用 constructor。 练习 9: (1)创建一个定义了两个 constructors(overloaded)的类。 使用 this,在 第一个 consructor 里调用第二个 constructor。 static 的涵义 头脑里有了this关键字的概念,你就能更加充分地理解定义一个方法为static的意思。 static的意思是你不能将this用于那个特定的定义为static的方法。 你不可以在static方法 里调用non-static方法2(尽管反过来是可以的),你可以在没有任何对象的情况下为类自身调 用static方法。 实际上,这就是static方法的主要意图。 看上去这似乎是在创建全局方法。 不过,全局方法在Java里不允许,Java允许为类定义static方法从而允许该方法访问别的 static方法和static fields。 一些人争论说 static 方法不属于 object-oriented 的范畴,因为这种方法带有全局方法 的意思;使用 static 方法时,既然不能有 this 的概念,你就没有为对象发送消息。 这或许 是个合理的意见,假如你发现自己使用了很多 static 方法,就应该重新思考一下你的策略。 不 过,statics 是实用地,很多时候你确实需要它们,所以它们到底是不是“正确的面向对象的 概念”就应该留给理论家来论证。 清理: finalization 和垃圾收集 程序员们明白初始化的重要性,但是经常忘记清理工作的重要性。 必定谁会觉得有必要 清理一个 int? 但是对于类库,对象用完了就不置之不理了可不总是安全地。 当然,Java 备 有垃圾收集器以便收回那些不再使用的对象的存贮空间。 现在考虑 一种特殊的情形: 设想 在没有使用 new 的情况下,你的对象分配有“特殊”的存贮空间。 垃圾收集器只知道如何释 2 有种情况是可能发生的,就是如果你传递对象引用给 static 方法(在 static 方法里也可以创建对象), 然后通过引用 (这里就是this),你能够调用non-static 方法和 non-static fields。 但是假如你真想像这样编写一些代码,那你其实 只是编写了一个平常的、non-static 方法。 放使用 new 创建的对象的存贮空间,所以它不知道怎么释放对象的“特殊”存贮空间。 为了 处理这种情形,Java 提供了一种称为 fina
本文档为【JAVA编程思想第四版中文版】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_740299
暂无简介~
格式:pdf
大小:461KB
软件:PDF阅读器
页数:49
分类:互联网
上传时间:2011-07-25
浏览量:34