Java中的类,抽象类和接口

想必大家对Java中的实体类都比较熟悉。实体类通过属性和方法去描述一系列相似对象的特征。例如我们要写一个程序去计算图形的面积,需要定义一个长方形的类:

public class Rect {

    private double width;
    private double height;

    public Rect(double width, double height, String color) {
        this.width = width;
        this.height = height;
        this.color = color;
    }

    public double area() {
        return width * height;
    }
}

Rect类中有着实例字段去描述长方形的宽高以及图形的颜色,area()方法用来计算长方形的面积。我们再定义一个圆形的类:

public class Circle {
    
    private double radius;
    private String color;

    public Circle(double radius, String color) {
        this.radius = radius;
        this.color = color;
    }
    
    public void area() {
        return Math.PI * radius * radius
    }
} 

我们可以发现,Rect类和Circle类有一部分代码是相似的,例如color字段和area()方法。然而,由于计算面积的公式不同,area()的具体实现在这两个类中是不同的。这时,我们就可以创建一个新的类将相似的代码抽象出来。

public abstract class Shape {

    private String color;

    public Shape(String color) {
        this.color = color;
    }

    public abstract double area();
}

可以看到Shape类和Rect类以及Circle类的一个明显不同是,class前面多了一个abstract修饰符,这代表着Shape是一个抽象类而非实体类。抽象类和实体类的一个明显的区别就是,抽象类中包含有抽象方法(没有具体实现的方法)。换句话说,包含有抽象方法的类被称为抽象类。在Shape类中,area()就是一个抽象方法。它比普通方法多了一个abstract修饰符,并且没有由花括号包裹起来的方法体。

抽象类不能被实例化,但可以被继承。抽象类的子类必须实现父类的抽象方法(重写并使其有着具体的实现),否则子类也应该被定义为抽象类(继承了父类的抽象方法而未实现)。在这个例子中,Shape类的子类必须实现area()方法。

可能有人会问,抽象方法的存在只是为了简化代码吗?其实并不是的。抽象方法实际上定义了子类必须实现的“规范”。上层代码只定义规范,具体的业务逻辑由不同的子类实现,调用者并不关心子类的具体实现。同时,由于抽象方法的存在,在编写代码的时候不需要子类就可以实现业务逻辑(正常编译不报错)。例如我们只有一个抽象类Shape,要写一个ShapeUtil类,里面有计算图形面积之和的方法。

public class ShapeUtil {

    public double sum(Shape[] shapes) {
        double s = 0;
        for (Shape shape : shapes) {
            s += shape.area();
        }
        return s;
    }
}

可以看到,在计算面积和时,我们并不关心每一个图形的面积是如何计算的,我们只负责处理面积求和的业务逻辑,而把计算每个图形面积的具体实现交给了子类处理。也就是说,抽象方法定义了规范(area()方法需要返回图形的面积),而具体的实现交给了子类。

接下来我们使用抽象类来实现Rect类和Circle类:

public class Rect extends Shape {

    private double width;
    private double height;

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

    @Override
    public double area() {
        return width * height;
    }
}
public class Circle extends Shape{

    private double radius;

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

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

可以看出,除了拥有抽象方法外,抽象类和普通类一样,同样可以拥有成员变量和普通的成员方法。抽象类和普通类在用法上也是相似的,在继承抽象类时使用extends关键字,一个类只能继承一个抽象类

如果一个抽象类没有字段,所有方法全部是抽象方法,就可以把该抽象类改写为接口。接口是一种声明的集合,是一种声明的规范,里面包含了很多抽象方法。需要明确的一点是,接口不是类,类描述对象的属性和方法,接口则包含类要实现的方法。

接口用interface来声明,它无法实例化。它可以被类实现(implements),无法被继承。当接口被类实现时,除非该类为抽象类,否则该类需要实现接口中所规定的所有抽象方法。我们将以上代码改写,使用interface来实现:

public interface Shape {

    double area();
    
}

public class Rect implements Shape {

    private double width;
    private double height;

    public Rect(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double area() {
        return width * height;
    }
}
public class Circle implements Shape {

    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

接口和抽象类是有相似之处的。接口可以继承自另一个接口(使用extends关键字),相当于扩展类接口的方法。接口定义的方法默认是public abstract(不需要写)。接口与抽象类的一些区别如下:

抽象类 接口
继承 一个子类只能继承一个抽象类 一个子类可以实现多个接口
成员变量类型 各种类型均可 只能是 public static final
抽象方法 可以定义抽象方法 可以定义抽象方法
非抽象方法 可以定义非抽象方法 可以定义default方法*
static方法 可以有static方法/代码块 不能含有static修饰的方法

*:default方法是一个非抽象方法,所有实现接口的类都不需要实现这个方法,任何子类都可以重写这个方法来实现它自己的逻辑。

接口的作用主要是作为一个上层的规范。比如我们要开发一个无人驾驶汽车的软件。开发过程中,我们需要GPS服务提供商提供GPS相关的服务(例如,定位、导航),我们还需要汽车制造厂商提供操控汽车的服务(例如,加速、刹车、转弯等)。这两种服务是不相关的,GPS公司和汽车厂商不需要了解对方公司是如何提供服务的(具体的代码实现)。在这种情况下,我们可以使用接口。我们写出GPS服务的接口和汽车操控的接口,根据接口来处理业务逻辑。与此同时,GPS提供商和汽车厂商可以根据我们提供的接口规范去实现具体功能。

如何判断该使用抽象类还是接口呢?

当以下情况适用于你时,可以考虑使用抽象类:

  • 你希望在一些紧密相关的类中共享代码
  • 你需要被继承的类拥有一些共同的方法、字段,或者需要除了public以外的访问修饰符(例如protected/private)
  • 你希望声明一些非静态/非常量的字段(你可以定义一些方法去获取/修改对象的属性)

当以下情况适用于你时,可以考虑使用抽象类:

  • 你希望让许多不相关的类来实现你的接口。例如,Java中ComparableCloneable接口被许多不相关的类所实现。
  • 你希望具体指明某一特定数据类型的某个具体行为,但并不关心是谁实现了这个行为
  • 你想利用对于类型的多重继承
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 首先看抽象类,它介于普通类和接口之间,尽管在构建某些未实现方法的类时,很多时候 往往会去创建接口,但抽象类仍然很重...
    一根烟的弹跳阅读 1,415评论 5 21
  • 一、抽象类 1、抽象方法 抽象方法只有声明,而没有具体的实现。也就是说没有{}。抽象方法的声明格式为:abstra...
    锈色的栅栏阅读 446评论 0 0
  • 说好了不熬夜,可是放假这段时间整个人像打了鸡血一样,浑身都有使不完的精力,韦先生笑称若是当初像现在这样勤奋努...
    秦雨筠阅读 682评论 0 0
  • 1、 我喜欢装配修理电器或玩具 A、符合 B、不符合 2、 我喜欢阅读科技类图书 A、...
    冯老师fy阅读 606评论 0 0
  • 在那些桂工广为流传的段子里, 专业 永远是不变的话题。 你的专业是什么呢? 资源勘查工程? 化学工程与工艺? 旅游...
    秋夜寒阅读 570评论 0 0

友情链接更多精彩内容