《JAVA编程思想》第四版学习 需要我记住的something --初始化和清除 (二)

来源:互联网 发布:淘宝退假货会怎么样 编辑:程序博客网 时间:2024/05/16 11:41

十. 成员初始化

     Java保证所有变量在使用之前都被初始化。

     类中的基本类型成员,初始化为0(boolean为false);对象引用赋为null(如果程序对null操作,运行时错误-异常)。我们称这种为自动初始化(automation initialization)。

     方法中的局部变量,需要手工赋初值,否则编译错误。基于这样的考虑:局部变量未初始化,更多的是程序bug,编译报错利于找bug;编译器如果赋值,反而会掩盖这种错误。

     定义时初始化(specifying initialization),即在变量或对象定义时赋给初始值(基本类型,直接赋值;对象new T(),C++不允许?)。可以通过调用方法来实现(如i=method()),当然方法可以有参数,也可以无参。注意的是:有参数时,其实际参数必须已经初始化。还要注意前向引用的问题(forward referencing),即注意执行顺序,如"int i=f(); int j=g(i);"没有问题,但"int j=g(i); int i=f()。"就有问题了。

     构造器初始化(constructor initialization)提供一种更灵活的初始化方案(每个对象可以有不同初始值;可以在运行时调用方法和进行其他操作决定初始值)。注意:构造器初始化不能屏蔽自动初始化和specifying initialization,自动初始化在构造器初始化之前,而且并不会受到定义位置的影响(即无论变量定义是在构造器定义之前还是之后,自动初始化都在之前发生)。如以下代码

i先赋值为0,而后才被赋值为7.

      初始化顺序,类中变量初始化的顺序由定义的顺序决定,但都在所有方法包括构造器之前(不论定义的位置是在方法前还是后)。参见例OrderOfInitialization.

 

十一. static数据初始化

     static仅能用于类成员,而不能用于局部变量。static成员初始化规则与non-static成员一样。

     static成员仅在必需时初始化(如创建该类的对象或者通过类名访问static方法或成员),且只有一次。

     static成员初始化在non-static初始化之前(后者只在创建该类对象是才进行初始化)。参见例StaticInitialiaztion。

     所以,初始化的顺序应该是这样的:

     1. static自动初始化;2. static specifying initialization;3. non-static 自动初始化;4. non-static specifying initialization;5. 构造器初始化

     创建对象的过程:

     1. 定位class;2. 装载class,static初始化(仅此一次);3. 在heap中分配对象内存;4. 对象内存清0,即完成自动初始化;5. specifying initialization,即赋给变量定义时指定的初值;6. 执行构造器(如果有继承就更复杂)。

 

十二. 显示static初始化

     static clause(或者static块-static block):static {...},类似静态方法,只是没有方法签名,规则与static初始化一样,只执行一次。好处在于可以在{}中加入其他语句,如System.out.println()。

 

十三. non-static实例初始化(instance initialization)

      实例初始化:{...},与显示static初始化类似,只是没有static关键字。规则与前面一样,在constructor之前执行,对于匿名内部类很有用,也能保证特定操作在constructor之前执行(通过在{}内加入其它语句,如System.out.println())。

 

十四. 数组初始化

      两种方式定义:T[] a和T a[]。提倡用T[] a的方式。注意定义时,不允许指定大小,而只能在分配对象空间时指定。也就是说,不能这样定义:T[10] a;你可以T[] a = new T[10]()。其实就是关于引用的概念,T[] a只是定义了数组的引用,该引用的内存已经分配;而数组内的对象需要通过new或者初始化列表的方式来分配内存,其所需内存大小也得在为对象分配内存时指定。

      数组的固定成员length,只读。Java限制数组index在0~length-1之间(为了安全),越界,运行时错误(异常)。

      基本类型数组,默认初始化为0(boolean为false)。可以用new来为基本类型数组分配空间(不能用new创建基本类型变量,只能创建包装器类),如int[] a = new int[10]。

      对象数组,数组内均为引用,初始化为null。

 

      数组定义:

      1. T[] a:a是一个数组的引用,null,数组内对象类型为T(T可以为基本类型),位于stack或静态存储,我觉得

      2. T[] a = new T[len]:数组引用不为null,len可以是变量,其值可以在运行时确定。数组本身位于heap,其内部为T类型对象的引用(如果是基本类型,应该就不是引用了),这些引用为null。

      3. T[] a = {new T(), new T(), ...}:数组已初始化,数组对象引用不为null,对象位于heap。所谓的初始化列表。对于基本类型,不能用new T()的方式,而应直接是基本类型数值。基本类型包装器类,可以用new T(),也可以直接用基本类型数值(autoboxing)。

      4. T[] a = new T[]{new T(), new T(), ...}:同上。

 

      所以,数组初始化三种方式:

     1. T[] a = new T[len]定义,利用循环为每个数组元素赋值;

     2. T[] a = {new T(), new T(), ...},这种只能在定义时初始化

     3. T[] a = new T[]{new T(), new T(), ...},可以拆分为两步,T[] a; a = new T[]{new T(), new T(), ...}。

     注意new T[]{new T(), new T(), ...}不仅可以用在数组初始化,也可用在方法的参数传递。参见例DynamicArray。还要注意的是此时的T[]中不能指定大小。

     Arrays.toString()方法(java.util库),返回一维数组可打印的版本,也就是可以System.out.println(Arrays.toString(a)),结果为[a[0].toString(), a[1] .toString(), a[2] .toString(), ...]。

 

十五. toString()方法默认答应类名@对象地址。class name:[表示数组,如[I表示整型数组

 

十六. 可变参数列表,参数数量和类型均未知,可以通过Object[] args做参数来实现,因为一切皆对象。此时类型都可以不相同。

      Java SE5开始,加入了对可变参数列表的原生支持(称为varargs),即T... args的方式,注意,此时传入的参数类型必须为T。此时你可以通过逗号分隔的多个参数的方式传入实际参数,即method(arg1, arg2, ...)的方式。(而通过Object[]定义的是不能这样传的),当然也可以直接传入一个T[]。

      实际上,编译器仍然是把传入的参数转换为一个T[]。

      对于varargs,可以不传参数,即0个参数。因此,可定义可选的尾参数(trailing arguments)。

      varargs支持基本类型,如int... args,不进行autoboxing,对于Object[]定义的是不可以的,只能用封装类。Object... args,如果传入基本类型,自动转型为Object,而不是包装类。

 

十七. varargs方法的重载:重载变复杂,多个varargs的重载方法,对于0参数调用,无法分辨。类型提升总是找最匹配的方法。

      当varargs与固定参数混合时,容易出现重载解析冲突。例如,定义两个重载方法f(float f, Character... args)和f(Character... args),对f(‘a', ‘b')这个调用将无法解析。

      所以,推荐在重载方法里,只有一个具有可变参数的的方法,或者不要重载可变参数的方法。对于前面的例子,如果我们把第一个方法改为f(float f),第二个不变,则f(‘a')的调用时可以解析的,自动匹配到f(float f),似乎它的优先级更高。

 

十八. Java SE5添加了enum(枚举)。oridnal()方法返回特定enum常量的定义顺序(int);static values()方法按照定义顺序返回所有enum常量。enum可以用于switch语句。

 

原创粉丝点击