随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。
类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
将父类加上abstract标识之后,就不用用它造对象。
既然父类抽象了,功能也并不具体,子类也需要重写它的方法才好具体使用。那为啥还需要父类呢?
1、父类中的一些方法,子类确实会重写,但也会存在一些方法子类直接拿去用。若没有父类,每个子类就都需要自己写。
2、父类的属性子类可以直接拿来用,若没有父类,子类还需要自己去声明。
3、多态的使用前提,基于继承性,比如一个方法的形参就是Person类型的,真正调用方法的时候,传进来的却是具体子类的对象。不管传什么子类对象,都赋给了Person。若是没有父类,那就麻烦了,一堆方法需要一个个写,不靠谱。
所以继承关系还是需要的,在继承的前提下,这个抽象类
只是表明父类不想实例化了。
我们声明一些几何图形类:圆、矩形、三角形类等,发现这些类都有共同特征:求面积、求周长。
那么这些共同特征应该抽取到一个共同父类:几何图形类中。
但是这些方法在父类中又无法给出具体的实现
(因为不知道具体什么图形),而是应该交给子类各自具体实现。
那么父类在声明这些方法时,就只有方法签名,没有方法体
(具体的需要子类去重写),我们把没有方法体的方法称为抽象方法。
Java语法规定,包含抽象方法的类必须是抽象类。
先将继承关系表示出来:
【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();
}
}
输出结果:
现在在现有的class Person
前面加上abstract
。
Person
类就叫做抽象类。意味着它不能够实例化了。
上一节说了final
,final也可以用来修饰Person,表示不能有子类。
现在这个可以有子类的,只是不能造对象了。
可以看到,这里就报错了:
既然不能造方法,那么此时方法也自然不能调用了。
单例模式只造一次对象,抽象类是禁止造对象。
随着Person提供的子类越来越丰富,Person里面的eat()
方法都不用指明具体怎么吃了,具体的子类会指明。
所以在Person里面,只需要声明了就行。
表示有这个功能,具体怎么吃不确定,需要子类重写它。
比如,现在在eat()方法前面加上abstract
,如下:
此时会报错,因为抽象方法不能有方法体。(代码连着花括号一块都不能要)
这样即可:(表明此方法是抽象方法)
既然这个抽象方法没有方法体了,那么它也就不应该被调用了。
在子类Student中将父类Person中的eat()方法重写了,可以发现这里的符号变了,抽象方法的重写换了一个词儿,叫Implements
(实现),而之前的重写叫Overrides
(重写)。
这里就不讲究那么多了,都叫重写也没什么毛病。
所以,在子类中,将父类的抽象方法给重写了,不耽误后面子类正常调用。
Java语法规定,包含抽象方法的类必须是抽象类。
刚才我们先说的是抽象类,表示该类不能被实例化了。然后说的是抽象方法,不能有方法体。没有任何问题。
但现在方法是抽象的,若是该类不是抽象的,就会报错,如下:
什么原因呢?
抽象方法没有方法体,那么这个方法就不应该被调用。
现在的方法是非静态方法,按道理说拿对象是可以调用它的。
现在既然不给调用抽象方法,那么就别有对象了。
如何保证这个类没有对象?删掉构造器?不行,就算没有写构造器,也会有默认的空参构造器,没有意义。那就限制该类呗,那就抽象,抽象类没有对象。
所以,抽象方法所属的类也是抽象类,因为这样就不能造对象了。
抽象方法存在的原因,主要是在多态应用中,对象只能调用父类中已有的方法,子类自己写的方法不能调用,所以要在抽象父类中声明抽象方法。
若此时,我们将sleep()方法也抽象化:
重新建一个Worker类,继承于Person类,会发现报错,如下:
以前是构造器的问题,但现在构造器没有问题。(有空参构造器)
看报错信息可知,要么将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
,然后点击“实现方法”:
将两个都选中,点击“确定”:
然后就可以自动生成啦:
当然,只重写了一个方法也会报错,如下:
现在【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()也给重写一下才不会报错。
如下:
现在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()也可以(不会报错)。
对于Worker类来说,它有三个方法,从Person类中继承来的(Person两个,Creature一个)。此时只重写了一个,剩下两个还是抽象的,所以Worker还只能是抽象类。
【Worker.java】
package yuyi08;
public abstract class Worker extends Person{ //只重写了一个父类抽象方法,还是抽象类
@Override
public void eat() {
System.out.println("工人很辛苦,多吃饭");
}
}
既然Worker类是抽象类,那么就不能去造对象。如下:
抽象类需要有子类,自己不能造对象,写了一些方法(抽象方法和非抽象方法),这些抽象方法想要用就需要造对象,自己造不了对象了,就只能造子类对象啦。言外之意就是抽象类必须有子类。
举例1:GeometricObject-Circle-Rectangle
父类【GeometricObject.java】
abstract class GeometricObject{ //几何图形
//求面积 (只能考虑提供方法的声明,而没有办法提供方法体。所以,此方法适合声明为抽象方法)
//求周长(只能考虑提供方法的声明,而没有办法提供方法体。所以,此方法适合声明为抽象方法)
}
子类1【Circle.java】
class Circle extends GeometricObject{
//求面积 (必须重写(或实现)父类中的抽象方法)
//求周长(必须重写(或实现)父类中的抽象方法)
}
子类2【Rectangle.java】
子类要么是抽象类,要么就需要重写父类中的抽象方法。
class Rectangle extends GeometricObject{
//求面积 (必须重写(或实现)父类中的抽象方法)
//求周长(必须重写(或实现)父类中的抽象方法)
}
举例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{ //信用卡
//取钱(需要重写父类中的抽象方法)
//存钱(需要重写父类中的抽象方法)
}
//....
abstract
的概念:抽象的
abstract
可以用来修饰:类、方法
abstract
修饰的类。abstract
修饰没有方法体的方法。①抽象类
的语法格式
[权限修饰符] abstract class 类名{
}
[权限修饰符] abstract class 类名 extends 父类{
}
abstract
修饰类:
> 此类称为抽象类。
> 抽象类不能实例化(不能造对象)。
> 抽象类中是包含构造器的,因为子类对象实例化时,需要直接或间接的调用到父类的构造器。
> 抽象类中可以没有抽象方法(仅仅表明抽象类不能造对象)。反之,抽象方法所在的类,一定是抽象类。
构造器是用来对对象进行初始化的,即为实例变量初始化,赋初值,而不是用来创建对象的,创建对象用new关键字。(子类在造对象的时候,还是要加载父类的结构–调用构造器的时候加载)
②抽象方法
的语法格式
[其他修饰符] abstract 返回值类型 方法名([形参列表]);
abstract
修饰方法:
> 此方法即为抽象方法。
> 抽象方法只有方法的声明,没有方法体。
> 抽象方法其功能是确定的(通过方法的声明即可确定),只是不知道如何具体实现(体现为没有方法体)。
> 子类必须重写父类中的所有的抽象方法之后,方可实例化。否则,此子类仍然是一个抽象类。
注意:抽象方法没有方法体。
抽象方法虽然没有声明,但是这个方法的功能已经确定了,只不过没有方法体(不知道功能该如何实现)而已。
代码举例:
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();
}
}
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法
。
基于继承以后,将代码改造了一下,父类不能实例化,里面有抽象方法。将造对象的事情交给子类,让子类继承父类之后,要是没有将父类中的抽象方法都给重写,就会报错。(要么全部重写了,要么也变成抽象类)
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
<1> abstract
不能修饰哪些结构?
属性、构造器、代码块等。
<2> abstract
不能与哪些关键字共用?(自洽)
不能用abstract修饰私有方法、静态方法、final的方法、final的类。
①私有方法不能重写
若abstract
修饰了一个方法,这个方法一定要让子类去重写的,否则就造不了对象了。
②静态方法不能重写
避免静态方法使用类进行调用
一般我们是这样来写的,如下:
此时将method()方法私有化是不可以的,如下:
那用static
修饰呢?
静态方法(static)本来就可以通过类来调;而抽象方法(abstract)没有方法体,这个方法就不能被调。
所以不能用static和abstract组合。
抽象方法
只有声明,没有方法体,这个方法不能被调。
调用方法的有“类.
”和“对象.
”两种方法。若此时不让调方法,那就让这两件事做不了。
不让对象去调用,就让类为抽象类,它就没有对象了,自然就不能用对象去调用什么方法;不让类去调用,这个方法就不要是静态方法,就不能拿类去调用了。
所以刚才就不能静态,因为静态(用static修饰)了,它就可以被类调用,而此时此方法又被abstract修饰,意味着它是抽象类不能被调用。矛盾呐。
静态方法可以直接用类名调用,抽象方法不能调用。
③final的方法不能被重写
abstract
修饰的方法一定要被重写,否则就不能造对象。
所以final与abstract水火不容。
④final修饰的类不能有子类
abstract
修饰的类一定要有子类,否则也造不了对象,而且自己也造不了,那有何用呢?
在航运公司系统中,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:模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
类比举例:英语六级模板
制作月饼的模板:
计算代码运行时间
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);
}
}
}
}
输出结果(部分)
银行相关模板
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万美元!!");
}
}
输出结果
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);
}
}
}
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万美元!!");
}
}
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
题目描述
针对多态性的练习题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()
,当时还纠结返回什么,现在来看根本不需要提供它的方法体。因为不确定,所以可以抽象化。同样,当前类也是抽象类了。
【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()
方法了,所以后续不用改了。
在抽象这块,一定要用多态。
输出结果
题目描述
编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月出现某个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();
}
}
输出结果
以后在开发中,会见到一些抽象类,见到抽象类,第一反应它可能有抽象方法(抽象类里面不一定有抽象方法,但是一般都有),若想用抽象类,需要找它的子类,因为它不能实例化。