java中对类的复用是java引人注目的功能之一。复用类的诀窍就是使用类而不破坏现有程序代码。
类的复用方式分为以下几种:
一.组合
在新的类中产生现有类的对象,由于新类是由现有类的对象组成,所以称为组合。组合只是复用了现有类程序代码的功能,而并没有复用它的形式,所以这种方法十分直观。
组合语法:将现有类对象的引用定义于新类中,使用时将现有类初始化即可。
在定义了现有类引用后,在使用这个引用前必须先对其进行初始化,否则在使用现有类的任何功能时都会抛出异常,因为现有类引用在初始化之前只是一个null引用。
对现有类引用的初始化可以出现在:
1.定义引用时初始化,这意味着它将总是在新类的构造器被调用之前被初始化;
2.在新类的构造器中初始化;
3.就在要使用这个对象的某些功能之前对其初始化,这种方式称为惰性初始化,它的好处是只有当我们需要使用它的某些功能时才对它进行初始化,这样可以减少jvm的负担。
4.显式实例中初始化,即在{......}中对其进行初始化,这种初始化也是在新类的构造器被调用之前就已经进行了。
下面的例子展示了组合的使用以及各种初始化方式:

输出结果为:

可以看到,显式实例初始化和定义时初始化都发生在构造器调用之前,他们两的初始化顺序取决于位于代码中的顺序。而惰性初始化则发生在构造器调用之后。
二.继承
继承是按照现有类的类型来创建新类,无需改变现有类的形式并采用现有类的形式在新类中添加代码。新类表现出继承现有类的形式,其中的大部分工作都由编译器来完成,我们只需要完成部分功能代码的编写即可。继承是面向对象编程的基石之一,也是所有OOP和java语言中不可缺少的一部分。当我们创建一个类时,总是在继承,因为除非已经明确指出要从其他类继承外,都是在隐式的继承java的标准根类Object类。
继承语法:通过关键字extends来申明新类继承现有类,extends关键字位于新类主体的左边的花括号之前,关键字后面紧跟基类名称。当我们这样写完之后,新类会自动得到基类中所有的域和方法。包括那些基类中由于访问控制符修饰的对现有类而言不可见的成员(例如private修饰的域与成员,但是无法显式的直接调用)。此时现有类称为基类的导出类(子类)。
看下面的例子:

子类ReuseClassTest01继承BaseClassTest01后,子类对象即获取了基类的成员与方法,并可以调用。
使用继承时需要注意的点:
1.基类为包访问权限时,无法在基类所处包外定义类来继承它。
2.子类继承基类后,会获得基类的所有域与方法。
3.在子类中不一定非得使用基类的方法,也可以自行添加新的方法。添加方式与在类中添加任意方法一致。
4.在子类中可以使用或修改基类中的方法
5.如果想在子类中调用基类原本的方法或域,可以使用super关键字,如super.a,super.f()等等。
基类的初始化:当创建了一个子类对象时,该对象会包含一个基类的子对象,这个子对象与用基类直接创建的对象是一样的,两者的区别在于后者来自外部,而前者中基类的子对象被包装在子类对象内部。对基类子对象的初始化是至关重要的,而且也只有一种方式来保证这一点:在子类构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的的所有知识与能力。java会自动在子类的构造器中插入对基类构造器的调用。这种构建过程是由基类向外扩散的,所以基类在子类构造器可以访问它之前,就已经完成了初始化。所以,子类的所有构造器(包括默认和重载构造器)中都默认并且隐式的调用了基类的默认构造器,如果基类没有定义默认构造器或定义了重载的构造器来取代了 默认构造器,编译器则会报错,此时我们需要在基类中添加上默认的构造器或在子类的构造器中显式的声明出调用基类的哪个重载构造器。并且这个声明必须位于子类构造器的首行。
探索发现:
我们知道在类中,如果有多个构造器定义,那么构造器之间可以相互调用,使用关键字this(param)可以在一个构造器中调用另外一个构造器,并且只能调用一次,而且必须出现在当前构造器的首行,那么当这个类继承了某个其他类时,在构造器中如果有出现相互调用的情况时创建基类子对象的过程是怎样进行的呢?问题可以简化一点:this(param)能否和super(param)同时显式的存在于同一个构造器中?
来测试一下:
(1)子类构造器相互调用时,基类子对象的创建过程
看例子:

子类ReuseClassTest01继承基类BaseClassTest01,子类创建对象时,调用的有参重载构造器,
在有参重载构造器中,通过this()显式调用了它的默认构造器,这种情况下基类子对象是在何时被创建的呢?看下输出结果:

按理来说,2个构造器的首行应该都默认有一个super(),从而进入基类的默认构造器中去创建基类子对象。然而,从结果来看,基类的默认构造器只进去了一次,所以说基类子对象只创建了一次,那么是在子类的哪一个构造器中进入的呢?从结果上来看看不出来。因此,我打断点debug了一下发现:

重载构造器进入后并没有跳到基类的默认构造器中,

而是进入了子类的默认构造,然后从子类的默认构造中进入了基类中的默认构造

从而知道了是在子类的默认构造器中进入基类中去的。那么我们换一个顺序,先用默认构造器去创建子对象,然后在子类的默认构造器中去调用重载构造器,看看结果如何

更改一下代码,然后先看看输出:

输出结果同没更改之前有了不同,子类的默认构造器和重载构造器输出的顺序相反,同时debug发现这次是从重载构造器中进入到基类的默认构造器中去的。debug图片就不发了,有疑问的自己可以去debug试试。通过以上实践,是不是可以总结出基类构造器的初始化是在子类调用的它的最后一次构造器中进入的呢?
看下面一个例子:
(2)子类构造器中相互调用,并有显式的声明调用基类的构造器
我们再次更改一下上面的例子:

这次先调用重载构造器,在重载构造器里面调用默认构造器,输出结果为:

看结果与第一次测试时的结果相同,其实这里的显式写入super()与不写的唯一区别在于显式写的可以指定调用基类的哪一个构造器,这个结果也验证了上面的结论:基类构造器的初始化是在子类调用的它的最后一次构造器中进入的。这个结论也可以理解成基类子对象的创建是在子类调用它的没有this()关键字使用的构造器中进入并创建的,上面的测试也印证了子类中this(param)不能和super(param)同时存在于同一个构造器中,当有显式的this(param)出现时,子类的当前构造器中隐式的super(param)被编译器忽略,并在this(param)指定的下一个构造器中重新验证,直到找到一个没有this(param)使用的构造器。在构造器中,this(param)和super(param)都必须位于首行,所以相互冲突,不能共存。
三.代理
java中并没有对代理提供直接支持,它是继承与组合之间的中庸之道。因为我们将一个成员对象置于索要构造的类中(就像组合),但与此同时,我们在新类中暴露了该成员对象的所有方法(就像继承)。
例如太空飞船SpaceShip需要一个控制模块SpaceShipControls,但是SpaceShip并非SpaceShipControls的一个类型,即便你可以告诉SpaceShip向前运动,准确的来说,SpaceShip包含SpaceShipControls,与此同时,SpaceShipControls的所有方法都在SpaceShip中暴露了出来,这时,代理可以解决这个难题:


使用代理时可以拥有更多的控制力,因为我们可以选择只提供在成员对象中的方法的某个子集。