菜鸟教程 -- 学的不仅是技术,更是梦想!

Java 教程
(追記) (追記ここまで)

Java 抽象类


在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。

在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。


抽象类

在 Java 语言中使用 abstract class 来定义抽象类。如下实例:

Employee.java 文件代码:

/* 文件名 : Employee.java */publicabstractclassEmployee{privateStringname; privateStringaddress; privateintnumber; publicEmployee(Stringname, Stringaddress, intnumber){System.out.println("Constructing an Employee"); this.name = name; this.address = address; this.number = number; }publicdoublecomputePay(){System.out.println("Inside Employee computePay"); return0.0; }publicvoidmailCheck(){System.out.println("Mailing a check to " + this.name + "" + this.address); }publicStringtoString(){returnname + "" + address + "" + number; }publicStringgetName(){returnname; }publicStringgetAddress(){returnaddress; }publicvoidsetAddress(StringnewAddress){address = newAddress; }publicintgetNumber(){returnnumber; }}

注意到该 Employee 类没有什么不同,尽管该类是抽象类,但是它仍然有 3 个成员变量,7 个成员方法和 1 个构造方法。 现在如果你尝试如下的例子:

AbstractDemo.java 文件代码:

/* 文件名 : AbstractDemo.java */publicclassAbstractDemo{publicstaticvoidmain(String[]args){/* 以下是不允许的,会引发错误 */Employeee = newEmployee("George W.", "Houston, TX", 43); System.out.println("\n Call mailCheck using Employee reference--"); e.mailCheck(); }}

当你尝试编译 AbstractDemo 类时,会产生如下错误:

Employee.java:46: Employee is abstract; cannot be instantiated
 Employee e = new Employee("George W.", "Houston, TX", 43);
 ^
1 error

继承抽象类

我们可以通过以下方式继承 Employee 类的属性:

Salary.java 文件代码:

/* 文件名 : Salary.java */publicclassSalaryextendsEmployee{privatedoublesalary; //Annual salarypublicSalary(Stringname, Stringaddress, intnumber, doublesalary){super(name, address, number); setSalary(salary); }publicvoidmailCheck(){System.out.println("Within mailCheck of Salary class "); System.out.println("Mailing check to " + getName() + " with salary " + salary); }publicdoublegetSalary(){returnsalary; }publicvoidsetSalary(doublenewSalary){if(newSalary >= 0.0){salary = newSalary; }}publicdoublecomputePay(){System.out.println("Computing salary pay for " + getName()); returnsalary/52; }}

尽管我们不能实例化一个 Employee 类的对象,但是如果我们实例化一个 Salary 类对象,该对象将从 Employee 类继承 7 个成员方法,且通过该方法可以设置或获取三个成员变量。

AbstractDemo.java 文件代码:

/* 文件名 : AbstractDemo.java */publicclassAbstractDemo{publicstaticvoidmain(String[]args){Salarys = newSalary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00); Employeee = newSalary("John Adams", "Boston, MA", 2, 2400.00); System.out.println("Call mailCheck using Salary reference --"); s.mailCheck(); System.out.println("\n Call mailCheck using Employee reference--"); e.mailCheck(); }}

以上程序编译运行结果如下:

Constructing an Employee
Constructing an Employee
Call mailCheck using Salary reference --
Within mailCheck of Salary class
Mailing check to Mohd Mohtashim with salary 3600.0
Call mailCheck using Employee reference--
Within mailCheck of Salary class
Mailing check to John Adams with salary 2400.

抽象方法

如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。

Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。

抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

publicabstractclassEmployee{privateStringname; privateStringaddress; privateintnumber; publicabstractdoublecomputePay(); //其余代码}

声明抽象方法会造成以下两个结果:

  • 如果一个类包含抽象方法,那么该类必须是抽象类。
  • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。

如果Salary类继承了Employee类,那么它必须实现computePay()方法:

Salary.java 文件代码:

/* 文件名 : Salary.java */publicclassSalaryextendsEmployee{privatedoublesalary; // Annual salarypublicdoublecomputePay(){System.out.println("Computing salary pay for " + getName()); returnsalary/52; }//其余代码}

抽象类总结规定

  • 1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。

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

  • 3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。

  • 4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。

  • 5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

AI 思考中...

5 篇笔记 写笔记

  1. #0

    java 中抽象类不能被实例化

    代码示例:

    class abstract A {
    }
    

    驱动函数:

    public static void main(String[] args){
     //下面这行会造成编译不通过
     A a=new A();
    }
    
    10年前 (2016年12月31日)
  2. #0

    菲菲小姐

    xf0***[email protected]

    46

    并不是说"一定要调用父类的显性构造器",而是子类在继承父类时,如果父类的显式构造器中有参数,子类要声明给出这个参数。这是一个关于继承的问题。

    举一个例子如果把上面的代码改成:

    abstract class Animal{
     private int age = 10;
     public Animal(){
     System.out.println("初始化Animal");
     }
     public void move(){
     System.out.println("跑步数:"+this.age);
     }
    }
    abstract class Dog extends Animal{
     public Dog(int age){
     // super(age);//去掉会报异常
     System.out.println("初始化Dog");
     }
    }
    public class BigDogs extends Dog{
     public BigDogs(){
     super(20);
     System.out.println("初始化BigDog");
     }
     public static void main(String[] args){
     BigDogs a = new BigDogs();
     a.move();
     }
    }

    将第一个父类构造器中要求参数那一行去掉,则第二个抽象类中调用父类构造器的部分就可以删去,编译是可以通过的。

    菲菲小姐

    xf0***[email protected]

    8年前 (2018年03月13日)
  3. #0

    定义 Shape 类表示一般二维图形。Shape 具有抽象方法 area 和 perimeter,分别计算形状的面积和周长。试定义一些二维形状类(如矩形、三角形、圆形等),这些均为 Shape 类的子类并计算出这些形状的面积和周长,打印输出相关信息。

    形状类:

    public abstract class Shape {
     public abstract double area();
     public abstract double perimeter();
    } 

    矩形类:

    public class Rectangle extends Shape {
     private double length;
     private double width;
     public double getLength() {
     return length;
     }
     public void setLength(double length) {
     this.length = length;
     }
     public double getWidth() {
     return width;
     }
     public void setWidth(double width) {
     this.width = width;
     }
     @Override
     public double area() {
     return getLength() * getWidth();
     }
     @Override
     public double perimeter() {
     return 2 * (getWidth() + getWidth());
     }
    }

    三角形类:

    public class Triangle extends Shape {
     private double a, b, c;
     public double getA() {
     return a;
     }
     public void setA(double a) {
     this.a = a;
     }
     public double getB() {
     return b;
     }
     public void setB(double b) {
     this.b = b;
     }
     public double getC() {
     return c;
     }
     public void setC(double c) {
     this.c = c;
     }
     @Override
     public double area() {
     double p = (getA() + getB() + getC()) / 2;
     return Math.sqrt(p * (p - getA()) * (p - getB()) * (p - getC()));
     }
     @Override
     public double perimeter() {
     return getA() + getB() + getC();
     }
    }

    圆形类:

    public class Circle extends Shape {
     private double diameter;
     public double getDiameter() {
     return diameter;
     }
     public void setDiameter(double diameter) {
     this.diameter = diameter;
     }
     @Override
     public double area() {
     return Math.PI * Math.pow(getDiameter() / 2, 2);
     }
     @Override
     public double perimeter() {
     return Math.PI * getDiameter();
     }
    }

    测试代码:

    public class Test {
     public static void main(String [] args){
     Rectangle rec = new Rectangle();
     rec.setLength(10);
     rec.setWidth(5);
     double rec_area = rec.area();
     double rec_perimeter = rec.perimeter();
     System.out.println("矩形的面积:"+rec_area+",周长"+rec_perimeter);
     Triangle tri = new Triangle();
     tri.setA(3);
     tri.setB(4);
     tri.setC(5);
     double tri_area = tri.area();
     double tri_perimeter = tri.perimeter();
     System.out.println("三角形的面积:"+tri_area+",周长"+tri_perimeter);
     Circle cir = new Circle();
     cir.setDiameter(10);
     double cir_area = cir.area();
     double cir_perimeter = cir.perimeter();
     System.out.println("圆形的面积:"+cir_area+",周长"+cir_perimeter);
     }
    }
    Linux 8年前 (2018年08月28日)
  4. #0

    抽象类和接口的区别

    1、语法层面上的区别

    • 1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
    • 2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
    • 3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
    • 4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

    2、设计层面上的区别

    1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类 Airplane,将鸟设计为一个类 Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

    2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过 ppt 里面的模板,如果用模板 A 设计了 ppt B 和 ppt C,ppt B 和 ppt C 公共的部分就是模板 A 了,如果它们的公共部分需要改动,则只需要改动模板 A 就可以了,不需要重新对 ppt B 和 ppt C 进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

    下面看一个网上流传最广泛的例子:门和警报的例子:门都有 open()close() 两个动作,此时我们可以定义通过抽象类和接口来定义这个抽象概念:

    abstract class Door {
     public abstract void open();
     public abstract void close();
    }

    或者:

    interface Door {
     public abstract void open();
     public abstract void close();
    }

    但是现在如果我们需要门具有报警 的功能,那么该如何实现?下面提供两种思路:

    1)将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;

    2)将这三个功能都放在接口里面,需要用到报警功能的类就需要实现这个接口中的 open( ) 和 close( ),也许这个类根本就不具备 open( ) 和 close( ) 这两个功能,比如火灾报警器。

    从这里可以看出, Door 的 open() 、close() 和 alarm() 根本就属于两个不同范畴内的行为,open() 和 close() 属于门本身固有的行为特性,而 alarm() 属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含 alarm() 行为,Door 设计为单独的一个抽象类,包含 open 和 close 两种行为。再设计一个报警门继承 Door 类和实现 Alarm 接口。

    interface Alram {
     void alarm();
    }
     
    abstract class Door {
     void open();
     void close();
    }
     
    class AlarmDoor extends Door implements Alarm {
     void oepn() {
     //....
     }
     void close() {
     //....
     }
     void alarm() {
     //....
     }
    }

    更多内容查看:深入理解 Java 的接口和抽象类

    8年前 (2019年01月12日)
  5. #0

    不想取昵称

    272***[email protected]

    参考地址

    52

    为什么需要抽象类?

    抽象方法和抽象类看上去是多余的,对于抽象方法,不知道如何实现,定义一个空方法体不就行了吗,而抽象类不让创建对象,看上去只是增加了一个不必要的限制。

    引入抽象方法和抽象类,是Java提供的一种语法工具,对于一些类和方法,引导使用者正确使用它们,减少被误用。

    使用抽象方法,而非空方法体,子类就知道他必须要实现该方法,而不可能忽略。

    使用抽象类,类的使用者创建对象的时候,就知道他必须要使用某个具体子类,而不可能误用不完整的父类。

    无论是写程序,还是平时做任何别的事情的时候,每个人都可能会犯错,减少错误不能只依赖人的优秀素质,还需要一些机制,使得一个普通人都容易把事情做对,而难以把事情做错。抽象类就是Java提供的这样一种机制。

    不想取昵称

    272***[email protected]

    参考地址

    5年前 (2021年11月29日)

点我分享笔记

  • 昵称 (必填)
  • 邮箱 (必填)
  • 引用地址

AltStyle によって変換されたページ (->オリジナル) /