标签:抽象类、接口
前面讲到了封装和继承,将某些实体对象的共同行为和属性抽象为类,而继承指的是父类定义了子类的共同方法,那根据这种思想,是否能将不同类[就算没有继承关系]的共同行为进一步抽象呢?
例如苹果、橘子都是水果类的实例,具有品种、水分多少等属性和吃等方法,而水果类和干果类二者没有继承关系,但是也有一些共同行为/属性,如营养价值等等。
这就是下面将要讲到的抽象类和接口。
1. 抽象类:abstract class:
在继承关系中,子类更为具体,父类更为抽象,有时为了需要,将父类抽象度大大提高以至于父类无法实例化对象,此时在父类中定义的抽象方法会在不同子类具体的,进行不同的实现。
例如,在“几何对象”类中,“圆”类和“矩形”类继承自它,而这家皆有面积计算方法,但是具体计算公式截然不同,故可在“几何对象类”进行抽象方法定义,然后在“圆”和“矩形”类中进行实现。
如上图所示,斜体表示抽象类和抽象方法,在父类中定义【未实现】,在子类中实现。
【注意】抽象类既然是抽象的,不能使用new 抽象类名操作符实例化,只能new 非抽象继承子类名实例化→因为创建具体子类实例时,父类构造方法被执行以便初始化父类特征。
即不能new但是可以使用构造方法。
【注意2】有抽象方法必须定义为抽象类,且必须在后续的继承子类中全部实现;反之不然。
【注意3】抽象方法是非静态的,即依赖于对象。
2. 数值老祖宗→Number
数值包装类:Integer(int)、Byte(byte)、Long(long)、Short(short)、Float(float)、Double(double)还有BigInteger、BigDecimal类都是Number类的具体子类。
如下所示为一个Number的示例:
【注意】事实上,在进行程序设计时,我们应该使其尽可能的通用,即泛型程序设计:
在上述中,我们的max方法中采用了数值类的祖宗→Number作为参数,调用其抽象方法doubleValue进行比对,这巧妙的借助了多态机制,运行时调用各个对象的实际包装类的doubleValue方法进行比对,由此可见,使得程序接收尽可能的更多的参数。
3.接口
接口可以理解为就是抽象类,不过其声明与实现方式不太一样,前面讲到,抽象类是具有继承关系的链条中,子类对抽象父类的方法的具体实现,而接口则专注于具有某些共同行为的对象之间的关系,例如苹果和鸡肉都可以食用,借助于“食用”这一指标就可以将其视为一类可食用的事物。
如上所示,接口的声明对应于抽象类,把abstract class修改为了interface,并且其中定义了抽象的方法,同时在实现时不再依赖于继承关系,而是在类名加上implemens来实现接口的具体行为。
【注意】接口在UML图中用虚线表示实现,而实现表示继承。
【注意2】接口的定义中包括常量和抽象方法,一般情况它们的修饰符为public static final和public abstract【抽象方法】,二者默认省略。
【注意3】接口与抽象类大体相似,也不可用new进行实例化对象。
【注意4】接口中的方法是抽象的public级别,因此具体实现接口的类中的方法必须也是public的。
4. 接口再续
java的源代码中可以看见许多接口,如Comparable<Interger>比较接口,用于实现各个对象的compareTo方法,如下图所示为Integer类通过比较2个Integer对象的value属性来实现两个对象的比较:
除此外,还有cloneable接口,采用implements的clone方法进行实现,用于无继承关系的类的clone实现。
【注意】默认使用Object类中的clone方法,基本值直接复制,引用类型则传递引用;如果其他类没有实现cloneable接口,则不能使用Object类中的clone方法,因为它是protected的,除此外,通过clone方法返回的类型为Object,这一点值得注意。
5.接口与抽象类
接口是某一行为主导下的对象集合,使得这些对象能够执行抽象方法,而抽象类是基于继承关系的父子之间的具体化操作,二者是不同层面的抽象,有许多区别:
5.1 变量方面
接口:必须是public static final 常量;
抽象类:各种类型都可以;
5.2 构造方法方面
接口:无构造方法,不能new生成实例
抽象类:子类通过构造方法调用父类构造方法,初始化特征,也不可以直接new生成实例
5.3 方法方面
接口:必须是抽象的实例方法,与对象有关。
抽象类:无限制,一般有抽象方法。
5.4 继承方面
类只能单一继承,即一个直接爸爸,而接口可以实现多个接口,也可以继承自多个接口【当然必须全部实现它们的抽象方法】,在UML中,接口的实现类似于继承。
5.5 命名
类名是一个名词,而接口名是形容词/名词。
5.6 选择
如果有强相关的父子关系,建议用基于继承的类,否则建议用接口,更为灵活。
实际来看:
- 如果某个类已经继承并且需要一些新特性,则必须使用接口;
- 如果需要实例变量和具体的方法实现,则考虑抽象类;
- 如果考虑千人千面,不强制每个子类进行重写,可考虑抽象类中写入空的抽象方法或者使用接口,不写方法,类似于cloneable。
5.7 关系
接口高于类,接口可以类的爸爸,反之不然,接口之间平级→接口可以拓展多个接口;
类只能有一个爸爸,可以拓展接口。
5.8 建议
实际使用中,我们通常结合接口的多重拓展和抽象类的实现,即首先声明一个接口,然后用抽象类进行实现,例如JAVA中Collection接口就有AbstractCollection抽象类的结合。