Java面向对象专题(三) 继承

  • Post author:
  • Post category:java




继承



概述

多个类中

存在相同属性和行为

时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承该类即可

单独的这个类称为父类、基类或者超类,这多个类可以称为子类或者派生类

有了继承之后,我们定义一个类的时候,可以

在父类的基础上,定义自己新的成员方法和成员变量



方式

通过extends关键字可以实现类与类的继承

格式:class 子类名 extends 父类名{ };



好处与弊端

  • 好处

    • 提高了代码的复用性
    • 提高了代码的维护性
    • 让类与类之间产生了关系,是多态的前提
  • 弊端

    • 使类的耦合性增强了,这样某个类的改变就会影响其他和该类相关的类
    • 打破了封装性

开发的原则:

低耦合,高内聚


耦合:类与类之间的关系

内聚:独立完成某事的能力



特点

  • Java只支持

    单继承

    ,不支持多继承
  • Java支持

    多层继承

    (继承体系)



注意事项

  • 子类

    只能继承父类所有非私有的成员

    (成员方法和成员变量)
  • 子类不能继承父类的构造方法,但可以

    通过super关键字访问父类构造方法
  • 不要为了部分功能而去继承
  • 使用继承的情况:

    如果有两个类A,B,只有他们符合A是B的一种,或者B是A的一种,就可以考虑使用继承


继承体现的是一种”is a”的关系。



成员关系



成员变量

  • 子类中的成员变量和父类中的成员变量名称不一样时,调用对应的变量即可
class Father{
	public int num = 10;
}
class Son extends Father{
	public int num2 = 20;
	public void show(){
		System.out.println(num); //10
		System.out.println(num2); //20
	}
}
  • 子类中的成员变量和父类中的成员变量名称一样时,就近原则

在子类方法中访问一个变量的查找顺序(找到就使用):

子类的局部范围 -> 子类的成员范围 -> 父类的成员范围 -> 报错

问题:子类中怎么明确变量名称一样的不同变量呢?

解决方法:引入this 和 super 关键字

this 和 super 的定义:

this:代表本类对应对象的引用

super:代表父类存储空间的标识(可以理解为父类引用)

例子:

class Father{
	public int num = 10;
}
public class Son extends Father{
	public int num = 20;
	public void show() {
		int num = 30;
		System.out.println(num); //30
		System.out.println(this.num); //20
		System.out.println(super.num); //10
	}
}



构造方法


子类中所有的构造方法默认都会访问父类中的无参构造方法

原因:

子类会继承父类中的数据,可能还会使用父类的数据,所以子类初始化前一定要先完成父类数据的初始化

子类每一个构造方法的第一条语句默认都是:super();

问题:如果父类没有无参构造方法(自己只定义一个有参构造方法,即可去除默认的无参构造方法),那么子类的构造方法会报错。如何解决?

解决方法:


1、在父类中加一个无参构造器

2、通过使用super关键字去显示调用父类的带参构造方法

3、子类通过this关键字去调用本类的其他构造方法,间接访问父类构造方法

class Father{
	String name;
	//带参构造器会取消默认的无参构造器
	public Father(String name){
		this.name = name;
		System.out.println("Father 带参构造");
	}
}
class Son extends Father{
	public Son(String name){
		super(name);
		//如果没有上面那句话,系统会默认调用super(); ,但由于父类中没有无参构造,会报错
		System.out.println("Son 有参构造");
	}
}

总之:

子类中的构造方法一定访问父类中的一个构造方法,否则父类数据就没有初始化


另外,需注意的是:

super(…)或this(…)必须出现在第一条语句上

,否则,就会出现父类数据多次初始化,报错(”…”表示需传入的参数)

例子:

class Father{
	public Father() {
		System.out.println("Father 无参构造方法");
	}
	private void Fathe(String name) {
		System.out.println("Father 带参构造方法");
	}
	public int num = 10;
}
public class Son extends Father{
	public int num = 20;
	public Son() {
		System.out.println("Son 无参构造方法");
	}
	public Son(String name) {
		System.out.println("Son 带参构造方法");
	}
	public void show() {
		int num = 30;
		System.out.println(num);
		System.out.println(this.num);
		System.out.println(super.num);
	}
}
class Test{
	public static void main(String[] args) {
		Son son = new Son();
		son.show();
		System.out.println("------------------");
		Son son2 = new Son("AAA");
	}
}

运行结果:

运行结果



成员方法

  • 子类中的方法和父类中的方法声明不一样,就调用对应的方法
  • 子类中的方法和父类中的方法声明一样,最终使用的是子类自己的方法

通过子类调用方法的查找顺序(找到就使用):

子类 -> 父类 -> 报错

其中涉及到了

方法重写

方法重写和方法重载的区别:


方法重写(Override)

:子类中出现了和父类中方法声明一模一样的方法


方法重载(Overload)

:本类中出现的方法名一样,参数列表不同的方法,与返回值无关

方法重写的应用:

当子类需要父类的功能,而功能主体子类有自己特有的内容时,可以重写父类中的方法。

这样,既沿袭了父类的功能,又定义了子类特有的内容

例子:

class Phone{
	public void call(String name) {
		System.out.println("call " + name);
	}
}
class NewPhone extends Phone{
	@Override
	public void call(String name) {  //方法重写
		super.call(name);
		System.out.println("呼叫过程中可以听到彩铃");
	}
	public void call() {	//方法重载
		System.out.println("打电话");
	}
}
public class OverrideTest {
	public static void main(String[] args) {
		NewPhone np = new NewPhone();
		np.call();
		np.call("Jay");
	}
}

运行结果:

运行结果

  • 方法重写的注意事项:

    • 父类中私有方法不能被重写, 因为父类私有成员根本无法被继承

    • 子类重写父类方法时,访问权限不能更低

      , 最好一致
    • 父类

      静态方法

      ,子类也必须通过静态方法重写(

      本质上不是方法重写

    子类重写父类方法时,最好声明一模一样



this 和 super 的使用

使用 this super
成员变量 调用本类的成员变量 调用父类的成员变量
构造方法 调用本类的构造方法 调用父类的构造方法
成员方法 调用本类的成员方法 调用父类的成员方法

具体的用法可参考:

https://www.cnblogs.com/hasse/p/5023392.html



final 关键字



概述

由于继承中方法有一个现象:方法重写。所以,父类的功能,会被子类给覆盖掉。有些时候,我们

不想让子类去覆盖父类的功能

。这个时候,针对这种情况,Java提供了一个关键字:

final

final 可以修饰类,方法,变量。



特点

  • final 可以修饰类,该

    类不能被继承
  • final 可以修饰方法,该

    方法不能被覆盖重写
  • final 可以修饰变量,该

    变量不能被重新赋值

    (这个变量其实是常量)

常量:

  • 字面值常量

    “hello” ,10 ,true
  • 自定义常量

    final int x = 10;



注意事项

  • final 修饰局部变量的问题:

    • 基本类型:值不能发生改变
    • 引用类型:地址值不能发生改变,但是,该对象堆内存中的内容可以修改

例子

class Student{
	int age = 10;
}

class FinalTest{
	public static void main(String[] args){
		int x = 10;
		x =100;
		System.out.println(x);
		final int y = 10 ;
		//无法为最终变量赋值
		//y = 100;
		System.out.println(y);
		
		Student s = new Student();
		System.out.println(s.age);
		s.age = 100;
		System.out.println(s.age);
		
		final Student ss = new Student();
		System.out.println(ss.age);
		ss.age = 100; //正确
		System.out.println(ss.age);
		
		//ss = new Student();//错误		
	}
}

  • final 修饰变量的初始化问题:

    • 被final修饰的变量

      只能赋值一次

      (不包括系统默认初始值)
    • 对于非静态的常量,尽量在构造方法完毕前赋值。



面试题

1、看程序写结果:

class Fu{
	static {
		System.out.println("静态代码块Fu");
	}
	{
		System.out.println("构造代码块Fu");
	}
	public Fu() {
		System.out.println("构造方法Fu");
	}
}
class Zi extends Fu{
	static {
		System.out.println("静态代码块Zi");
	}
	{
		System.out.println("构造代码块Zi");
	}
	public Zi() {
		System.out.println("构造方法Zi");
	}
}
public class ExtendsTest {
	public static void main(String[] args) {
		Zi zi = new Zi();
	}
}

结果:

结果

分析要点:

A:一个类的静态代码块,构造代码块,构造方法的执行顺序:

静态代码块>构造代码块>构造方法

B:静态的内容随着类的加载而加载

静态代码块的内容会优先执行

C:子类初始化之前会先进行父类的初始化

从main()方法入口,执行

Zi zi = new Zi();

时,程序加载Zi类,发现Zi类继承自Fu类,便把Fu类也加载了(实际的加载顺序是先父类后子类)。类加载后便会优先执行静态代码块。子类初始化之前会先进行父类的初始化,所以会先执行父类的构造代码块和构造方法,之后再执行子类的构造代码块和构造方法。

2、看程序写结果

class X {
	Y b = new Y();
	X(){
		System.out.println("X");
	}
}
class Y{
	Y(){
		System.out.println("Y");
	}
}
public class Z extends X{
	Y y = new Y();
	Z() {
		System.out.println("Z");
	}
	public static void main(String[] args) {
		new Z();
	}
	
}

结果:

结果

分析要点:

A:成员变量的问题

int x = 10; //成员变量是基本类型

Student s = new Student(); //成员变量是引用类型

B:一个类中成员变量的初始化过程

默认初始化 -> 显示初始化 -> 构造方法的初始化

C:子类的初始化(分层初始化)

先进行父类初始化,后进行子类初始化

从main()方法入口,执行

new Z();

时,加载类X和类Z,先执行类X的成员变量初始化,

Y b = new Y();

创建类Y的对象b,调用构造方法,打印出“Y”,再执行类X的构造方法,输出“X”;类X初始化完毕后,执行类Z的初始化,

Y y = new Y();

创建类Y的对象y,调用构造方法,打印出“Y”,再执行类Z的构造方法,输出“Z”。

关于Java中父类和子类的加载和初始化顺序,可参考:


https://blog.csdn.net/sunroyfcb/article/details/81637565


关于Java中初始化的本质,可参考:


https://blog.csdn.net/sunroyfcb/article/details/81637565



版权声明:本文为JayFan_Ma原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。