(注:本文思想主要来源于哈工大计算学部王忠杰教授的《软件构造》)
一、基本概念:对象、类、属性和方法
现实世界的对象有两个共同的特征:它们都有状态和行为。识别现实世界对象的状态和行为是开始考虑面向对象的好方法。例如,狗有状态(名字,颜色,品种,饥饿)和行为(吠叫,抓取,摇尾巴)。自行车有状态(当前档位、当前踏板节奏、当前速度)和行为(换档、改变踏板节奏、刹车)。对于你看到的每个对象,问自己两个问题,这些现实世界的观察都可以转化为面向对象的世界:这个对象可能处于什么状态?状态有哪些? 该对象可能执行什么行为?行为有哪些?
每个对象都有一个类,类定义方法和字段,方法和字段统称为成员。
二、接口与枚举
Java的接口是设计和表达ADT的一种有用的语言机制,它的实现是实现该接口的类。
Java中的接口是一个方法签名列表,但没有方法体。如果一个类在implements子句中声明了接口,并且为接口的所有方法提供了方法体,那么它就实现了接口。一个接口可以扩展一个或多个其他接口。一个类可以实现多个接口。
接口和类的区别:接口确定ADT规约而类用于实现ADT。实际中更倾向于使用接口来定义变量。
//一般使用
Set<Integer> set = new HashSet<>();
//而不是
HashSet<Interger> set = new HashSet<>();
定义接口助力实现多态性。
在多个实现中,我们可以对接口中的方法进行重写,给ADT不同的实现细节。但当客户端使用接口类型时,静态检查确保客户端只使用该接口定义的方法。
枚举:有时一个类型有一个小的有限的不可变值集合,例如:一年中的月份:1月、2月、…、11月、12月;星期几:星期一、星期二、……、星期六、星期日。当值集很小且有限时,将所有值定义为命名常量(称为枚举)是有意义的。Java有枚举结构。
三、封装和信息隐藏
区分一个设计良好的模块与一个设计糟糕的模块的最重要的因素是它对其他模块隐藏内部数据和其他实现细节的程度。而使用上文提到的接口类型声明变量时,客户端仅使用接口中定义的方法,且无法访问其内部属性,这就很好地做到了封装和信息隐藏。
另外也可以在成员的变量修饰符方面下功夫。private关键字代表只能在类内访问,protected关键字代表可从声明类的子类访问(以及在包内),而public代表能够在任何位置访问。
四、继承和重写
1.重写
重写:重新实现父类的方法。重写的函数需要完全同样的signature,而实际调用哪个方法由运行决定。例如:如果使用父类的对象来调用该方法,则将执行父类中的版本,如果使用子类的对象来调用该方法,则将执行子类中的版本。下面为一个重写的例子:
严格继承:子类只能添加新方法,无法重写父类的方法(每个方法前面要跟final)。
2.抽象类
抽象方法有署名但没有实现的方法(也称为抽象操作)——由关键字abstract定义。而包含至少一个抽象方法的类称为抽象类。而接口为只有抽象方法的抽象类——主要用于系统或子系统的规范,其实现由子类或其他机制提供。
五、多态、子类型、重载
1、多态
特设多态性:当一个函数根据有限的单独指定的类型和组合表示不同的和潜在的异构实现时。许多语言使用函数重载支持特别多态性。
参数多态性:当代码编写时没有提及任何特定类型,因此可以透明地与任何数量的新类型一起使用。在面向对象编程社区中,这通常被称为泛型或泛型编程。
子类型多态:当一个名称表示由某个公共超类相关的许多不同类的实例时。
2、特殊多态性和重载
当一个函数在几个不同的类型上工作(这些类型可能没有共同的结构),并且对于每个类型可能以不相关的方式工作时,就可以获得Ad-hoc多态性。
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型。
重载方便client调用,client可用不同的参数列表,调用同样的函数。但注意以下规则:
重载为静态类型检查,重写为动态类型检查。
同时,注意重载不影响原方法的继承。重写则会覆盖原方法。
3.参数多态性与泛型编程
当函数在一组类型上均匀工作时,得到参数多态性;这些类型通常表现出一些共同的结构。它能够以泛型方式定义函数和类型,以便基于运行时传递的参数工作,也就是说,允许静态类型检查而无需完全指定类型。这便是泛型。
类型变量是一个非限定标识符。它们由泛型类声明、泛型接口声明、泛型方法声明和泛型构造函数声明引入。如果一个类声明了一个或多个类型变量,那么它就是泛型的。
注意:泛型编程在运行时会出现擦除,任何依赖具体类型的操作都无法工作。
4.子类型多态
在Java中:每个类只能直接继承一个父类,一个类可以实现多个接口。子类型的规约不能弱化超类型的规约。不同类型的对象可以统一的处理而无需区分。