继承与组合
继承与组合的作用:复用代码(使用类而不破坏现有的程序代码)
- 组合:在新的类中产生现有类的对象。新的类是由现有类的对象所组成,只是复用了现有程序代码的功能,而非它的形式。
- 继承:无需改变现有类的形式,基于现有类的形式进行拓展。
1、继承(A is a B)
基本思想:基于某个父类进行拓展,得到一个新的子类。子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。
当要设计的新类与原有的类的关系是“is-a”(是一个)时,适合用继承的方式。
例如:原有的类为交通工具类,要设计的新类为飞机类,船类和车类。飞机、船和车都可以说成:( 飞机、船、车 )是一个交通工具
可以用如下代码描述:
public class Vehicle{ //交通工具类
protected void run(){
System.out.println("");
}
public static void main(String args[]){
Vehicle plane = new Plane(); //类的向上转型
plane.run();
Vehicle ship = new Ship(); //类的向上转型
ship.run();
}
}
class Plane extends Vehicle{ //飞机类
protected void run(){
System.out.println("飞机在天上飞!");
}
}
class Ship extends Vehicle{ //船类
protected void run(){
System.out.println("船在水里游!");
}
}
/*
输出结果:
飞机在天上飞!
船在水里游!
*/
重写
继承并不只是拓展父类的功能,还可以重写父类的成员方法。
重写(覆盖):在子类中将父类的成员方法的名称保留,重写成员方法的实现内容,更改成员方法的存储权限或是修改成员方法的返回值类型(重写父类成员方法的返回值类型是基于 J2SE5.0 版本以上编译器提供的新功能)
重构
重构:在继承中还有一种特殊的重写方式,子类与父类的成员方法返回值、方法名称、参数类型及个数完全相同,唯一不同的是方法实现内容,这种特殊的重写方式被称为重构。
重写父类的构造方法
在继承的机制中,创建一个子类对象,将包含一个父类子对象,这个对象与父类创建的对象是一样的,两者的区别在于后者来自外部,而前者来自子类对象的内部。当实例化子类对象时,父类对象也被相应的实例化,换句话说,在实例化子类对象时,Java编译器会在子类的构造方法中自动调用父类的无参构造方法。
class a{
a(int a){
System.out.println("调用a类的构造方法!");
}
}
public class Demo extends a {
Demo(){
System.out.println("调用Demo类的构造方法!");
}
}
/*
因为a类中定义了一个有参的构造函数,如果a类中未定义无参的构造方法,那么在Demo类构造无参的构造方法时系统将会报错——未定义隐式超构造函数a( ),必须显示调用另一个有参构造函数。
此时有两种解决方法
1、在a类中再构造一个无参的构造方法
2、将Demo类中的构造方法也改写为有参的形式,如下
*/
public class Demo extends a {
Demo(int a){
super(a);
System.out.println("调用Demo类的构造方法!");
}
}
正常情况下,我们生成一个类,会自动生成它的无参构造方法。
但当我们创自己构造了一个有参的构造方法后,系统就不会自动给我们生成无参构造方法,此时我们要是想要使用无参构造方法,就必须自己再构造一个无参构造方法。
情景假设:
1、
此时有一个父类 A 类,不在里面声明任何东西,此时系统会自动生成一个 A 类的无参构造方法。
当有一个子类 a 类继承父类 A 类时,父类 A 类只有一个无参构造方法,此时编译器会在子类的构造方法中自动(隐式)调用父类的无参构造方法,所以子类的构造方法中可以不用书写 super( ); 这条语句。甚至 a(){…} 都可以省略,因为系统会自动生成 a 类的无参构造方法 a(){…}
public class A{
}
class a extends A{
a(){
//super(); 显式调用父类的无参构造方法
}
}
2、
此时有一个父类 A 类,在里面声明一个有参构造方法,此时系统不会自动给我们生成无参构造方法,A 类中只有一个有参构造方法。
当有一个子类 a 类继承父类 A 类时,父类 A 类只有一个有参构造方法,此时编译器不会在子类中自动调用父类的有参构造方法。要想在子类构造方法中创建父类对象,只能显式的调用父类的有参构造方法或者在父类中再自己创建一个无参的构造方法,然后让编译器自动的去调用这个无参的构造方法,同样的,此时子类的无参构造方法中可以不用书写 super( ); 这条语句。甚至 a(){…} 都可以省略。
//显式的调用父类的有参构造方法
public class A{
A(int A){
}
}
class a extends A{
a(int a){
super(a);
}
}
/*
1、反正最终的目的都是调用父类的有参构造方法,只要给super( )语句一个 int 型的参数即可,因此也可以采取下面这种方式显式调用父类的有参构造方法
2、在这里需要注意的是super( )语句必须放在第一行的位置,否则会报错
3、虽然两种方法都可以显示调用父类的有参构造方法,但还是建议使用上面那种
*/
public class A{
A(int A){
}
}
class a extends A{
static int a; //显式调用构造函数时不能引用实例字段 a
a(){
super(a);
}
}
//在父类中再自己创建一个无参的构造方法,然后让编译器自动的去调用这个无参的构造方法
public class A{
A(int A){
}
A(){
}
}
class a extends A{
a(){
//super(); 显式调用父类的无参构造方法
}
}
总结
1、在实例化子类对象时,父类无参构造方法将会被自动调用;有参构造方法不能被自动调用,用户只能使用super关键字显示的调用父类的构造方法。
2、实例化子类对象时首先要实例化父类对象,然后再实例化子类对象,所以在子类构造方法访问父类的构造方法之前,父类已经完成实例化操作。
class Parent{ //父类
Parent(){
System.out.println("调用父类的Parent()构造方法");
}
}
class SubParent extends Parent{ //继承Parent类
SubParent(){
System.out.println("调用子类的SubParent()构造方法");
}
}
public class Subroutine extends SubParents{ //继承SubParent类
Subroutine(){
System.out.println("调用子类的Subroutine()构造方法");
}
public static void main(String args[]){
Subroutine s = new Subroutine(); //实例化子类对象
}
}
/*
输出结果:
调用父类的Parent()构造方法
调用子类的SubParent()构造方法
调用子类的Subroutine()构造方法
*/
3、如果父类的构造方法是无参的,那么子类的构造方法可以是无参也可以是有参,且系统会自动调用父类的无参构造方法。
4、如果父类的构造方法是有参的,那么子类的构造方法必须是有参的,且必须使用super关键字对父类的有参构造方法进行显示调用(这条语句必须放在第一句,否则会报错);或再在父类中构造一个无参的构造方法,让系统自动调用父类的无参构造方法。
5、如果使用finalize()方法对对象进行清理,需要确保子类finalize()方法的最后一个动作是调用父类的finalize()方法,以保证当垃圾回收对象占用内存时,对象的所有部分都能被正常终止。
public class A{
A(int A){
}
protected void finalize(){
}
}
class a extends A{
a(int a){
super(a);
}
protected void finalize(){
A AA = new A(7);
AA.finalize();
}
}
下面的例子对继承做了一个整体的概述
class Test {
public Test() { //构造方法
//SomeSentence
}
protected void doSomething() { //成员方法
//SomeSentence
}
protected Test dolt() { //方法返回值类型为Test类型
return new Test();
}
}
class Test2 extends Test{ //继承父类Test类
public Test2() { //构造方法
/**
* 子类中可以连同初始化父类构造方法来完成子类的初始化操作
* 既可以在子类的构造方法中使用super()语句调用父类的构造方法
* 也可以在子类中使用super关键字调用父类的成员方法等,但需要注意的是,super()语句一定得放在第一句,否则会报错
* 但是子类没有权限调用父类中被修饰为private的方法,只可以调用父类中修饰为public和protected的成员方法
*/
super(); //调用父类构造方法
super.doSomething(); //调用父类成员方法
}
public void doSomethingnew() { //在子类中定义的新方法doSomethingnew()
//在子类中可以定义一些新方法
//SomeSentence
}
public void doSomething() { //重写父类方法
/**
* 重写还可以称为覆盖,就是在子类中将父类的成员方法的名称保留,重写成员方法的实现内容,更改成员方法的存储权限,或是修改成员方法的返回值类型
* 或是修改成员方法的返回值类型(这种重写方式需要遵循一个原则,即重写的返回值类型必须是父类中同一方法返回值类型的子类,而Test2类正好是Test类的子类)
* 子类Test2中的doSomething()方法,除了重写方法的实现内容之外,还将方法的修饰权限符改为public
* 当重写父类方法时,修改方法的权限修饰只能从小的范围到大的范围改变
* 如,父类中的doSomething()方法的权限修饰符为protected
* 继承后子类中的方法doSomething()的权限修饰符只能修改为public,不能修改为private
* 在继承中还有一种特殊的重写方法,子类和父类的成员方法返回值、方法名称、参数类型及个数完全相同,唯一不同的是方法实现内容,这种特殊的重写方式称为重构
*/
//SomeSentence
}
protected Test dolt() { //重写父类方法,方法返回值类型为Test2类型
//这种重写方式需要遵循一个原则,即重写的返回值类型必须是父类中同一方法返回值类型的子类,而Test2类正好是Test类的子类
return new Test2();
}
}
2、组合(A has a B)
当要设计的新类与原有的类的关系是“has-a”(有一个)时,适合用组合的方式。
例如:一个简单的房子(SimpleHouse)有两个窗户(Window)、有一个门(Door)、有一个桌子(Table)以及四个椅子(Chair)等等
可以用如下代码描述:
public class SimpleHouse { //房子类
private Window[] windows = new Window[2];
private Door door = new Door();
private Table table = new Table();
private Chair[] chairs = new Chair[4];
/*some code...*/
}
class Window {/*some code...*/} //窗户类
class Door {/*some code...*/} //门类
class Table {/*some code...*/} //桌子类
class Chair {/*some code...*/} //椅子类
注:本例为了便于展示,只写了足以说明问题的代码。所有字段均按照习惯修饰为private,使用时,可以根据需求为private字段提供相应的get、set方法。