Java面向对象(高级)-- 抽象类与抽象方法(或abstract关键字)

一、抽象类的由来

(1)举例1

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。

类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类
image.png

将父类加上abstract标识之后,就不用用它造对象。

💬既然父类抽象了,功能也并不具体,子类也需要重写它的方法才好具体使用。那为啥还需要父类呢?

1、父类中的一些方法,子类确实会重写,但也会存在一些方法子类直接拿去用。若没有父类,每个子类就都需要自己写。

2、父类的属性子类可以直接拿来用,若没有父类,子类还需要自己去声明。

3、多态的使用前提,基于继承性,比如一个方法的形参就是Person类型的,真正调用方法的时候,传进来的却是具体子类的对象。不管传什么子类对象,都赋给了Person。若是没有父类,那就麻烦了,一堆方法需要一个个写,不靠谱。

所以继承关系还是需要的,在继承的前提下,这个抽象类只是表明父类不想实例化了

(2)举例2

我们声明一些几何图形类:圆、矩形、三角形类等,发现这些类都有共同特征:求面积、求周长。

那么这些共同特征应该抽取到一个共同父类:几何图形类中。

但是这些方法在父类中无法给出具体的实现(因为不知道具体什么图形),而是应该交给子类各自具体实现。

那么父类在声明这些方法时,就只有方法签名,没有方法体(具体的需要子类去重写),我们把没有方法体的方法称为抽象方法

Java语法规定,包含抽象方法的类必须是抽象类

二、案例引入

(1)抽象类

先将继承关系表示出来:

【Person.java】

package yuyi08;

/**
 * ClassName: Person
 * Package: yuyi08
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/23 0023 13:13
 */
public class Person {
    String name;
    int age;

    //构造器
    public Person(){

    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //方法
    public void eat(){
        System.out.println("人吃饭");
    }

    public void sleep(){
        System.out.println("人睡觉");
    }

}

【Student.java】

package yuyi08;

/**
 * ClassName: Student
 * Package: yuyi08
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/23 0023 13:15
 */
public class Student extends Person{
    String school;

    public Student() {

    }

    public Student(String name, int age, String school) {
        super(name, age);
        this.school = school;
    }

    public void eat(){
        System.out.println("学生多吃有营养的食物");
    }

    public void sleep(){
        System.out.println("学生不要熬夜");
    }
}

【AbstractTest.java】

package yuyi08;

/**
 * ClassName: AbstractTest
 * Package: yuyi08
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/23 0023 13:17
 */
public class AbstractTest {
    public static void main(String[] args) {
        Person p1=new Person();
        p1.eat();

        Student s1=new Student();
        s1.eat();
    }
}

输出结果:

image.png

现在在现有的class Person前面加上abstract

Person类就叫做抽象类。意味着它不能够实例化了。

上一节说了final,final也可以用来修饰Person,表示不能有子类

现在这个可以有子类的,只是不能造对象了。

可以看到,这里就报错了:

image.png

既然不能造方法,那么此时方法也自然不能调用了。

单例模式只造一次对象,抽象类是禁止造对象。

(2)抽象方法

随着Person提供的子类越来越丰富,Person里面的eat()方法都不用指明具体怎么吃了,具体的子类会指明。

所以在Person里面,只需要声明了就行

表示有这个功能,具体怎么吃不确定,需要子类重写它。

比如,现在在eat()方法前面加上abstract,如下:

image.png

此时会报错,因为抽象方法不能有方法体。(代码连着花括号一块都不能要)

这样即可:(表明此方法是抽象方法)

image.png

既然这个抽象方法没有方法体了,那么它也就不应该被调用了。

在子类Student中将父类Person中的eat()方法重写了,可以发现这里的符号变了,抽象方法的重写换了一个词儿,叫Implements(实现),而之前的重写叫Overrides(重写)。

image.png

这里就不讲究那么多了,都叫重写也没什么毛病。

所以,在子类中,将父类的抽象方法给重写了,不耽误后面子类正常调用。

Java语法规定,包含抽象方法的类必须是抽象类

刚才我们先说的是抽象类,表示该类不能被实例化了。然后说的是抽象方法,不能有方法体。没有任何问题。

但现在方法是抽象的,若是该类不是抽象的,就会报错,如下:

image.png

什么原因呢?

抽象方法没有方法体,那么这个方法就不应该被调用。

现在的方法是非静态方法,按道理说拿对象是可以调用它的。

现在既然不给调用抽象方法,那么就别有对象了。

如何保证这个类没有对象?删掉构造器?不行,就算没有写构造器,也会有默认的空参构造器,没有意义。那就限制该类呗,那就抽象,抽象类没有对象。

所以,抽象方法所属的类也是抽象类,因为这样就不能造对象了。

抽象方法存在的原因,主要是在多态应用中,对象只能调用父类中已有的方法,子类自己写的方法不能调用,所以要在抽象父类中声明抽象方法。

(3)补充1

若此时,我们将sleep()方法也抽象化:

image.png

重新建一个Worker类,继承于Person类,会发现报错,如下:

image.png

以前是构造器的问题,但现在构造器没有问题。(有空参构造器)

看报错信息可知,要么将Worker类声明为抽象类,要么实现Person中的抽象方法eat()

因为子类Worker将父类中的两个抽象方法继承过来了。

所以,

第一种解决方法:将Worker类声明为抽象类

public abstract class Worker extends Person{

}

第二种解决方法:实现Person中的抽象方法eat()

public  class Worker extends Person{
    @Override
    public void eat() {

    }

    @Override
    public void sleep() {
        
    }

}

【小Tips】

将光标放在Worker上,Alter+Enter,然后点击“实现方法”:

image.png

将两个都选中,点击“确定”:

image.png

然后就可以自动生成啦:

image.png

当然,只重写了一个方法也会报错,如下:

image.png

(4)补充2

现在【Worker.java】代码写成这样的:

package yuyi08;

public  abstract class Worker extends Person{   //只重写了一个父类抽象方法,还是抽象类

    @Override
    public void eat() {
        System.out.println("工人很辛苦,多吃饭");
    }

    /*@Override
    public void sleep() {

    }*/

}

再写一个Creature类:

package yuyi08;

public abstract class Creature { //生物类
    public abstract void breath();  //呼吸

}

现在将Person类继承于Creature,此时Person里面就包含Creature的抽象方法breath()

此时会报错!

//报错了
public abstract class Person extends Creature {  //抽象类
    String name;
    int age;

    //构造器
    public Person(){

    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //方法
    public abstract void eat(); //抽象方法

    public abstract void sleep();

}

学生类Student继承于Person的,除了将Person类中的抽象方法重写了,要想实例化,此时必须将breath()也给重写一下才不会报错

如下:

image.png

现在Student类中,将直接父类Person和间接父类Creature的抽象方法都做了重写,如下:

package yuyi08;

public class Student extends Person{
    String school;

    public Student() {

    }

    public Student(String name, int age, String school) {
        super(name, age);
        this.school = school;
    }

    public void eat(){
        System.out.println("学生多吃有营养的食物");
    }

    public void sleep(){
        System.out.println("学生不要熬夜");
    }

    @Override
    public void breath() {
        System.out.println("学生要多呼吸新鲜空气");
    }
}

那么就可以用Student类去造对象了。

当然,若是父类Person已经将breath()方法重写了,在Student类中不重写breath()也可以(不会报错)。

image.png


对于Worker类来说,它有三个方法,从Person类中继承来的(Person两个,Creature一个)。此时只重写了一个,剩下两个还是抽象的,所以Worker还只能是抽象类。

【Worker.java】

package yuyi08;

public  abstract class Worker extends Person{   //只重写了一个父类抽象方法,还是抽象类

    @Override
    public void eat() {
        System.out.println("工人很辛苦,多吃饭");
    }

}

既然Worker类是抽象类,那么就不能去造对象。如下:

image.png

(5)举例

抽象类需要有子类,自己不能造对象,写了一些方法(抽象方法和非抽象方法),这些抽象方法想要用就需要造对象,自己造不了对象了,就只能造子类对象啦。言外之意就是抽象类必须有子类

1. 举例1

举例1:GeometricObject-Circle-Rectangle

父类【GeometricObject.java】

abstract class GeometricObject{  //几何图形
    //求面积 (只能考虑提供方法的声明,而没有办法提供方法体。所以,此方法适合声明为抽象方法)
    
    //求周长(只能考虑提供方法的声明,而没有办法提供方法体。所以,此方法适合声明为抽象方法)
}

子类1【Circle.java】

class Circle extends GeometricObject{
    //求面积 (必须重写(或实现)父类中的抽象方法)
    
    //求周长(必须重写(或实现)父类中的抽象方法)
}

子类2【Rectangle.java】

子类要么是抽象类,要么就需要重写父类中的抽象方法

class Rectangle extends GeometricObject{
    //求面积 (必须重写(或实现)父类中的抽象方法)
    
    //求周长(必须重写(或实现)父类中的抽象方法)
}

2. 举例2

举例2:Account-SavingAccount-CheckAcount

父类【Account.java】

abstract class Account{

    double balance;//余额

    //取钱 (声明为抽象方法)

    //存钱 (声明为抽象方法)

}

子类1【SavingAccount.java】

class SavingAccount extends Account{ //储蓄卡
    //取钱 (需要重写父类中的抽象方法)

    //存钱(需要重写父类中的抽象方法)
}

子类2【CheckAccount.java】

class CheckAccount extends Account{ //信用卡
    //取钱(需要重写父类中的抽象方法)

    //存钱(需要重写父类中的抽象方法)
}
//....

三、 抽象类与抽象方法

(1)语法格式

abstract的概念:抽象的

abstract可以用来修饰:方法

  • 抽象类:被abstract修饰的
  • 抽象方法:被abstract修饰没有方法体的方法

抽象类的语法格式

[权限修饰符] abstract class 类名{
    
}
[权限修饰符] abstract class 类名 extends 父类{
    
}

abstract修饰
> 此类称为抽象类
> 抽象类不能实例化(不能造对象)。
> 抽象类中是包含构造器的,因为子类对象实例化时,需要直接或间接的调用到父类的构造器。
> 抽象类中可以没有抽象方法(仅仅表明抽象类不能造对象)。反之,抽象方法所在的类,一定是抽象类

构造器是用来对对象进行初始化的,即为实例变量初始化,赋初值,而不是用来创建对象的,创建对象用new关键字。(子类在造对象的时候,还是要加载父类的结构–调用构造器的时候加载)

抽象方法的语法格式

[其他修饰符] abstract 返回值类型 方法名([形参列表]);

abstract修饰方法
> 此方法即为抽象方法
> 抽象方法只有方法的声明,没有方法体
> 抽象方法其功能是确定的(通过方法的声明即可确定),只是不知道如何具体实现(体现为没有方法体)。
> 子类必须重写父类中的所有的抽象方法之后,方可实例化否则,此子类仍然是一个抽象类

注意:抽象方法没有方法体。

抽象方法虽然没有声明,但是这个方法的功能已经确定了,只不过没有方法体(不知道功能该如何实现)而已。

image.png

代码举例:

public abstract class Animal {
    public abstract void eat();
}
public class Cat extends Animal {
    public void eat (){
      	System.out.println("小猫吃鱼和猫粮"); 
    }
}
public class CatTest {
 	 public static void main(String[] args) {
        // 创建子类对象
        Cat c = new Cat(); 
       
        // 调用eat方法
        c.eat();
  	}
}

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

基于继承以后,将代码改造了一下,父类不能实例化,里面有抽象方法。将造对象的事情交给子类,让子类继承父类之后,要是没有将父类中的抽象方法都给重写,就会报错。(要么全部重写了,要么也变成抽象类)

(2)使用说明

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。

  1. 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。

理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。

  1. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  1. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

(3) 注意事项

1. abstract不能使用的场景

<1> abstract 不能修饰哪些结构

属性构造器代码块等。

<2> abstract 不能与哪些关键字共用?(自洽)

不能用abstract修饰私有方法静态方法final的方法final的类

①私有方法不能重写

abstract修饰了一个方法,这个方法一定要让子类去重写的,否则就造不了对象了。

②静态方法不能重写

避免静态方法使用类进行调用

一般我们是这样来写的,如下:

image.png

此时将method()方法私有化是不可以的,如下:

image.png

那用static修饰呢?

静态方法(static)本来就可以通过类来调;而抽象方法(abstract)没有方法体,这个方法就不能被调。

所以不能用static和abstract组合。

image.png

抽象方法只有声明,没有方法体,这个方法不能被调。

调用方法的有“类.”和“对象.”两种方法。若此时不让调方法,那就让这两件事做不了。

不让对象去调用,就让类为抽象类,它就没有对象了,自然就不能用对象去调用什么方法;不让类去调用,这个方法就不要是静态方法,就不能拿类去调用了。

所以刚才就不能静态,因为静态(用static修饰)了,它就可以被类调用,而此时此方法又被abstract修饰,意味着它是抽象类不能被调用。矛盾呐。

静态方法可以直接用类名调用,抽象方法不能调用。

③final的方法不能被重写

abstract修饰的方法一定要被重写,否则就不能造对象。

所以final与abstract水火不容。

④final修饰的类不能有子类

abstract修饰的类一定要有子类,否则也造不了对象,而且自己也造不了,那有何用呢?

(4) 应用举例

1. 举例1 航运

image.png

在航运公司系统中,Vehicle类需要定义两个方法分别计算运输工具的燃料效率行驶距离

问题:卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法完全不同。Vehicle类不能提供计算方法,但子类可以。

解决方案:Java允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。

//Vehicle是一个抽象类,有两个抽象方法。
public abstract class Vehicle{
	public abstract double calcFuelEfficiency();	//计算燃料效率的抽象方法
    
	public abstract double calcTripDistance();	//计算行驶距离的抽象方法
}

public class Truck extends Vehicle{
	public double calcFuelEfficiency( )   { //写出计算卡车的燃料效率的具体方法   }
        
	public double calcTripDistance( )    {  //写出计算卡车行驶距离的具体方法   }
}
        
public class RiverBarge extends Vehicle{
	 public double calcFuelEfficiency( ) { //写出计算驳船的燃料效率的具体方法  }
         
	 public double calcTripDistance( )  {  //写出计算驳船行驶距离的具体方法}
}

2. 举例2 模板方法设计模式

2.1 描述

应用举例2:模板方法设计模式(TemplateMethod)

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

解决的问题

  • 当功能内部一部分实现是确定的(非抽象),另一部分实现是不确定的(抽象化)。这时可以把不确定的部分暴露出去,让子类去实现。 (父类中有方法体的都是确定的,没有方法体的都是不确定的)
  • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。

类比举例:英语六级模板

image.png

制作月饼的模板:

image.png

2.2 案例1

🌱计算代码运行时间

package yuyi09;

/**
 * ClassName: TemplateTest
 * Package: yuyi09
 * Description:
 *      抽象应用案例:模板方法的设计模式
 * @Author 雨翼轻尘
 * @Create 2023/11/24 0024 10:03
 */
public class TemplateTest {
    public static void main(String[] args) {
        PrintPrimeNumber p=new PrintPrimeNumber();
        p.spendTime();  //现在想算一下代码花费了多长时间,调用spendTime()是父类中的方法,在执行code()时是子类重写的方法(已经将原来父类中的覆盖了)
    }
}

//父类
abstract class Template{
    //计算某段代码的执行,需要花费的时间
    public void spendTime(){    //花费的时间
        long start= System.currentTimeMillis(); //记录开始时间

        code(); //代码执行完

        long end=System.currentTimeMillis();    //记录结束时间

        System.out.println("花费时间为:"+(end-start));
    }

    //代码不确定,不妨将它写到方法中
    public abstract void code();
}

//子类
class PrintPrimeNumber extends Template{
    //输出质数
    @Override
    public void code() {
        for (int i = 2; i <=100000 ; i++) {
            boolean isFlag=true;
            for (int j = 2; j <=Math.sqrt(i) ; j++) {
                if(i%j==0){
                    isFlag=false;
                    break;
                }
            }
            if(isFlag){
                System.out.println(i);
            }
        }
    }
}

👻输出结果(部分)

image.png

2.3 案例2

🌱银行相关模板

package yuyi09;
//抽象类的应用:模板方法的设计模式


//测试类
public class TemplateMethodTest {

	public static void main(String[] args) {
		BankTemplateMethod btm = new DrawMoney();	//若想取钱,就new一个抽象类的子类,可以赋给父类(也可以不赋)
		btm.process();	//new的子类也继承了父类的process()方法,其中办理业务的transact()方法是子类重写的方法,其他的继承父类中的

		System.out.println();	//换个行喽

		BankTemplateMethod btm2 = new ManageMoney();
		btm2.process();
	}
}

//父类--模板
abstract class BankTemplateMethod {
	// 具体方法
	public void takeNumber() {
		System.out.println("取号排队");
	}

	public abstract void transact(); // 办理具体的业务,不确定每个人所办理的业务,将它抽象化

	public void evaluate() {
		System.out.println("反馈评分");
	}

	// 模板方法,把基本操作组合到一起,子类一般不能重写
	public final void process() {	//流程   加了final,不要重写了
		this.takeNumber();	//取号

		this.transact();// 办理业务,每个人不一样(像个钩子,具体执行时,挂哪个子类,就执行哪个子类的实现代码)

		this.evaluate();	//反馈评分
	}
}

//取钱--子类
class DrawMoney extends BankTemplateMethod {
	//将父类中的抽象方法重写
	public void transact() {
		System.out.println("我要取款!!!");
	}
}

//理财--子类
class ManageMoney extends BankTemplateMethod {
	//将父类中的抽象方法重写
	public void transact() {
		System.out.println("我要理财!我这里有2000万美元!!");
	}
}

👻输出结果

image.png

2.4 案例3
abstract class Template {
    public final void getTime() {
        long start = System.currentTimeMillis();
        code();
        long end = System.currentTimeMillis();
        System.out.println("执行时间是:" + (end - start));
    }

    public abstract void code();
}

class SubTemplate extends Template {
    public void code() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(i);
        }
    }
}
2.5 案例4
package com.atguigu.java;
//抽象类的应用:模板方法的设计模式
public class TemplateMethodTest {

	public static void main(String[] args) {
		BankTemplateMethod btm = new DrawMoney();
		btm.process();

		BankTemplateMethod btm2 = new ManageMoney();
		btm2.process();
	}
}
abstract class BankTemplateMethod {
	// 具体方法
	public void takeNumber() {
		System.out.println("取号排队");
	}

	public abstract void transact(); // 办理具体的业务 //钩子方法

	public void evaluate() {
		System.out.println("反馈评分");
	}

	// 模板方法,把基本操作组合到一起,子类一般不能重写
	public final void process() {
		this.takeNumber();

		this.transact();// 像个钩子,具体执行时,挂哪个子类,就执行哪个子类的实现代码

		this.evaluate();
	}
}

class DrawMoney extends BankTemplateMethod {
	public void transact() {
		System.out.println("我要取款!!!");
	}
}

class ManageMoney extends BankTemplateMethod {
	public void transact() {
		System.out.println("我要理财!我这里有2000万美元!!");
	}
}

模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:

  • 数据库访问的封装
  • Junit单元测试
  • JavaWeb的Servlet中关于doGet/doPost方法调用
  • Hibernate中模板程序
  • Spring中JDBCTemlate、HibernateTemplate等

四、练习

(1)练习1

🌋题目描述

针对多态性的练习题1:GeometricObject等类进行升级,体现抽象的使用。

【Circle.java】

package yuyi10;

/**
 * ClassName: Circle
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 8:51
 * @Version 1.0
 */
public class Circle extends GeometricObject {

    private double radius;//半径

    public Circle(String color, double weight, double radius) {
        super(color, weight);
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public double findArea() {
        return 3.14 * radius * radius;
    }
}

【GeometricObject.java】

package yuyi10;

/**
 * ClassName: GeometricObject
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 8:47
 * @Version 1.0
 */
public class GeometricObject {
    protected String color;
    protected double weight;

    protected GeometricObject(String color, double weight) {
        this.color = color;
        this.weight = weight;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    public double findArea(){
        return 0.0;
    }
}

【MyRectangle.java】

package yuyi10;

/**
 * ClassName: MyRectangle
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 8:53
 * @Version 1.0
 */
public class MyRectangle extends GeometricObject {

    private double width;//宽
    private double height;//高

    public MyRectangle(String color, double weight, double width, double height) {
        super(color, weight);
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    @Override
    public double findArea() {

        return width * height;
    }
}

【GeometricTest.java】

package yuyi10;

/**
 * ClassName: GeometricTest
 * Description:
 *      编写equalsArea方法测试两个对象的面积是否相等(注意方法的参数类型),
 *      编写displayGeometricObject方法显示对象的面积(注意方法的参数类型)。
 * @Author 雨翼轻尘
 * @Create 8:55
 * @Version 1.0
 */
public class GeometricTest {

    public static void main(String[] args) {
        GeometricTest test = new GeometricTest();

        Circle c1 = new Circle("red",1.0,2.3);
        Circle c2 = new Circle("red",1.0,3.3);

        test.displayGeometricObject(c1);
        test.displayGeometricObject(c2);

        boolean isEquals = test.equalsArea(c1,c2);
        if(isEquals){
            System.out.println("面积相等");
        }else{
            System.out.println("面积不相等");
        }

        //使用匿名对象
        test.displayGeometricObject(new MyRectangle("blue",1.0,2.3,4.5));

    }

    /**
     * 比较两个几何图形的面积是否相等
     * @param g1
     * @param g2
     * @return true:表示面积相等   false:面积不相等
     */
    public boolean equalsArea(GeometricObject g1, GeometricObject g2){
        return g1.findArea() == g2.findArea();
    }

    /**
     * 显示几何图形的面积
     * @param g
     */
    public void displayGeometricObject(GeometricObject g){ //GeometricObject g = new Circle("red",1.0,2.3);
        System.out.println("几何图形的面积为:" + g.findArea()); //动态绑定  <---> 静态绑定
    }
}

🌴分析

当初在写几何图形GeometricObject的时候,其中有一个求面积的方法findArea(),当时还纠结返回什么,现在来看根本不需要提供它的方法体。因为不确定,所以可以抽象化。同样,当前类也是抽象类了。

image.png

【GeometricObject.java】

public abstract class GeometricObject {
    protected String color;
    protected double weight;

    protected GeometricObject(String color, double weight) {
        this.color = color;
        this.weight = weight;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    public abstract double findArea();
}

因为之前子类都重写过findArea()方法了,所以后续不用改了。

在抽象这块,一定要用多态。

🍺输出结果

image.png

(2)练习2

🌋题目描述

编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月出现某个Employee对象的生日,则将该雇员的工资增加100元。

实验说明:

(1)定义一个Employee类,该类包含:

private成员变量name,number,birthday,其中birthday 为MyDate类的对象;

abstract方法earnings();

toString()方法输出对象的name,number和birthday。

(2)MyDate类包含:

private成员变量year,month,day ;

toDateString()方法返回日期对应的字符串:xxxx年xx月xx日

(3)定义SalariedEmployee类继承Employee类,实现按月计算工资的员工处理。

该类包括:private成员变量monthlySalary;

实现父类的抽象方法earnings(),该方法返回monthlySalary值;

toString()方法输出员工类型信息及员工的name,number,birthday。

(4)参照SalariedEmployee类定义HourlyEmployee类,实现按小时计算工资的员工处理。该类包括:

private成员变量wage和hour;

实现父类的抽象方法earnings(),该方法返回wage*hour值;

toString()方法输出员工类型信息及员工的name,number,birthday。

(5)定义PayrollSystem类,创建Employee变量数组并初始化,该数组存放各类雇员对象的引用。

利用循环结构遍历数组元素,输出各个对象的类型,name,number,birthday,以及该对象生日。

当键盘输入本月月份值时,如果本月是某个Employee对象的生日,还要输出增加工资信息。

//提示:
//定义People类型的数组People c1[]=new People[10];
//数组元素赋值
c1[0]=new People("John","0001",20);
c1[1]=new People("Bob","0002",19);
//若People有两个子类Student和Officer,则数组元素赋值时,可以使父类类型的数组元素指向子类。
c1[0]=new Student("John","0001",20,85.0);
c1[1]=new Officer("Bob","0002",19,90.5);

🌱代码

【Employee.java】

package yuyi11;

/**
 * ClassName: Employee
 * Package: yuyi11
 * Description:
 *      private成员变量name,number,birthday,其中birthday 为MyDate类的对象;
 *      提供必要的构造器;
 *      abstract方法earnings(),返回工资数额;
 *      toString()方法输出对象的name,number和birthday。
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/24 0024 16:21
 */
public abstract class Employee {
    private String name;
    private int number;
    private MyDate birthday;

    public Employee() {

    }

    public Employee(String name, int number, MyDate birthday) {
        this.name = name;
        this.number = number;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    public abstract double earnings();

    public String toString(){
        return "name= "+name +",number= "+number
        +",birthday= "+birthday.toDateString(); //注意birthday的调用
    }

}

【MyDate.java】

package yuyi11;

/**
 * ClassName: MyDate
 * Package: yuyi11
 * Description:
 *      private成员变量year,month,day;
 *      提供必要的构造器;
 *      toDateString()方法返回日期对应的字符串:xxxx年xx月xx日
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/24 0024 16:22
 */
public class MyDate {
    private int year;
    private int month;
    private int day;

    public MyDate() {

    }

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    public String toDateString(){
        return year +"年" +month +"月"+day+"日";
    }
}

【SalariedEmployee.java】

package yuyi11;

/**
 * ClassName: SalariedEmployee
 * Package: yuyi11
 * Description:
 *      定义SalariedEmployee类继承Employee类,实现按月计算工资的员工处理。
 *      该类包括:private成员变量monthlySalary;
 *      提供必要的构造器;
 *      实现父类的抽象方法earnings(),该方法返回monthlySalary值;
 *      toString()方法输出员工类型信息及员工的name,number,birthday。
 *      比如:SalariedEmployee[name = '',number = ,birthday=xxxx年xx月xx日]
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/25 0025 8:34
 */
public class SalariedEmployee extends Employee{
    private double monthlySalary;   //月工资

    public SalariedEmployee(){

    }

    @Override
    public double earnings() {
        return monthlySalary;
    }

    public SalariedEmployee(String name,int number,MyDate birthday,double monthlySalary){
        super(name,number,birthday);
        this.monthlySalary=monthlySalary;
    }

    public void setMonthlySalary(double monthlySalary){
        this.monthlySalary=monthlySalary;
    }

    /*public double getMonthlySalary(){
        return monthlySalary;
    }*/

    public String toString(){
        return "SalariedEmployee[" + super.toString() +"]"; //注意这里要加super
    }

}

【HourlyEmployee.java】

package yuyi11;

/**
 * ClassName: HourlyEmployee
 * Package: yuyi11
 * Description:
 *      参照SalariedEmployee类定义HourlyEmployee类,实现按小时计算工资的员工处理。该类包括:
 *      private成员变量wage和hour;
 *      提供必要的构造器;
 *      实现父类的抽象方法earnings(),该方法返回wage*hour值;
 *      toString()方法输出员工类型信息及员工的name,number,birthday。
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/25 0025 8:45
 */
public class HourlyEmployee extends Employee{
    private double wage;    //单位小时工资
    private int hour;    //月工作的小时数

    public HourlyEmployee() {

    }

    public HourlyEmployee(String name, int number, MyDate birthday, double wage, int hour) {
        super(name, number, birthday);
        this.wage = wage;
        this.hour = hour;
    }

    public double getWage() {
        return wage;
    }

    public void setWage(double wage) {
        this.wage = wage;
    }

    public int getHour() {
        return hour;
    }

    public void setHour(int hour) {
        this.hour = hour;
    }

    @Override
    public double earnings() {
        return wage * hour;
    }

    public String toString(){
        return "HourlyEmployee[" + super.toString() +"]"; //注意这里要加super
    }
}

【PayrollSystem.java】

package yuyi11;

import java.util.Scanner;

/**
 * ClassName: PayrollSystem
 * Package: yuyi11
 * Description:
 *      定义PayrollSystem类,创建Employee变量数组并初始化,该数组存放各类雇员对象的引用。
 *      利用循环结构遍历数组元素,输出各个对象的类型,name,number,birthday,以及该对象生日。
 *      当键盘输入本月月份值时,如果本月是某个Employee对象的生日,还要输出增加工资信息。
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/25 0025 8:51
 */
public class PayrollSystem {
    public static void main(String[] args) {
        Scanner scan=new Scanner(System.in);


        //abstract类型的不能创建对象,但是可以创建抽象类数组,然后将子类实例化对象放进去
        Employee[] emps=new Employee[2];    //这里并没有实例化,只是在堆内存开辟了数组空间emps,用来加载Employee类

        emps[0]=new SalariedEmployee("张三",1001,
                                     new MyDate(2002,9,1),15000); //new的是子类对象--多态

        emps[1]=new HourlyEmployee("李四",1002,
                                   new MyDate(2001,10,5),240,100);

        System.out.println("请输入当前的月份:");
        int month=scan.nextInt();

        for (int i = 0; i < emps.length; i++) {
            System.out.println(emps[i].toString());
            System.out.println("工资为: "+emps[i].earnings());

            if(month==emps[i].getBirthday().getMonth()){
                System.out.println("生日快乐!加薪100");
            }
        }

        scan.close();
    }
}

🍺输出结果

image.png

以后在开发中,会见到一些抽象类,见到抽象类,第一反应它可能有抽象方法(抽象类里面不一定有抽象方法,但是一般都有),若想用抽象类,需要找它的子类,因为它不能实例化。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨翼轻尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值