4对象与类
2016-11-07 本文已影响36人
我快要上天啦
4对象与类
- 面向对象程序设计
- 如何创建标准java类库中的类对象
- 如何编写自己的类
4.1概述
- 程序由对象构成,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。一些对象来自于标准库,一些是自定义的。在OOP中,不必关心对象的具体实现,只要能满足用户需求即可。
.1类
- 类(class)是构造对象的模板或蓝图。由类构造(construct)对象的过程称为创建类的实例(instance)。用java编写的所有代码都位于某个类的内部。
- 封装(encapsulation),将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。
- 对象中的数据称为实例域(instance field),操纵数据的过程称为方法(method)。
- 实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域。程序仅通过对象的方法与对象数据进行交互,即“黑盒”特性。
- 可以通过拓展一个类来建立一个新的类,所有类都来自于一个超类Object。通过拓展一个类来建立另一个类的过程称为继承(inheritance)。
.2对象
- OOP中,对象的三个主要特征:
- 对象的行为(behavior):可以对对象施加哪些操作,或可以对对象施加哪些方法
- 对象的状态(state):当施加那些方法时,对象如何响应
- 对象标识(identify):如何辨别具有相同行为和状态的不同对象
- 每个对象都包含着描述当前特征的信息,即对象的状态。状态是非自发的,对象状态的改变必须通过调用方法实现,如若不然,则封装性受到破坏。作为一个类的实例,每个对象的标识永远是不同的,状态也经常存在差异。
.3识别类
- 首先从设计类开始,然后再往每个类中添加方法。
.4类之间的关系
- 在类之间,最常见的关系有:
- 依赖(uses-a)
- 聚合(has-a)
- 继承(is-a)
- 依赖(dependence):如果一个类的方法操控另一个类的对象,我们就说一个类依赖于另一个类。应该尽可能将相互依赖的关系减至最少,即让类之间的耦合度最小。
- 聚合(aggregation):类A的对象包含类B的对象。
- 继承(inheritance):是用于表示特殊与一般关系的。
4.2使用预定义类
.1对象与对象变量
- 要想使用对象,就必须首先构造对象,并指定其初始状态。然后,对对象应用方法。
- 在java语言中,使用构造器(constructor)构造新实例。构造器名应该与类名相同。
- 例如:
new Name();
- 通常将对象存放在一个变量中:
Name name = new Name();
,以便多次使用。 - 定义对象变量之后,必须先初始化,才可以引用对象/应用方法。
- 一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。在Java中,任何对象变量的值都是对存储在另一个地方的一个对象的引用。new操作的返回值也是一个引用。
- 局部变量不会自动地初始化为null,而必须通过调用new或将它们设置为null进行初始化。
- 所有Java对象都存储在堆中。
.2Java类库中的GregorianCalendar类
- 纪元(epoch),UTC时间,1970年1月1日00:00:00
- Date类表示时间点,GregorianCalendar类表示日历。
- Date可以使用before/after方法表示早于/晚于:
today.before(birthday)
- GregorianCalendar构造器表达式:
new GregorianCalendar();
new GregorianCalendar(1999, 11, 31);//年月日
new GregorianCalendar(1999, Calendar.DECEMBER, 31);//常量
new GregorianCalendar(1999, Calendar.DECEMBER, 31, 23, 59, 59);//时间
.3更改器方法与访问器方法
- 对实例域做出修改的方法称为更改器方法(mutator method),仅访问实例域而不进行修改的方法称为访问器方法(accessor method)。通常为get和set。
4.3用户自定义类
- 要想创建一个完整的程序,应该将若干类组合在一起。
.1Employee类
- 实例域、构造器、方法
.2多个源文件的使用
.3剖析Employee类
- 类通常包含类型属于某个类类型的实例域。
.4构造器
- 构造器总是伴随着new操作符的执行被调用,而不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的。
- 构造器与类同名
- 每个类可以有一个以上的构造器
- 构造器可以有0个、1个或多个参数
- 构造器没有返回值
- 构造器总伴随着new操作一起调用
- ps:不要在构造器中定义与定义域重名的局部变量
.5隐式参数与显式参数
public void raiseSalary(double byPersent){
double raise = Salary * byPersent / 100;
Salary += raise;
}
number007.raiseSalary(5);
- 隐式(implicit)参数,是出现在方法名前的类对象;显式(explicit)参数,位于方法名后面的括号中。显式参数明显地列在方法声明中,隐式参数没有出现在方法声明中。在每一个方法中,关键字this表示隐式参数。
.6封装的优点
- 一个只读域,一旦在构造器中设置完毕,就没有任何一个办法可以对它进行修改,这样来确保域不会受到外界的破坏。
- 在有些时候,需要获得或设置实例域的值,应提供以下三项内容:
- 一个私有的数据域
- 一个公有的域访问器方法
- 一个公有的域更改器方法
- 有下列优势:
- 可以改变内部实现,除了该类的方法之外,不会影响其他代码
- 更改器方法可以执行错误检查
- ps:不要编写返回可变对象的访问器方法(4.2.1对象引用)。如果需要返回一个可变对象的引用,首先应该对它进行克隆(clone)。
class Employee{
private Date hireDay;
...
public Date getHireDay(){
return hireDay.clone();
}
...
}
.7基于类的访问权限
class Employee{
...
public boolean equals(Employee other){
return name.equals(otehr.name);
}
}
if(harry.equals(boss))...
- 该方法访问了harry的私有域,也访问了boss的私有域。这是合法的,因为boss是Employee类对象,而Employee类的方法可以访问Employee类的任何一个对象的私有域。
.8私有方法
- 只要方法是私有的,它就不会被外部的其他类操作调用,可以将其删去。如果方法是公有的,就不能将其删去,因为其他代码可能依赖它。
.9final实例域
- 将实例域定义成fianl,构建对象时必须初始化这样的域。也就是说,必须确保在每一个构造器执行之后,这个域的值被设置,并且在后面的操作中,不能在对它进行修改。
- fianl修饰符大都应用于基本(primitive)类型域,或不可变(immutable)类的域。对于可变的类,使用final修饰符可能会给读者造成混乱。
4.4静态域与静态方法
.1静态域
- 如果将域定义为static,每个类中都只有一个这样的域。而每一个对象对于所有实例域却都有自己的一份拷贝。例:
class Employee{
private static int nextId = 1;
private int id;
...
} - 现在,每一个雇员对象都有一个自己的id域,但这个类的所有实例域将共享一个nextId域。换句话说,如果有1000个Employee类的对象,则有1000个实例域id,但是只有一个静态域nextId。即使没有一个Employee对象,静态域nextId也存在。它属于类,而不属于任何独立的对象。
.2静态常量
public static fianl double PI = 3.141592653;
.3静态方法
-
静态方法是一种不能向对象实施操作的方法。换句话说,没有隐式的参数。可以认为静态方法是没有this参数的方法。Employee类的静态方法不能访问Id实例域,因为它不能操作对象。但是,静态方法可以 访问自身类中的静态域。例:
public static int getNextId(){
return nextId;//returns static field
}
int n = Employee.getNextId(); -
建议使用类名,而不是对象来调用静态方法。在如下两种情况下使用静态方法:
- 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供。
- 一个方法只需要访问类的静态域。
.4工厂方法
- 静态方法的一种常见用途。如NumberFormat类使用工厂方法产生不同风格的格式对象。
- NumberFormat类不利用构造器完成操作的原因:
- 无法命名构造器。构造器的名字必须与类名相同,但是这里希望各种实例采用不同的名字。
- 当使用构造器时,无法改变所构造的对象类型
.5main方法
- 不需要使用对象调用静态对象。同理,main函数也是一个静态方法。main方法不对任何对象进行操作。事实上,在启动程序时还没有任何一个对象。静态的main方法将执行并创建程序所需要的对象。
4.5方法参数
- 术语:按值调用(call by value)表示方法接收的是调用者提供的值。按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改引用所对应的变量值,而不能修改传递值调用所对应的变量值。Java总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝。特别是,方法不能修改传递给它的任何参数变量的内容。
public static void tripleValue(double x){
x=3*x;//doesn't work
}
double percent = 10;
tripleValue(percent); - 对于基本数据类型的参数。无论怎样调用,percent的值都不会改变。x只是被初始化为percent值的一个拷贝。
public static void tripleSalary(Employee x){
x.raiseSalary(200);//raise to 200%
}
harry = new Employee(...);
tripleSalary(harry); - 对于对象引用,具体的执行过程为:
- 1)x被初始化为harry值得拷贝,这里是一个对象的引用。
- 2)raiseSalary方法应用于这个对象引用。x和harry同时引用的那个Employee对象的薪金提高了200%。
-
3)方法结束后,参数变量x不再使用。当然,对象变量harry继续引用那个薪金增至300%的雇员对象。
4-7
- 所以说,实现一个改变对象参数状态的方法并不是一件难事。理由是,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。
-
Java总是采用按值调用。对于Java采用引用调用的反例:
public static void swap(Employee x, Employee y){
Employee temp = x;
x = y;
y = temp;
}
swap(a, b);//never work - 如果Java对对象采用的是引用调用,那么该方法就能够实现交换数据的效果。实际上方法没有改变存储在变量a和b中的对象引用。在方法结束时,参数变量x和y被丢弃了。原来的a,b仍然引用这个方法调用之前所引用的对象。
- Java中方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数
- 一个方法可以改变一个对象参数的状态
- 一个方法不能让对象参数引用一个新的对象
4.6对象构造
.1重载
- 重载(overloading):多个方法有相同的名字、不同的参数,便产生了重载。编译器通过匹配挑选出相应的方法,若不存在便会报错。
- 返回类型不是方法签名的一部分,所以不能有两个名字相同、参数类型也相同却返回不同类型值的方法。
.2默认域初始化
- 如果在构造器中没有显示地给域赋予初值,那么就会自动地赋为默认值:数值为0,布尔值为false,对象引用为null。局部变量必须明确初始化,域中变量不初始化会被赋予初值(这样不好)。
.3无参数构造器
- 如果在编写一个类时没有编写构造器,那么系统就会提供一个无参数的构造器。这个构造器将所有的实例域设置为默认值。
- 如果类中提供了至少一个构造器,但是没有提供无参数的构造器,则在构造对象时如果没有提供参数就会被视为不合法。
.4显示域初始化
- 由于类的构造器方法可以重载,所以可以采用多种形式设置类的实例域的初始状态,以确保不管怎样调用构造器,每个实例域都可以被设置为一个有意义的初值。
- 在构造器执行之前,先执行赋值操作。初始值不一定是常量。例:
class Employee{
private static int nextId;
private int id = assignId();
...
private static int assignId(){
int r = nextId;
nextId++;
return r;
}
...
}
.5参数名
- 参数名有字面意思更好。
- 用this访问实例域。例:
public Employee(String name, double salary){
this.name = name;
this.salary = salary;
}
.6调用另一个构造器
- 关键字this引用方法的隐式参数。
- 如果构造器的第一个语句形如this(...),这个构造器将调用同一个类的另一个构造器。例:
public Employee(double s){
//call Employee(String, double)
this("Employee #"+nextId, s);
nextId++;
}
调用new Employee(60000)时,Employee(double)构造器将调用Employee(String, double)。
.7初始化块
- 初始化数据域:1)在构造器中设置值;2)在声明中赋值
- 3)初始化块(initialization block),例:
class Employee{
private static int nextId;
private int id;
private String name;
private double salary;
//object initialization block
{
id = nextId;
nextId++;
}
public Employee(String n, double s){
name = n;
salary = s;
}
public Employee(){
name = "";
salary = 0;
}
...
}
建议将初始化块放在域定义之后。 - 调用构造器的具体步骤:
- 1)所有数据域被初始化为默认值(0、false或null)
- 2)按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块
- 3)如果构造器第一行调用了第二个构造器,则执行第二个构造器主体
- 4)执行这个构造器主体
4.7包(Package)
- 从编译器角度看,嵌套的包之间没有任何关系。如,java.util与java.util.jar毫无关系。
.1类的导入
- 一个类可以使用所属包中的所有类,以及其他包中的公有类(public class)。
- 访问其他包中的公有类,第一种方式是在每个类名之前添加完整的包名。第二种是使用import语句。
- 导入的包内类发生命名冲突时,需要指定。
.2静态导入
- import增加了导入静态方法和静态域的功能。如:
import static java.lang.System.*
就可以使用System类的静态方法和静态域,而不必加类名前缀。
.3将类放入包中
- 将包名放在源文件的开头,如果没有该语句,则放置在一个默认包(defaulf package)中。
.4包作用域
- 标记为public的部分可以被任意的类使用,标记为private的部分只能被定义它们的类使用。如果没有指定,这个部分可以被同一个包中的所有方法访问。包密封(package sealing)
4.8类路径
- 类存储在文件系统的子目录中,类的路径必须与包名匹配。另外,类文件也可以存储在JAR文件中。
- 为了使类能够被多个程序共享,需要做到以下几点:
- 1)把类放到一个目录中
- 2)将JAR文件放在一个目录中
- 3)设置类路径(class path),类路径是所有包含文件的路径的集合。类路径包括:
- 1)基目录/home/user/classdir或c:\classes;
- 2)当前目录;
- 3)JAR文件/home/user/archives/archive.jar或c:\archives\archive.jar
4.9文档注释
.1注释的插入
-
javadoc实用程序(utility)从下面几个特性中抽取信息:
- 包
- 公有类与接口
- 公有的和受保护的构造器及方法
- 公有的和受保护的域
- 每个注释在标记/**...*/之后紧跟着自由格式文本(free-form text)。标记有@开头,第一句应该是一个概要行的句子,javadoc会自动将这些句子抽取出来形成概要页。
.2类注释
- 类注释放在import语句之后,类定义之前。
.3方法注释
- 每一个方法注释必须放在所描述的方法之前。除了通用标记以外,还可以使用以下标记:
- @param变量描述
- @return描述
- @throws类描述
.4域注释
- 只需要对公有域(通常指的是静态常量)建立文档
.5通用注释
- 下列标记可以用在类文档的注释中:
- @author姓名:作者
- @version文本:版本描述
- @since文本:对引入特性的版本描述
- @see引用:提供javadoc中的超链接
.6包与概述注释
- 想要产生包注释,就需要在每一个包目录中添加一个单独的文件。可以选择:
- 1)提供一个以package.html命名的HTML文件。在标记<body>...</body>之间的所有文本都会被抽取出来
- 2)提供一个以package-info.java命名的Java文件。这个文件必须包含一个初始的以/**和*/界定的Javadoc注释,紧跟在报语句之后。它不应该包含更多的代码和注释。
.7注释的抽取
- 步骤:
- 1)切换到包含想要生成文档的源文件目录。
- 2)如果是一个包,应该运行命令:
javadoc -d docDirectory nameOfPackage
还有多个文件和在默认包中的情况。
4.10类设计技巧
- 1)一定要保证数据私有,绝对不要破坏封装性
- 2)一定要对数据域初始化
- 3)不要再类中使用过多的基本类型
- 4)不是所有的域都需要独立的域访问器和域更改器
- 5)将职责过多的类进行分解
- 6)类名和方法名要能够提现它们的职责