使用抽象方法有什么意义?

39

使用“抽象方法”的目的是什么?抽象类无法实例化,但是抽象方法呢?它们只是用来声明“你必须实现我”,如果我们忘记实现它们,编译器就会报错吗?

这是否意味着还有其他含义?我也读到过“我们不必重写相同的代码”,但在抽象类中,我们只是“声明”了抽象方法,因此我们仍需在子类中重写代码。

您能帮助我更好地理解吗?我查阅了关于“抽象类/方法”的其他主题,但没有找到答案。


是的,抽象方法意味着“你必须实现我”。抽象类也可以有已实现的方法。事实上,抽象类中没有任何抽象方法的要求。只要有任何抽象方法,该类就必须是抽象的。 - Gray
2
你理解多态吗?想要扩展功能,因为猫是动物吗?抽象类和方法可以给你相同的功能,但不允许你使用父类。拥有一只宠物猫是有意义的,但你没有一个通用的宠物动物。 - dann.dev
7个回答

38

假设你需要为三个打印机编写驱动程序,分别是 LexmarkCanonHP

这三个打印机都有 print()getSystemResource() 方法。

然而,每个打印机的 print() 方法都不同,而 getSystemResource() 方法对于所有三个打印机都相同。另外,你希望应用多态性。

由于 getSystemResource() 对于所有三个打印机都相同,因此可以将其上推到超类中实现,并让子类实现 print() 方法。在Java中,这可以通过在超类中使 print() 方法抽象化来完成。注意:当在一个类中将方法抽象化时,该类本身也需要抽象化。

public abstract class Printer{
  public void getSystemResource(){
     // real implementation of getting system resources
  }
  
  public abstract void print();
}

public class Canon extends Printer{
  public void print(){
    // here you will provide the implementation of print pertaining to Canon
  }
}

public class HP extends Printer{
  public void print(){
    // here you will provide the implementation of print pertaining to HP
  }
}

public class Lexmark extends Printer{
  public void print(){
    // here you will provide the implementation of print pertaining to Lexmark
  }
}

注意,HP、Canon和Lexmark类没有提供getSystemResource()的实现。

最后,在你的主类中,你可以这样做:

public static void main(String args[]){
  Printer printer = new HP();
  printer.getSystemResource();
  printer.print();
}

1
@ChinBoon 很棒的回答。真的帮助我理解了抽象方法的概念! - jskidd3
那么在这种情况下,它类似于Interface的作用,对吗? - Neerkoli

24

除了提醒你必须实现它之外,另一个重要的优点是任何按其抽象类类型引用对象的人(包括在抽象类本身中使用this)都可以使用该方法。

例如,假设我们有一个负责获取状态并以某种方式进行操作的类。 抽象类将负责获取输入,将其转换为long(例如),并以某种方式将该值与先前的值组合在一起——这个“某种方式”就是抽象方法。 抽象类可能如下所示:

public abstract class StateAccumulator {
    protected abstract long accumulate(long oldState, long newState);

    public handleInput(SomeInputObject input) {
        long inputLong = input.getLong();
        state = accumulate(state, inputLong);
    }

    private long state = SOME_INITIAL_STATE;
}

现在您可以定义一个加法累加器:

public class AdditionAccumulator extends StateAccumulator {
    @Override
    protected long accumulate(long oldState, long newState) {
        return oldState + newState;
    }
}

如果没有这个抽象方法,基类就无法说“以某种方式处理这个状态”。但我们不想在基类中提供默认实现,因为这并没有什么意义——如何为“其他人将实现此内容”定义默认实现呢?

请注意,有多种方法可以解决问题。 策略模式 将涉及声明一个接口,该接口声明了accumulate模式,并将该接口的实例传递给不再是抽象的基类。按照行话的说法,这是使用组合而非继承(您已将两个对象(聚合器和加法器)组合成一个加法聚合器)。


谢谢yshavit的回答,我之前不知道这个。只有一件事:AdditionAccumulator应该继承抽象类对吗?关于解释,现在对我来说很有意义了,再次感谢 - Paul
好的,谢谢你的回答!那么以你的例子为例:抽象类将“调用”子类来获取其答案并更新“状态”,是这样吗? - Paul
谢谢提供这个例子,它有助于解释这些概念。 - Rich

5

抽象类是包含一个或多个抽象方法的类。抽象方法是声明但不包含实现的方法。抽象类不能被实例化,要求子类提供抽象方法的实现。让我们看一个抽象类和一个抽象方法的例子。

假设我们正在通过创建以Animal为基类的类层次结构来对动物的行为进行建模。动物可以做各种事情,如飞行、挖掘和行走,但也有一些共同的操作,如进食、睡觉和发出声音。所有动物都会执行一些共同的操作,但它们的方式却不尽相同。当某个操作的方式不同时,这就是抽象方法的好候选对象(强制子类提供自定义实现)。让我们看一个非常原始的Animal基类,它定义了一个用于发出声音的抽象方法(如狗叫、牛哞哞、猪哼哼等)。

public abstract Animal {

public void sleep{
// sleeping time
}
public void eat(food)
{
//eat something
}
public abstract void makeNoise();
}
public Dog extends Animal {
 public void makeNoise() {
 System.out.println("Bark! Bark!");
 }
}
public Cow extends Animal {
 public void makeNoise() {
 System.out.println("Moo! Moo!");
 }
}

注意,关键字abstract既用于表示抽象方法,也用于表示抽象类。现在,任何想要实例化的动物(如狗或牛)都必须实现makeNoise方法 - 否则无法创建该类的实例。让我们来看一个扩展了Animal类的Dog和Cow子类。
现在你可能会问为什么不将抽象类声明为接口,并让Dog和Cow实现该接口。当然,你可以这样做 - 但你还需要实现eat和sleep方法。通过使用抽象类,你可以继承其他(非抽象)方法的实现。在接口中无法这样做 - 接口不能提供任何方法实现。
简单来说,接口应该包含所有抽象方法,但不包含任何方法的实现,或者我们不能在接口中定义非抽象方法,在接口中所有方法都应该是抽象的,但在抽象类中,我们可以定义抽象和非抽象方法,因此为了定义非抽象方法,我们不必定义另一个类来实现相同对象的行为,这就是抽象类优于接口的优点。

2

抽象方法仅定义派生类必须实现的合同。这是确保它们始终如一的方式。

例如,让我们看一个抽象类 Shape。它将具有一个抽象方法 draw()来绘制它。( Shape是抽象的,因为我们不知道如何绘制通用形状)通过在 Shape中具有 抽象方法draw,我们保证所有可以被绘制的派生类都实现了draw,例如 Circle。之后,如果我们忘记在从 Shape派生的某个类中实现draw,编译器将会给出错误提示。


我建议在定义“抽象”时不要使用“接口”这个词。常用的词是“合同”。 - Gray

1

简单来说,将一个类声明为“抽象”,你正在强制要求继承它的子类遵守一个“合同”,因此它为维护这个“合同”提供了一种好的方式。


0

如果一个抽象类只是声明了抽象方法,那么你是正确的,这有点傻,使用接口可能更好。

但通常一个抽象类会实现一些(甚至全部)方法,只留下少数方法作为抽象方法。例如,AbstractTableModel。这样可以节省大量重写代码的时间。

另一个比接口更好的“优势”是,抽象类可以为子类声明字段以供使用。因此,如果您相信任何合理的实现都会有一个名为uniqueID的字符串,您可以在抽象类中声明它,以及相关的getter/setter,以后就可以节省一些打字时间。


-2

抽象方法必须被任何不是抽象的子类重写。

例如,您定义了一个抽象类Log,并强制子类重写该方法:

public abstract class Log{
  public void logError(String msg){
    this.log(msg,1)
  }
  public void logSuccess(String msg){
    this.log(msg,2)
  }
  public abstract void log(String msg,int level){}
}

public class ConsoleLog{
  public void log(String msg,int level){
    if(level=1){
       System.err.println(msg)
    }else{
       System.out.println(msg)
    }
  }
}

你不强制子类重写,但是你强制要求它们提供一个实现。 - Vincent J. Michuki

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