Java中的抽象类

297

Java中的"抽象类"是什么?


41
这个问题非常基础和根本,是SO的经典问题。我很惊讶它以前还没有被问过。+1 - Yuval
6
对于Clement的评论,我会给一个负分(如果可以的话);lmgtfy不是一个有帮助的回复。为什么不是有帮助的回复,请参考这个链接:http://meta.stackexchange.com/questions/5280/embrace-the-non-googlers - Jonik
27
@tuergeist. 只要在 SO 上没有提出过,即使可以通过谷歌轻松获得答案也是无关紧要的。此外,谁说初学者有关程序设计语言的问题不适合在 SO 上发问呢? - Jonik
13
我喜欢 Stack Overflow 的一点就是你能够得到一个简明扼要、表达清晰、直截了当的答案,而不会像其他网站那样充斥着通常的废话……不过,总体来说也差不多。给这个问题点赞! - Anders Hansson
1
SO 不仅仅是应该有长尾!J&J甚至在第56期播客中谈到了这个问题... - kwutchak
显示剩余7条评论
15个回答

354

抽象类是一种无法实例化的类。通过创建继承抽象类的子类,可以实例化子类。抽象类对继承的子类做了以下几件事情:

  1. 定义可供继承的子类使用的方法。
  2. 定义必须由继承的子类实现的抽象方法。
  3. 提供一个公共接口,使得子类可以与所有其他子类互换使用。

以下是一个示例:

abstract public class AbstractClass
{
    abstract public void abstractMethod();
    public void implementedMethod() { System.out.print("implementedMethod()"); }
    final public void finalMethod() { System.out.print("finalMethod()"); }
}

需要注意的是,“abstractMethod()”没有任何方法体。因此,您不能执行以下操作:

public class ImplementingClass extends AbstractClass
{
    // ERROR!
}

没有实现abstractMethod()的方法!因此当JVM收到new ImplementingClass().abstractMethod()时,它无法知道应该执行什么操作。

这里是一个正确的ImplementingClass

public class ImplementingClass extends AbstractClass
{
    public void abstractMethod() { System.out.print("abstractMethod()"); }
}

请注意,您不必定义implementedMethod()finalMethod()。它们已经被AbstractClass定义了。

这里是另一个正确的ImplementingClass

public class ImplementingClass extends AbstractClass
{
    public void abstractMethod() { System.out.print("abstractMethod()"); }
    public void implementedMethod() { System.out.print("Overridden!"); }
}
在这种情况下,您已经重写了implementedMethod()方法。
然而,由于使用了final关键字,以下操作是不可能的。
public class ImplementingClass extends AbstractClass
{
    public void abstractMethod() { System.out.print("abstractMethod()"); }
    public void implementedMethod() { System.out.print("Overridden!"); }
    public void finalMethod() { System.out.print("ERROR!"); }
}

由于AbstractClass中的finalMethod()被标记为最终实现,所以您不能这样做:不允许任何其他实现。

现在您也可以两次实现抽象类:

public class ImplementingClass extends AbstractClass
{
    public void abstractMethod() { System.out.print("abstractMethod()"); }
    public void implementedMethod() { System.out.print("Overridden!"); }
}

// In a separate file.
public class SecondImplementingClass extends AbstractClass
{
    public void abstractMethod() { System.out.print("second abstractMethod()"); }
}

现在你可以在某处编写另一个方法。

public tryItOut()
{
    ImplementingClass a = new ImplementingClass();
    AbstractClass b = new ImplementingClass();

    a.abstractMethod();    // prints "abstractMethod()"
    a.implementedMethod(); // prints "Overridden!"     <-- same
    a.finalMethod();       // prints "finalMethod()"

    b.abstractMethod();    // prints "abstractMethod()"
    b.implementedMethod(); // prints "Overridden!"     <-- same
    b.finalMethod();       // prints "finalMethod()"

    SecondImplementingClass c = new SecondImplementingClass();
    AbstractClass d = new SecondImplementingClass();

    c.abstractMethod();    // prints "second abstractMethod()"
    c.implementedMethod(); // prints "implementedMethod()"
    c.finalMethod();       // prints "finalMethod()"

    d.abstractMethod();    // prints "second abstractMethod()"
    d.implementedMethod(); // prints "implementedMethod()"
    d.finalMethod();       // prints "finalMethod()"
}

请注意,尽管我们将b声明为AbstractClass类型,但它显示的是"Overriden!"。这是因为我们实例化的对象实际上是一个ImplementingClass,它的implementedMethod()被覆盖了。(您可能已经看到这被称为多态。)

如果我们希望访问特定子类的成员,必须首先向下转换为该子类:

// Say ImplementingClass also contains uniqueMethod()
// To access it, we use a cast to tell the runtime which type the object is
AbstractClass b = new ImplementingClass();
((ImplementingClass)b).uniqueMethod();

最后,您不能执行以下操作:

public class ImplementingClass extends AbstractClass, SomeOtherAbstractClass
{
    ... // implementation
}

每次只能扩展一个类。如果你需要扩展多个类,这些类必须是接口。你可以这样做:

public class ImplementingClass extends AbstractClass implements InterfaceA, InterfaceB
{
    ... // implementation
}

这是一个示例界面:

interface InterfaceA
{
    void interfaceMethod();
}

这基本上与以下内容相同:

abstract public class InterfaceA
{
    abstract public void interfaceMethod();
}

唯一的区别在于第二种方式不会让编译器知道它实际上是一个接口。如果您希望仅允许其他人实现您的接口而不是其他接口,这可能很有用。但是,总的来说,初学者应该遵循以下规则:如果您的抽象类只包含抽象方法,最好将其设计为接口。

以下是非法的:

interface InterfaceB
{
    void interfaceMethod() { System.out.print("ERROR!"); }
}

接口中不能实现方法。这意味着如果你实现了两个不同的接口,这些接口中的不同方法就不会冲突。由于接口中的所有方法都是抽象的,所以你必须实现这些方法,而且由于你的方法是继承树中唯一的实现,编译器知道它必须使用你的方法。


6
@c.Imagist -1是因为对语句c.implementedMethod(); 的描述错误。它将打印“Overriden!”而不是“implementedMethod()”。 - Sachin Kumar
2
@Sachin 我浪费了半个小时试图理解为什么它会打印“implementedMethod()”,然后我看到了你的评论。Java 有什么变化还是其他人只是忽略了这个错误? - Rounak
1
@SachinKumar 由于作者没有回应,我决定自己修复这个错误。如有不对请指正。 - Mateen Ulhaq
@SachinKumar 我来晚了,但你认为一个好的比喻是C++头文件中的方法声明(但没有实现)吗? - Schwaitz
7
为什么调用c.implementedMethod()会输出"Overriden!"呢?SecondImplementingClass并没有重写implementedMethod() - John Red
https://youtu.be/n9z_jWx2FNg - Shubham Jain

79

Java类在满足以下条件下会成为抽象类:

1. 至少有一个方法被标记为抽象方法:

public abstract void myMethod()

在这种情况下,编译器会强制你将整个类标记为抽象。

2. 类被标记为抽象:

abstract class MyClass

正如已经说过的:如果你有一个抽象方法,编译器会强制你将整个类标记为抽象类。但是即使你没有任何抽象方法,你仍然可以将该类标记为抽象类。

常见用法:

抽象类的常见用途是提供一个类的概要,类似于接口的作用。但与接口不同的是,它可以提供功能实现,也就是类的某些部分已经被实现,而其他部分则仅用方法声明进行概述。(“抽象”)

抽象类不能被实例化,但您可以基于抽象类创建一个具体类,然后可以对其进行实例化。为此,你需要从抽象类继承并重写抽象方法,也就是实现它们。


1
吹毛求疵地说,第二个“条件”是多余的,因为你只能在明确声明为抽象的类中声明抽象方法。 - Stephen C
2
同意,这个建议并不是很正确或者写得很好,只是格式排版比较漂亮。 - Noon Silk
但是你的建议“使类具体”也用词不当。你不能使一个类具体,它要么是具体的,要么不是,这取决于它是否是抽象的。 - Noon Silk
1
这是完全错误的。抽象类不一定要有任何抽象方法。你可以创建一个没有方法或只有具体方法的抽象类。 - Jorn
1
虽然晚了10年,但这是最精确的答案。@Jorn,我认为你对答案感到困惑了。我确定暗示了abstract关键字是使类成为抽象类所必需的。但是具体类不能包含abstract 方法。因此,如果您的类具有abstract方法,则必须将其声明为abstract类以供编译器使用。 - user2453382
显示剩余2条评论

25
使用 abstract 关键字声明的类被称为 抽象类。抽象是一种隐藏数据实现细节,仅向用户展示功能的过程。抽象使您专注于对象执行的操作,而不是其如何执行。 抽象类的主要特点:
  • 抽象类可以包含抽象方法,也可以包含非抽象方法。
  • 如果一个类至少有一个抽象方法,则该类必须是抽象的。
  • 抽象类不能被实例化(不允许创建抽象类的对象)。
  • 要使用抽象类,必须从另一个类继承它,并为其中的所有抽象方法提供实现。
  • 如果继承了一个抽象类,则必须为其中的所有抽象方法提供实现。
声明抽象类 在声明类时,在类名称前加上 abstract 关键字可将其变成抽象类。请参阅下面的代码示例:
abstract class AbstractDemo{ }

声明抽象方法 在声明方法时,在方法前加上abstract关键字即可将它声明为抽象方法。请看下面的代码,

abstract void moveTo();//no body

为什么我们需要抽象类

在面向对象的绘图应用中,您可以绘制圆形、矩形、直线、贝塞尔曲线和许多其他图形对象。这些对象都有某些共同的状态(例如:位置、方向、线条颜色、填充颜色)和行为(例如:moveTo、rotate、resize、draw)。其中一些状态和行为对于所有图形对象都是相同的(例如:填充颜色、位置和moveTo)。其他状态和行为则需要不同的实现(例如:resize或draw)。所有图形对象都必须能够自我绘制或调整大小,它们只是以不同的方式实现。

这是使用抽象超类的完美情况。您可以利用相似之处,并声明所有图形对象从同一个抽象父对象(例如:GraphicObject)继承,如下图所示。 enter image description here

首先,您声明一个抽象类GraphicObject,提供成员变量和方法,这些成员变量和方法被所有子类完全共享,例如当前位置和moveTo方法。GraphicObject还声明抽象方法,例如draw或resize,需要由所有子类实现,但必须以不同的方式实现。 GraphicObject类可能如下所示:

abstract class GraphicObject {

  void moveTo(int x, int y) {
    // Inside this method we have to change the position of the graphic 
    // object according to x,y     
    // This is the same in every GraphicObject. Then we can implement here. 
  }

  abstract void draw(); // But every GraphicObject drawing case is 
                        // unique, not common. Then we have to create that 
                        // case inside each class. Then create these    
                        // methods as abstract 
  abstract void resize();
}

在子类中使用抽象方法 GraphicObject的每个非抽象子类,如CircleRectangle,都必须为drawresize方法提供实现。

class Circle extends GraphicObject {
  void draw() {
    //Add to some implementation here
  }
  void resize() {
    //Add to some implementation here   
  }
}
class Rectangle extends GraphicObject {
  void draw() {
    //Add to some implementation here
  }
  void resize() {
    //Add to some implementation here
  }
}

main方法中,您可以像这样调用所有方法:

public static void main(String args[]){
   GraphicObject c = new Circle();
   c.draw();
   c.resize();
   c.moveTo(4,5);   
}

Java中实现抽象化的方式

Java中有两种实现抽象化的方式:

  • 抽象类(0到100%)
  • 接口(100%)

带有构造函数、数据成员、方法等的抽象类

abstract class GraphicObject {

  GraphicObject (){
    System.out.println("GraphicObject  is created");
  }
  void moveTo(int y, int x) {
       System.out.println("Change position according to "+ x+ " and " + y);
  }
  abstract void draw();
}

class Circle extends GraphicObject {
  void draw() {
    System.out.println("Draw the Circle");
  }
}

class TestAbstract {  
 public static void main(String args[]){

   GraphicObject  grObj = new Circle ();
   grObj.draw();
   grObj.moveTo(4,6);
 }
}

输出:

GraphicObject  is created
Draw the Circle
Change position according to 6 and 4

记住两个规则:

  • 如果一个类有少量的抽象方法和少量的具体方法,将其声明为abstract类。

  • 如果一个类只有抽象方法,将其声明为interface

参考资料:


为什么在上面的示例、下面的示例和下面的输出中,moveTo参数x和y的顺序不同?如果我们试图说明接口和抽象类等概念的重要性,那么我们不应该始终使用与我们实现或扩展的接口或抽象类相同的函数签名吗? - Jonathan Rys
两个规则揭示了它。 - linker

4

这是一个无法实例化的类,它强制要求实现类可能要实现它所概述的抽象方法。


3

来自oracle的文档

抽象方法和抽象类:

抽象类是声明为abstract的类,它可能包含或不包含抽象方法

抽象类不能被实例化,但可以被子类化

抽象方法是声明为没有实现的方法(没有大括号,并在后面跟着一个分号),像这样:

abstract void moveTo(double deltaX, double deltaY);

如果一个类包含抽象方法,那么这个类本身必须被声明为抽象类,如下所示:
public abstract class GraphicObject {
   // declare fields
   // declare nonabstract methods
   abstract void draw();
}

当一个抽象类被子类继承时,通常子类会为父类中的所有抽象方法提供实现。然而,如果没有提供实现,则子类也必须声明为抽象类。
由于抽象类和接口相关,请参阅下面的SE问题: 接口和抽象类之间的区别是什么? 我应该如何解释接口和抽象类之间的区别?

3
简而言之,您可以将抽象类视为具有更多功能的接口。
无法实例化接口,这也适用于抽象类。
在接口上,您只能定义方法头,并且所有实现者都必须实现其中的所有方法。在抽象类中,您也可以定义方法头,但是与接口不同的是,在此处,您还可以定义方法的主体(通常是默认实现)。此外,当其他类扩展(注意,不是实现,因此每个子类也可以只有一个抽象类)您的抽象类时,除非您指定了抽象方法(在这种情况下,它的工作方式类似于接口,您不能定义方法主体),否则它们不需要实现您的所有方法。
public abstract class MyAbstractClass{
  public abstract void DoSomething();
}

否则对于抽象类的普通方法,"继承者"可以像往常一样使用默认行为或覆盖它。
示例:
public abstract class MyAbstractClass{

  public int CalculateCost(int amount){
     //do some default calculations
     //this can be overriden by subclasses if needed
  }

  //this MUST be implemented by subclasses
  public abstract void DoSomething();
}

如果提问者不知道接口是什么,那么这个答案就没有帮助。由于抽象类和接口是相互关联的,所以提问者很可能不会只知道其中一个而不知道另一个。 - Imagist
但这是有可能的。他可能只是知道什么是接口以及它的工作原理,然后遇到抽象类,想知道为什么需要它们。这不是有可能吗? - Juri

3

在这里获取您的答案:

Java中的抽象类与接口

抽象类可以有final方法吗?

顺便说一下 - 这些是您最近提出的问题。考虑一个新问题来建立声誉...

编辑:

刚刚意识到,这篇文章和引用的问题的发布者具有相同或至少相似的名称,但用户ID始终不同。因此,要么存在技术问题,即keyur无法再次登录并找到他的问题的答案,要么这是一种娱乐SO社区的游戏;)


这就是为什么我选择了“社区维基”的原因 - 不能通过回答那些问题来增加声望 ;) - Andreas Dolk

1

解决方案 - 基类(抽象)

public abstract class Place {

String Name;
String Postcode;
String County;
String Area;

Place () {

        }

public static Place make(String Incoming) {
        if (Incoming.length() < 61) return (null);

        String Name = (Incoming.substring(4,26)).trim();
        String County = (Incoming.substring(27,48)).trim();
        String Postcode = (Incoming.substring(48,61)).trim();
        String Area = (Incoming.substring(61)).trim();

        Place created;
        if (Name.equalsIgnoreCase(Area)) {
                created = new Area(Area,County,Postcode);
        } else {
                created = new District(Name,County,Postcode,Area);
        }
        return (created);
        }

public String getName() {
        return (Name);
        }

public String getPostcode() {
        return (Postcode);
        }

public String getCounty() {
        return (County);
        }

public abstract String getArea();

}

1
请尝试将所有代码格式化为代码,并添加一些解释,现在这几乎不能被视为答案。 - NomeN
3
除非您解释您的代码,否则您将被认为是一个糟糕的答案制作者。请在这里给出解释。 - devsda

1
一个抽象类不能直接实例化,但必须从中派生才能使用。如果一个类包含抽象方法,则该类必须是抽象的:可以直接声明为抽象类。
abstract class Foo {
    abstract void someMethod();
}

或间接地

interface IFoo {
    void someMethod();
}

abstract class Foo2 implements IFoo {
}

然而,一个类可以是抽象的,而不包含抽象方法。这是一种防止直接实例化的方式,例如:

abstract class Foo3 {
}

class Bar extends Foo3 {

}

Foo3 myVar = new Foo3(); // illegal! class is abstract
Foo3 myVar = new Bar(); // allowed!

后一种风格的抽象类可用于创建“类似接口”的类。与接口不同,抽象类允许包含非抽象方法和实例变量。您可以使用这些内容来为扩展类提供一些基本功能。
另一种常见模式是在抽象类中实现主要功能,并定义部分算法作为由扩展类实现的抽象方法。愚蠢的例子:
abstract class Processor {
    protected abstract int[] filterInput(int[] unfiltered);

    public int process(int[] values) {
        int[] filtered = filterInput(values);
        // do something with filtered input
    }
}

class EvenValues extends Processor {
    protected int[] filterInput(int[] unfiltered) {
        // remove odd numbers
    }
}

class OddValues extends Processor {
    protected int[] filterInput(int[] unfiltered) {
        // remove even numbers
    }
}

1

对于所有这些帖子的小补充。

有时候,您可能想要声明一个类,但不知道如何定义属于该类的所有方法。例如,您可能想要声明一个名为Writer的类,并在其中包含一个名为write()的成员方法。但是,您不知道如何编写write(),因为它对于每种类型的Writer设备都是不同的。当然,您计划通过派生Writer的子类(例如Printer、Disk、Network和Console)来处理此问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接