继承

1.继承的概念:

	继承:如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类”,也可以称“A是B的超类”。
子类继承了父类的除了构造函数的其他非私有数据成员。继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同
的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与
父类不同的功能。另外,为子类追加新的属性和方法也是常见的做法。 

2.继承的优点:

·提高了代码的复用性
·让类与类之间产生了联系,为多态提供了条件

3.继承的特点:

·父类的内部细节对子类是可见的。
·子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。
·子类与父类是一种高耦合,违背了面向对象思想。
·继承关系最大的弱点是打破了封装,子类能够访问父类的实现细节,子类与父类之间紧密耦合,子类缺乏独立性,从而影响了
	子类的可维护性。
·不支持动态继承。在运行时,子类无法选择不同的父类。

4.父类数据成员在子类中的访问权限

这里写图片描述


5.Java的单继承性

Java支持单继承,不直接支持多继承,因为会产生调用的不确定性。
·单继承:一个子类只能有一个直接父类
·多继承:一个子类可以有多个直接父类

Java支持多层继承(多重继承),即C继承B,B继承A。

当要使用一个继承体系时:
·查看该体系的顶层类,了解体系的基本功能
·创建体系的最子类对象,完成功能的使用

6.重写与覆盖

重写与覆盖的区别:

	重写和覆盖都是对一个方法进行重新定义,但对一个方法重写后,方法的原有版本和新版本可以共存,即都可以使用;而
覆盖以后,原有版本不可使用,或者原有版本根本就不存。

继承中的重写:

	当子类中的方法名或变量名与父类中的非私有方法名相同时(方法名相同,参数列表相同,返回值相同),子类就不能继承父
类的该方法,此时称子类的方法重写了父类的方法。重写体现了子类补充或修改父类方法的能力,通过重写可以使一个方法在不
同的子类中表现出不同的行为。如果要在子类中使用该方法或变量,就要使用super关键字,使用super关键字可以调用父类
的构造方法,也可以操作被隐藏的成员变量或被重写的成员方法。

函数的两个特征:

·重载(overload):发生在同一个类中,没有权限要求,返回值可以不同。
·覆盖(override):发生在子类中,要求子类的权限不能比父类严格,且参数类型、个数,返回值必须相同。

覆盖的注意事项:

·在子类方法覆盖父类非私有方法时,子类方法的权限必须要大于等于父类权限。
·当子类或父类方法中有一个为static修饰的静态方法时无法完成覆盖,只有当双方都是静态时才能实现覆盖。

覆盖的使用场景:

	当对一个类进行子类扩展时,子类需要保留父类的功能申明,但是要定义子类中该功能的特有内容时,就可以使用覆盖操
	作完成。

super关键字的用法和this类似:

·this代表一个本类对象的引用,即指向当前对象的内存
·super代表一个父类空间

7.子类的实例化

子类的实例化过程:子类中所有的构造方法都会访问父类的无参的构造方法。
在执行子类的构造方法时,会同时执行父类的构造方法,原因是在子类的构造方法第一行有一句默认的隐式语句:super();

看一个例子:

class Fu{
	Fu(){
		System.out.println("fu");
	}
	Fu(int x){
		System.out.println("fu x");
	}
}

class Zi  extends  Fu{
	Zi(){ 
	    // super();
		System.out.println("zi");
	}
	Zi(int y){
		// super();
		System.out.println("zi y");
	}
}

public class Test {
	public static void main(String[] args) {
		Zi z = new Zi(0);
	}
}

此程序段的结果是:

		fu 
		zi y

当主方法声明无参的子类对象Zi z = new Zi();时,结果为:

		fu 
		zi

若是父类中定义了有参的构造方法而没有无参的构造方法,必须在子类的构造方法中手动声明要调用父类的哪个构造方法,否则会编译出错。
在以上的例子中删除父类的无参构造方法,即

class Fu{
	/*Fu(){
		System.out.println("fu");
	}*/
	Fu(int x){
		System.out.println("fu x");
	}
}

编译错误:

	错误: 无法将类 Fu中的构造器 Fu应用到给定类型;
	        Zi(){

子类实例化时访问父类构造方法的原因:

	因为子类继承了父类,获取到了父类中的内容(属性),所以在使用父类内容之前,要先看看父类对于自己的内容是如何进
行初始化的。所以子类在构造对象时就必须访问父类的构造方法,为了实现这个必须的操作,就在子类的所有构造方法之前加入
了super()语句。如果父类中没有无参的构造方法,则在子类的构造方法中必须用super明确声明要使用父类的哪个构造方法。

**注意:**super语句必须定义在子类构造方法的第一行。因为父类的初始化要先于子类完成。如果同时子类的构造方法中使用了this关键字调用本类的其他构造方法,则本方法中的隐式super()语句就没有了,因为super和this都只能定义在第一行,不能同时存在。

在Java中所有的类都直接或间接的继承于object类,所以每个类的构造方法的第一句都有super()语句。


8.子类的实例化顺序:

静态块初始化(一次) → 实例块初始化 → 构造方法初始化。

看以下例子:

class Fu{
	Fu(){
		System.out.println("fu");
	}
	Fu(int x){
		System.out.println("fu x");
	}
	static {
		System.out.println("fu static");
	}
	{
		System.out.println("fu {}");
	}
   }

class Zi extends Fu{
	Zi(){
		System.out.println("zi");
	}
	Zi(int y){
		System.out.println("zi y");
	}
	static {
		System.out.println("zi static");
	}
	{
		System.out.println("zi {}");
	}
   }

   public class Test {
	public static void main(String[] args) {
		Zi z = new Zi();
		Zi z1 = new Zi(0);	
	}
   }

以上程序的结果是:

	fu static
	zi static
	/* 执行Zi z = new Zi();语句,先进行父类实例块,再执
	行父类构造方法,而后执行子类的的实例块和无参构造方法 */
	fu {}
	fu
	zi {}
	zi
	/* 执行Zi z = new Zi(0);语句,先进行父类实例块,再执
	行父类构造方法,而后执行子类的的实例块和带参构造方法 */
	fu {}
	fu
	zi {}
	zi y

9.一个对象的实例化过程:

以Person p = new Person()为例:

·JVM会读取指定路径下的Person.class文件,并加载进内存,若是有直接父类,则会先加载Person的父类
·在堆内存中开辟空间,分配地址
·在对象空间中对对象的属性进行默初始化
·调用对应的构造方法进行初始化
·构造方法的第一句会先调用父类的构造方法进行初始化
·父类初始化完成后,再对子类的属性进行显式初始化
·再进行子类构造函数的特定初始化
·初始化完毕后,将地址值赋值给引用变量

先来明确一个概念:

绑定
	·把一个方法与其所在的类/对象 关联起来叫做方法的绑定。绑定分为静态绑定(前期绑定)和动态绑定(后期绑定)。
    ·静态绑定(前期绑定)是指在程序运行前就已经知道方法是属于那个类的,在编译的时候就可以连接到类的中,定位到这
个方法。
    ·在Java中,final、private、static修饰的方法以及构造函数都是静态绑定的,不需程序运行,不需具体的实例对象
就可以知道这个方法的具体内容。
    ·动态绑定(后期绑定)是指:在程序运行过程中,根据具体的实例对象才能具体确定是哪个方法。

动态绑定是多态性得以实现的重要因素,它通过方法表来实现:

	每个类被加载到虚拟机时,在方法区保存元数据,其中包括一个叫做方法表(methodtable)的东西,表中记录了这个类定
义的方法的指针,每个表项指向一个具体的方法代码。如果这个类重写了父类中的某个方法,则对应表项指向新的代码实现处。
从父类继承来的方法位于子类定义的方法的前面。

看以下例子:

class Fu{ 
	Fu(){
		super();
		show();
		return ;
	}
	void show() {
		System.out.println("fu show");
	}
}

class Zi extends Fu{
	int num = 8;
	Zi(){
		super();
		return ;
}
	void show() {
		System.out.println("zi show……" + num);
	}
}

public class Test {
	
	public static void main(String[] args) {
		Zi z = new Zi();
		z.show();
	}
}

结果是:

		zi show……0
        zi show……8

结果分析:
这里写图片描述
执行Zi z = new Zi(); 语句时,在堆中为子类对象开辟空间并默认初始化为0,此时子类的构造器Zi()进栈并指向刚开辟的子类对象空间地址,接着Zi()中super()语句就调用父类的无参构造方法,故父类的构造器Fu()就入栈了,并且也指向了刚开辟的子类对象空间地址,此时父类的show()方法动态绑定到子类对象的show()地址,即此时是子类对象调用show()方法,会优先在子类空间中寻找名为show()d的方法,输出此时num的默认值0。父类的构造结束后,父类构造器Fu()弹栈(出栈),即super()语句执行完毕,而后进行子类对象属性的显式初始化,即给num赋值为8;此时子类对象的所有属性显式初始化完成,子类构造器弹栈,至此对象的创建完毕,将对象的引用赋给z进行下一步的运行。

对以上分析的验证:
将子类的构造方法变为:

Zi(){
		super();
System.out.println("zi cons……" + num);
		return ;
}

则结果是:

			zi show……0
            zi cons……8
            zi show……8

此结果验证了子类对象的初始化过程。
再将子类的show()方法屏蔽,即:

class Zi extends Fu{
	int num = 8;
	Zi(){
		super();
		return ;
}
	/* void show() {
		System.out.println("zi show……" + num);
	}*/
}

则结果是:

		fu show
		fu show

此结果验证了方法的动态绑定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值