初始化与清理
随着计算机革命的发展,“不安全”的编程方式已逐渐成为编程代价高昂的主因之一。
C++引入了构造嚣(constructor)的概念,这是一个在创建对象时被自动调用的特殊方法。Java中也采用了构造器,并额外提供了“垃圾回收器”。对于不再使用的内存资源,垃圾回收器能自动将其释放。
5.1 用构造器确保初始化
假定每个类中都有一个initizlize()方法,该方法提醒你在使用对象之前,应先调用initizlize()方法,然而用户必须记得自己去调用这个方法。Java中,通过提供构造器,类的设计者可以确保每个对象都得到初始化。
//:initialization/SimpleConstructor.java
//Demonstration of a simple constructor.
class Rock
{
Rock()
{
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();将会为对象分配存储空间,并调用相应的构造器。这就确保了在你能操作对象之前,它已经被恰当地初始化了。
请注意,由于构造器的名称必须与类名完全相同,所以“每个方法首字母小写”的编码风格并不适用于构造器。
不接受任何参数的构造器叫做默认构造器。
//:initialization/SimpleConstructor2.java
class Rock2
{
Rock2(int i)
{
System.out.print("Rock2 "+i+" ");
}
public static void main(String[] args)
{
new Rock2(i);
}
}
有了构造器形式参数,就可以在初始化对象时提供实际参数。例知,假设类Tree有一个构造器,它接受一个整型变量来表示树的高度,就可以这样创建一个Tree对象:
Tree t = new Tree(12); //12-foot tree
如果Tree(int)是Tree类中唯一的构造器,那么编译器将不会允许你以其他任何方式创建Tree对象。
5.2 方法重载##
构造器是强制重载方法名的另一个原因。既然构造器的名字已经由类名所决定,就只能有一个构造器名。那么要想用多种方式创建一个对象该怎么办呢?假设你要创建一个类,既可以用标准方式进行初始化,也可以从文件里读取信息来初始化。这就需要两个构造器:一个默认构造器,另一个取字符串作为形式参数——该字符串表示初始化对象所需的文件名称。由于都是构造器,所以它们必须有相同的名字,即类名。为了让方法名相同而形式参数不同的构造器同时存在,必须用到方法重载。
class Tree
{
int height;
public Tree()
{
height = 0;
System.out.println("种植树苗");
}
public Tree(int initialHeight)
{
height = initialHeight;
System.out.println("新创建了一颗 " + height + " 高的树");
}
void info()
{
System.out.println("本树高为 " + height);
}
void info(String s)
{
System.out.println(s + ":本树高为 " + height);
}
}
public class Overloading
{
public static void main(String[] args)
{
for (int i = 0; i < 3; i++)
{
Tree t = new Tree(i);
t.info();
t.info("重载的方法");
}
//重载构造器
new Tree();
}
}
5.2.1 区分重载方法
规则很简单:每个重载的方法都必须有一个独一无二的参数类型列表。
甚至参数顺序的不同也足以区分两个方法。
5.3 默构造器
默认构造器是没有形式参数的——它的作用是创建一个默认对象。
如果你写的类中没有构造器,则编译器会自动帮你创建一个默认构造器。
class Bird
{
}
public class DefaultConstructor
{
public static void main (String[] args)
{
Brid b = new Bird();//Default
}
}
表达式 new Bird()行创建了一个新对象,并调用其默认构造器——即使你没有明确定义它。没有它的话,就没有方法可调用,就无法创建对象。但是,如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器:
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);
}
}
如果你写的类中没有构造器,则编译器会自动帮你创建一个默认构造器。
如果你定义了构造器,编译器不会自动创建默认构造器。
5.4 this关键字##
this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。
/**
* this返回当前对象
* @author Administrator
*
*/
public class Leaf {
int i = 0;
Leaf increment(){
i++;
return this;
}
void print(){
System.out.println("i= "+i);
}
public static void main(String[] args) {
Leaf leaf = new Leaf();
leaf.increment().increment().print();
}
}
1)在构造器里调用构造器:
public class Flower {
int petalCount = 0;
String s = "initial value";
public Flower(int petals) {
petalCount = petals;
System.out.println("Constructor w/ int arg only,"
+ "petalCount = "+ petalCount);
}
public Flower(String ss) {
System.out.println("Constructor w/ int arg only,"
+ "s = "+ ss);
s = ss;
}
public Flower(String s,int petals) {
this(petals);
//this(s) 不能调用两次
this.s = s;
System.out.println("String & int args");
}
public Flower() {
this("hi",47);
System.out.println("default constructor ,no args");
}
void printPetalCount(){
System.out.println("PetalCount = "+petalCount +" s" +s);
}
public static void main(String[] args) {
Flower flower = new Flower();
flower.printPetalCount();
}
}
输出结果为:
Constructor w/ int arg only,petalCount = 47
String & int args
default constructor ,no args
PetalCount = 47 shi
尽管可以使用this调用一个构造器,但却不能调用两个,此外,构造器必须置于最起始处,否则编译器报错。
由于参数s和数据成员s名字相同,容易产生歧义,可以用this.s = s;
解决。
2)static的含义
static方法就是没有this的方法。
static方法内部不能调用非静态方法。(可以传递一个对象引用,然后通过引用,可以调用非静态方法和非静态数据成员)
5.5 终极处理和垃圾回收
- 对象可能不被垃圾回收。
- 垃圾回收并不等于“析构”
- 垃圾回收只与对象有关。
垃圾回收器技术:
引用计数技术。自适应技术。
5.6 成员初始化##
数据默认值:
5.6.1 指定初始化
如果想为某个变量赋初值,该怎么做呢?有一种很直接的办法,就是在定义类成员变量的地方为其赋值(注意在C++里不能这样做,尽管C++的新手们总想这样做)。以下代码片段修改了InitialValues类成员变量的定义,直接提供了初值。
public class Init
{
boolean t = true;
char c = 'x';
byte b = 47;
short s = 0xff;
int i = 999;
long l = 1;
float f = 3.14f;
double d = 3.14159;
}
5.7 构造器初始化
5.7.1 初始化顺序
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。例如:
public class OrderOfInitialValue {
public static void main(String[] args) {
House house = new House();
// house.f();
}
}
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);
}
输出结果为:
window(1)
window(2)
window(3)
House()
window(33)
5.7.2 静态数据的初始化
无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始化值就是null。
静态初始化只有在必要时刻才会进行。如果不创建Table对象,也不引用Table.b1或Table.b2,那么静态的Bowl b1和b2永远都不会被创建。只有在第一个Table对象被创建(或者第一次访问静态数据)的时候,它们才会被初始化。此后,静态对象不会再次被初始化。
5.7.3 显式的静态初始化
Java允许将多个静态初始化动作组织成一个特殊的“静态子句”(有时也叫做“静态块”)。
public class Spoon
{
static int i;
static
{
i = 47;
}
}
5.8 数组初始化
数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或者基本数据类型序列。
数组定义 int [ ] a 或者int a [ ]。现在拥有的只是对数组的引用,并没有给数组本身分配存储空间。
数组初始化方法:
int [] a = {1,2,3,4,5,6};
Integer[] a = new Integer[10];
Integer[] a = new Integer[]{
1,2,3,4,5
};
5.8.1 可变参数列表
public class AutoboxingVarargs
{
static void f(Integer... args)
{
for (Integer i : args)
System.out.print(i + " ");
System.out.println();
}
public static void main(String[] args)
{
f(new Integer(1), new Integer(2));
f(4, 5, 6, 7, 8, 9);
f(10, new Integer(11), 12);
}
}/*Output
1 2
4 5 6 7 8 9
10 11 12
*///:~
5.9 枚举类型
声明一个枚举类:
public enum Spiciness {
NOT,MILD,MEDIUM,HOT,FLAMING
}
枚举类与switch结合,作为switch的条件。