- 静态初始化:
静态初始化只在必要的时刻进行.(即当程序需要加载类进入内存的时候,执行静态初始化.静态变量和静态代码块的初始化顺序,按照在代码中声明的顺序老执行.例如:如果要执行某个public类,那么首先需要加载进内存,这时候就开始静态初始化.随后将主函数加载进栈.静态初始化只在Class对象首次加载的时候执行一次.(对象不创建,非静态变量不初始化.类不加载,静态变量不初始化.下面是代码示例:
public class Test3 { public static void main(String[] args) { /*主函数中不声明任何语句静态变量也会初始化,因为主函数进栈就需要把类加载进内存.*/ //ADog dog; //声明引用,非静态变量也不会初始化 //ADog dog3=new ADog(); //创建对象.执行非静态变量和构造函数的初始化 } static ADog dog1=new ADog(); static ADog dog2=new ADog(); static { System.out.println("汪汪!"); } /*静态代码块和静态变量初始化顺序看语句中的顺序.*/ } class ADog { public ADog() { System.out.println("小狗狗在此!"); } }
- 可变参数列表:
参数列表中,采用参数类型加...代表数组的称为可变参数.例如:int... 可变参数可以接受任意大小的元素进入数组,编译器会为你去填充数组.例如:
f(int ... arr) //f(1)OK f(2,3,4)OK
实际上arr也是数组,可以接受任意类型的数组.这里有几个特殊情况说明:
- 如果向可变参数列表中传入了一个数组,而不是一系列数组的元素,编译器会发现它已经是个数组了,所以不会在其上做任何转换.
public class Test4 { public static void main(String[] args) { // Integer[] ins={1,2,3,4}; f(ins); } static void f(Integer...arr) { for(int x:arr) { System.out.println(x); } }
输出1,2,3,4
- 如果向一个Object参数的可变参数列表传入一个基本数据类型的数组,则会将整个数组引用当成一个Object,因此会输出数组的引用的toString.
public class Test4 { public static void main(String[] args) { f((new int[]{1,2,3,4})); } static void f(Object...arr) { for(Object x:arr) { System.out.println(x); //输出[I@15db9742 } }
3.可变参数列表也是存在自动装箱的,因此如果往Integer...可变参数列表中加入一系列int元素是可以的.但是如果在其中传入int[]元素,编译器在检查的时候会发现int[]元素无法装入Integer[]中,因此报错.
public class Test4 { public static void main(String[] args) { f(1,2,3,4); //Ok f(new int[]{1,2,3,4}); //Error } static void f(Integer...arr) { for(Object x:arr) { System.out.println(x); } }
4.应该总是在重载方法的一个版本上使用可变参数,或者根本不使用它.因为可变参数有可能有的时候,表面看上去方法不同,但是在传入参数的时候,两个方法都可以调用.
public class Test4 { public static void main(String[] args) { f('a','b'); //编译错误. } static void f(float ... args) /*float也接受char类型.*/{ System.out.println("first"); } static void f(Character... args) { System.out.println("second"); } }
这个特性显著不同于在方法中传递参数,传入的参数将优先寻找类型匹配的参数,但是在可变参数中,只要可接受,编译器就默认可以装入数组.容易造成方法调用的不确定性.
public class Test4 { public static void main(String[] args) { f('a'); } static void f(float args)/*可接受char.但是优先匹配Character和char,然后是int,然后才是float*/{ System.out.println("first"); } static void f(Character args) { System.out.println("second"); } }
- 在继承的时候确保正确清理
在清理方法中,还必须注意对基类清理方法和成员对象清理方法的调用顺序,以防止某个子对象依赖于另一个子对象的情形的发生.一般而言,首先执行类的所有特定清理动作,其顺序同生成顺序相反(通常这要求基类元素仍然存活.然后再调用基类的清理方法.
- final参数
Java允许在参数列表中以声明的方式将参数声明为final.这意味着无法在方法中更改参数的值.(引用无法更改参数指向的对象).
- final和private关键字
类中所有的private方法都隐式的被指定为final的,由于无法取用private方法,所以也就无法覆盖它.可以给private方法添加final修饰词,但这并不能给方法增加额外的意义.覆盖只有在能将一个对象向上转型并调用它的方法的时候才会出现,如果方法是private,则它不是基类接口的一部分,仅仅是隐藏与类中的程序代码,当声明一个public protected和包访问权限的方法的话,该方法不会产生在基类中出现的仅仅具有相同名称的情况.此时并没有覆盖方法,仅仅是生成了一个新的方法.下面的代码说明了这个问题:
public class Dad { private void say() { System.out.println("I am Dad"); } public static void main(String[] args) { Dad d=new Son(); //子类的Public访问权限的say相当于定义在子类的全新的方法,不符合动态绑定的情况,仍然使用父类的say方法 d.say();//输出I am Dad } } class Son extends Dad{ public void say() { //在子类中无法访问父类的private方法,private包装在子类对象内部的父类对象内,无法访问. //super.say();错误.无法访问,类的访问权限. System.out.println("I am son."); } }
- 初始化以及类的加载
下面的代码提供了初始化的示例:
import static net.mindview.util.Print.*; class Insect { private int i=9; protected int j; Insect() { print("i="+i+"j="+j); j=39; } private static int x1=printInit("static Insect.x1 initialized."); static int printInit(String s) { print(s); return 47; } } public class Beetle extends Insect{ private int k=printInit("Bettle.k initialized"); public Beetle() { print("k="+k); print("j="+j); } private static int x2=printInit("static Bettle.x2 initialized"); public static void main(String[] args) { print("Beetle constructor:"); Beetle b=new Beetle(); } }
首先在Beetle上运行Java的时候,视图访问Beetle.main,于是加载器开始启动并找出Beetle类的编译代码,在对它进行加载的过程中,编译器注意到它有一个基类,于是它继续进行加载,不管是否打算产生一个基类的对象,这都会发生.
接下来,根基类的static元素会被初始化,然后是下一个子类,以此类推.这种方式很重要,因为子类的static初始化可能会依赖于基类成员是否被正确初始化.至此,必要的类加载完毕,对象就可以创建了.首先对象中所有的基本类型都会设为默认值,对象的引用被设为null-然后基类的构造器会被调用,基类构造器和子类的一样,以相同的顺序经历相同的过程,在基类构造器完成后,实例变量按次序被初始化.最后,构造器的其余部分被执行.
- 多态发生时的域
只有普通的方法调用才可以是多态的.如果直接访问某个域,这个访问就在编译器开始进行解析:
class Super { public int field=0; public int getField() { return field; } } class Sub extends Super { public int field=1; public int getField() { return field; //相当于return this.field. } } public class Test3 { public static void main(String[] args) { Super sup=new Sub(); System.out.println("sup.field="+sup.field+",sup.getField="+sup.getField()); //sup.field=0,sup.getField=1 } }
当Sub对象转型为Super引用的时候,任何域访问操作都将有编译器解析,因此不是多态的.Sub实际上包含两个称为field的域,它自己的和从Super处得到的.而在引用Sub中的field域时候所产生的默认域并非Super版本的field域.