一个人至少拥有一个梦想,有一个理由去坚强

心若没有栖息的地方,到哪里都是在流浪

java编程思想——第五章初始化与清理

随着计算机革命的发展,“不安全”的编程方式已逐渐成为编程代价高昂的主因之一。

初始化和清理(cleaup)正是涉及安全的两个问题。

5.1用构造器确保初始化

构造器的名称与类名完全相同。没有约束与返回值。

Class Tree1 {
   // This is the constructor
   Tree1() {
      system.out.print("it is tree");
   }
}

不接受任何参数的构造器叫做默认构造器,java文档中通常使用术语叫无参构造器。构造 器也能带有形式参数,以便指定如何创建对象。例如:

Class Tree2 {
   // This is the constructor
   Tree2(int hight) {
      system.out.print("tree" + i +" ");
   }
}

有了构造器形式参数,就可以初始化对象时提供实际参数。例如,创建一个高度为10的Tree对象:

Tree2 t = new Tree2(10);

注意:当没有显示的创建构造器时,java会默认创建无参构造器。在一个类中,即使我们没有创建构造器,例如在Tree1类中,我们创建对象 Tree1 t = new Tree1() 这样是完全可以的。但是如果我们像在Tree2类中Tree2(int)是Tree2类中唯一的构造器,那么编译器将不会允许你以其他任何方式创建Tree对象:Tree2 t2 = new Tree2();这样会报错。除非你像下面这样创建类:

Class Tree2 {
   // This is the constructor
   Tree2() {
      system.out.print("It is tree");
   }
   Tree2(int hight) {
      system.out.print("tree" + i +" ");
   }
}

5.2方法重载

所谓方法则是给某个动作取名字。通过使用名字,你可以引用所有的对象和方法。

方法重载:方法名相同而形式参数不同,与返回值无关。

5.2.1区分重载方法

要区分重载方法,也正是区分重载方法的参数。形式参数不同包括:参数个数不同,参数类型不同,不同参数类型同个数的顺序不同。

5.2.2涉及基本类型的重载

基本类型能从一个“较小”的类型自动提升至一个“较大”的类型。此类型一旦涉及到重载,可能会造成一些混淆。

如果某个重载方法有接受相应类型的参数,它就会被调用。置于其他情况。如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型就会被提升。char型略有不同,如果无法找到恰好接受char参数的方法,就会把char直接提升至int类型。

若方法接受较小的基本类型作为参数,如果传入的实际参数较大,就得通过类型转换来执行窄化转换。如果不这样做,编译器就会报错。

5.2.3 为什么不以返回值区分重载方法

void f() {  }
int f() { return 1;}

比如上面两个方法,你会不会疑惑,虽然它们有相同的名字和形式参数,但是却很容易区分它们,以返回值区分重载方法还是可以的嘛。

只要编译器可以根据语境明确判断出语义,比如在int x = f()中,那么的确可以据此区分重载方法。不过,有时你并不关心方法的返回值,你想要的是方法调用的其他效果(这常被称为“为了副作用而调用”),这时你可能会调用方法而忽略其返回值。所以如果像下面这样调用方法:

f();

此时java如何才能判断该调用哪一个f()呢?别人该如何理解这种代码呢?因此,根据方法的返回值来区分重载方法是行不通的。

5.4 this关键字

this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。this的用法和其他对象引用并无不同。但是要注意,如果在方法内部调用同一个类中的另一个方法,就不必使用this,直接调用即可。当前方法中的this引用会自动应用于同一类中的其他方法。

需要返回当前对象的引用时,就可以在return语句中这样写:

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 x = new Leaf();
    x.increment().increment().increment().print();
  }
}

/**Output:
  i = 3
**/

由于increment()通过this关键字返回了对当前对象的引用,所以很容易在一条语句里对同一个对象执行多次操作。

this关键字对于将当前对象传递给其他方法也很有用:

class Person {
  public void eat(Apple apple) {
    Apple peeled = apple.getPeeled();
    System.out.println("Yummy");
  }
}

class Peeler {
  static Apple peel(Apple apple) {
    //... remove peel
    return apple;  //Peeled
 }
}

class Apple {
  Apple getPeeled() {
   return Peeler.peel(this);
 }
}

public class PassingThis {
  public static void main(String[]  args) {
     new Person().eat(new Apple());
  }
}

/**Output:
  Yummy
**/

Apple需要调用Peeler.peel()方法,它时一个外部的工具方法,将执行由于某种原因而必须放在Apple外部的操作(也许是因为该外部的方法要应用于许多不同的类,而你却不想重复这些代码)。为了将其自身传递给外部方法,Apple必须使用this关键字。

5.4.1 在构造器中调用构造器

可能为一个类写了多个构造器,有时想在一个构造器中调用另一个构造器,以避免重复代码。可用this关键字做到这一点。

通常写this的时候,都是指“这个对象”或者“当前对象”,而且它本身表示对当前对象的引用。在构造器中,如果为this添加了参数列表,那么就有了不同的含义。这将产生对符合此参数列表的某个构造器的明确调用。

注意:用this调用构造器,不能同时调用两个,并且必须将构造器调用置于最起始处,否则编译器会报错。除构造器之外,编译器禁止在其他任何方法中调用构造器,

this还用来区分成员变量和参数。

5.4.2 static的含义

static方法就是没有this的方法。在static方法的内部不能调用非静态方法,反过来倒是可以的。在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。

5.5 清理:终结处理和垃圾回收

只看明白java有垃圾回收器,能够自动垃圾回收。其他的看不明白。后续看明白再补充。

5.6成员初始化

java尽力保证:所有变量在使用前都得到恰当的初始化。对于方法的局部变量,java以编译时错误的形式来贯彻这些保证。所以如果写成:

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

如上就会得到出错的消息,尚未初始化。

5.6.1 指定初始化

定义类成员变量的地方为其赋值。

5.7 构造器初始化

可以用构造器来进行初始化。在运行时刻,可以调用方法或执行某些动作来确定初值。但是要牢记:无法阻止自动初始化的进行,它将在构造器被调用之前发生。

 

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

那么i首先会被置于0,然后变成7.对于所有基本类型和对象引用,包括在定义时已经指定初值的变量,这种情况都是成立的;因此,编译器不会强制你一定要在构造器的某个地方或在它们之前对元素进行初始化—-因为初始化早已得到保证。

5.7.1 初始化顺序

在类的内部,变量定义的先后顺序决定了初始化的顺序。

5.7.2 静态数据的初始化

无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始化值就是null。

一个类中有静态数据成员,如果不创建此类对象,也不引用这个静态数据成员,那么静态的数据成员永远都不会被创建。

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

1.即使没有显示地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,java解释器必须查找类路径,以定位Dog.class文件。

2.然后载入Dog.class(后面会学到,这将创建一个Class对象)。有关静态初始化的所有动作都会执行。因此静态初始化只在Class对象首次加载的时候进行一次。

3.当用new Dog()创建对象的时候,首先将在堆上为Dog()对象分配足够的存储空间。

4.这块存储空间会被清零,这就自动地将Dog对象中地所有基本类型数据都设置成了默认值(对数字来说就是0.对布尔型和字符型也相同),而引用则被设置成了null。

5.执行所有出现于字段定义处的初始化动作。

6.执行构造器。

5.7.3 显示的静态初始化

java允许将多个静态初始化动作组织成一个特殊的“静态子句”,也叫“静态块”。像下面这样:

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

静态初始化动作只进行一次。

5.7.4 非静态实例初始化

java中也有被称为实例初始化的类似语法,用来初始化每一个对象的非静态变量。例如:

 

class Mug {
  Mug(int marker) {
   print("Mug("+ marker +")");
  }
  void f(int marker) {
   print("f(" + marker + ")");
 }
}
public class Mugs {
  Mug mug1;
  Mug mug2;
  {
    mug1 = new Mug(1);
    mug2 = new Mug(2);
    print("mug1 & mug2 initailized");
  }

 Mugs() {

  print("Mugs()");
 }
 
 Mugs(int i) {
   print("Mugs(int)")
  
 }

 public static void main(String[] args) {

   print("Inside main()");
   new Mugs();
   print("new Mugs() completed");
   new Mugs(1);
   print("new Mugs(1) completed");
  
 }
}

/**Output:
  Inside main()
  Mug(1)
  Mug(2)
  mug1 & mug2 initailized
  Mugs()
  new Mugs() completed
  Mug(1)
  Mug(2)
  mug1 & mug2 initailized
  Mugs(int)
  new Mugs(1) completed
  **/

看起来它与静态初始化子句一模一样,只不过少了static关键字。这种语法对于支持“匿名内部类”的初始化是必须的,但是它也使得你可以保证无论调用了那个显示构造器,某些操作都会发生。从输出中可以看出实例初始化子句是在两个构造器之前执行的。

5.8数组初始化

创建数组格式:

int[] a1; 或者 int a1[]两种方式都可以,前者java中更为普遍。

数组初始化:

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

java中可以将一个数组赋值给另一个数组。如上所示:a2 = a1,其实真正做的只是复制了一个引用。如若a2变化了,a1也会随着变化,因为a2和a1指向的是一个对象。

5.8.1 可变参数列表

格式:

static void printArray(Object ... args) {
   for (Object obj : args) {
    System.out.print( obj + " ");
  }
}

如上可知,可变参数可看为数组。

5.9 枚举类型

javaSE5中添加一个看似很小特性,即enum类型。

初始化格式简单例子:

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

enum有一个特别实用的特性,即它可以在switch语句内使用。

 

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注