java中抽象类的一些实际例子是什么?

时间:2009-10-02 14:15:28

标签: java oop abstract-class

何时以及为什么要使用抽象类?我想看看它们用途的一些实际例子。另外,抽象类和接口有什么区别?

11 个答案:

答案 0 :(得分:9)

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

接口只是指定应该可用于类的行为的契约。您可以使用IWalker这样的界面,该界面需要公共方法Walk(),但没有具体说明如何实施。

答案 1 :(得分:5)

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

例如,如果要为文件系统建模,您就会知道,无论对象类型如何,您都将拥有项目的路径。你想要有一个共同的实现来获得这条路径(一遍又一遍地写同样的东西没有意义),并为孩子们留下一些特别的实现。

答案 2 :(得分:5)

  

抽象类与接口

     

与接口,抽象类不同   可以包含非静态字段   和final,他们可以包含   实施方法。这样的抽象   类与接口类似,   除了他们提供部分   实施,留下来   子类来完成   实现。如果是抽象类   仅包含 抽象方法   声明,应声明为   而是一个界面。

     

可以实现多个接口   按班级中的任何班级   层次结构,无论它们是否存在   以任何方式相互关联。   想想ComparableCloneable,为   示例

     

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

     

抽象类示例

     

在面向对象的绘图应用程序中,   你可以画圆圈,矩形,   线条,贝塞尔曲线等等   图形对象。这些对象都是   有某些状态(例如:   位置,方向,线条颜色,   填充颜​​色)和行为(为   例如:moveTo,rotate,resize,draw)   共同的。其中一些状态和   所有图形的行为都是一样的   对象 - 例如:position,fill   颜色,并移动。其他要求   不同的实现 - 例如,   调整大小或绘制。全部GraphicObjects   必须知道如何绘制或调整大小   他们自己;他们只是如何不同   他们这样做。这是完美的   抽象超类的情况。   你可以利用   相似之处并宣布所有   图形对象继承自   相同的抽象父对象   例如,GraphicObject,如图所示   下图。

     

Classes Rectangle, Line, Bezier, and Circle inherit from GraphicObject

     

Classes Rectangle,Line,Bezier和   Circle继承自GraphicObject

     

[...]

来源:The Java™ Tutorials

答案 3 :(得分:4)

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

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

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);
    }
}

答案 4 :(得分:3)

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

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

举个例子:

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

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

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

答案 5 :(得分:2)

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

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

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

答案 6 :(得分: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
     }

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

答案 7 :(得分:2)

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

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

答案 8 :(得分:2)

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

public abstract class Instruction {

    void perform() {
        firstStep();
        secondStep();
        thirdStep();
    }

    abstract void firstStep();

    abstract void secondStep();

    abstract void thirdStep();

}

答案 9 :(得分:1)

  

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

以下帖子回答您的问题:

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?

答案 10 :(得分:0)

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

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

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

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

通常,如果存在使用常见方法和常见实现的继承以及在同一子类型中需要多个子类型实现方法,那么我使用抽象类,这很少见,但我确实使用它。如果你只是因为存在继承而使用抽象类,那么如果代码发生很大变化,你可能会遇到问题。这里的示例非常详细: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