Thinking in Java ---final关键字总结+初始化和类的加载

来源:互联网 发布:十大网络官场小说 编辑:程序博客网 时间:2024/05/19 17:09

final关键字既可以用来修饰基本变量,引用也可以用来修饰方法和类。在修饰不同的对象时有不同的含义,很容易搞混淆,在此做一个小结。了解一下继承情况下变量的初始化和类的加载机制,也能让我们对程序运行时发生的一切有一个全局性的把握。

一。final关键字
1.1 final关键字修饰变量
final关键字类似于C++中的const关键字,当它修饰基本变量时表明该变量一旦被初始化为某一值就不能再被改变。当final用于修饰一个引用时情况有点不同,它只能表示该引用一旦初始化时指向某个对象,那么这个引用的指向就不能再改变了,但是其指向的对象的值却还是可以改变的,下面的程序示范了这一点。

package lkl;public class Value {      public int i;      Value(int i){          this.i=i;      }}package lkl;import java.util.Random;public class FinalTest {    public static Random rand = new Random(45);    public final int valueOne=9;   ///final修饰普通变量    public final int valueTwo = rand.nextInt(34);///final修饰的普通变量,不一定要用常量赋值    public final Value val = new Value(1); ///final修饰引用    public static final int INT_1=10; ///final和static连用表示一个编译时就可以确定的常量    public static void main(String[] args){        FinalTest ft=new FinalTest();        //ft.valueOne++;  ///试图修改final型基本变量的值,Error        System.out.println(ft.valueTwo);        ft.val.i++;  ///修改final引用指向对象的值是可以的        //ft.val=new Value(2);//试图修改final修饰引用的指向,Error    }}

上面的代码中还出现了static和final共同修饰一个变量的情况,这时这个变量只占据一段不能被修改的存储空间,称为编译时常量.
在Java中final类型的变量也可以不在声明时就初始化,可以将初始化的过程放到构造器中(必须所有的构造器中都有初始化的语句),这样也可以保证变量在使用前肯定会被初始化.这样的好处在于可以根据不同的情况对final型变量赋不同的值.这种final变量被称为”空白final”.
下面的代码示范了这种情况:

package lkl;import java.util.Random;public class FinalTest {    public final int i;    public final int j;    public FinalTest(){        i=0; j=0;    }    public FinalTest(int i,int j){        this.i=i; this.j=j;    }    public static void main(String[] args){        FinalTest ft=new FinalTest();        FinalTest ft1 = new FinalTest(1,2);        System.out.println("ft: i ,j "+ft.i+" "+ft.j);        System.out.println("ft1:i,j "+ft1.i+" "+ft1.j);    }}

最后还有一种final用于修饰形参变量的用法,这时意味着你不能在方法中更改参数引用指向的对象.此时的形参只可读而不可写,如下面的代码所示:

package lkl;import java.util.Random;public class FinalTest {     public Value with(final Value val){         val= new Value(2); ///试图改变val的指向,Error         return val;     }     public Value w(final Value val){         return val;     }     public int go(final int i){         i++;///试图改变i的值,Error         return i;     }     public int f(final int i)     {          return i+1;     }    public static void main(String[] args){    }}

1.2 final修饰方法和类
final用于修饰方法,则表明不能被子类重写(覆盖).这样可以保证在继承中方法行为不变.父类中所有的private方法都是隐式指定为final的.对于private方法,如果我们在子类中定义一个与其完全一样的方法,也不能叫做重写,只是重写定义了一个新的方法.但对于final修饰的方法,我们是不可以在子类中进行重写.所以重写只是针对在子类中可见的父类接口.

package lkl;public class Base {      private void f(){          System.out.println("Base.private()");      }      public final void g(){          System.out.println("Base.final()");      }}package lkl;public class OverringBase1 extends Base{    private void f(){        System.out.println("OverringBase1.f()");    }    public final void g(){ ///Error        System.out.println("OverringBase1.g()");    }}

对于final修饰类来讲就要简单的多,我们只需要知道如果一个类被final修饰则该类不能被继承就行了,当然此时该类中的所有的域都默认是final的.

二.初始化和类的加载.
Java中的每个类都会有它自己的编译代码,然后如果我们想要使用这个类时,就需要载入这个编译代码文件.当一个类的第一个对象被声明时,或者是类的static 变量被调用时,类就会被初始化,每个类只会被初始化一次.
如果一个类有基类,那么在载入这个类的过程中就会先载入基类,如果这个基类还有父类,那么也会先载入他的父类,如此.
在此过程中从最初的基类开始,所有类的static变量会被初始化.
对于其它变量的初始化:创建对象时,首先对象中的所有基本类型都会被设为默认值,对象的引用被设为null,然后如果有初始化块,则初始化块会被执行,最后再调用构造器进行初始化,变量会按其次序被初始化.下面的代码演示了初始化的流程:

package lkl;public class Base {    public int i=9; ///先指定一个默认值    public int j;    public int k;    public int d;    public Base(int x){        j=i;        i=x;            k=i;    }    {        i=10; ///初始化块        d=i;    }    public static void main(String[] args){        Base be =new Base(2);        ///输出的值说明初始化块先被执行,然后再是构造器        System.out.println(be.j+" "+" "+be.k+" "+be.d);    }}
1 0