JavaSE第10篇:面向对象之继承

本篇我们将继续学习面向对象编程,在之前我们已经学习过了面向对象之封装,知道了如何定义一个标准的类及如何创建和使用对象。面向对象有三大特征,封装、继承、多态。那么接下来,我们将会学习面向对象编程的继承。

第一章:继承

1.1-继承概述(了解)

什么是继承

继承,指的是事物与事物之间的关系。

在生活中,我们所理解的“继承”更多的是“子承父业”,就是儿子与父亲之间存在“继承”关系。

在Java编程中,我们用类来描述事物,那么在面向对象编程中,“继承”指的是类与类之间的关系,通常是子类父类之间的关系,子类可以继承父类中的非私有成员(属性和方法)。

为什么要有继承

继承有什么好处呢?

在生活中,很显然,儿子继承父亲的财产,可以少奋斗几十年甚至几辈子。

在编程中,继承有什么作用呢?我们先来看一个需求,需求如下:

  • 用面向对象的方式,描述几种动物,狗、猫、牛
    • 狗,有名字、年龄、性别、毛色、爱吃食物、会看门
    • 猫,有名字、年龄、性别、毛色、爱吃食物、会撒娇
    • 牛,有名字、年龄、性别、毛色、爱吃食物、会耕地

此时面临这个需求时,我们通常会定义三种类来描述,描述如下:

我们观察可以发现,会存在这样一种现象:猫、狗、牛,存在一些共性的成员,这些共性都分别在不同的类中定义了。

问题:这样在编程中,就出现的代码冗余(重复),若是以后有更多的动物种类出现,还是要重新定义它们的共性成员,在整个工程看来,代码将会变得越来越冗余,越来越臃肿,不易于程序后期的维护。那如何解决呢?

解决:此时就可以使用继承机制解决代码的冗余问题,抽取共性,让共性复用。抽取的方式就是,抽象出更高级的类(超类、父类),把共性定义在父类中,让其他也要拥有这些共性成员的类作为子类继承父类。

对于上述需求,我们可以抽象出它们的父类,动物类。它们都是动物,动物都有属性-名字、年龄、毛色、性别,都是吃货,最终类的定义描述如下:

所以,在面向对象编程中,继承的作用:

  • 提高代码的复用性,减少代码冗余
  • 继承是多态的基础(后续讲解)

总结

  1. 什么是继承:
    • 继承就是,子类与父类之间的关系,子类可以继承父类中的成员。
  2. 继承的作用:
    • 提高代码复用性,减少代码冗余。

1.2-继承的格式(记忆)

通过上述讲解,我们知道了什么是继承以及继承的作用,接下来我们来学习一下Java中定义继承的格式。

格式

关键字:extends

class 父类 {
	...
}

class 子类 extends 父类 {
	...
}

示例

父类:动物类

package www.penglei666.com.demo03;
/**
 * 动物类,父类
 */
public class Animal {
    String name;
    int age;
    String color;
    String gender;
    public void eat(){
        System.out.println("我是吃货!");
    }
}

子类:狗类

package www.penglei666.com.demo03;

/**
 * 狗类,子类,继承了Animal
 */
public class Dog extends Animal {
    public void alert(){
        System.out.println("警告,我是看门专家");
    }
}

测试类:Test

package www.penglei666.com.demo03;

public class Test {
    public static void main(String[] args) {
        Dog wc = new Dog();
        // 可以使用父类中的属性:name、age、gender、color
        wc.name = "旺财";
        wc.age = 10;
        wc.gender = "公";
        wc.color = "yellow";
        // 可以调用父类中的方法:eat,也可以调用自己的方法
        wc.eat();   // 我是吃货!
        wc.alert(); // 警告,我是看门专家

    }
}

在上述代码中,Dog类通过extends关键字继承了Animal类,这样Dog类便是Animal类的子类。

从运行结果不难看出,子类虽然没有定义name、age、gender等属性和eat方法,但是却能操作这几个成员。这就说明,子类在继承父类的时候,会自动拥有父类的成员。

1.3-super关键字(记忆)

若是父类中的成员和子类中的成员重名,子类对象在调用重名的成员时,会使怎样的现象呢?

子类和父类成员重名时

父类:Animal

package www.penglei666.com.demo03;
/**
 * 动物类,父类
 */
public class Animal {
    int age = 10;
}

子类:Dog

package www.penglei666.com.demo03;

/**
 * 狗类,子类,继承了Animal
 */
public class Dog extends Animal {
    int age = 11;
    public void printAge(){
        System.out.println("年龄:" + this.age);
    }
}

测试类:Test

package www.penglei666.com.demo03;

public class Test {
    public static void main(String[] args) {
        Dog wc = new Dog();
        wc.printAge(); // 输出结果:年龄:11
    }
}

通过输出结果可以发现,输出的并不是父类中的结果值,而是子类自己的。

那么,如何在重名的情况下,如何访问到父类中的成员呢?此时可以使用关键字:super

我们之前,学习过this关键字,表示代表调用者本身的引用,而super关键字,表示父类的引用。

super 关键字的使用

使用格式:super.父类成员变量名

修改子类代码:Dog类

package www.penglei666.com.demo03;

/**
 * 狗类,子类,继承了Animal
 */
public class Dog extends Animal {
    int age = 11;
    public void printAge(){
        System.out.println("父age:" + super.age);
        System.out.println("子age:" + this.age);
    }
}

测试类:Test

package www.penglei666.com.demo03;

public class Test {
    public static void main(String[] args) {
        Dog wc = new Dog();
        wc.printAge(); 
        /* 输出结果:
        	父age:10
			子age:11
        */
    }
}

1.4-super和this关键字(理解)

父类空间优先于子类对象的产生

在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。

目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。

代码体现在子类的构造方法调用时,一定先调用父类的构造方法。理解图解如下:

super和this的含义:

  • super :代表父类的存储空间标识(可以理解为父亲的引用)。
  • this :代表当前对象的引用(谁调用就代表谁)。

super和this调用成员属性和方法

this.成员变量    	--    本类的
super.成员变量    	--    父类的

this.成员方法名()  	--    本类的    
super.成员方法名()   --    父类的

super和this调用构造方法

this(...)    	--    本类的构造方法
super(...)   	--    父类的构造方法

子类的每个构造方法中均有默认的super(),调用父类的空参构造。

手动调用父类构造会覆盖默认的super()。

super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

1.5-子类可重写父类方法(理解)

方法重写Override

如果子类父类中出现不重名的成员方法,这时的调用是没有影响的

对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。代码如下:

父类:Fu

package www.penglei666.com.demo04;

public class Fu {
    public void fn1(){
        System.out.println("父类方法:fn");
    }
}

子类:Zi

package www.penglei666.com.demo04;

public class Zi extends Fu {
    @Override
    public void fn2() {
        System.out.println("Zi类中的方法:fn");
    }
}

测试类:Test

package www.penglei666.com.demo04;

public class Test {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.fn1(); // 输出结果:父类方法:fn
        zi.fn2(); // 输出结果:Zi类中的方法:fn
    }
}

如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

代码如下:

父类:Fu

package www.penglei666.com.demo04;

public class Fu {
    public void fn(){
        System.out.println("父类方法:fn");
    }
}

子类:Zi

package www.penglei666.com.demo04;

public class Zi extends Fu {
    @Override
    public void fn() {
        System.out.println("Zi类中的方法:fn");
    }
}

测试类:

package www.penglei666.com.demo04;

public class Test {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.fn(); // 输出结果:Zi类中的方法:fn
    }
}

方法重写的应用

子类可以根据需要,定义特定于自己的行为。

既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。

比如新的手机增加来电显示头像的功能,代码如下:

父类:Phone

package www.penglei666.com.demo05;

/**
 * 父类:Phone
 */
public class Phone {
    public void sendMessage(){
        System.out.println("发送短信");
    }
    public void call(){
        System.out.println("拨打电话");
    }
    public void showNum(){
        System.out.println("来电显示对方号码");
    }
}

子类:

package www.penglei666.com.demo05;

/**
 * 子类:NewPhone
 */
public class NewPhone extends Phone {
    //重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
    public void showNum(){
        //调用父类已经存在的功能使用super
        super.showNum();
        //增加自己特有显示姓名和图片功能
        System.out.println("来电显示对方姓名");
        System.out.println("来电显示对方头像");
    }
}

测试类:Test

package www.penglei666.com.demo05;

public class Test {
    public static void main(String[] args) {
        NewPhone np = new NewPhone();
        np.showNum();
        /**
         * 输出结果:
         * 来电显示号码
         * 来电显示对方姓名
         * 来电显示对方头像
         */
    }
}

这里重写时,用到super.父类成员方法,表示调用父类的成员方法。

1.6-子类的初始化过程(理解)

构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代码如下:

父类:

package www.penglei666.com.demo06;

public class Fu {
    public Fu(){
        System.out.println("Fu类构造方法初始化");
    }
}

子类:

package www.penglei666.com.demo06;

public class Zi extends Fu {
    public Zi(){
        // super(); 默认调用super
        System.out.println("Zi类构造方法初始化");
    }
}

测试类:

package www.penglei666.com.demo06;

public class Test {
    public static void main(String[] args) {
        Zi zi = new Zi();
        /* 执行结果:
        	Fu类构造方法初始化
			Zi类构造方法初始化
        */
    }
    
}

1.7-继承的特点(理解)

特点1:Java只支持单继承,不支持多继承。

//一个类只能有一个父类,不可以有多个父类。
class C extends A{} 	//ok
class C extends A,B...	//error

特点2:Java支持多层继承(继承体系)。

class A{}
class B extends A{}
class C extends B{}

第二章:抽象类

2.1-抽象类概述(了解)

抽象类的由来

当编写一个类时,我们往往会为该类定义一些方法,这些方法是用来描述该类的功能具体实现方式,那么这些方法都有具体的方法体。

分析事物时,发现了共性内容,就出现向上抽取(父类中定义)。

但是,可能会有这样一种特殊情况,就是方法功能声明相同,但方法功能主体不同。

那么这时也可以抽取,但只抽取方法声明,不抽取方法主体。那么此方法就是一个抽象方法。

如:

  • 描述狗的行为:吃
  • 描述猫的行为:吃
  • 描述牛的行为:吃

狗、猫、牛之间有共性,可以进行向上抽取父类定义共性。

抽取它们的所属共性类型:动物。

由于狗、猫、牛都具有吃的功能,但是它们吃的食物不一样(比如:开篇我们的需求狗、猫、牛都是吃货,都有吃的行为,但是吃的食物第不同的,狗啃骨头、猫吃鱼、牛吃草)。

这时在描述动物类时,发现了有些功能不能够具体描述,那么,这些不具体的功能,需要在类中标识出来,通过java中的关键字abstract(抽象)修饰。当定义了抽象方法的类也必须被abstract关键字修饰,被abstract关键字修饰的类是抽象类

总结

  • 抽象方法 : 没有方法体的方法。
  • 抽象类:包含抽象方法的类。

2.2-抽象类、抽象方法的定义和使用(记忆)

抽象方法:

使用abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。

定义格式:

修饰符 abstract 返回值类型 方法名 (参数列表);

示例代码:

public abstract void eat();

抽象类:

如果一个类包含抽象方法,那么该类必须是抽象类。

定义格式:

public abstract class 类名字 { 
  
}

示例代码:

public abstract class Animal { 
  public abstract void eat();
}

抽象类的使用:

继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。

父类:Animal

package www.penglei666.com.demo07;
public abstract class Animal {
    public abstract void eat();
}

子类:Dog

package www.penglei666.com.demo07;
public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("我爱啃骨头");
    }
}

此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法

2.3-注意事项(了解)

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
    • 理解方式:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
    • 理解方式:子类的构造方法中,有默认的super(),需要访问父类构造方法。
  3. 抽象类中,可以有成员变量。
    • 理解方式:子类的共性的成员变量 , 可以定义在抽象父类中。
  4. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
    • 理解方式:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
  5. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
    • 理解方式:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

第三章:综合案例

案例需求:

某IT公司有多名员工,按照员工负责的工作不同,进行了部门的划分(研发部员工、维护部员工)。研发部根据所需研发的内容不同,又分为JavaEE工程师、Android工程师;维护部根据所需维护的内容不同,又分为网络维护工程师、硬件维护工程师。

公司的每名员工都有他们自己的员工编号、姓名,并要做它们所负责的工作。

工作内容:

  • JavaEE工程师: 员工号为xxx的 xxx员工,正在研发淘宝网站

  • Android工程师:员工号为xxx的 xxx员工,正在研发淘宝手机客户端软件

  • 网络维护工程师:员工号为xxx的 xxx员工,正在检查网络是否畅通

  • 硬件维护工程师:员工号为xxx的 xxx员工,正在修复打印机

请根据描述,完成员工体系中所有类的定义,并指定类之间的继承关系。进行XX工程师类的对象创建,完成工作方法的调用。

案例分析:

根据上述部门的描述,得出如下的员工体系图

根据员工信息的描述,确定每个员工都有员工编号、姓名、要进行工作。则把这些共同的属性与功能抽取到父类中(员工类),关于工作的内容由具体的工程师来进行指定。

工作内容:

  • JavaEE工程师:员工号为xxx的 xxx员工,正在研发淘宝网站
  • Android工程师:员工号为xxx的 xxx员工,正在研发淘宝手机客户端软件
  • 网络维护工程师:员工号为xxx的 xxx员工,正在检查网络是否畅通
  • 硬件维护工程师:员工号为xxx的 xxx员工,正在修复打印机

创建JavaEE工程师对象,完成工作方法的调用。

实现代码:

员工类:Employee

public abstract class Employee {
	private String id;// 员工编号
	private String name; // 员工姓名

	public String getId() {
		returnid;
	}
	publicvoid setId(String id) {
		this.id = id;
	}
	public String getName() {
		returnname;
	}
	publicvoid setName(String name) {
		this.name = name;
	}
	
	//工作方法(抽象方法)
	public abstract void work(); 
}

定义研发部员工类Developer 继承 员工类Employee

public abstract class Developer extends Employee {
}

定义维护部员工类Maintainer 继承 员工类Employee

public abstract class Maintainer extends Employee {
}

定义JavaEE工程师 继承 研发部员工类,重写工作方法

public class JavaEE extends Developer {
	@Override
	public void work() {
		System.out.println("员工号为 " + getId() + " 的 " + getName() + " 员工,正在研发淘宝网站");
	}
}

定义Android工程师 继承 研发部员工类,重写工作方法

public class Android extends Developer {
	@Override
	public void work() {
		System.out.println("员工号为 " + getId() + " 的 " + getName() + " 员工,正在研发淘宝手机客户端软件");
	}
}

定义Network网络维护工程师 继承 维护部员工类,重写工作方法

public class Network extends Maintainer {
	@Override
	public void work() {
		System.out.println("员工号为 " + getId() + " 的 " + getName() + " 员工,正在检查网络是否畅通");
	}
}

定义Hardware硬件维护工程师 继承 维护部员工类,重写工作方法

public class Hardware extends Maintainer {
	@Override
	public void work() {
		System.out.println("员工号为 " + getId() + " 的 " + getName() + " 员工,正在修复打印机");
	}
}

在测试类中,创建JavaEE工程师对象,完成工作方法的调用

public class Test {
	public static void main(String[] args) {
		//创建JavaEE工程师员工对象
		JavaEE ee = new JavaEE();
		//设置该员工的编号
		ee.setId("000015");
		//设置该员工的姓名
		ee.setName("小明");
		//调用该员工的工作方法
		ee.work();
	}
}

posted @ 2020-07-24 16:55  雷哒哒  阅读(175)  评论(0编辑  收藏  举报