抽象过程
“类型”是指“所抽象的是什么?”
问题空间中的元素及其在解空间中的表示称为“对象”,对象是类的一个实例;
面向对象语言的基本特征:
- 万物皆为对象;
- 程序是对象的集合,他们通过消息来告知彼此所要做的;
- 每个对象都有自己的由其他对象所构成的存储;
- 每个对象都拥有其类型,对象是class的一个instance;
- 某一特定类型的对象都可以接受同样的消息(可替代性);
每个对象都有一个接口
创建“类”是面向对象程序设计的基本概念之一。
类描述了具有相同特性和行为的对象集合,类实际上就是一个数据类型。
每一个对象都属于定义了特性和行为的某个特定的类;
每个对象都只能满足某些请求,这些请求由对象的“接口(interface)”所定义,满足请求的代码和数据构成接口的实现(implementation);
学会使用UML(Unified Modelling Language);
每个对象都提供服务
对象作为服务提供者。
程序本身向用户提供服务,他通过调用对象提供的服务来实现这一点。
在良好的OOP设计中,每个对象都可以很好地完成一项任务,但是他并不试图做得更多。
被隐藏的具体实现
访问控制:
让客户端程序员无法触及他们不应该触及的部分;
允许库设计者可以改变类内部的工作方式而不用担心会影响到客户端程序员;
- public:对任意人都是可用的;
- private:除类型创建者和类型内部方法之外的任何人都不能访问的元素;
- protected:与 private 相当,差别在于继承的类可以访问 protected 成员;
- package 包访问权限:Java 的默认访问权限,当没有提到之前的三个指定词时,类可以访问在同一个包中的其他类成员,但在包之外不能。
为了形成良好的编程习惯,我们通常希望对所有的变量和方法都应当有适当的访问限制词;
复用具体实现(类的复用)
代码复用是面向对象程序设计语言所提供的最了不起的优点之一。
类的复用方式:
- 组合(composition):直接使用该类的对象,也可以将那个类的一个对象置于某个新的类中;
- 继承(extend)
不要觉得继承是最优的复用方式,在建立新类的时候应当优先考虑组合;
继承
以现有类为基础,复制他,然后通过添加和修改这个副本来创建新类;
现有类被称为『基类』、『父类』、『超类』,用来表示系统中某些对象的核心概念;
新类被称为『导出类』、『继承类』、『子类』,表示核心可以被实现的不同方式;
当源类发生变化时,被修改的子类也会反映出这种变动。
当继承现有类型时,也就创造了新的类型,这个新的类型不仅包括现有的类型成员,也复制了基类的接口;所有可以发送给父类的消息同样也可以发送给子类,即理解通过继承产生的类型等价性。
基类和导出类产生差异的方式:
- addition:直接在导出类中添加新的方法;
- overriding:改变现有基类的方法;
例如,定义基类Shape:
public class Shape {
public void draw(){
System.out.print("Shape");
}
}
定义导出类 Circle,覆盖方法,注意 @Override 修饰符的使用:
public class Circle extends Shape {
@Override
public void draw() {
System.out.print("Circle");
}
}
定义导出类 Rectangle,添加方法:
public class Rectangle extends Shape {
public void show() {
System.out.print("Rectangle");
}
}
伴随多态的可互换对象
通过导出新的子类而轻松扩展设计的能力是对改动进行封装的基本方式之一。
基类通过继承变为导出类很显然,但是在将导出类对象作为基类对象处理时又会出现问题,因为基类的方法有可能会被导出类覆盖,所以编译器有可能不知道应该执行哪一个方法。如何实现多态的问题也就因此产生。
引入『前期绑定』和『后期绑定』的概念;
例如,定义 deal() 方法,是针对基类Shape的处理:
public static void deal(Shape shape){
shape.draw();
}
那么我们运行以下代码会有什么结果?
Shape shape = new Shape();
Circle circle = new Circle();
Rectangle rectangle = new Rectangle();
deal(shape);
deal(circle);
deal(rectangle);
结果如下:
"Shape"
"Circle"
"Rectangle"
编译器正确处理了多态问题,至于多态问题,我们会在之后的章节讨论;
Java的编译器会自动实现向上转型;
单根继承结构
所有类最终都继承于单一的基类Object;
单根继承的优势:
- 保证所有对象都具备某些功能,利于对象在heap上创建,也简化了参数传递;
- 使垃圾回收器的实现变得容易得多,也使异常处理变得更加容易;
容器
- List:存储序列
- Map:关联数组,用于存储对象之间的关联
- Set:每种对象的类型只持有一个,类似于数学中的集合概念
- 以及Tree、Queue、Heap等都是容器
不同的容器提供了不同的接口和外部行为;
不同的容器对于某些操作具有不同的效率,例如 ArrayList 和 LinkedList;
由于容器的普适性,它用于存放Object类型,因此在存储其他数据类型时会向上转型,但是再取出时会发生向下转型这种不安全的行为,因此我们要为容器引入泛型的概念,让容器记住向下转型的正确类型。
例如:
ArrayList<Shape> shapes = new ArrayList<Shape>();
对象的创建和生命周期
C++认为效率是最重要的议题,所以编程人员有权利决定对象的生命周期和存储空间,将对象置于堆栈或静态存储区来实现。但是你需要知道对象的确切数量、生命周期和类型,牺牲了灵活性。
Java 采用动态内存的分配方式,在 Heap 的内存池中动态的创建对象。增大了维护内存的开销,但是解决了一般化编程的问题要点。Java 的垃圾回收机制,可以自动发现对象是否已经不再使用,并继而销毁它,避免了暗藏的内存泄露问题。
异常处理:处理错误
异常处理将错误处理直接置于编程语言中,异常本身也是一个对象;
错误的处理过程在另一条路径,不会影响正常运行的代码;
异常不能被忽略,所以保证异常一定能得到处理;
并发编程
在程序中,彼此独立运行的部分被称为线程。
如果有多个并行任务都要访问一个资源,那么就要求某个人物锁定这个资源,直到使用完成,再释放资源,使其他资源可以使用。