zoukankan      html  css  js  c++  java
  • 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();
    	}
    }
    

  • 相关阅读:
    leetcode231 2的幂 leetcode342 4的幂 leetcode326 3的幂
    leetcode300. Longest Increasing Subsequence 最长递增子序列 、674. Longest Continuous Increasing Subsequence
    leetcode64. Minimum Path Sum
    leetcode 20 括号匹配
    算法题待做
    leetcode 121. Best Time to Buy and Sell Stock 、122.Best Time to Buy and Sell Stock II 、309. Best Time to Buy and Sell Stock with Cooldown 、714. Best Time to Buy and Sell Stock with Transaction Fee
    rand7生成rand10,rand1生成rand6,rand2生成rand5(包含了rand2生成rand3)
    依图
    leetcode 1.Two Sum 、167. Two Sum II
    从分类,排序,top-k多个方面对推荐算法稳定性的评价
  • 原文地址:https://www.cnblogs.com/lpl666/p/13372872.html
Copyright © 2011-2022 走看看