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

时间:2021-10-21 19:41:55

随着计算机的发展,“不安全”的编程方式已逐渐成为编程代价高昂的主因之一。
1. 用构造器确保初始化
   Java中有默认构造器,是无参构造器,所以如果必要,还是使用无参构造器。也有带参数的构造器,以便指定如何创建对象。构造器确保对象被创建的时候初始化,而且Java会在用户有能力操作对象之前自动调用了相应的构造器。
  构造器命名规则:1)构造器采用与类相同的名称。2)“每个方法首字母小写”的编程风格不适用于构造器。
  注意点:在Java中,“初始化”和“创建”捆绑在一起,两者不能分离。
2. 方法重载
  既然构造器也是方法,那么就可以对构造器进行重载(有参构造器和无参构造器),构造器重载确保了创建对象的不同方式。当然也可以对类中的方法进行重载。这是最基本的。
  2.1区分重载方法
  1)每个重载的方法都必须有一个独一无二的参数类型列表。
  2)参数类型相同,但顺序不同也可以区分不同的方法,但是一般不要这么做,因为这会使代码难以维护。
  2.2涉及基本类型的重载
  1)如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型就会被提。char类型稍有不同,如果无法找到适当的char参数的方法,就会把char直接提升为int。
  2)如果传入的实际参数大于重载方法中声明的参数,就会通过类型转化来执行窄化转化。
  2.3以返回值区分重载方法:这种方法行不通,所以还是不要用的好。
3. 默认构造器
  1)默认构造器的作用是创建一个“默认对象”。如果类中没有构造器,编译器自动帮你创建一个构造器。
  2)如果已经定义了构造器(无论是否有参数),编译器都不会帮你创建自动创建默认构造器。
4. this关键字
 解决的问题:
  1)在方法的内部获得对当前对象的引用。如果在方法内部调用同一个类的另一个方法,就没必要使用this,直接调用即可。当前方法中的this引用会自动引用到同一类中的不同方法。
程序举例:

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 *///:~

  2)将当前对象传递给其他方法。
程序举例:

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 *///:~

  3)在构造器中调用另一个构造器,注意:this本身表示对当前对象的引用。在构造器中,如果为this添加了参数列表,这将产生对符合此参数列表的某个构造器的明确调用;这样,调用其他构造器就有了直接途径。
程序举例:

 public class Flower {
  int petalCount = 0;
  String s = "initial value";
  Flower(int petals) {
    petalCount = petals;
    System.out.println("Constructor w/ int arg only, petalCount= "
      + petalCount);
  }
  Flower(String ss) {
    System.out.println("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"
    System.out.println("String & int args");
  }
  Flower() {
    this("hi", 47);
    System.out.println("default constructor (no args)");
  }
  void PetalCount() {
//! this(11); // Not inside non-constructor!
    System.out.println("petalCount = " + petalCount + " s = "+ s);
  }
  public static void main(String[] args) {
    Flower x = new Flower();
    x.PetalCount();
  }
} /* Output: Constructor w/ int arg only, petalCount= 47 String & int args default constructor (no args) petalCount = 47 s = hi *///:~

  构造器Flower(String s,int petals)表明:尽管可以用this调用构造器,但却不能调用两个。此外,必选将构造器至于最起始处,否则编译会报错。
  4)想通过构造方法将外部传入的参数赋值给类的成员变量,构造方法的形式参数名称与类的成员变量名相同.

 class Person
 ...{
 String name;
 public Person(String name)
 ...{
 this.name = name;
 } 
 }

 5. 清理:终结处理与垃圾回收
     一般情况下,Java的垃圾回收器负责回收无用对象占据的内存空间。但是也有特殊情况:假定你的对象(并非是用new创建)获得一块特殊的内存区域,由于垃圾回收器只知道释放那些由new分配的内存。为了应对这种情况,java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用该方法,并且再下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要是打算使用该方法,就能在垃圾回收时刻做一些特殊的清理工作。
    这里的finallze()并不是C++中的析构函数。C++中的对象一定会被销毁,但是Java中的对象却并非总是被垃圾回收。或者换句话说:
    1)对象可能不被垃圾回收。
    2)垃圾回收并不等于”析构“。
    3)垃圾回收只和内存有关。
  finalize()的函数用处:1)通过某种创建对象方式以外的方式为对象分配了内存。例如:本地方法(在java中使用非java代码的方式)。
  2)对象终结条件的验证:尽管finalize()方法不会总是被调用,但是某次finalize()可以用来最终发现某些意外缺陷。
  程序举例:

class Book {
  boolean checkedOut = false;
  Book(boolean checkOut) {
    checkedOut = checkOut;
  }
  void checkIn() {
    checkedOut = false;
  }
  protected void finalize() {
    if(checkedOut)
      System.out.println("Error: checked out");
    // Normally, you'll also do this:
    // super.finalize(); // Call the base-class version
  }
}

public class TerminationCondition {
  public static void main(String[] args) {
    Book novel = new Book(true);
    // Proper cleanup:
    novel.checkIn();
    // Drop the reference, forget to clean up:
    new Book(true);
    // Force garbage collection & finalization:
    System.gc();//强制进行终结动作
  }
} /* Output: Error: checked out *///:~

 本例的终结条件是:所有的Book对象在被当做垃圾回收前都应该被签入check in,但是在main()中,有一本书没有被签入,通过finalize()验证终结条件,发现缺陷。
6. 成员初始化
  1)基本类型定义的同时初始化。如果不初始化,按照Java规则将默认初始化。也可以以同样的方式初始化非基本类型的对象。
  程序举例:

public class InitialValues2 {
  boolean bool = true;
  char ch = 'x';
  byte b = 47;
  short s = 0xff;
  int i = 999;
  long lng = 1;
  float f = 3.14f;
  double d = 3.14159;
} ///:~
//非基本类型初始化
class Depth {}
public class Measurement {
  Depth d = new Depth();
  // ...
} ///:~

  2)通过调用某个方法来提供初值。也可以以带有参数的方法初始化初值,但这些参数必须已经被初始化。
  程序举例:

1.无参方法初始化
public class MethodInit {
  int i = f();
  int f() { return 11; }
} ///:~
2.有参方法初始化
public class MethodInit2 {
  int i = f();
  int j = g(i);
  int f() { return 11; }
  int g(int n) { return n * 10; }
} ///:~

  
7. 构造器初始化:无法阻止自动初始化的进行,他将在构造器被调用之前发生。
  1.初始化顺序
  在类的内部,变量定义的先后顺序决定了初始化顺序。即使变量散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。
  程序举例:

class Window {
  Window(int marker) { System.out.println("Window(" + marker + ")"); }
}

class House {
  Window w1 = new Window(1); // Before constructor
  House() {
    // Show that we're in the constructor:
    System.out.println("House()");
    w3 = new Window(33); // Reinitialize w3
  }
  Window w2 = new Window(2); // After constructor
  void f() { System.out.println("f()"); }
  Window w3 = new Window(3); // At end
}

public class OrderOfInitialization {
  public static void main(String[] args) {
    House h = new House();
    h.f(); // Shows that construction is done
  }
} /* Output: Window(1) Window(2) Window(3) House() Window(33) f() *///:~

  2.静态数据的初始化:初始化顺序是先静态对象初始化,然后在非静态对象初始化。静态对象在整个过程中初始化只执行一次。
  程序举例:

class Bowl {
  Bowl(int marker) {
    System.out.println("Bowl(" + marker + ")");
  }
  void f1(int marker) {
    System.out.println("f1(" + marker + ")");
  }
}

class Table {
  static Bowl bowl1 = new Bowl(1);
  Table() {
    System.out.println("Table()");
    bowl2.f1(1);
  }
  void f2(int marker) {
    System.out.println("f2(" + marker + ")");
  }
  static Bowl bowl2 = new Bowl(2);
}

class Cupboard {
  Bowl bowl3 = new Bowl(3);
  static Bowl bowl4 = new Bowl(4);
  Cupboard() {
    System.out.println("Cupboard()");
    bowl4.f1(2);
  }
  void f3(int marker) {
    System.out.println("f3(" + marker + ")");
  }
  static Bowl bowl5 = new Bowl(5);
}

public class StaticInitialization {
  public static void main(String[] args) {
    System.out.println("Creating new Cupboard() in main");
    new Cupboard();
    System.out.println("Creating new Cupboard() in main");
    new Cupboard();
    table.f2(1);
    cupboard.f3(1);
  }
  static Table table = new Table();
  static Cupboard cupboard = new Cupboard();
} /* Output: Bowl(1) Bowl(2) Table() f1(1) Bowl(4) Bowl(5) Bowl(3) Cupboard() f1(2) Creating new Cupboard() in main Bowl(3) Cupboard() f1(2) Creating new Cupboard() in main Bowl(3) Cupboard() f1(2) f2(1) f3(1) *///:~

  3.显示的静态初始化
  程序举例:

class Cups {
  static Cup cup1;
  static Cup cup2;
  static {
    cup1 = new Cup(1);
    cup2 = new Cup(2);
  }

  4.非静态实例初始化
  程序举例:

public class Mugs {
  Mug mug1;
  Mug mug2;
  {
    mug1 = new Mug(1);
    mug2 = new Mug(2);
    System.out.println("mug1 & mug2 initialized");
  }

  
8. 数组初始化
  数组只是相同类型的、用一个标识符封装起来到一起的一个对象序列或者基本类型数据序列。可以这样初始化。
  int [ ] a={1,2,3,4,5};
  int [ ] a=new int [rand.nextInt(20)];
  Integer [ ] a={new Integer(1),new Integer[2],3,};
  可变参数列表:用于处理参数个数和类型未知的情况。
  程序举例:

public class NewVarArgs {
  static void printArray(Object... args) {
    for(Object obj : args)
      System.out.print(obj + " ");
    System.out.println();
  }
  public static void main(String[] args) {
    // Can take individual elements:
    printArray(new Integer(47), new Float(3.14),
      new Double(11.11));
    printArray(47, 3.14F, 11.11);
    printArray("one", "two", "three");
    printArray(new A(), new A(), new A());
    // Or an array:
    printArray((Object[])new Integer[]{ 1, 2, 3, 4 });
    printArray(); // Empty list is OK
  }
} /* Output: (75% match) 47 3.14 11.11 47 3.14 11.11 one two three A@1bab50a A@c3c749 A@150bd4d 1 2 3 4 *///:~

  
9. 枚举举例(enum):特别适合用在switch语句中。
  枚举变量必须大写。使用枚举是要创建该类型的引用,并将其赋给某个实例。
  程序举例:

public class Burrito {
  Spiciness degree;
  public Burrito(Spiciness degree) { this.degree = degree;}
  public void describe() {
    System.out.print("This burrito is ");
    switch(degree) {
      case NOT:    System.out.println("not spicy at all.");
                   break;
      case MILD:
      case MEDIUM: System.out.println("a little hot.");
                   break;
      case HOT:
      case FLAMING:
      default:     System.out.println("maybe too hot.");
    }
  } 
  public static void main(String[] args) {
    Burrito
      plain = new Burrito(Spiciness.NOT),
      greenChile = new Burrito(Spiciness.MEDIUM),
      jalapeno = new Burrito(Spiciness.HOT);
    plain.describe();
    greenChile.describe();
    jalapeno.describe();
  }
} /* Output: This burrito is not spicy at all. This burrito is a little hot. This burrito is maybe too hot. *///:~