Java中的接口和抽象类混淆与示例

时间:2012-07-09 04:35:36

标签: java oop interface abstract-class

我无法理解何时使用接口而不是抽象类,反之亦然。此外,我很困惑何时扩展与另一个接口的接口。对于长篇文章感到抱歉,但这非常令人困惑。

创建形状似乎是一个受欢迎的起点。假设我们想要一种模拟2D形状的方法。我们知道每个形状都有一个区域。以下两个实现之间有什么区别:

带接口:

public interface Shape {
    public double area();
}

public class Square implements Shape{
    private int length = 5;
    public Square(){...}

    public double area()
         return length * length;
    }
}

使用抽象类:

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

public class Square extends Shape {
    private length = 5;
    public Square(){...}

    public double area(){
        return length * length;
    }

我理解抽象类允许您定义实例变量并允许您提供方法实现,而接口不能执行这些操作。但在这种情况下,似乎这两个实现是相同的。所以使用任何一个都没问题?

但是现在说我们要描述不同类型的三角形。我们可以有等腰,锐角和直角三角形。对我来说,在这种情况下使用类继承是有意义的。使用'IS-A'定义:右三角形“IS-A”三角形。三角形“IS-A”形状。此外,抽象类应该定义所有子类中常见的行为和属性,因此这是完美的:

使用抽象类

abstract Triangle extends Shape {
    private final int sides = 3;
}
class RightTriangle extends Triangle {
    private int base = 4;
    private int height = 5;

    public RightTriangle(){...}

    public double area() {
        return .5 * base * height
    }
}

我们也可以使用接口来实现这一点,Triangle和Shape是接口。但是,与类继承不同(使用'IS-A'关系来定义什么应该是子类),我不知道如何使用接口。我看到两种方式:

第一种方式:

  public interface Triangle {
      public final int sides = 3;
  }
  public class RightTriangle implements Triangle, Shape {
      private int base = 4;
      private int height = 5;

      public RightTriangle(){}
      public double area(){
          return .5 * height * base;
      }
  }

第二种方式:

public interface Triangle extends Shape {
     public final int sides = 3;
} 
public class RightTriangle implements Triangle {
    ....

    public double area(){
         return .5 * height * base;
    }
}

在我看来,这两种方式都有效。但是什么时候你会用另一种方式呢?使用接口而不是抽象类来表示不同的三角形有什么好处吗?尽管我们对形状的描述进行了复杂化,但使用接口与抽象类仍然相似。

接口的一个关键组件是它可以定义可以在不相关的类之间共享的行为。因此Flyable接口将出现在Airplane和Bird中。因此,在这种情况下,很明显接口方法是首选。

另外,要构建扩展另一个界面的混乱界面: 在决定什么是接口时,应该何时忽略'IS-A'关系? 举个例子:LINK

为什么'VeryBadVampire'应该是一个类而'吸血鬼'是一个接口?一个'VeryBadVampire'IS-A'吸血鬼',所以我的理解是'吸血鬼'应该是一个超类(可能是抽象类)。 “吸血鬼”类可以实施“致命”来保持其致命行为。此外,'吸血鬼'IS-A'怪物',所以'怪物'也应该是一个类。 “吸血鬼”类也可以实现一个名为“危险”的界面来保持其危险行为。如果我们想创造一个名为'BigRat'的新怪物,这个怪物很危险但不致命,那么我们就可以创建一个'BigRat'类来扩展'Monster'并实现'Dangerous'。

上述不会与使用'Vampire'作为接口(在链接中描述)实现相同的输出吗?我看到的唯一区别是使用类继承并保留'IS-A'关系会消除很多混乱。然而,这并没有遵循。这样做有什么好处?

即使你想要一个怪物来分享吸血鬼的行为,人们总是可以重新定义对象的表示方式。如果我们想要一种名为'VeryMildVampire'的新型吸血鬼怪物并且我们想创造一个名为'Chupacabra'的吸血鬼般的怪物,我们可以这样做:

'吸血鬼'课程延伸'怪物'实施'危险','致命','血腥'' 'VeryMildVampire'课程延伸'吸血鬼'班级 'Chupacabra'课程延伸'Monster'实施'BloodSuckable'

但我们也可以这样做:

''VeryMildVampire'扩展'怪物'实现了危险,致命,吸血鬼 'Chupacabra'扩展'Monster'实现Dangerous,Vampiric

第二种方式创建了一个'吸血鬼'界面,这样我们就可以更容易地定义一个相关的怪物,而不是创建一堆定义吸血鬼行为的界面(如第一个例子中所示)。但这打破了IS-A的关系。所以我很困惑......

7 个答案:

答案 0 :(得分:6)

记住使用抽象类或接口时的基本概念。

当扩展的类与实现它的类更紧密地耦合时,即当两者都具有父子关系时,使用抽象类。

例如:

       abstract class Dog {}

       class Breed1 extends Dog {}

       class Breed2 extends Dog {}

Breed1Breed2都是狗的类型,并且作为狗有一些共同的行为。

然而,当实现类具有可以从类实现的功能时,使用接口。

     interface Animal {
         void eat();
         void noise();
     }

     class Tiger implements Animal {}

     class Dog  implements Animal {}

TigerDog是两个不同的类别,但它们都是吃饭和发出噪音,这些都是不同的。所以他们可以使用来自Animal的吃和噪音。

答案 1 :(得分:3)

如果要使一个或多个方法不抽象,请使用抽象类。

如果你想保留所有摘要,请使用界面。

答案 2 :(得分:3)

这是在设计有点复杂的类层次结构时会遇到的问题。但是通常在使用抽象类和接口时需要知道的事情

抽象类

  • 允许您利用使用构造函数和构造函数重写的功能
  • 限制具有多重继承的类(如果您正在设计复杂的API,这尤其有用)
  • 实例变量和方法实现
  • 利用方法超级调用的功能(使用super调用父抽象类的实现)

接口

  • 启用多重继承 - 您可以实现n个接口
  • 仅允许表示概念方法(无方法体)

通常使用“-able”子句的接口(如功能一样)。 例如: -

  1. Runnable
  2. Observable
  3. 将抽象类用于类似is-a(进化格式)的东西。 例如: -

    1. Number
    2. Graphics
    3. 但是硬性规则并不容易创建。希望这有帮助

答案 3 :(得分:1)

你这里有很多问题。但我认为基本上你是在询问界面与抽象类。

使用接口,您可以拥有实现多个接口的类。但是,如果要将其用作API,则界面不耐用。界面发布后,很难修改界面,因为它会破坏其他人的代码。

使用抽象类,您只能扩展一个类。但是,抽象类对于API是持久的,因为您仍然可以在以后的版本中进行修改而不会破坏其他人的代码。同样使用抽象类,您可以预定义实现。例如,在Triangle示例中,对于抽象类,您可能有一个方法countEdges(),默认情况下返回3.

答案 4 :(得分:1)

这是一个经常出现的问题,但没有单一的“正确”答案会让所有人满意。

类表示是-a 关系,接口表示 can-do 行为。我通常会遵循一些经验规则:

  • 坚持使用类(抽象/具体),除非您确定需要接口。
  • 如果您使用接口,请将它们切片为非常具体的功能。如果一个接口包含多个方法,那你就错了。

此外,形状和人物(或吸血鬼)的大多数例子通常都是真实世界模型的不良例子。 “正确”的答案取决于您的应用程序需要什么。例如,你提到过:

class Vampire extends Monster implements Dangerous, Lethal, BloodSuckable

您的应用程序是否真的需要所有这些接口?有多少种不同类型的Monster?您实际上是否有Vampire以外的其他类来实现BloodSuckable

尽量不要过于概括,并在不需要时提取接口。这可以追溯到经验法则:坚持使用简单的类,除非你的用例需要一个接口。

答案 5 :(得分:1)

这是一个很好的问题。这个问题有很多好的和坏的答案。典型的问题是,抽象类和接口有什么区别?让我们看看你在哪里使用抽象类以及在哪里使用接口。

在哪里使用抽象类: 就OOP而言,如果存在继承层次结构,那么您应该使用抽象类来设计您的设计 enter image description here

在哪里使用接口: 当您必须使用一个通用合同连接不同的合同(非相关类)时,您应该使用一个接口。让我们以Collection框架为例。 enter image description here

Queue,List,Set与它们的实现有不同的结构。但它们仍然共享一些常见的行为,如add(),remove()。所以我们可以创建一个名为Collection的接口,并且我们已经在接口中声明了常见的行为。如您所见,ArrayList实现了List和RandomAccess接口的所有行为。这样我们就可以轻松添加新的合同而无需更改现有逻辑。这被称为"编码到接口"。

答案 6 :(得分:0)

你的身材很好。我这样看:

当您拥有共享的方法或成员变量时,您只有抽象类。对于Shape的示例,您只有一个未实现的方法。在这种情况下,始终使用接口。

假设您有一个Animal课程。每只动物都会记录它有多少肢体。

public abstract class Animal
{
    private int limbs;
    public Animal(int limbs)
    {
        this.limbs = limbs;
    }

    public int getLimbCount()
    {
        return this.limbs;
    }

    public abstract String makeNoise();
}

因为我们需要跟踪每只动物有多少肢,所以在超类中使用成员变量是有意义的。但每只动物都会产生不同类型的噪音。

因此我们需要将它作为一个抽象类,因为我们有成员变量和实现的方法以及抽象方法。

对于你的第二个问题,你需要问问自己。

三角形总是会成形吗?

如果是这样,您需要从Shape界面扩展Triangle。

总而言之 - 使用您的第一组代码示例,选择接口。使用最后一组,选择第二种方式。