第十三章 抽象类和接口
13.1 引言
13.2 抽象类
13.2.1 为什么要使用抽象方法
在编译(compilation)期,一个声明为Person p = new Student()的Person类变量p可以通过多态,使用Person类的方法p.getName()来获取子类Student的名字
使用多态时,首先要保证p的Person类中有getName()方法,子类Student中有正确覆写getName()的方法,然后才会根据p的实际类型去调用自己的Student复写方法
可以看出,Person类的getName()方法没有被执行,但这个方法也是需要显示的编写的。因此,我们可以仅仅把Person类的getName()方法定义成一种规范,而方法内部没有必要执行
13.2.2 关于抽象类的知识点
抽象类不可以通过new操作符创建实例,但是可以声明为一种数据类型,例如
// 抽象类GeometricObject
GeometricObject[] objects = new GeometricObject[10];
objects[0] = new Circle();
抽象类可以有字段、(建议protected)构造方法和(必须写{}括号)普通方法
一个包含抽象方法的类必须声明为抽象类,但一个抽象类可以没有抽象方法
抽象类中,抽象方法不能有方法体,普通方法必须有方法体
所有的抽象方法必须是非静态的
如果一个非抽象子类继承了抽象类,那么这个子类必须执行所有的抽象方法,相当于抽象类定义了规范,强迫子类去执行
子抽象类可以继承父抽象类,子抽象类也可以继承一个常规父类
子类可以覆写父类的方法并将它定义为abstract
13.3示例学习
Number类是数值包装类、Biglnteger以及BigDecimal的抽象父类
13.4 示例学习
Calendar类是GregorianCalendar类的抽象父类
13.5 接口
理解:Java的接口其实是单继承逻辑下,实现多继承的解决方案
在接口中声明的方法必须是public abstract的,声明的常量必须是public static final的,因此在实现接口方法时,必须使用public访问修饰符进行修饰
13.6 Comparable接口
public int compareTo(T o);
Java提供了一种接口Comparable,内部实现了compareTo方法,用来比较两个相同类型的对象中的较大者
13.7 Cloneable接口
protected native Object clone() throws CloneNotSupportedException;
Cloneable
接口是标记接口,没有常量也没有方法,实现Cloneable
接口仅仅是为了告诉开发人员该类可以进行克隆操作,并不会强制要求实现克隆
clone()
方法是定义在Object类中的方法,返回一个Object类型的引用,用于复制一个对象。
Object中的clone
方法声明了一个CloneNotSupportedException异常,异常的抛出会通过native本地方法进行处理,包括调用该方法的对象有没有实现Cloneable
接口,或者在子类中有没有正确覆写clone()
方法,如果没有,会导致抛出异常。因此,如果一个对象想要执行clone
方法,那么这个对象所在的类必须同时实现Cloneable
接口并正确覆写 clone()
由于 CloneNotSupportedException
异常是从本地方法native抛出的,并不是在Java代码中真正的抛出,所以在实际调用 clone()
方法时,并不需要显式捕获该异常,因为它不会在Java代码中实际抛出。这种设计是为了保持与 Object
类的规范一致。
在浅拷贝中,如果对象的字段是基本类型,复制这个字段的值到一个新的地址,如果对象的字段是引用类型,新旧字段共享一个内存地址
在深拷贝中,所有字段彼此独立,分配在不同的内存地址
13.8 接口与抽象类
抽象类和接口都不可以通过new操作符创建实例,但是都可以声明为一种数据类型
在抽象类和接口的抽象方法中,都不能有方法体,而且必须是非静态的
抽象类实现接口不会强制覆写方法,普通类继承了抽象类继承了接口,那么就要实现接口中的抽象方法。
抽象类 | 接口 | |
---|---|---|
字段 | 可以 | 不可以 |
构造方法 | 可以 | 不可以 |
普通方法 | 可以 | 不可以 |
常量 | 可以 | 可以 |
抽象方法 | 可以 | 可以 |
父抽象类 | 父接口 | 父普通类 | |
---|---|---|---|
子普通类-> | 只能继承1个父抽象类 | 实现多个父接口 | 只能继承1个父普通类 |
子抽象类-> | 只能继承1个父抽象类 | 实现多个父接口 | 只能继承1个父普通类 |
子接口-> | 不能继承父抽象类 | 实现多个父接口 | 不能继承父普通类 |
13.9 示例学习:Rational 类
13.10 类的设计原则
13.10.1 内聚性
类应该只描述一个单一的实体。例如,学生和教职员工应该分成2个类
如果一个实体担负太多的职责,就应该按各自的职责分成几个类。例如,String类处理不可变字符串,StringBuilder类创建可变字符串
13.10.2 一致性
在类中,通常的风格是将数据声明置于构造方法之前,并且将构造方法置于方法之前
给类似的操作选择相同的名字是一个良好的实践,例如length()方法可以应用于String、StringBuilder和StringBuffer,如果每个类中length()都有不同的叫法,就会让人混乱
一般来说,应该具有一致性地提供一个公共无参构造方法,用于构建默认实例
13.10.3 封装性
类应该使用 private 修饰符隐藏其数据
只在希望数据域可读的情况下,才提供 get 方法;也只在希望数据域可更新的情况下, 才提供 set 方法
13.10.4 清晰性
用户可以以各种不同组合 、顺序,以及在各种环境中结合使用多个类。例如: Loan 类包含属性 loanAmount 、numberOfYears 和 annualInterestRate, 这些属性的值可以按任何顺序来设置
方法应在不产生混淆的情况下进行直观定义。例如: String 类中的 substring(int beginlndex, int endlndex)方法就有一点混乱。这个方法返回从 beginlndex到endlndex-1 而不是 endlndex 的子串
不应该声明一个来自其他数据域的数据域。例如,Person 类有两个数据域: birthDate 和 age。由于 age 可以从 birthDate 导出,所以 age 不应该声明为数据域
13.10.5 完整性
类是为许多不同用户的使用而设计的。为了能在一个广泛的应用中使用,一个类应该通过属性和方法提供多种方案以适应用户的不同需求。例如: 为满足不同的应用需求,String 类包含了 40 多种很实用的方法
13.10.6 实例和静态
要正确的区分实例和静态数据域/方法,不要将本应该声明为静态方法的方法声明为实例方法
不要从构造方法中传入参数来初始化静态数据域。最好使用 set 方法改变静态数据域
13.10.7 继承与聚合
苹果是一种水果;因此,可以使用继承来对 Apple 类和 Fruit 类之间的关系进行建模。人具有名字;因此,可以使用聚合来对 Person 类和 Name 类之间的关系建模
13.10.8 接口和抽象类
通常,比较强的is-a(是一种)关系清晰地描述了父子关系,应该采用类来建模。例如,因为桔子是一种水果,它们的关系就应该采用类的继承关系来建模。
弱的is-a关系,也称为is-kind-of(是一类)关系,表明一个对象拥有某种属性。弱的is-a 关系可以使用接口建模。例如,所有的字符串都是可以比较的,因此 String 类实现了 Comparable 接口。