1. 面向对象程序设计概述
面向对象的程序是由对象组成了,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。
程序中很多对象来自标准库,还有一些是自定义的。
传统的结构化程序设计通过设计一系列的过程(即算法)来求解问题。一旦确定了这些过程,就要开始考虑存储数据的方式。这就是Pascal语言的设计者 Nikaus Wirth将其著作命名为《算法+数据结构=程序》的原因。
在这个命名中,算法是第一位,数据结构是第二位。
这就明确地表述了程序员的工作,首先要确定如何操作数据,然后再决定如何组织数据,以便于数据操作。
而面向对象编程却改变了这个次序,将数据放在第一位,然后再考虑操作数据的算法。
对于一些规模较小的问题,使用面向过程的开发方式比较理想。
对于较大规模的问题,面向对象更加适合。
1.1 类
类(class)是构造对象的模板或蓝图。
由类构造(construct)对象的过程称为创建类的实例(instance)。
封装(encapsulation)是与对象有关的一个重要概念。从形式上看,封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。
对象中的数据称为实例域(instance field)
操作数据的过程称为方法(method)
对于每个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态(state).无论何时,只要向对象发送一个消息,它的状态就有可能发生改变。
实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域。程序仅通过对象的方法与对象数据进行交互。
封装给对象赋予了“黑盒”特征。
1.2 对象
对象的三个主要特性:
- 对象的行为(behavior)—可以对对象发送哪些消息?
- 对象的状态(state)—当发送消息时,对象如何响应?
- 对象的标识(identity)—如何判别具有相同行为和状态的不同对象?
1.3 定义类
面向过程进行程序设计时,必须从main函数开始编写程序。
面向过程的设计时,先从设计类开始,然后再往每个类中添加方法。
识别一个类的简单规则是在分析问题的过程中寻找名词,而方法对应着动词。
当然,“找名词和动词”原则只是一个经验之谈。
1.4 类之间的关系
类之间的关系:
- 依赖(“uses-a”)
- 聚合(“has-a”)
- 继承(“is-a”)
依赖:如果一个类的方法操纵别一个类的对象,我们就说一个类依赖于另一个类。
聚合:类A的对象包含类B的对象。
继承:如果类A扩展类B,类A不但包含从类B继承的方法,还会拥有一些额外的方法。
2.使用类
在Java中,没有类就无法做任何事情。
并不是所有的类都具有面向对象特征。如,Math类,它只封装了功能,它不需要也不必隐藏数据。由于没有数据,因些也不必担心生成对象以及初始化实例域。
2.1 对象与对象变量
要想使用对象,就必须首先构造对象,并指定其初始状态。
在Java中,使用构造器(constructor)构造新实例。
构造器是一种特殊的方法,用来构造并初始化对象。
构造器的名字应该与类名相同。如:想要构造一个Date对象,需要在构造器前面加上new操作符。
3. 自定义类
2.1 构造器
所有的对象都是在堆中构造的,构造器总是伴随着new 操作符一起使用。
2.2 隐式参数与显式参数
方法用于操作对象以及存取它们的实例域。
关键字this 表示隐式参数。
2.3 final实例域
可以将实例域定义为final 。构建对象时必须初始化这样的域。也就是说,必须确保在每个构造器执行之后,这个域的值被设置,并且在后面的操作中,不能够再对它进行修改。
final修饰大都应用于基本(primitive)类型域,或不可变(immutable)类的域。如:String.
4.静态域与静态方法
4.1 静态域
如果将域定义为satic ,每个类中只有一个这样的域。而每个对象对于所有的实例域却都有一份自己的拷贝。
4.2 静态常量
静态常量如:Math.PI
4.3 静态方法
可以认为静态方法是没有this参数的方法。
静态方法可以访问自身类中的静态域。
下面两种情况使用静态方法:
- 一个方法不需要访问对象状态,其所需参数都是通过显示参数提供。
- 一个方法只需要访问类的静态域。
4.4 工厂方法
静态方法还有一种常见的用途。使用静态工厂方法(factory method)来构造对象。
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.getPercentInstance();
double x = 0.1;
System.out.println(currencyFormatter.format(x)); // prints $0.10
System.out.pringln(percentFormatter.format(x)); // prints 10%
为什么NumberFormat类不使用构造器完成这些操作呢?
原因有两个:
- 无法命名构造器。构造器的名字必须与类名相同。但是,这里希望将得到的货币实例和百分比实例采用不同的名字。
- 当使用构造器时,无法改变所构造的对象类型。
5.方法参数
按值调用(call by value)表示方法接收的是调用者提供的值。
按引用调用(call by reference)表示方法接收是调用者提供的变量地址。
java 总是采用按值调用。也就是说,方法得到的所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容。
方法的参数共有两种类型:
- 基本数据类型(数字、布尔值)
- 对象引用
6. 对象构造
6.1 重载
重载(overloading):如果多个方法有相同的名字、不同的参数,便产生了重载。
要完整的描述一个方法,需要指出方法名以参数类型。这叫方法的签名(signature)
注意:返回类型不是方法签名的一部分。
6.2 默认域初始化
如果在构造器中没有显式地给域赋初值,那么会被自动地赋上默认值:数值为0、布尔值为false、对象引用为null 。
这是域与局部变量的主要不同点:必须为局部变量赋初值。但如果不有初始化类中的域,将会被自动初始化为默认值(0、false或null).
6.3 无参构造器
如果编写一个类时没有编写构造器,那么系统就会提供一个无参构造器。
这个无参构造器将所有的实例域设置为默认值。
注意:仅当类没有提供任何构造器的时候,系统才会提供一个默认的构造器。
7.包
java中使用包(package)将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。
使用包的主要原因是确保类名的唯一性。
从编译器的角度来看,嵌套的包之间没有任何关系。例如:java.util包与java.util.jar包毫无关系。每一个都拥有独立的类集合。
7.1 类的导入
有两种方式访问另一个包中的公有类:
- 每个类之前添加完整的包名。
- 使用import语句。(常用)
在包中定位类是编译器(compiler)的工作。类文件中的字节码肯定使用完整的包名来引用其它类。
7.2 将类放入包中
要想将一个类放入包中,就必须将包的名字放在源文件的开头,包中定义类的代码之前。
8 类路径
类存储在文件系统的子目录中,类的路径必须与包名匹配。
类文件也可以存储在JAR(Java归档)文件中。在一个JAR文件中,可以包含多个压缩形式的类文件和子目录,这样既可以节省双可以改善性能。
提示:JAR文件使用ZIP 格式组织文件和子目录。可以使用zip软件来查看jar包的文件。
9.类设计技巧
- 一定要保证数据私有
- 一定要对数据初始化
- 不要在类中使用过多的基本类型
- 不是所有的域都需要独立的getter或settter方法
- 将方法过多的类进行分解
- 类名和方法名要能够体现它们的职责
- 优先使用不可变的类