Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

java基础(四)之初始化与清理

2017-06-10
nivelle

初始化与清理

用构造器确保初始化

在java中,通过构造器,类的设计者可确保每个对象都会得到初始化。创建对象时,如果其类具有构造器,java就会在用户有能力操作对象之前自动调用相应的构造器,从而保证了初始化的进行。

考虑命名:

  • 取名不能与类的某个成员名称冲突
  • 调用构造器是编译器的责任,所以必须让编译器知道应该调用那个方法。

考虑到在初始化期间要自动调用构造器,所以采用与类名称相同的构造器名就顺理成章了。

再调用new Rock() 将会为对象分配存储空间,并调用相应的构造器。这就确保了你能操作对象之前,它已经被恰当地初始化了。

构造器是一种特殊类型的方法,因为它没有返回值。这与返回值为空(void)明显不同。对于空返回值,尽管方法本身不会返回什么,但任可选择让它返回别的东西。构造器则不会返回任何东西,别无选择(new表达式确实返回了对新建对象的引用,但构造器本身并没有返回任何值)

方法重载

为了让方法名相同而形式参数不同的构造器同时存在,必须用到方法重载。

区分方法重载:每个重载方法都必须有一个独一无二的参数类型列表。

涉及基本类型的重载

如果传入的数据类型(实际参数类型)小于方法声明中的形式参数类型,实际数据类型就会被提升。char型略有不同,如果无法找到恰好接受char参数的方法,就会把char提升到int。其他都是一级一级的提升。

有时并不关心返回值,你想要的是方法调用的其他效果(为了副作用而调用),这时你可能会调用方法而忽略其返回值。因此,通过返回值来区分重载方法是行不通的。

默认构造器

如果写的类中没有构造器,则编译器会自动创建一个默认构造器,无参构造器。如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器。

this 关键字

为了能简单、面向对象的语法来编写代码————即”发送消息给对象”,编译器做了一些幕后工作。它暗自把“所操作对象的引用”作为第一个参数传递给方法。

假设你希望在方法内部获得对当前对象的引用。由于这个是由编译器“偷偷”传入的,所以没有标识符可用。但是,专门有一个关键字:this。 this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。this的用法和其他对象引用并无不同。


但要注意:如果在方法内部调用同一个类的另一个方法,就不必使用this,直接调用即可。当前方法中的this引用会自动应用于同一类中的其他方法。

public class Apricot{
    void pick(){}
    void pit(){pick();}
}

在pit()内部,你可以写this.pick(),但无此必要。编译器会自动添加。自由明确需要显示当前对象的引用的时候,才使用this关键字。比如需要返回当前引用,进行连续调用。


在构造器中使用构造器

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

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

可以用this调用一个构造器,但却不能调用两个,此外,必须将构造器调用置于最起始处,否则编译器会报错。

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

为类清理使用new获得的特殊内存区域,java允许在类中定义一个名为finalize()的方法。它的工作原理是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize(),并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

之所以有finalize(),是由于在分配内存时可能采用了类似C语言的做法,而不是java中的常用做法。可能是java调用了本地方法,本地方法是一种在java中调用非java代码的方法。

如果Java虚拟机并未面临内存消耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。

Finalize

  • finalize抛出的未捕获异常只会导致该对象的finalize执行退出。

  • 用户可以自己调用对象的的finalize方法,但是这种调用时正常的方法调用,和对象的销毁过程无关

  • JVM保证在一个对象所占内存被回收之前,如果它实现了finalize方法,则该方法一定会被调用.Object的默认finalize什么都不做,为了效率,GG可以认为一个设么都不做的finalize不存在.

  • 调用GC并不保证GC实际执行。

  • java的GC只负责内存相关的清理,所有其它资源的清理必须由程序员手工完成。要不然会引起资源泄露,有可能导致程序崩溃。

  • 可以用来保护非内存资源被释放。即使我们定义了其它的方法来释放非内存资源,但是其它人未必会调用该方法来释放。在finalize里面可以检查一下,如果没有释放就释放好了,晚释放总比不释放好。

成员初始化

  • 方法局部变量

所有变量在使用前都能得到恰当的初始化。对于** 方法的局部变量**,java以编译时错误的形式来贯彻这种保证。

void f(){
    int i;
    i++;//Error -- i not initialized
}
  • 成员变量

要是类的数据成员(即字段)是基本类型,情况就会变得有些不同,类的每个基本类型数据成员保证都会有一个初始值。其中char值为0,所以就显示空白。在类里定义一个对象引用时,如果不将其初始化,此引用就会获得一个特殊值null。

指定初始化

在定义类成员变量的地方为其赋值,也可以用同样的方法初始化非基本类型的对象,甚至可以通过调用某个方法来提供初值。

构造器初始化

可以调用构造器来进行初始化,在运行时刻,可以调用方法或执行某些动作来确定初值,这为编程带来了更大的灵活性。但要牢记:无法阻止自动初始化的进行,它将在构造器被调用之前发生。

初始化顺序

在类的内部,变量定义的先后顺序决定了初始化顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。

静态数据的初始化(static关键字)

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

初始化的顺序是先静态对象(如果他们尚未因前面的对象创建过程而被初始化),而后是“非静态”对象。

package learn;

public class StaticInitializaton {
  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();
  
}

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

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

}

class Table {
  static Bowl bowl = new Bowl(1);

  public 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);
  public Cupboard() {
    System.out.println("Cupboard()");
    bowl4.f1(2);
  }
  void f3(int marker){
    System.out.println("f3(" + marker + ")");
  }
  static Bowl bowl5= new Bowl(5);
  
}

结果:

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)


先初始化静态对象,且仅仅被初始化一次,而后是非静态对象。

对象创建过程,假设有个Dog类:

  • 即使没有显示地使用static关键字,构造器实际上也是静态方法,或者Dog类的静态方法/静态域首次被访问时,java解释器必须查找类路径

  • 载入Dog.class ,有关静态初始化的所有动作都会执行。静态初始化只在Class对象首次加载的时候执行一次

  • 当用new Dog()创建对象时,首先会堆上的Dog对象分配足够的存储空间。

  • 这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据设置成了默认值,而引用则被设置成了null

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

  • 执行构造器。

显示的静态初始化

java允许将多个静态初始化动作组织成有一个特殊的“静态子句”(静态块)

非静态实例初始化

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

package learn;

public class Mugs {
  Mug mug1;
  Mug mug2;

  {
    mug1 = new Mug(1);
    mug2 = new Mug(2);
    System.out.println("Mug1&Mug2 initialized");
  }

  public Mugs() {
    System.out.println("Mugs");
  }

  Mugs(int i) {
    System.out.println("Mugs(int)");
  }

  public static void main(String[] args) {
    System.out.println("inside main");
    new Mugs();
    System.out.println("Mugs completed");
    new Mugs(1);
    System.out.println("Mugs1 completed");
  }
}

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

  void f(int marker) {
    System.out.println("f(" + marker + ")");
  }
}

结果:

inside main Mug(1) Mug(2) Mug1&Mug2 initialized Mugs Mugs completed Mug(1) Mug(2) Mug1&Mug2 initialized Mugs(int) Mugs1 completed

结论:非静态块和对象关联,多次调用多次执行,先于构造函数执行非静态块。


static的主要用途:

“static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。”

  • static方法

static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。 但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的

即使没有显示地声明为static,类的构造器实际上也是静态方法。

  • static变量

static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。注意:static是不允许用来修饰局部变量

  • static代码块

static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。因为只会在类加载的时候执行一次,所以可以显著提升性能.

数组初始化

数组只是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列,数组通过括号下标操作符[]来定义和使用。定义一个数组,只需要在类型后面加上一对空方括号即可。

编译器不允许指定数组的大小,为了给数组创建相应的存储空间,必须写初始化表达式。

数组初始化操作:

int a[] :一个数组引用,只为引用分配了存储空间,数组对象本身没有分配任何空间。

  • 在数值创建的地方,由一对花括号括起来的值组成。这种情况下,存储空间的分配相当于new将由编译器负责。

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

java中可以将一个数组赋值给另一个数组。

所有数组都有一个固有成员:Length,可获知数组有多少成员,但是不能修改它。

  • 创建数组时,并不知道数组需要多少个元素,可以用new在数组里创建元素。

Interger[] a = new Integer[rand.nextInt(20)];

它还只是个数组引用,必须继续创建新的数组元素对象,并把具体对象赋值给引用,初始化动作才算结束。

可变参数列表

这种情形适合参数个数或类型未知的长河,由于所有的类型都直接或借鉴继承与Object类,所以可以创建以Object数组为参数的方法。

public class NewVarArgs {
   static void printArray(Object...args){
     for(Object obj:args){
       System.out.println(obj+"");
       System.out.println();
     }
   }
   public static void main(String[] args) {
     printArray(new Integer(47),new Float(3.14),new Double(11.11));
     printArray(47,3.14F,11.11);
     printArray("a","b","c");
     printArray((Object[])new Integer[]{1,2,3,4});
  }
}

结果:

473.1411.11 473.1411.11 abc 1234


如果你有一组事物,可以把它们当做参数列表传递,而如果你已经有了一个数组,该方法可以把它们当做可变参数列表来接受。

可变参数列表中可以使用任何类型的参数,包括基本类型。

枚举类型

enum,它使得我们在需要群组并使用枚举类型集时,可以很方便地处理。

在创建enum时,编译器会自动添加一些有用的特性。

 -  values()方法: 静态方法,返回一个包含全部枚举值的数组。

 -  toString()方法: 返回枚举常量的名称。
 
 -  valueOf()方法: 这个方法和toString方法是相对应的,返回带指定名称的指定枚举类型的枚举常量。
  • enum很像特殊的class,实际上enum声明定义的类型就是一个类。 而这些类都是类库中Enum类的子类(java.lang.Enum)

  • 枚举类就是class,而且是一个不可以被继承的final类。其枚举值都是枚举类型的类静态常量. **注意:这些枚举值都是public static final的,也就是我们经常所定义的常量方式,因此枚举类中的枚举值最好全部大写。 **

  • 即然枚举类是class,当然在枚举类型中有构造器,方法和数据域。但是,枚举类的构造器有很大的不同:

 1. 构造器只是在构造枚举值的时候被调用。

 2. 构造器只能私有private,绝对不允许有public构造器。  

  • 枚举类可以在switch语句中使用。

评论