[JAVA编程思想]chap4初始化和清除

4.1 用构建器自动初始化

每个类都调用一次 initialize()。
在 Java 中,由于提供了名为“构建器”的一种特殊方法,所以类的设计者可担保每个对象都会得到正确的初始化。
构建器的名字与类名相同。
构建器也能使用自变量,以便我们指定对象的具体创建方式。利用构建器的自变量,我们可为一个对象的初始化设定相应的参数。

4.2 方法过载

为了让相同的方法名伴随不同的自变量类型使用,“方法过载”是非常关键的一项措施。

4.2.1 区分过载方法

每个过载的方法都必须采取独一无二的自变量类型列表

4.2.2 主类型的过载

在其他所有情况下,若我们的数据类型“小于”方法中使用的自变量,就会对那种数据类型进行“转型”处理。
若我们的自变量范围比它宽,就必须用括号中的类型名将其转为适当的类型。

4.2.3 返回值过载

4.2.4 默认构建器

若创建一个没有构建器的类,则编译程序会帮我们自动创建一个默认构建器。
如果已经定义了一个构建器(无论是否有自变量),编译程序都不会帮我们自动合成一个。

4.2.5 this 关键字

假定我们在一个方法的内部,并希望获得当前对象的句柄。由于那个句柄是由编译器“秘密”传递的,所以没有标识符可用。然而,针对这一目的有个专用的关键字:this。this 关键字(注意只能在方法内部使用)可为已调用了其方法的那个对象生成相应的句柄。

1.在构建器里调用构建器

若为一个类写了多个构建器,那么经常都需要在一个构建器里调用另一个构建器,以避免写重复的代码。可用this 关键字做到这一点。
由于自变量s 的名字以及成员数据s 的名字是相同的,所以会出现混淆。为解决这个问题,可用 this.s 来引用成员数据。
在print()中,我们发现编译器不让我们从除了一个构建器之外的其他任何方法内部调用一个构建器。

2. static 的含义

static意味着一个特定的方法没有this。我们不可从一个 static 方法内部发出对非 static 方法的调用,尽管反过来说是可以的。
若将一个 static 方法置入一个类的内部,它就可以访问其他static 方法以及static 字段。

4.3 清除:收尾和垃圾收集

一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用 finalize(),而且只有在下
一次垃圾收集过程中,才会真正回收对象的内存。所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作。
C++的对象肯定会被清除(排开编程错误的因素),而Java 对象并非肯定能作为垃圾被“收集”去。

4.3.1 finalize()用途何在

垃圾收集只跟内存有关!

4.3.2 必须执行清除

Java 不允许我们创建本地(局部)对象——无论如何都要使用 new。
但在 Java 中,没有“delete”命令来释放对象,因为垃圾收集器会帮助我们自动释放存储空间。

4.4 成员初始化

一个类的所有基本类型数据成员都会保证获得一个初始值。

4.4.1 规定初始化

一个最直接的做法是在类内部定义变量的同时也为其赋值(注意在C++里不能这样做,尽管C++的新手们总“想”这样做)。

4.4.2 构建器初始化

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

4.5 数组初始化

数组代表一系列对象或者基本数据类型,所有相同的类型都封装到一起——采用一个统一的标识符名称。数组的定义和使用是通过方括号索引运算符进行的([])。为定义一个数组,只需在类型名后简单地跟随一对空方括号即可:
int[ ] al;
也可以将方括号置于标识符后面,获得完全一致的结果:
int al[ ];
为了给数组创建相应的存储空间,必须编写一个初始化表达式。
Java 数组从元素 0 开始计数,所以能索引的最大元素编号是“length-1”。
由于数组的大小是随机决定的(使用早先定义的pRand()方法),所以非常明显,数组的创建实际是在运行期间进行的。除此以外,从这个程序的输出中,大家可看到基本数据类型的数组元素会自动初始化成“空”值(对于数值,空值就是零;对于 char,它是 null;而对于boolean,它却是 false)。

4.5.1 多维数组

4.6 练习

1.使用未初始化的String引用创建一个类。 证明此引用由Java初始化为null。
package initialization;

public class E01_StringRefInitialization{
	String s;
	public static void main(String args[]){
		E01_StringRefInitialization sri=new E01_StringRefInitialization();
		System.out.println("sri.s="+sri,s);
	}
}
2.创建一个具有在定义点初始化的String字段的类,以及由构造函数初始化的另一个类。 这两种方法有什么区别?
package initialization;

public class E02_StringInitialization{
	String s1="Initialized at definition";
	String s2;
	public E02_StringInitialization(String s2i){
		s2=s2i;
	}
	public static void main(String args[]){
		E02_StringInitialization si=new E02_StringInitialization("Initialized at construction");
		System.out.println("si.s1="+si.s1);
		System.out.println("si.s2="+si.s2);
	}
}

在输入构造函数之前初始化s1字段; 从技术上讲,s2字段也是如此,在创建对象时将其设置为null。 更灵活的s2字段允许您在调用构造函数时选择赋予它的值,而s1总是具有相同的值。

3.使用默认构造函数(不带参数的构造函数)创建一个打印消息的类。 创建此类的对象。
package initialization;

public class E03_DefaultConstructor{
	E03_DefaultConstructor(){
		System.out.println("Default constructor");
	}
	public static void main(String args[]){
		new E03_DefaultConstructor();
	}
}
4.将重载的构造函数添加到练习3中,该构造函数接受String参数并将其与消息一起打印。
public class E04_OverloadedConstructor{
	E04_OverloadedConstructor(){
		System.out.println("Default constructor");
	}
	E04_OverloadedConstructor(String s){
		System.out.println("String arg constructor");
		System.out.println(s);
	}
	public static void main(String args[]){
		new E04_OverloadedConstructor();
		new E04_OverloadedConstructor("Overloaded");
	}
}
5.使用重载的bark()方法创建一个名为Dog的类。 您的方法应该根据各种原始数据类型重载,并且应该打印不同类型的barking,howling等,具体取决于调用哪个重载版本。 编写一个调用所有不同版本的main()。
class Dog{
	public void bark(){
		System.out.println("Default bark!");
	}
	public void bark(int i){
		System.out.println("int bark=howl");
	}
	public void bark(double d){
		System.out.println("double bark=yip");
	}
}

public class E05_OverloadedDog{
	public static void main(String args[]){
		Dog dog=new Dog();
		dog.bark();
		dog.bark(1);
		dog.bark(1.1);
	}
}
6.修改练习5,因此两个重载方法有两个不同类型的参数,但相对于彼此的顺序相反。 验证这是否有效。
7.创建一个没有构造函数的类,然后在main()中创建该类的对象,以验证是否自动合成了默认构造函数。

因为可以调用构造函数,所以即使您看不到它,也知道它已创建。

8.使用两种方法创建一个类。 在第一种方法中,调用第二种方法两次以查看它是否有效,第一次不使用此方法,第二次使用此方法。
public class E08_ThisMethodCall{
	public void a(){
		b();
		this.b();
	}
	public void b(){
		System.out.println("b() called");
	}
	public static void main(String args[]){
		new E08_ThisMethodCall().A();
	}
}

输出:

b() called 
b() called

此练习显示这指的是当前对象。 仅在必要时使用this.b()样式的方法调用; 否则你可能会混淆代码的读者/维护者。

9.创建一个包含两个(重载)构造函数的类。 使用它,在第一个内部调用第二个构造函数。
public class E09_ThisConstructorCall{
	public E09_ThisConstructorCall(String s){
		System.out.println("s="+s);
	}
	public E09_ThisConstructorCall(int i){
		this("i="+i);
	}
	public static void main(String args[]){
		new E09_ThisConstructorCall("String call");
		new E09_ThisConstructorCall(47);
	}
}
10.使用finalize()方法创建一个打印消息的类。 在main()中,创建一个类的对象。 解释你的程序的行为。
public class E10_FinalizeCall{
	protected void finalize(){
		System.out.println("finalize() called");
	}
	public static void main(String args[]){
		new E10_FinalizeCall();
	}
}
11.修改练习10,以便始终调用finalize()。

按顺序调用System.gc()和System.runFinalization()可能会但不一定会调用你的终结器(从一个版本的JDK到另一个版本,finalize的行为是不确定的。)对这些方法的调用只是一个请求; 它不能确保终结器实际运行。 最终,没有任何保证会调用finalize()。

12.创建一个名为Tank的类,可以填充和清空,其终止条件是在清理对象时必须为空。 写一个finalize()来验证这个终止条件。 在main()中,测试使用Tank时可能出现的情况。
class Tank{
	static int counter;
	int id=counter++;
	boolean full;
	public Tank(){
		System.out.println("Tank"+id+"created");
		full=true;
	}
	public void empty(){full=false;}
	protected void finalize(){
		if(full)
			System.out.println(
				"Error:tank"+id+"must be empty at cleanup");
		else
			System.out.println("Tank"+is+"cleaned up OK");
		public String toString(){return"Tank"+id;}
	}
	
	public class E12_TankWithTerminationCondition{
		public static void main(String args[]){
			new Tank().empty();
			new Tank();
			System.gc();                 //强迫finalization
			System.runFinalization();
		}
	}

我们没有创建对类型为Tank的两个实例的引用,因为当调用System.gc()时这些引用将在范围内,因此它们不会被清除,因此它们不会被最终确定。 另一种选择是在需要对垃圾进行垃圾收集时将引用设置为零。 修改上面的示例以尝试此方法。
你永远不能确定将调用终结器,因此它们的效用是有限的。 例如,终结器可以在运行时检查对象的状态,以确保它们已被正确清理。

14.创建一个具有静态String字段的类,该字段在定义点初始化,另一个由静态块初始化。 添加一个静态方法,打印两个字段并演示它们在使用之前都已初始化。
public class E14_StaticStringInitialization { 
	static String s1 = "Initialized at definition"; 
	static String s2; 
	static { s2 = "Initialized in static block"; } 
	public static void main(String args[]) { 
		System.out.println("s1 = " + s1); 
		System.out.println("s2 = " + s2); 
	} 
}
15.使用“实例初始化”初始化的String创建一个类。
public class E15_StringInstanceInitialization {
	String s; 
	{ s = "'instance initialization'"; } 
	public E15_StringInstanceInitialization() { 
		System.out.println("Default constructor; s = " + s); 
	} 
	public E15_StringInstanceInitialization(int i) { 
		System.out.println("int constructor; s = " + s); 
	} 
	public static void main(String args[]) { 
		new E15_StringInstanceInitialization(); 
		new E15_StringInstanceInitialization(1); 
	} 
}
16.为String对象数组的每个元素分配一个字符串。 使用for循环打印数组。
17.使用带有String参数的构造函数创建一个类。 在创建期间,打印参数。 创建对此类的对象引用数组,但不要创建要分配给数组的对象。 运行程序时,请注意是否打印了构造函数调用的初始化消息。
class Test { 
	Test(String s) { 
		System.out.println("String constructor; s = " + s); 
	} 
} 
public class E17_ObjectReferences { 
 // You can define the array as a field in the class: 
	Test[] array1 = new Test[5]; 
	public static void main(String args[]) { 
 // Or as a temporary inside main: 
		Test[] array2 = new Test[5]; 
	} 
}
18.创建要附加到练习17的引用数组的对象。
public class E18_ObjectArray { 
	public static void main(String args[]) { 
		Test[] array = new Test[5]; 
		for(int i = 0; i < array.length; i++) 
			array[i] = new Test(Integer.toString(i)); 
	} 
}

Integer.toString()返回表示指定整数的String对象。

19.编写一个采用vararg String数组的方法。 验证您是否可以将逗号分隔的字符串列表或字符串[]传递给此方法。
public class E19_VarargStringArray {
	static void printStrings(String... strs) { 
		for(String s : strs) 
		System.out.println(s); 
	} 
	public static void main(String args[]) { 
		printStrings("These", "are", "some", "strings"); 
		printStrings( 
			new String[] { "These", "are", "some", "strings" } 
		); 
	} 
}

NOTE:

for (String str : s){}

这种写法是增强for循环,

for(int i = 0;i < s.length(); i++){ 
String str = s[i]; //当成数组的写法
}

编译器会认为:
1.创建名称为str 的String变量。
2.将s的第一个元素赋给str 。
3.执行重复的内容。
4.赋值给下一个元素str 。
5.重复执行至所有的元素都被运行为止

优点:
这种写法让我们代码看起来更加的简洁
缺点话:
1只能顺次遍历所有元素,无法实现较为复杂的循环
2对于数组,不能方便的访问下标值;
3对于集合,与使用Interator相比,不能方便的删除集合中的内容(在内部也是调用Interator).
4 除了简单遍历并读取其中的内容外,不建议使用增强的for循环

20.创建一个使用varargs而不是普通main()语法的main()。 打印生成的args数组中的所有元素。 使用各种数量的命令行参数进行测试。
21.创建六种最低面值纸币的枚举。 循环遍历values()并打印每个值及其序数()。
enum PaperCurrencyTypes { 
	ONE, TWO, FIVE, TEN, TWENTY, FIFTY 
} 
public class E21_PaperCurrencyTypesEnum { 
	public static void main(String args[]) { 
		for(PaperCurrencyTypes s : PaperCurrencyTypes.values()) 
			System.out.println(s + ", ordinal " + s.ordinal()); 
	}	 
}
22.在练习21中为枚举写一个switch语句。对于每种情况,输出该特定货币的描述。
package initialization;
import static net.mindview.util.Print.*;

public class E22_PaperCurrencyTypesEnum2{
	static void describe(PaperCurrencyTypes pct){
		printnb(pvb+"has a portrait of");
		switch(pct){
			case ONE:print("George Washington");break;
			case Two:print("Thomas Jefferson");break;
			case Five:print("Abraham Lincoln");break;
			case TEN:print("Alexander Hamilton");break;
			case TWRNTY:print("Andrew Jackson");break;
			case SISTY:print("U.S. Grant");break;
		}
	}
	public static void main(String args[]){
		for(PaperCurrencyTypes s:PaperCurrencyTypes.values())
			describe(s);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值