第三章第四节 面向对象编程OOP
本节讲学习ADT的具体实现技术:OOP
Outline
- OOP的基本概念
- 对象
- 类
- 接口
- 抽象类
- OOP的不同特征
- 封装
- 继承与重写(override)
- 多态与重载(overload)
- 泛型
- 设计好的类
Notes
## OOP的基本概念
【对象】
- 对象是类的一个实例,有状态和行为。
- 例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
- 概念:一个对象是一堆状态和行为的集合。
- 状态是包含在对象中的数据,在Java中,它们是对象的fields。
- 行为是对象支持的操作,在Java中,它们称为methods。
【类】
- 类是一个模板,它描述一类对象的行为和状态。
- 每个对象都有一个类
- 类定义了属性类型(type)和行为实现(implementation)
- 简单地说,类的方法是它的应用程序编程接口(API)。
- 类成员变量(class variable)又叫静态变量;类方法(class method)又叫静态方法:
- 实例变量(instance variable)和实例方法(instance method)是不用static形容的实例和方法;
- 二者有以下的区别:
- 类方法是属于整个类,而不属于某个对象。
- 类方法只能访问类成员变量(方法),不能访问实例变量(方法),而实例方法可以访问类成员变量(方法)和实例变量(方法)。
- 类方法的调用可以通过类名.类方法和对象.类方法,而实例方法只能通过对象.实例方法访问。
- 类方法不能被覆盖,实例方法可以被覆盖。
- 当类的字节码文件被加载到内存时,类的实例方法不会被分配入口地址 当该类创建对象后,类中的实例方法才分配入口地址, 从而实例方法可以被类创建的任何对象调用执行。
- 类方法在该类被加载到内存时,就分配了相应的入口地址。 从而类方法不仅可以被类创建的任何对象调用执行,也可以直接通过类名调用。 类方法的入口地址直到程序退出时才被取消。
- 注意:
- 当我们创建第一个对象时,类中的实例方法就分配了入口地址,当再创建对象时,不再分配入口地址。
- 也就是说,方法的入口地址被所有的对象共享,当所有的对象都不存在时,方法的入口地址才被取消。
- 总结:
- 类变量和类方法与类相关联,并且每个类都会出现一次。 使用它们不需要创建对象。
- 实例方法和变量会在每个类的实例中出现一次。
- 举例:
【接口】
- 概念:接口在JAVA编程语言中是一个抽象类型,用于设计和表达ADT的语言机制,其是抽象方法的集合,接口通常以interface来声明。
- 一个类通过继承接口的方式,从而来继承接口的抽象方法。
- 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
- 一个接口可以扩展其他接口,一个类可以实现多个接口;一个接口也可以有多重实现
- 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
- 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
一个接口的实例:
/** MyString represents an immutable sequence of characters. */ public interface MyString { // We'll skip this creator operation for now // /** @param b a boolean value // * @return string representation of b, either "true" or "false" */ // public static MyString valueOf(boolean b) { ... } /** @return number of characters in this string */ public int length(); /** @param i character position (requires 0 <= i < string length) * @return character at position i */ public char charAt(int i); /** Get the substring between start (inclusive) and end (exclusive). * @param start starting index * @param end ending index. Requires 0 <= start <= end <= string length. * @return string consisting of charAt(start)...charAt(end-1) */ public MyString substring(int start, int end); }
一种实现:
1 public class FastMyString implements MyString { 2 3 private char[] a; 4 private int start; 5 private int end; 6 7 /** Create a string representation of b, either "true" or "false". 8 * @param b a boolean value */ 9 public FastMyString(boolean b) { 10 a = b ? new char[] { 't', 'r', 'u', 'e' } 11 : new char[] { 'f', 'a', 'l', 's', 'e' }; 12 start = 0; 13 end = a.length; 14 } 15 16 // private constructor, used internally by producer operations. 17 private FastMyString(char[] a, int start, int end) { 18 this.a = a; 19 this.start = start; 20 this.end = end; 21 } 22 23 @Override public int length() { return end - start; } 24 25 @Override public char charAt(int i) { return a[start + i]; } 26 27 @Override public MyString substring(int start, int end) { 28 return new FastMyString(this.a, this.start + start, this.end + end); 29 } 30 }
客户端如何使用此ADT?这是一个例子:
MyString s = new FastMyString(true); System.out.println("The first character is: " + s.charAt(0));
但其中有问题,这么实现接口打破了抽象边界,接口定义中没有包含constructor,也无法保证所有实现类中都包含了同样名字的constructor。 故而,客户端需要知道该接口的某个具体实现类的名字。因为Java中的接口不能包含构造函数,所以它们必须直接调用其中一个具体类的构造函数。该构造函数的规范不会出现在接口的任何地方,所以没有任何静态的保证,即不同的实现甚至会提供相同的构造函数。
在Java 8中,我们可以用valueof的静态工厂方法 代替构造器。
public interface MyString { /** @param b a boolean value * @return string representation of b, either "true" or "false" */ public static MyString valueOf(boolean b) { return new FastMyString(true); } // ...
此时,客户端使用ADT就不会破坏抽象边界:
MyString s = MyString.valueOf(true); System.out.println("The first character is: " + s.charAt(0));
总结:接口的好处
-
Safe from bugs
ADT是由其操作定义的,接口就是这样做的。
当客户端使用接口类型时,静态检查确保他们只使用由接口定义的方法。
如果实现类公开其他方法,或者更糟糕的是,具有可见的表示,客户端不会意外地看到或依赖它们。
当我们有一个数据类型的多个实现时,接口提供方法签名的静态检查。 -
Easy to understand
客户和维护人员确切知道在哪里查找ADT的规约。
由于接口不包含实例字段或实例方法的实现,因此更容易将实现的细节保留在规范之外。 -
Ready for change
通过添加实现接口的类,我们可以轻松地添加新类型的实现。
如果我们避免使用静态工厂方法的构造函数,客户端将只能看到该接口。
这意味着我们可以切换客户端正在使用的实现类,而无需更改其代码。
【抽象类】
- 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
- 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
- 父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
- 在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
- 如果一个类包含抽象方法,那么该类必须是抽象类。
- 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
- 构造方法,类方法(用static修饰的方法)不能声明为抽象方法。