重复运用Class

2020-09-23  本文已影响0人  刚子来简书啦
组合与继承

在新的class中产生既有class的对象,这种方法称为组合(composition)。这种情形只是很单纯地重复运用既有程序代码的功能,而非重复运用其形式。而继承则既可以接收既有class的形式,并加入新代码,也无需更动既有的class。

Java编译器会自动在derived class构造函数中插入对base class的构造函数的调用动作。如果base class不具备default构造函数(无引数列表),或如果就是想调用带有引数的base class构造函数,那么就得运用super关键字,并搭配适当的引数列,显式地写出调用动作。该调用必须写在构造函数的第一行,也就是这必须是构造函数所做的第一件事情。

关键字final

不变的数据可能很有用,因为它:

  1. 可以是永不改变的“编译器常量”。
  2. 可以在执行期被初始化,然后不想再被改变。

在Java中,此类常量必须属于基本型别,而且必须以关键字final修饰之。定义此类常量的同时,必须给定其值。

如果数据既是static也是final,那么它会拥有一块无法改变的存储空间。

将final用于基本型别时,final让数值保持不变;但是用于 object reference 时,final 让 reference 保持不变。某个reference一旦被初始化用以代表某个对象后,便再也不能改而指向另一个对象。但此时对象本身的内容却是可以更动的。Java并未提供“让任何对象保持不变”的机制。

不能只因为某个数据被声明为final,就认为在编译期便知其值。

编译器强迫你一定得对所有final执行赋值动作,如果不是发生在其定义处,就得在每个构造函数中以表达式设定其值。这也就是为什么能够保证“final数据成员在被使用前绝对会被初始化”的原因。

Java允许将引数(arguments)声明为final,意味着无法在此函数中令该引数(一个reference)改指它处。

在函数前使用final的原因有两个:

  1. 锁住这个函数,使 derived class 无法改变其含义。
  2. 效率。如果某个函数被声明为final,等于允许编译器将所有对此函数的调用动作转为行内调用。

class中的所有private函数自然而然会是final。因为无法取用private函数,自然也就无从覆写。但是你可以在 derived class 中使用同样的函数名,只是此时与覆写没有任何关系。

初始化和class装载

class程序代码在初次被使用时才被装载。所谓初次使用,不仅是其第一个对象被构建之时,也可能是在某个static数据成员或static函数被取用时。

“首次使用class”的时间点,也正是static初始化的进行时机,任何static对象和static程序区段被装载时,都会依据它们在程序代码中的次序加以初始化。当然,static只会被初始化一次。

// base class
class Insect {

    int i = 1;
    int j;

    Insect() {
        prt("i = " + i + ", j = " + j);
        j = 8;
    }

    static int x1 = prt("static Insect.x1 initialized");

    static int prt(String s) {
        System.out.println(s);
        return 27;
    }
}

// derived class
class Beetle extends Insect {

    int k = prt("Beetle.k initialized");

    Beetle() {
        prt("k = " + k);
        prt("j = " + j);
    }

    static int x2 = prt("static Beetle.x2 initialized");

    public static void main(String[] args) {
        prt("Beetle constructor");
        Beetle beetle = new Beetle();
    }
}

这个程序的运行结果如下:

static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 1, j = 0
Beetle.k initialized
k = 27
j = 8

当执行Beetle时,首先发生的动作便是取用Beetle.main(),于是装载器被启动,找出Beetle class编译过的程序代码(Beetle.class文件)。装载过程中由于关键字extends存在,转载器得知这个class拥有base class,于是继续装载。无论你是否产生base class的对象,这个动作都会发生。

如果base class还有base class,那么便会继续装载第二个base class,依此类推。接下来,root base class中的静态初始化动作会被执行,然后是其derived class,依此类推。上述方式很重要,因为derived class的静态初始化动作是否正确,可能和base class的成员是否被正确初始化有关。

至此,所有必要的class都已被装载,可以开始产生对象了。首先,对象内的所有基本型别都会被设置缺省值,object reference则被设为null,也就是将对象内存都设为二进位零值。然后,base class的构造函数会被唤起。base class的构建过程和其derived class构造函数中的次序相同。base class构造函数完成之后,其实例变量会以其出现次序被初始化。最后才执行构造函数本体的剩余部分。

上一篇 下一篇

猜你喜欢

热点阅读