Java编程思想——day 5(初始化与清理)


Java编程思想——day 5

从现在开始,每天学习并记录。

2018/03/21


第五章 初始化与清理

  1. 初始化和清理(cleanup)是涉及安全的两个问题。
  2. C++引入了构造器(constructor)的概念,这是一个在创建对象时被自动调用的特殊方法。
  3. Java中也采用了构造器,并额外提供了“垃圾回收器”。对于不再使用的内存资源,垃圾回收器能自动将其释放。

1、用构造器确保初始化

  1. 在Java中,通过提供构造器,类的设计者可确保每个对象都会得到初始化。
  2. 调用构造器是编译器的责任;构造器采用与类相同的名称。
  3. 利用new创建对象时(new表达式返回了对新建对象的引用),编译器将会为对象分配存储空间,并调用相应的构造器。
  4. 不接受任何参数的构造器叫做默认构造器,又称为无参构造器
  5. 构造器有助于减少错误,并使代码更易于阅读。在Java中,“初始化”和“创建”捆绑在一起,不能分离。
  6. 构造器是一种特殊类型的方法,因为它没有返回值。这与返回值为空(void)明显不同。

2、方法重载

  1. 同名方法参数类型列表不同,实现了方法重载。
  2. 同名方法参数顺序不同也可以实现重载,但一般不建议使用参数顺序不同实现重载,因为容易造成混乱。
  3. 基本类型能从一个“较小”的类型自动提升至一个“较大”的类型,此过程一旦牵涉到重载,可能会造成一些混淆。
  4. 如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型就会被提升。char型略有不同,如果无法找到恰好接受char参数的方法,就会把char直接提升至char型。
  5. 方法接受较小的基本类型作为参数。如果传入的实际参数较大,就得通过类型转换来执行窄化转换。如果不这样做,编译器就会报错。
  6. 方法重载不可以用方法的返回值进行实现。

3、默认构造器

  1. 默认构造器又称为“无参构造器”:没有参数,作用是创建一个默认的对象。如果你写的类中没有构造器,则编译器会自动帮你创建一个默认构造器。
  2. 但是,如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器。

4、this关键字

//: 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);//即Banana.peel(a,1);这是内部的表示形式
    b.peel(2);//即Banana.peel(b,2);
  }
} ///:~
  1. 为了实现“发送消息给对象”,编译器暗自把“所操作对象的引用”作为第一个参数传递给对象所调用的方法。
  2. 假设你希望在方法的内部获得对当前对象的引用,可以使用this关键字。
  3. 注意:this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。this的用法和其他对象引用并无不同。但要注意,如果在方法内部调用同一个类的另一个方法,就不必使用this ,直接调用即可。当前方法中的this用会自动应用于同一类中的其他方法(编译器会自动帮你添加)。只有当需要明确指定对当前对象的引用时,才需要使用this关键字。例如return this;
  4. this关键字可以实现将当前对象传递给其他方法。
//: initialization/PassingThis.java

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
*///:~
  1. 通常写this时,指当前对象或这个对象,并且this本身表示对当前对象的引用。
  2. 在构造器中调用构造器:为this添加参数列表,将产生对符合此参数列表的某个构造器的明确调用。
  3. this调用构造器需要注意的地方:只能调用一个构造器;必须将构造器调用至于最起始位置。
  4. 除构造器之外,编译器禁止在其它任何地方调用构造器。
//: 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
*///:~
  1. static方法,即静态方法,就是没有this的方法。
  2. static内部不能调用非静态方法(不绝对:如果你传递一个对象的引用到静态方法里(静态方法可以创建其自身的对象),然后通过这个引用(和this效果一样),你就可以调用非静态方法和访问非静态数据成员了,要想达到这样的效果,需要写一个非静态方法),非静态方法可调用static方法。
  3. 可以直接通过类本身调用static方法(不用创建对象),类似全局方法(Java禁止使用全局方法)

5、清理:终结处理和垃圾回收

  1. 垃圾回收器只能回收由new分配的内存;
  2. Java允许在类中定义一个名为finalize()的方法。使用“本地方法”,在Java中调用非Java代码的方式。
  3. finalize()不是C++中的析构函数,Java未提供“析构函数”或相似的概念。
  4. 注意:对象可能不被垃圾回收;垃圾回收并不等于“析构”;垃圾回收只与内存有关。
  5. 在java中不允许创建局部对象,必须使用new创建对象。
  6. 如果Java虚拟机(JVM)并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。
  7. 通常,不能指望 finalize(),必须创建其他的“清理’方法,并且明确地调用它们。
//: initialization/TerminationCondition.java
// Using finalize() to detect an object that
// hasn't been properly cleaned up.

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
*///:~
  1. 本例的终结条件是:所有的Beak对象在被当作垃圾回收前都应该被签入(check in)。但是在main()方法中,由于程序员的错误,有一本书未被签入。要是没有finalize()来验证终结条件很难发现这种缺陷。
    垃圾回收器工作方式:
  2. 存储空间的释放会影响存储空间的分配——JVM的工作方式。
  3. Java的“堆指针”只是简单地移动到尚未分配的区域,C++是在堆上分配空间。内存页面调度会显著地影响性能。
  4. 垃圾回收器工作的时候,将一面回收空间,一面使堆中的对象紧凑排列,这样“堆指针”就可以很容易移动到更靠近传送带的开始处,也就尽量避免了页面错误。通过垃圾回收器对对象重新排列,实现了一种高速的、有无限空间可供分配的堆模型。
  5. “引用计数”是一种简单但速度很慢的垃圾回收技术。
  6. “交互自引用对象组”
  7. 自适应的垃圾回收技术,它依据的思想是:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。由此,如果从堆栈和静态存储区开始,遍历所有的引用,就能找到所有“活”的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是此对象对象包含的所有引用,如此反复进行,直到“根源于堆栈和静态存储区的引用”所形成的网络全部被访问为止。
  8. 自适应的垃圾回收技术停止-复制(stop-and-copy),即复制式回收器→→→→→标记-清扫(mark-and-sweep)
  9. “自适应的、分代的、停止-复制、标记-清扫”式垃圾回收器。
  10. JVM用于提升速度的技术:与加载器操作有关的为:被称为“即时”(Just-In-Time, JIT)编译器的技术

6、成员初始化

  1. 所有变量在使用前都能得到恰当的初始化。

7、构造器初始化

  1. 程序在运行时刻,可以调用方法或执行某些动作来确定初值,但无法阻止自动初始化(赋0值)的进行,然后再进行赋值操作(包括构造器的赋值操作)
  2. 初始化顺序:在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。例如:
//: initialization/OrderOfInitialization.java
// Demonstrates initialization order.
import static net.mindview.util.Print.*;

// When the constructor is called to create a
// Window object, you'll see a message:
class Window {
  Window(int marker) { print("Window(" + marker + ")"); }
}

class House {
  Window w1 = new Window(1); // Before constructor
  House() {
    // Show that we're in the constructor:
    print("House()");
    w3 = new Window(33); // Reinitialize w3
  }
  Window w2 = new Window(2); // After constructor
  void f() { print("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()
*///:~

w3这个引用会被初始化两次:一次在调用构造器前,一次在调用期间(第一次引用的对象将被丢弃,并作为垃圾回收)。
一定要记住:先对各个变量赋初值,然后再运行方法。

  1. 静态数据的初始化: 无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标淮初值;如果它是一个对象引用,那么它的默认初始化值就是null。例子(好好看看,对理解对象的初始化以及静态非静态对象初始化很有用处,很经典的一道题)
//: initialization/StaticInitialization.java
// Specifying initial values in a class definition.
import static net.mindview.util.Print.*;

class Bowl {
  Bowl(int marker) {
    print("Bowl(" + marker + ")");
  }
  void f1(int marker) {
    print("f1(" + marker + ")");
  }
}

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

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

public class StaticInitialization {
  public static void main(String[] args) {
    print("Creating new Cupboard() in main");
    new Cupboard();
    print("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)
*///:~
  1. 初始化的顺序是先静态对象(如果它们尚未因前面的对象创建过程而被初始化),而后是“非静态”对象。
  2. 注意:main()也是一个静态方法。
  3. 本例中,在这个特殊的程序中,所有类在main()开始之前就都被加载了。
  4. 实际情况通常并非如此,因为在典型的程序中不会像在本例中所做的那样,将所有的事物都通过static联系起来。
  5. 初始化的顺序:先静态对象(注意:只会被初始化一次),而后是非静态对象,

8、数组初始化

  1. 数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列,通过方括号下标操作符 “ [ ] ” 来定义和使用。
  2. 定义数组:int [] a1;int a1[];
  3. 编译器不允许指定数组的大小。现在拥有的只不过是对数组的一个引用,而且也没给数组对象本身分配任何空间。
  4. 为了给数组创建相应的存储空间,必须写初始化表达式。数组的初始化动作可以出现在任何地方。特殊的初始化表达方式:由一对花括号括起来的值组成的,此时的存储空间的分配(等同于使用new)将由编译器负责。
  5. 未初始化而定义一个引用的目的是将一个数组赋值给另一个数组。
  6. 不确定数组里有多少个元素时,可以直接使用new在数组里创建元素。(注意:不能用new创建单个的基本类型数据。但基本类型数组是可以直接用new创建的 )
    几种使用方法:

初始化并赋值:

//: initialization/ArraysOfPrimitives.java
import static net.mindview.util.Print.*;

public class ArraysOfPrimitives {
  public static void main(String[] args) {
    int[] a1 = { 1, 2, 3, 4, 5 };
    int[] a2;
    a2 = a1;
    for(int i = 0; i < a2.length; i++)
      a2[i] = a2[i] + 1;
    for(int i = 0; i < a1.length; i++)
      print("a1[" + i + "] = " + a1[i]);
  }
} /* Output:
a1[0] = 2
a1[1] = 3
a1[2] = 4
a1[3] = 5
a1[4] = 6
*///:~

利用new初始化:

//: initialization/ArrayNew.java
// Creating arrays with new.
import java.util.*;
import static net.mindview.util.Print.*;

public class ArrayNew {
  public static void main(String[] args) {
    int[] a;
    Random rand = new Random(47);
    a = new int[rand.nextInt(20)];//或者int[] a = new int[rand.nextInt(20);进行定义并初始化
    print("length of a = " + a.length);
    print(Arrays.toString(a));
  }
} /* Output:
length of a = 18
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
*///:~

创建一非基本类型的数组,相当于创建了一个引用数组。例如:整型的包装器类Integer:

//: initialization/ArrayClassObj.java
// Creating an array of nonprimitive objects.
import java.util.*;
import static net.mindview.util.Print.*;

public class ArrayClassObj {
  public static void main(String[] args) {
    Random rand = new Random(47);
    Integer[] a = new Integer[rand.nextInt(20)];
    print("length of a = " + a.length);
    for(int i = 0; i < a.length; i++)
      a[i] = rand.nextInt(500); // Autoboxing
    print(Arrays.toString(a));
  }
} /* Output: (Sample)
length of a = 18
[55, 193, 361, 461, 429, 368, 200, 22, 207, 288, 128, 51, 89, 309, 278, 498, 361, 20]
*///:~
//注意:即使使用new创建数组之后:
//Integer[] a=new Integer[rand.nextInt(20)];
//它还只是一个对象引用,并且直到创建一个新的Integer对象(在本例中是通过自动包装机制创建的),并把对象赋值给引用,初始化进程才算结束:
//a[i]=rand.nextInt(500);

利用花括号括起来的列表初始化对象数组:两种形式:

//: initialization/ArrayInit.java
// Array initialization.
import java.util.*;

public class ArrayInit {
  public static void main(String[] args) {
    Integer[] a = {
      new Integer(1),
      new Integer(2),
      3, // Autoboxing
    };
    Integer[] b = new Integer[]{
      new Integer(1),
      new Integer(2),
      3, // Autoboxing
    };
    System.out.println(Arrays.toString(a));
    System.out.println(Arrays.toString(b));
  }
} /* Output:
[1, 2, 3]
[1, 2, 3]
*///:~

在这两种形式中,初始化列表的最后一个逗号都是可选的(这一特性使维护长列表变得更容易)。
第一种方式比较受限:只能用于数组被定义的地方;
第二种方式可以在任何地方调用,甚至在方法调用的内部,例如,创建一个String对象数组,将其传递给另一个main()方法,以提供参数,用来替换传递给该main()方法的命令行参数:

//: initialization/DynamicArray.java
// Array initialization.

public class DynamicArray {
  public static void main(String[] args) {
    Other.main(new String[]{ "fiddle", "de", "dum" });
  }
}

class Other {
  public static void main(String[] args) {
    for(String s : args)
      System.out.print(s + " ");
  }
} /* Output:
fiddle de dum
*///:~
  1. 可变参数列表:可以创建以Object数组为参数的方法,并调用:
//: initialization/VarArgs.java
// Using array syntax to create variable argument lists.

class A {}

public class VarArgs {
  static void printArray(Object[] args) {
    for(Object obj : args)
      System.out.print(obj + " ");
    System.out.println();
  }
  public static void main(String[] args) {
    printArray(new Object[]{
      new Integer(47), new Float(3.14), new Double(11.11)
    });
    printArray(new Object[]{"one", "two", "three" });
    printArray(new Object[]{new A(), new A(), new A()});
  }
} /* Output: (Sample)
47 3.14 11.11
one two three
A@1a46e30 A@3e25a5 A@19821f
*///:~

打印对象的内容时,如果没有定义toString()方法时,默认输出类的名字和对象的地址。
可变参数列表:

//: initialization/NewVarArgs.java
// Using array syntax to create variable argument lists.

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
*///:~
  1. getClass()方法属于Object的一部分。
  2. 使用可变参数列表不依赖于自动包装机制,实际上使用的是基本类型。

9、枚举类型

  1. enum关键字。
  2. 枚举类型的实例为常量,因此需要按照命名惯例,他们都用大写字母表示(如果在一个名字中有多个单词,用下划线将其隔开)。
  3. 为了使用enum,需要创建一个该类的引用,并将其赋值给某个实例。
  4. 在创建enum时,编译器会自动添加一些有用的特性,例如:toString()方法,显示某个enum实例的名字;ordinal()方法,表示某个特定enum常量的声明顺序(从0开始计数);static values()方法,用来按照enum常量的声明顺序,产生由这些常量值构成的数组。
    枚举定义:
//: initialization/Spiciness.java

public enum Spiciness {
  NOT, MILD, MEDIUM, HOT, FLAMING
} ///:~
//: initialization/EnumOrder.java

public class EnumOrder {
  public static void main(String[] args) {
    for(Spiciness s : Spiciness.values())
      System.out.println(s + ", ordinal " + s.ordinal());
  }
} /* Output:
NOT, ordinal 0
MILD, ordinal 1
MEDIUM, ordinal 2
HOT, ordinal 3
FLAMING, ordinal 4
*///:~
  1. 把enum当做类进行使用:可以在switch语句中使用
//: initialization/Burrito.java

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

可以将enum当做另外一种创建数据类型的方式,然后直接将所得到的类型拿来使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值