3.4 Object-Oriented Programming (OOP)
3-3节学习了ADT理论
本节学习ADT的具体实现技术:OOP
声明:此篇博客参考了https://www.cnblogs.com/hithongming/p/9170815.html中部分内容
Outline
OOP的基本概念
- 对象
- 类
- 接口
- 抽象类
OOP的不同特征
- 封装
- 继承与重写(override)
- 多态与重载(overload)
- 重写与重载的区别
- 泛型
Object(对象)
对象是类的一个实例,有状态和行为。
例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
概念:一个对象是一堆状态和行为的集合。
状态是包含在对象中的数据,在Java中,它们是对象的fields,也叫做成员变量。
行为是对象支持的操作,在Java中,它们称为methods,也称为方法。
Class(类)
类是一个模板,它描述一类对象的行为和状态。
每个对象都有一个类
类定义了属性类型(type)和行为实现(implementation)
简单地说,类的方法是它的应用程序编程接口(API)。
类成员变量(class variable)又叫静态变量;类方法(class method)又叫静态方法:
实例变量(instance variable)和实例方法(instance method)是不用static形容的实例和方法;
二者有以下的区别:
类方法是属于整个类,而不属于某个对象。
类方法只能访问类成员变量(方法),不能访问实例变量(方法),而实例方法可以访问类成员变量(方法)和实例变量(方法)。
类方法的调用可以通过类名.类方法和对象.类方法,而实例方法只能通过对象.实例方法访问。
类方法不能被覆盖,实例方法可以被覆盖。
当类的字节码文件被加载到内存时,类的实例方法不会被分配入口地址 当该类创建对象后,类中的实例方法才分配入口地址, 从而实例方法可以被类创建的任何对象调用执行。
类方法在该类被加载到内存时,就分配了相应的入口地址。 从而类方法不仅可以被类创建的任何对象调用执行,也可以直接通过类名调用。 类方法的入口地址直到程序退出时才被取消。
注意:
当我们创建第一个对象时,类中的实例方法就分配了入口地址,当再创建对象时,不再分配入口地址。
也就是说,方法的入口地址被所有的对象共享,当所有的对象都不存在时,方法的入口地址才被取消。
总结:
类变量和类方法与类相关联,并且每个类都会出现一次。 使用它们不需要创建对象。
实例方法和变量会在每个类的实例中出现一次。
Interface(接口)
接口是一系列方法签名,没有实现
继承接口的类需要实现(overrride)类中定义的方法,所以一个接口可以有多种实现;
– Interface和Class: 定义和实现ADT
接口:确定ADT规约;类:实现ADT
– 接口之间可以继承与扩展
– 一个类可以实现多个接口(从而具备了多个接口中的方法)
也可以不需要接口直接使用类作为ADT,既 有ADT定义也有ADT实现 ,实际中更倾向于使用接口来定义变量
但是使用接口又会导致下图的问题:用户必须知道该接口的具体实现类名,这实际上一定程度上暴露的内部实现,破坏了抽象和封装的原则。
解决方式:使用静态工厂方法(java8中接口允许了静态方法)
abstract class
Abstract method:用关键词abstract修饰,只有函数签名,没有具体实现(函数体)
Abstract class:至少含有一个抽象方法的类
Abstract Interface:只拥有抽象方法的接口
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
对于那些子类型都有不同实现的方法可以使用abstract关键词修饰,将函数具体实现交给子类
Enumerations(枚举)
对于一些对象中状态固定的类,我们可以使用枚举的形式实现:
Inheritance and Overriding
override
可重写方法:java中默认子类可以重写父类的方法
严格继承:子类只能添加新方法,无法重写超类中的方法(用final关键词修饰方法)
当子类包含一个覆盖超类方法的方法时,它也可以使用关键字super调用超类方法。
方法重写的规则
参数列表必须完全与被重写方法的相同;
返回类型必须完全与被重写方法的返回类型相同;
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
父类的成员方法只能被它的子类重写。
声明为final的方法不能被重写。
声明为static的方法不能被重写,但是能够被再次声明。
子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
构造方法不能被重写。
如果不能继承一个方法,则不能重写这个方法。
父类调用重写方法是会调用父类的,同理子类调用子类的;但是如果出现 下图中的情况,s调用重写方法还是会调用父类的。 (会调用子类,参考下文实验)
Polymorphism, subtyping and overloading
overloading(重载)
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型 (是实现多态的方式)
重载的规则:
- 必须要有不同的参数列表
- 相同/不同的返回值类型
- 相同/不同的public/private/protected
- 相同/不同的异常
- 可以在同 一个类内重载,也可在子类中重载
Which overridden version of the method to call is decided at runtime based on object type, but which overloaded version of the method to call is based on the reference type of the argument passed at compile time.
重写方法调用时根据运行时对象类型(即内存中对象类型),但是重载方法调用根据编译时传入引用的类型
做个试验验证一下:
public class Animal {
public void print() {
System.out.println("animal");
}
public void add(int i) {
System.out.println("animal"+i);
}
class Horse extends Animal{
@Override
public void print() {
// TODO Auto-generated method stub
System.out.println("horse");
}
public void add(int i,int n) {
// TODO Auto-generated method stub
System.out.println("horse"+i+n);
}
}
public static void main(String[] arg) {
Horse horse = new Horse();
Animal animal = new Animal();
Animal hnAnimal = new Horse();
hnAnimal.print();
animal.print();
hnAnimal.add(1);
animal.add(2);
horse.add(1,2);
}
}
实验结果:
上述实验确实上述结论的正确性,而且上述hnAnimal调用add(int,int)方法通不过静态检查
Polymorphism(多态)
三种类型的多态
Ad hoc polymorphism (特殊多态):功能重载,一个函数可以有多个同名的实现。
Parametric polymorphism (参数多态): 泛型或泛型编程,一个类型名字可以代表多个类型
Subtyping (also called subtype polymorphism or inclusion polymorphism 子类型多态、包含多态):当一个名称表示许多不同的类与一些常见的超类相关的实例。
泛型(参数多态)
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的)。
每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。
注意点:
- 可以有多个类型参数:例如Map<E, F>, Map<String, Integer>
- 通配符,只在使用泛型的时候出现,不能在定义中出现,例:List<?> list = new ArrayList();
- 泛型类型信息被删除 Cannot use instanceof() to check generic type 运行时泛型消失了!
- 无法创建通用数组
Pair<String>[] foo = new Pair<String>[42]; // won't compile
总结:
如何设计一个好的ADT
好的类具有的特点
- 简单
- 本质上是线程安全的
- 可以自由分享
- 不需要防御式拷贝
- 优秀的building blocks
如何编写一个不可变的类
- 不要提供任何mutators
- 确保没有方法可能被覆盖
- 使所有的fields有final修饰
- 使所有的fields有private修饰
- 确保任何可变组件的安全性(避免表示泄露)
- 实现toString(),hashCode(),clone(),equals()等。