大纲
OOP基本概念:object, class, attribute, method, interface, and enumerations
OOP的独特特征:
- 封装与信息隐藏
- 继承与重写
- 多态、子类型、重载
- 静态与动态分派
Java中的一些重要的对象方法
设计良好的CLASS
基本概念:对象、类、属性和方法
1.Object
- 状态 该对象中所包含的数据 fields
- 行为 该对象所支持的操作 methods
Class variable 类成员变量(static 修饰)
class methods类方法(static 修饰)
instance methods 实例方法(必须创建实例)
instance variables 实例成员变量(必须创建实例)
类变量和类方法与一个类相关联,并且每个类发生一次。使用它们并不需要创建对象。
实例方法和变量在每个类的实例中发生一次。
实例变量/方法vs 类变量/方法
class DateApp {
public static void main(String args[]) {
Date today = new Date();
System.out.println(today);
}
}
class Another {
public static void main(String[] args) {
int result;
result = Math.min(10, 20);
System.out.println(result);
System.out.println(Math.max(100, 200));
}
}
静态方法不与类的任何特定实例进行关联,而必须在特定对象上调用实例方法(没有静态关键字而声明)。
class Difference {
public static void main(String[] args) {
display(); //calling without object
Difference t = new Difference();
t.show(); //calling using object
}
static void display() {
System.out.println("Programming is amazing.");
}
void show(){
System.out.println("Java is awesome.");
}
}
public class MyStatic {
private String name;
private static String staticStr = "STATIC-STRING";
public MyStatic (String n){
this.name = n;
}
public static void testStaticMethod(){
//you can call static variables here, can not call instance variables
System.out.println(MyStatic.staticStr);
}
public void testObjectMethod(){
//you can also call static and instance variables here
System.out.println(MyStatic.staticStr);
System.out.println("Name: "+this.name);
}
public static void main(String a[]){
//By using class name, you can call static method
MyStatic.testStaticMethod();
MyStatic msm = new MyStatic ("Java2novice");
msm.testObjectMethod();
}
}
2.Interface and Enumerations
Java的接口是设计和表达ADT的有用语言机制,其实现为实现该接口的类。
- Interface和Class: 定义和实现ADT
- 接口之间可以继承与扩展
- 一个类可以实现多个接口(从而具备了多个接口中的方法)
- 一个接口可以有多种实现类
Java接口和类
接口:确定ADT规约;
类:实现ADT;
实际中更倾向于使用接口来定义变量
- 对变量和参数使用接口类型,除非您知道只有一个实现就足够了
- 支持改变实现情况
一个例子
/** Represents an immutable set of elements of type E. */
public interface Set<E> {
/** make an empty set */
public Set(); //Java interfaces can’t have constructors
/** @return true if this set contains e as a member */
public boolean contains(E e);
/** @return a set which is the union of this and that */
public ArraySet<E> union(Set<E> that);
//It isn’t representation-independent
}
/** Implementation of Set<E>. */
public class ArraySet<E> implements Set<E> {
/** make an empty set */
public ArraySet() { ... }
/** @return a set which is the union of this and that */
public ArraySet<E> union(Set<E> that) { ... }
/** add e to this set */
public void add(E e) { ... }
//This violates the spec for Set, its immutability, so ArraySet is not a legal implementation of Set.
//It’s missing the contains() method
}
– 打破了抽象边界,接口定义中没有包含constructor,也无法保证所有实现类中都包含了同样名字的constructor。
– 故而,客户端需要知道该接口的某个具体实现类的名字。
使用静态工厂,而不是构造函数
在接口中使用默认方法:
接口中的每个方法在所有实现类里都要实现。
以增量式的为接口增加额外的功能而不破坏已实现的类。
Enumerations
public enum Month {
JANUARY, FEBRUARY, MARCH, ...,
OCTOBER, NOVEMBER, DECEMBER;
}
public enum PenColor {
BLACK, GRAY, RED, ..., BLUE;
}
PenColor drawingColor = PenColor.RED;
Month month = Month.MARCH;
if(month.equals(Month.MARCH)) {...}
for(Month m : Month.values())
m.name();
m.ordinal();
m.comparedTo();
m.toString();
......
public enum Planet {
MERCURY(3.302e+23, 2.439e6), VENUS (4.869e+24, 6.052e6),
EARTH(5.975e+24, 6.378e6), MARS(6.419e+23, 3.393e6);
private final double mass; // In kg.
private final double radius; // In m.
private static final double G = 6.67300E-11;
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
public double mass() { return mass; }
public double radius() { return radius; }
public double surfaceGravity() {
return G * mass / (radius * radius);
}
}
for (Planet p : Planet.values())
System.out.println(p.surfaceGravity());
4封装和信息隐藏
精心设计的代码隐藏了所有实现细节
—将API与实现清晰分离
—模块仅通过API通信
—忽略彼此的内部工作
信息隐藏的好处:
- Decouples(解耦)组成一个系统的类
允许它们被单独地开发、测试、优化、使用、理解和修改 - 加快系统开发速度
- 减轻维护工作的负担
- 启用有效的性能调整
- 增加了软件的可重用性
-松散耦合的类在其他上下文中通常被证明是有用的
使用接口隐藏的信息:
- 使用接口类型声明变量
- 客户端仅使用接口中定义的方法
- 客户端代码无法直接访问属性
private -声称类
protected -声称类的子类
public -所有类
5 Inheritance and Overriding
(1) Overriding重写
可重写的方法和严格的继承
可重写方法:一种允许重新实现的方法。
严格继承:子类只能添加新方法,无法重写超类中的方法
父类
public class Car {
public final void drive() {…}
public final void brake() {…}
public final void accelerate() {…}
}
子类
public class LuxuryCar extends Car {
public void playMusic() {…}
public void ejectCD() {…}
public void resumeMusic() {…}
public void pauseMusic() {…}
}
final
final字段:防止在初始化后重新分配到该字段
final方法:防止覆盖该方法
final类:阻止扩展该类
e.g., public final class CheckingAccountImpl { … }
Overriding (覆盖/重写)
重写的函数:完全同样的signature
实际执行时调用哪个方法,运行时决定。
如果使用父类的对象来调用该方法,则将执行父类中的版本;
如果使用子类的对象来调用该方法,则将执行子类中的版本。
覆盖一个方法
class Device {
int serialnr;
public final void help() {….}
public void setSerialNr(int n) {
serialnr = n;
}
}
父类型中的被重写函数体不为空:意味着对其大多数子类型来说,该方法是可以被直接复用的。
class Valve extends Device {
Position s;
public void on() {
…
}
public void setSerialNr(int n) {
serialnr = n + s.serialnr;
}
}
对某些子类型来说,有特殊性,故重写父类型中的函数,实现自己的特殊要求。
可重写的方法被设置为空:
class Device {
int serialnr;
public void setSerialNr(int n) {}
}
class Valve extends Device {
Position s;
public void on() {
…..
}
public void setSerialNr(int n) {
seriennr = n + s.serialnr;
}
} // class Valve
如果父类型中的某个函数实现体为空,意味着其所有子类型都需要这个功能,但各有差异,没有共性,在每个子类中均需要重写。
当子类包含覆盖超类方法的方法时,它也可以通过使用关键字super援引超类法。
class Thought {
public void message() {
System.out.println(“Thought.");
}
}
public class Advice extends Thought {
@Override // @Override annotation in Java 5 is optional but helpful.
public void message() {
System.out.println(“Advice.");
super.message(); // Invoke parent's version of method.
}
}
Thought parking = new Thought();
parking.message(); // Prints "Thought."
Thought dates = new Advice();
dates.message(); // Prints “Advice. \n Thought.”
Constructors with this and super
重写的时候,不要改变原方法的本意
(2) Abstract Class
Abstract method:一种具有签名但没有实现(也称为抽象操作)的方法。
Abstract class:包含至少一个抽象方法的类称为抽象类。
接口:一个只有抽象方法的抽象类
abstract class GraphicObject {
int x, y;
...
void moveTo(int newX, int newY) {
...
}
abstract void draw();
abstract void resize();
}
如果某些操作是所有子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写
class Circle extends GraphicObject {
void draw() {
...
}
void resize() {
...
}
}
class Rectangle extends GraphicObject {
void draw() {
...
}
void resize() {
...
}
}
所有子类型完全相同的操作,放在父类型中实现,子类型中无需重写。
有些子类型有而其他子类型无的操作,不要在父类型中定义和实现,而应在特定子类型中实现。
Polymorphism, subtyping and overloading 多态、子类型、重载
(1) Three Types of Polymorphism
特殊多态:function overloading (功能重载)
参数化多态:泛型
子类型多态、包含多态
(2) Ad hoc polymorphism and Overloading 特殊多态和重载
public class OverloadExample {
public static void main(String args[]) {
System.out.println(add("C","D"));
System.out.println(add("C","D","E"));
System.out.println(add(2,3));
}
public static String add(String c, String d) {
return c.concat(d);
}
public static String add(String c, String d, String e){
return c.concat(d).concat(e);
}
public static int add(int a, int b) {
return a+b;
} }
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型
价值:方便client调用,client可用不同的参数列表,调用同样的函数
重载是静态多态:
- 根据参数列表进行最佳匹配
- 静态类型检查
- 在编译阶段时决定要具体执行哪个方法 (static type checking)
可以在一个类中重载,也可以在子类中重载
要调用的方法的哪个重写版本在***运行时***根据对象类型决定,但是要调用的方法的哪个重载版本基于在***编译时***传递的参数的引用类型。
Overriding vs. Overloading
public class Test {
public static void main(String[] args) {
A a = new A();
a.p(10);
}
}
class B {
public void p(int i) {
}
}
class A extends B {
// This method overrides the method in B
public void p(int i) {
System.out.println(i);
}
}
public class Test {
public static void main(String[] args) {
A a = new A();
a.p(10);
}
}
class B {
public void p(int i) {
}
}
class A extends B {
// This method overloads the method in B
public void p(double i) {
System.out.println(i);
}
}
注意,当派生类重载原始方法时,它仍然会从基类继承原始方法。
(3) Parametric polymorphism and Generic programming 参数多态
它能够以泛型方式定义函数和类型,以便基于运行时传递的参数来工作,即允许在不完全指定类型的情况下进行静态类型检查。
泛型编程是一种编程风格,其中数据类型和函数用稍后指定的类型编写,然后在需要时为参数提供的特定类型进行实例化。
通用编程围绕着从具体的、有效的算法中抽象的想法,以获得可以与不同数据表示相结合的通用算法,以产生各种有用的软件。
-类型变量
它们由泛型类声明、泛型接口声明、泛型方法声明和泛型构造函数声明引入。
泛型类:
其定义中包含了类型变量。这些类型变量被称为类的类型参数。
- 它定义了一个或多个作为参数的类型变量。
- 泛型类声明定义一组参数化类型,对于类型参数部分的每个可能调用一个。
- 所有这些参数化的类型在运行时共享相同的类。
泛型接口
这些类型变量被称为接口的类型参数。
-它定义了一个或多个作为参数的类型变量。
-通用接口声明定义一组类型,类型参数部分的每个可能调用。
-所有参数化的类型在运行时共享相同的接口。
泛型方法
- 这些类型变量被称为该方法的形式类型参数。
- 形式类型参数列表的形式与类或接口的类型参数列表相同。
使用菱形运算符<>来帮助声明类型变量
– List ints = new ArrayList();
– public interface List
– public class Entry<KeyType, ValueType>
public class PapersJar<T> {
private List<T> itemList = new ArrayList<>();
public void add(T item) {
itemList.add(item);
}
public T get(int index) {
return (T) itemList.get(index);
}
public static void main(String args[]) {
PapersJar<String> papersStr = new PapersJar<>();
papersStr.add("Lion");
String str = (String) papersStr.get(0);
System.out.println(str);
PapersJar papersInt = new PapersJar();
papersInt.add(new Integer(100));
Integer integerObj = (Integer) papersInt.get(0);
System.out.println(integerObj);
} }
public class Pair<E> {
private final E first, second;
public Pair(E first, E second) {
this.first = first;
this.second = second;
}
public E first() { return first; }
public E second() { return second; }
}
Client:
Pair<String> p = new Pair<>("Hello", "world");
String result = p.first();
集合是一些其他类型的E的有限元素集的ADT。
泛型接口,非泛型的实现类
泛型接口,泛型的实现类 Java’s HashSet does that for Set.
(4) Subtyping Polymorphism
子类型仅仅是超类型的子集
- ArrayList 和 LinkedList 是 List的子集.
在JAVA中,一个类只能继承一个父类,一个类可以实现多个接口。
“B是A的一个子类型”的意思是“每个B都是一个A” .”
就规范而言:“每个B都满足规范 A.”
但是编译器不能检查我们是否没有以其他方式削弱了规范:
子类型的规约不能弱化超类型的规约。
子类型多态:不同类型的对象可以统一的处理而无需区分
里氏替换原则:
- 若对每个类型S的对象o1,都存在一个类型T的对象o2,使得在所有针对T编写的程序P中用o1替换o2后,程序P行为功能不变,则S是T的子类型。
永远不要downcast within superclass to a subclass
10在Java中的一些重要的对象方法
§ equals() – true if the two objects are “equal”
§ hashCode() – a hash code for use in hash maps
§ toString() – a printable string representation
替代的hashCode覆盖
11 Designing good classes
不可变类的优点:
简单的
固有线程安全
可以自由共享
不需要防御拷贝
优秀的构建块