【问题标题】:What are some practical examples of abstract classes in java?java中抽象类的一些实际例子是什么?
【发布时间】:2010-12-03 08:18:31
【问题描述】:

何时以及为什么应该使用抽象类?我想看看他们使用的一些实际例子。还有,抽象类和接口有什么区别?

【问题讨论】:

  • 也许是因为这个问题很容易通过简单的互联网搜索来回答......只是猜测。
  • @Bart:互联网搜索只会给出 jim 抽象类;不是帮助理解这个概念的好例子。
  • 同意,一个非常明智的问题。我认为这实际上是一个比人们最初想象的更复杂的问题......
  • jdk中的AbstractList怎么样?抽象集...

标签: java oop abstract-class


【解决方案1】:

http://java.sun.com/docs/books/tutorial/java/IandI/abstract.html

http://java.sun.com/docs/books/tutorial/java/concepts/interface.html

简而言之,抽象类可以部分实现,接口不能。上面的链接中的更多详细信息。

【讨论】:

  • 链接中的实际示例。我认为当您指向正确的文档时,您可以自己进行一些研究。但是,这里有:java.util.AbstractList(抽象类)和 java.util.List(inerface)。瞧!
【解决方案2】:

接口根本不包含任何实现。

抽象类可能包含一些实现,对所有子类都有用,但并不完整:它需要在子类中以某种方式完成。
接口允许您在多个类上使用多态性,抽象类还允许它们重用代码

     public abstract class Figure {
        protected Point position;

        public abstract void draw();
     }

     public class Square extends Figure {
       // position is usable

       public void draw() {
         // this method must be implemented, for Square not to be abstract
       }

       // here is other code
     }

另一个区别是一个类只能有一个超类,但实现了许多接口。这可能是一个限制因素。

【讨论】:

    【解决方案3】:

    抽象类是类的“半实现”。它们可以通过一些通用功能部分实现,但将部分实现留给继承类。您可以有一个名为Animal 的抽象类,它实现了一些通用行为/值,例如AgeNameSetAge(...)。您还可以拥有未实现的方法(它们是abstract),就像一个接口。

    接口只是指定类应该可用的行为的简单契约。你可以有一个接口,例如IWalker,它需要公共方法Walk(),但没有具体说明它是如何实现的。

    【讨论】:

      【解决方案4】:

      完全抽象的类(所有方法都是抽象的)(几乎)与接口相同(主要区别在于它们可以包含字段和非公共抽象方法,而接口不能)。不同之处在于,当您有一个包含方法的抽象类时,该方法具有一些对所有派生子都相同的通用功能。

      例如,如果您想对文件系统进行建模,您知道,无论对象类型如何,您都会有一个项目的路径。您希望有一个通用的实现来获取该路径(一遍又一遍地编写相同的东西没有意义),并为孩子们留下任何特殊的实现。

      【讨论】:

      • 吹毛求疵:完全抽象的类与接口不同——抽象类可以包含接口不能包含的字段,并且接口允许多重继承,而(抽象)类则不能。
      【解决方案5】:

      正如 KLE 正确解释的那样,接口和抽象类之间的主要区别在于抽象类可能包含字段和方法体,而接口可能只包含方法签名(和常量,即公共静态最终字段)。

      另一个重要的区别是一个类可以实现多个接口,但它只能(直接)从一个类(抽象或非抽象)继承。因此,对于人们可能会在其他功能之外使用的东西,接口比抽象类更有意义。参见例如JDK 中的 Comparable 接口。

      举个例子:

      在我们开发的系统中,我们有一个用于启动数据导入的类。我们有许多不同类型的数据导入,但大多数都有一些共同点:它们从文件中读取数据,将数据写入数据库,生成导入协议等。

      所以我们有一个抽象类“Import”,其中包含诸如编写协议条目、查找要导入的所有文件、删除处理的导入文件等实现的方法。每个导入的细节都会有所不同,因此有抽象方法用作扩展钩子,例如getFilenamePattern() 被读取方法用来查找可以导入的文件。 getFilenamePattern 在具体子类中实现,具体取决于需要导入什么类型的文件。

      这样,共享导入功能在一个地方,而一种导入的细节是分开的。

      【讨论】:

      • 一个接口也可以包含(常量)字段。
      【解决方案6】:

      如果您需要深入了解抽象类的概念,请查看标准库中的 Swing UI 工具包(或 AWT)。

      因为您可以想象可以可视化的内容(例如,按钮、标签),所以很容易将其与无法实例化的内容(例如,JComponent)进行对比。

      【讨论】:

        【解决方案7】:

        抽象类与接口

        与接口不同,抽象类 可以包含非静态字段 和final,它们可以包含 实施的方法。如此抽象 类类似于接口, 除了它们提供了部分 实施,留给 子类来完成 执行。如果一个抽象类 包含 only 抽象方法 声明,它应该被声明为 而是一个界面。

        可以实现多个接口 按班级任何地方的班级 层次结构,无论它们是否 以任何方式相互关联。 想想ComparableCloneable,对于 例子。

        相比之下,抽象类是 最常见的子类共享 件的实施。一个 抽象类的子类是 类似的课程有很多 常见的(实现的部分 抽象类),但也有一些 差异(抽象方法)。

        抽象类示例

        在面向对象的绘图应用程序中, 你可以画圆形,矩形, 线、贝塞尔曲线和许多其他 图形对象。这些对象都 有某些状态(例如: 位置,方向,线条颜色, 填充颜​​色)和行为(对于 示例:moveTo、旋转、调整大小、绘制) 共同点。其中一些州和 所有图形的行为都是相同的 对象——例如:位置、填充 颜色和移动到。其他需要 不同的实现——例如, 调整大小或绘制。全部GraphicObjects 必须知道如何绘制或调整大小 他们自己;他们只是在方式上有所不同 他们这样做。这是一个完美的 抽象超类的情况。 您可以利用 相似之处并声明所有 要继承的图形对象 相同的抽象父对象——对于 例如GraphicObject,如图 下图。

        类 Rectangle、Line、Bezier 和 Circle继承自GraphicObject

        [...]

        来源:The Java™ Tutorials

        【讨论】:

          【解决方案8】:

          您可以通过特定步骤限制指令的执行顺序,但允许对每个步骤的行为进行委派:

          public abstract class Instruction {
          
              void perform() {
                  firstStep();
                  secondStep();
                  thirdStep();
              }
          
              abstract void firstStep();
          
              abstract void secondStep();
          
              abstract void thirdStep();
          
          }
          

          【讨论】:

            【解决方案9】:

            令人惊讶的是,这里给出的许多示例/解释都没有为使用抽象类提供好的论据。仅仅将公共字段/方法放在超类中并不要求它是抽象的。另外(开始咆哮),对于那些所谓的知识渊博的工程师仍然提出动物/车辆/图形层次结构来“解释”面向对象的概念感到羞耻。这些类型的示例非常具有误导性,因为它们指向错误的方向;您通常不应该支持直接子类化,因为它会在类之间创建非常紧密的耦合。而是使用协作(咆哮结束)。

            那么我认为抽象类的好用例是什么?我最喜欢的示例之一是“模板方法”GoF 模式的应用。在这里,您希望指定一次算法的通用流程,但允许单个步骤的多次实现。这里我只是将一个包含主要病毒扫描算法(查找下一个病毒,删除或报告它,继续直到扫描完成)的 VirusScanEngine 和一个实现所需算法步骤(findVirus,deleteVirus 和 reportVirus)的 LinearVirusScanner 放在一起的示例)。对于这种可怕的简化,我向所有真正致力于病毒扫描软件的开发人员道歉。

            import java.util.Arrays;
            
            public abstract class VirusScanEngine {
            
                public static void main(String[] args) {
            
                    byte[] memory = new byte[] { 'a', 'b', 'c', 'M', 'e', 'l', 'i', 's', 's',
                            'a' , 'd', 'e', 'f', 'g'};
                    System.out.println("Before: " + Arrays.toString(memory));
                    new LinearVirusScanner().scan(memory, Action.DELETE);
                    System.out.println("After: " + Arrays.toString(memory));
                }
            
                public enum Action {
                    DELETE, REPORT
                };
            
                public boolean scan(byte[] memory, Action action) {
            
                    boolean virusFound = false;
                    int index = 0;
                    while (index < memory.length) {
            
                        int size = findVirus(memory, index);
                        if (size > 0) {
                            switch (action) {
            
                            case DELETE:
                                deleteVirus(memory, index, size);
                                break;
                            case REPORT:
                                reportVirus(memory, index, size);
                                break;
                            }
                            index += size;
                        }
                        index++;
                    }
                    return virusFound;
                }
            
                abstract int findVirus(byte[] memory, int startIndex);
            
                abstract void reportVirus(byte[] memory, int startIndex, int size);
            
                abstract void deleteVirus(byte[] memory, int startIndex, int size);
            }
            

            public class LinearVirusScanner extends VirusScanEngine {
            
                private static final byte[][] virusSignatures = new byte[][] {
                        new byte[] { 'I', 'L', 'O', 'V', 'E', 'Y', 'O', 'U' },
                        new byte[] { 'M', 'e', 'l', 'i', 's', 's', 'a' } };
            
                @Override
                int findVirus(byte[] memory, int startIndex) {
            
                    int size = 0;
                    signatures: for (int v = 0; v < virusSignatures.length; v++) {
            
                        scan: {
                            for (int t = 0; t < virusSignatures[v].length; t++) {
            
                                if (memory[startIndex + t] != virusSignatures[v][t]) {
                                    break scan;
                                }
                            }
                            // virus found
                            size = virusSignatures[v].length;
                            break signatures;
                        }
                    }
                    return size;
                }
            
                @Override
                void deleteVirus(byte[] memory, int startIndex, int size) {
            
                    for (int n = startIndex; n < startIndex + size - 1; n++) {
                        memory[n] = 0;
                    }
                }
            
                @Override
                void reportVirus(byte[] memory, int startIndex, int size) {
            
                    System.out.println("Virus found at position " + startIndex
                            + " with length " + size);
                }
            }
            

            【讨论】:

            • 如果你不介意我问,“:”(冒号)是做什么的?我知道它用于指示增强 for 循环中的迭代,但在您的代码中,您有:签名:和扫描:,它在做什么?
            • 'signature: {...}' 是一个命名代码块。可以通过调用'break signature;'来跳出。见docs.oracle.com/javase/specs/jls/se5.0/html/statements.html
            • 哦,谢谢。如果我在休息后看的话,我应该意识到这一点。
            【解决方案10】:

            在我自己的想法中,解决这个问题的正确方法是首先确定您正在处理的关于域或实体对象的上下文,即您的用例是什么?

            根据我的个人经验,我使用自上而下然后自下而上的方法来执行此操作。我开始通过查看用例并查看我需要哪些类来寻找继承。然后我看看是否有一个包含所有对象的 superClassOrInterfaceType(因为类和接口都定义了类型,为了简单起见,我将它们组合成一个词。希望它不会让它更混乱)域对象,如果我正在处理处理 subtypeClassOrInterfaceTypes 的用例,例如车辆的 superClassOrInterfaceType,例如:汽车、卡车、吉普车和摩托车。如果有层次关系,那么我定义了superClassOrInterfaceType和subtypeClassOrInterfaceTypes。

            正如我所说,我通常首先要做的是为我正在处理的对象寻找一个公共域 superClassOrInterfaceType。如果是这样,我在 subtypeClassOrInterfaceTypes 之间寻找公共方法操作。如果没有,我看看是否有通用方法实现,因为即使您可能有一个 superClassOrInterfaceType 并且可能有通用方法,这些实现可能不利于代码重用。在这一点上,如果我有通用方法,但没有通用实现,我倾向于接口。但是,通过这个简单的示例,我应该在车辆子类型ClassOrInterfaceTypes 之间有一些通用方法和一些通用实现,我可以重用代码。

            另一方面,如果没有继承结构,那我就从下往上开始,看看有没有常用的方法。如果没有通用的方法,也没有通用的实现,那我选择具体的类。

            一般来说,如果有通用方法和通用实现的继承,并且在同一个子类型中需要多个子类型实现方法,那么我会选择一个抽象类,这很少见,但我确实使用它。如果仅仅因为继承而使用抽象类,那么如果代码更改很多,您可能会遇到问题。这在此处的示例中非常详细:Interfaces vs Abstract Classes in Java,用于不同类型的电机域对象。其中一个需要双动力电机,这需要在单个子类型类中使用多种子类型实现方法。换句话说,单个类需要来自太阳能和电池供电的电机的实现方法,这是对行为进行建模,而不是您想用抽象类做的事情。

            总而言之,您通常希望使用接口而不是抽象类来定义行为(对象将做什么)。抽象类专注于实现层次结构和代码重用。

            这里有一些更详细的链接。

            Thanks Type & Gentle Class

            The Magic behind Subtype Polymorphism

            Maximize Flexibility with Interfaces & Abstract Classes

            Interfaces vs Abstract Classes in Java

            【讨论】:

              【解决方案11】:

              何时以及为什么应该使用抽象类?

              下面的帖子回答了您的问题:

              Interface vs Abstract Class (general OO)

              我想看一些实际使用的例子。

              当您想在几个密切相关的类之间共享代码时

              一个实际例子:JDK Reader.java类中的模板方法实现

              看看下面的帖子:

              Template design pattern in JDK, could not find a method defining set of methods to be executed in order

              抽象类和接口有什么区别?

              参考这篇文章:

              How should I have explained the difference between an Interface and an Abstract class?

              【讨论】:

                猜你喜欢
                • 2019-06-04
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-09-08
                • 2016-09-19
                • 2014-03-08
                • 1970-01-01
                相关资源
                最近更新 更多