现在的位置: 首页 > 综合 > 正文

java学习笔记《java面向对象编程》——继承

2018年01月25日 ⁄ 综合 ⁄ 共 5262字 ⁄ 字号 评论关闭

一、继承的基本语法

如果Sub继承了Base类。那么Sub类到底继承了Base类的哪些东西呢?

当Sub类和Base类位于同一个包中:Sub类继承Base类中public、protected和默认访问级别的成员变量和成员方法。

当Sub类和Base类位于不同的包中:Sub类继承Base类中public和protected访问级别的成员变量和成员方法。

Java语言不支持多继承,即一个类只能直接继承一个类,但是可以有多个间接的父类,即父类的父类以及向上多个父类。

所有的Java类都直接或间接地继承了java.lang.Object类。Object类是所有Java类的祖先,在这个类中定义了所有的Java对象都具有的相同行为。

二、方法重载(Overload)

对于类的方法(包括从父类中继承的方法),如果有两个方法的方法名相同,但参数不一致,那么可以说,一个方法是另一个方法的重载方法。

重载方法必须满足一下条件:

(1)方法名相同。

(2)方法的参数类型、个数、顺序至少有一项不相同。

(3)方法的返回类型可以不相同。

(4)方法的修饰符可以不相同。

在一个类中不允许定义两个方法名相同,并且参数签名也完全相同的方法。因为假如存在这样的两个方法,Java虚拟机在运行时就无法决定到底执行哪个方法。

三、方法覆盖(Override)

如果在子类中定义的一个方法,其名称、返回类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,那么可以说,子类的方法覆盖了父类的方法。

覆盖方法必须满足多种约束:

(1)子类的方法的名称、参数签名和返回类型必须与父类的方法的名称、参数签名和返回类型一致。

在这里要注意的是:Java编译器的判断规则是,如果子类和父类的两个方法,名字相同、方法签名相同,那么编译器就根据这两点判断子类的方法试图覆盖父类的方法,这时候如果返回类型一致,则编译没问题,如果返回类型不一致,则编译出错。

(2)子类方法不能缩小父类的访问权限。例如:如果父类Base中的方法是public类型的,子类Sub中的方法是private类型的,那么Base base=new Sub(); base.method();这时候,根据动态绑定规则,Java虚拟机会调用base变量所引用的Sub实例的method()方法,如果这个方法为private类型,Java虚拟机就会无法访问它。这样就会出现矛盾。

(3)子类方法不能抛出比父类更多的异常。子类方法抛出的异常不许和父类方法抛出的异常相同,或者子类方法抛出的异常是子类方法抛出的异常类的子类。例如:Base base=new Sub();编译的时候只是看到了Base类型,所以不能捕获Base类声明之外的类型,否则编译出错,但是如果不捕获这些异常,将导致程序异常终止。

(4)方法覆盖只存在于子类和父类(包括直接父类和间接父类)之间。在同一个类中方法只能被重载,不能被覆盖。重载也可以发生在继承中,子类中的方法和继承自父类中的重载。

(5)父类的静态方法不能被子类覆盖为非静态方法。

(6)子类可以定义与父类的静态方法同名的静态方法,以便在子类中隐藏父类的静态方法。在编译时,子类定义的静态方法也必须满足与方法覆盖类似的约束:方法的参数签名一致,返回类型一致,不能缩小父类方法的访问权限,不能抛出更多的异常。(,为什么会有这些规则:这里只是站在Override的角度看的,如果前提不是为了覆盖或是隐藏,而且父类中定义了一个静态方法,子类中继承了该方法,那么子类可以按照实例方法重载的规则,重载该静态方法,也可以按照上述的规则,来隐藏父类的静态方法,总结起来在重载这个角度上,静态方法和实例方法是一致的,而在方法覆盖这个角度上,实例方法是覆盖,动态绑定机制,而静态方法是隐藏,静态绑定机制)

子类隐藏父类的静态方法和子类覆盖父类的实例方法,这两者的区别是:运行时,Java虚拟机把静态方法和所属的类绑定,而把实例方法和所属的实例绑定。例如:Base base=new Sub(); base.method();如果method方法是实例方法,发生覆盖,执行的是子类的method方法,如果method方法是静态方法,发生隐藏,执行的是父类的method方法。

(7)父类的非静态方法不能被子类覆盖为静态方法。

这里解释一下(5)和(7)的规定,覆盖(override)是在继承+多态的前提下的概念。Java中的静态方法不多态,所以不涉及覆盖,无论静态方法是在基类还是派生类上。 当父类和子类方法发生覆盖时,如果发现两个方法一个静态一个动态,就会编译报错,这样会有矛盾吗?

(8)父类的私有方法不能被子类覆盖。

(9)父类的抽象方法可以被子类通过两种途径覆盖:一是子类实现父类的抽象方法;二是子类重新声明父类的抽象方法(例如,扩大访问权限)。

(10)父类的非抽象方法可以被覆盖为抽象方法。这样是:普通类的子类是抽象类,并且,非抽象方法被抽象方法覆盖。

四、方法覆盖与方法重载的异同

方法覆盖和方法重载有以下相同点:都要求方法同名,都可以用于抽象方法和非抽象方法。

方法覆盖和方法重载具有一下不同点:

(1)方法覆盖要求参数签名必须一致,方法重载要求参数签名必须不不一致。

(2)方法覆盖要求返回类型必须一致,而方法重载不对此做限制。

(3)方法覆盖只能用于子类覆盖父类的方法,方法重载用于同一个类的所有方法(包括从父类中继承而来的方法)。

(4)方法覆盖对方法的访问权限和抛出的异常有特殊的要求,而方法重载在这方面没有任何限制。

(5)父类的一个方法只能被子类覆盖一次,而一个方法在所在的类中可以被重载多次。

五、super关键字

super和this关键字都可以用来覆盖Java语言的默认作用域,使被屏蔽的方法或变量变为可见。

在以下场合会出现方法或变量被屏蔽的现象。

场合一:在一个方法内,当局部变量和类的成员变量同名,或者局部变量和父类的成员变量同名时,按照局部变量的作用域规则,只有局部变量在方法内可见。

场合二:当子类的某个方法覆盖了父类的一个方法,在子类的范围内,父类的方法不可见。

场合三:当子类中定义了和父类同名的成员变量时,在子类的范围内,父类的成员变量不可见。

值得注意的是,如果父类中的成员变量和方法被定义为private类型,那么子类永远无法访问它们。

在程序中,在以下情况下会使用super关键字:

(1)在类的构造方法中,通过super语句调用这个类的父类的构造方法。

(2)在子类中访问父类的被屏蔽的方法和属性。

还有需要注意的是,只能在构造方法或实例方法内使用super关键字,而在静态方法和静态代码块内不能使用super关键字。

六、多态

Java语言允许某个类型的引用变量引用子类的实例,而且可以对这个引用变量进行类型转换。

如果把引用变量转换为子类类型,称为向下转型,如果把引用变量转换为父类类型,称为向上转换。

多态的各种特性:

(1)对于一个引用类型的变量,Java编译器按照它的声明类型来处理,运行时Java虚拟机按照它实际引用的对象来处理。

(2)在运行时环境中,通过引用变量来访问所引用对象的方法和属性时,Java虚拟机采用以下绑定规则:

A:实例方法与引用变量实际引用的对象的方法绑定,这种绑定属于动态绑定,因为是在运行时由Java虚拟机动态决定的。

B:静态方法与引用变量所声明的类型的方法绑定,这种绑定属于静态绑定,因为实际上是在编译阶段就已经做了绑定。

C:成员变量(包括静态变量和实例变量)与引用变量声明的类型的成员变量绑定,这种绑定属于静态绑定,因为实际上是在编译阶段就已经做了绑定。

七、继承的利弊与使用原则

继承关系最大的弱点:打破封装(隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别)。在继承关系中,子类能够访问父类的属性和方法,也就是说,子类会访问父类的实现细节,子类与父类之间是紧耦合关系,当父类的实现发生变化时,子类的实现也不得不随之变化,这就削弱了子类的独立性。而组合没有打破封装,组合是通过这个类的对象来访问类的属性和方法,继承是直接访问父类的属性和方法,而且可以进行覆盖篡改。

由于继承关机会打破封装,这增加了维护软件的工作量。尤其在一个Java软件系统使用了一个第三方提供的Java类库的场合。 当第三方提供的框架发生改变的时候,系统中所有继承这个类的子类也要相应的修改。

由于继承会打破封装,这将导致父类的实现细节很容易被子类恶意篡改(通过覆盖)。

由于继承关系会打破封装,因此随意基础功能对象模型中的任意一个类是不安全的做法。在建立对象模型时,应该先充分考虑软件系统中哪些地方需要扩展,为这些地方提供扩展点,也就是提供一些专门用于被继承的类。所以我们需要精心设计专门用于继承的类:

(1)对这些类必须提供良好的文档说明,使得创建该类的子类的开发人员知道如何正确安全的扩展它。对于那些允许子类覆盖的方法,应该详细的描述该方法的自用性,以及子类覆盖此方法可能带来的影响。所谓方法的自用性,是指在这个类中,有其他的方法会调用这个方法。例如:Account类的idEnough()方法会被save()方法调用,因此子类覆盖isEnough()方法还会影响到save()方法。

(2)尽可能的封装父类的实现细节,也就是把代表实现细节的属性和方法定义为private类型。如果某些实现细节必须被子类访问,也可以在父类中把包含这种实现细节的方法定义为protected类型。当子类仅调用父类的protected类型的方法而不覆盖它时,可把这种protected类型的方法看做父类仅向子类但不对外公开的接口。

(3)把不允许子类覆盖的方法定义为final类型。

(4)父类的构造方法不允许调用可被子类覆盖的方法,因为如果这样做,可能会导致程序运行时出现未预料的错误。例如:

public class Base{
	public Base(){
		method();
	}
	public void method(){}
}
public class Sub extends Base{
	private String str=null;
	public Sub(){
		str="1234";
	}
	public void method(){
		System.out.println(str.length());
	}
	public static void main(String args[]){
		Sub sub=new Sub();
		sub.method();
	}
}

运行sub类的main()方法。由于在创建子类的实例时,Java虚拟机先调用父类的构造方法,因此Java虚拟机先执行Base类的构造方法Base(),然后在这个方法中调用method()方法。根据动态绑定规则,Java虚拟机调用Sub实例的method()方法,由于此时Sub实例的成员变量str为null,因此在执行str.length()方法时,会抛出NullPointerException运行时异常。

(5)如果某些类不是为了继承而设计,那么随意继承它是不安全的。因此可以采取以下两种措施来禁止继承:

A:把类声明为final类型。B:把这个类的构造方法声明为private类型,然后通过一些静态方法来负责构造自身的实例。(这里为什么不允许继承,还有上面怎么调用父类的构造方法,后面再讲)
八、比较组合和继承

组合和继承相比,前者的最主要优势是不会破坏封装,,当类A和类C之间为组合关系时,类C封装实现,仅向类A提供接口;而当类A与类C之间为继承关系时,类C会向类A暴露部分实现细节。在软件开发阶段,组合关系虽然不会比继承关系减少代码量,但是到了软件维护阶段,由于组合关系系统具有较好的松耦合性,因此使得系统更加容易维护。

组合关系的缺点是比继承关系要创建更多的对象。对于组合关系,创建整体类的实例时,必须创建其所有局部类的实例;而对于继承关系,创建子类的实例时,无须创建父类的实例。

组合关系具有以下优点:

(1)组合关系使系统具有更好地可扩展性。扩展功能的时候创建的类少。

(2)继承关系是静态的,在运行时,子类无法改变它的父类。组合的情况是,整体类里面的部分类引用可以是父类的类型,这样灵活接受不同的子类。

(3)在组合关系中,整体类能够灵活的对局部类封装,改变局部类的接口。也就是整体类重新定义我有什么方法,这些方法中用到了局部类接口所提供的服务,但是向外公布的接口只是整体类的接口,局部类的接口被隐藏了。而继承中,子类只能继承父类的接口,不能取消父类的方法。如果子类不希望用户调用它继承过来的方法,一种方法是覆盖这些方法,在这些方法中直接抛出java.lang.UnsupportedOperationException运行时异常。

在继承关系中,子类能够自动继承父类的属性和方法。和继承关系相比,组合关系的一个缺点是,整体类不会自动获得局部类的接口,就是说不会直接获得局部类的方法,自己重新定义,这和(3)比较来看,既是优点也是缺点。

抱歉!评论已关闭.