4.1 面向对象概述
面向对象层序设计(OOP),Java是完全面向对象。
面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。
面向对象将数据放在第一位。
4.1.1 类
类(class)是构造对象的模板或蓝图。由类构造(construct)对象的过程称为创建类的实例(instance).
封装(encapsulation) 将数字和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。对象中的数据称为实例域(instance),操纵数据的过程称为方法(method)。每个特定的类实例都有一组特定的实例域值,这些值的集合就是这个对象的当前状态(stated)
4.1.2 对象
要想使用OOP.需要清楚对象的三个主要特性:
- 对象的行为(behavior):对对象施加那些操作?
- 对象的状态(state):当施加那些方式时,对象如何响应?
- 对象的标志。如何判别具有相同行为与状态的不同对象?
对象的状态影响行为,对象的行为也影响状态。对象的标志区分同一类的不同对象。
4.1.3 识别类
面向OOP设计通常从设计类开始,然后再往每个类中添加方法。
在设计过程中通常识别名词和动词。
4.1.4 类之间的关系
- 继承( is-a)
- 实现 ()
- 依赖 (use-a)
- 关联(has-a)
4.2 使用预定义类
4.2.1 对象与对象变量
构造器(constructor)是一种特殊方法, 用来构造并初始化对象。构造器的名字应与类名相同。
创建一个对象
new Date();//创建一个对象
String s = new Date().toString();//用新创建的对象创建方法。
Date birthday = new Date();//将对象存放在变量中。
Date deadline = null;//创建一个对象变量,没有引用任何对象,调用的话将产生异常。
deadline = birthday;//birthday和deadline 两个对象变量引用的是同一个对象
注意:一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。在Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new 操作符的返回值也是一个引用。
局部变量不会自动地初始化为null,必须通过调用new或将它们设置为null进行初始化。
4.2.2 Java类库中的GregorianCalendar类
new GregorianCalendar();
new GregorianCalendar(1999,11,31);
new GregorianCalendar(1999,Calendar.DECEMBER,31);
new GregorianCalendar(1999,Calendar.DECEMBER,31,23,59,59);
GregorianCalendar deadline = new GregorianCalendar(...)
4.2.3 更改器方法与访问器方法
GregorianCalendar now = new GregorianCalendar();
int month = now.get(Calendar.MONTH);
int weekday =now.get(Calendar.DAY_OF_WEEK);
deadline.set(Calendar.YEAR,2001);//设置年
deadline.set(Calendar.MONTH,Calendar.APRIL)//设置月
deadline.set(Calendar.DAY_OF_MONTH,15);//设置日期
deadline.set(2001 ,Calendar.APRIL,15);
对实例域做出修改的方法称为更改器方法(mutator method),仅访问实例域而不进行修改的方法称为访问器方法(accessor methdod)
Date time = calendar.getTime();
calendar.setTime(time);
//由日期创建Date类
GregorianCalendar calendar = new GregorianCalendar(year ,month ,day);
Date hireDay = calendar.getTime();
//由Date类创建日期
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(hireDay);
int year = calendar.get(Calendar.YEAR);
4.3 用户自定义类
4.3.1 Employee类
在一个源文件中只能有一个公共类,且源文件名必须与公共类名相同,其它的全是非公共类。
package EmployeeTest;
import java.util.Date;
import java.util.GregorianCalendar;
public class EmployeeTest {
public static void main(String[] args) {
Employee[] staff = new Employee[3];
staff[0]= new Employee("Carl Cracker",75000,1987,12,15);
staff[1]= new Employee("Harry Hacker",50000,1989,10,1);
staff[2]= new Employee("Tony Tester",40000,1990,3,15);
for(Employee e: staff){
e.raiseSalary(5);
}
for(Employee e:staff){
System.out.println("name="+e.getName()+",salary="+e.getSalary()+",hireDay="+e.getHireDay());
}
}
}
class Employee{
private String name;
private double salary;
private Date hireDay;
public Employee(String name, double salary, int year ,int month,int day) {
super();
this.name = name;
this.salary = salary;
GregorianCalendar calendar = new GregorianCalendar(year,month,day);
this.hireDay = calendar.getTime();
}
public String getName(){
return name;
}
public double getSalary(){
return salary;
}
public Date getHireDay(){
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent/100;
salary += raise;
}
}
4.3.2 多个源文件使用
编译器会查找名为Employee.class的文件,如果没有找到这个文件,就会 自动搜索Employee.java,对它进行编译。
4.3.3 剖析Employee类
public String getName()
方法被标为public 表示任何任何类和任何方法都可使用
private String name;
private 表示只有自身的方法能够访问这些实例域
4.3.4 从构造器开始
构造器与类同名,构造器总是伴随着new操作符的执行被调用。已经存在的对象调用 构造器来达到重新设置实例域的目的不行。
- 构造器与类同名
- 每个类可以有一个以上的构造器
- 构造器可以有0个、1个或多个参数
- 构造器没有返回值
- 构造器总是伴随着new 操作一起调动
4.3.5 隐式参数与显示参数
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent/100;
salary += raise;
}
raiseSalary方法有两个参数,第一参数称为隐式(implicit)参数,是出现在方法名前的Employee对象。第二个参数是位于方法名后面括号的数值,这是一个显式(explicit)参数。
在每一方法中,关键字this表示隐式参数。
4.3.6 封装的优点
4.3.7 基于类的访问权限
public boolean equals(Employee obj) {
// TODO Auto-generated method stub
return name.equals(obj.name);
}
Employee类的方法可以访问Employee类的任何一个对象的私有域
4.3.8 私有方法
方法被private修饰,只在本类内被使用
4.3.9 final实例域
将实例域定义为final。构建对象时必须初始化这些实例域。并且在以后的操作中这些域 不可变。
class Employee
{
private final String name;
...
}
final 修饰符大豆应用于基本(primitive)类型域。或不可变(immutable)类的域。
private final Date hiredate;
仅仅意味着存储在hiredate变量中的对象引用在对象构造之后不能被改变,并不意味着hiredate对像是一个常量。
4.4 静态域与静态方法
4.4.1 静态域
如果将域定义为static ,每个类只有一个这样的域。每一个对象对于所有的实例域都有自己的一份拷贝。
4.4.2 静态常量
静态常量使用较多。Math类中定义了一个静态常量。
public class Math
{
public static final double PI = 3.14159;
}
4.4.3 静态方法
静态方法是一种不能面向对象实施操作的方法。
public static int getNextId(){
return nextId;
}
静态方法没有隐私参数 this。
静态方法不能操作对象,不能在静态方法中访问实例域。
静态方法通过类名调用。
静态方法使用实例:
- 一个方法不需要访问对象状体,其所需参数都是通过显式参数提供
- 一个方法只需要访问类的实例域
4.4.4 工厂方法
NumberFormat类使用工厂方法产生不同风格的格式对象
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
4.4.5 main 方法
public class Application{
public static void main(String[] arg)
{
}
}
4.4.5 main方法
package staticTest;
import java.util.Date;
import java.util.GregorianCalendar;
public class StaticTest {
public static void main(String[] args) {
Employee[] staff = new Employee[3];
staff[0]= new Employee("Tom",40000);
staff[1]= new Employee("Dick",60000);
staff[2]= new Employee("Harry",65000);
for(Employee e:staff){
e.setId();
System.out.println("name="+e.getName()+",id="+e.getId()+",salary="+e.getSalary());
}
int n = Employee.getNextId();
System.out.println("Next available id="+n);
}
}
class Employee{
private static int nextId = 1;
private String name;
private double salary;
private int id;
public Employee(String name, double salary) {
super();
this.name = name;
this.salary = salary;
id = 0;
}
public String getName(){
return name;
}
public double getSalary(){
return salary;
}
public int getId(){
return id;
}
public void setId(){
id =nextId;
nextId ++;
}
public static int getNextId(){
return nextId;
}
}
4.5 方法参数
方法参数包括值引用和对象引用。但Java方法总是值引用
package paramtest;
public class ParamTest {
public static void main(String[] args) {
/*
* Test 1: Methods can't modify numeric parameters
*/
System.out.println("Testing tripleValue:");
double percent = 10;
System.out.println("Before: percent=" + percent);
tripleValue(percent);
System.out.println("After: percent=" + percent);
/*
* Test 2: Methods can change the state of object parameters
*/
System.out.println("\nTesting tripleSalary:");
Employee harry = new Employee("Harry", 50000);
System.out.println("Before: salary=" + harry.getSalary());
tripleSalary(harry);
System.out.println("After: salary=" + harry.getSalary());
/*
* Test 3: Methods can't attach new objects to object parameters
*/
System.out.println("\nTesting swap:");
Employee a = new Employee("Alice", 70000);
Employee b = new Employee("Bob", 60000);
System.out.println("Before: a=" + a.getName());
System.out.println("Before: b=" + b.getName());
swap(a, b);
System.out.println("After: a=" + a.getName());
System.out.println("After: b=" + b.getName());
}
public static void tripleValue(double x) // doesn't work
{
x = 3 * x;
System.out.println("End of method: x=" + x);
}
public static void tripleSalary(Employee x) // works
{
x.raiseSalary(200);
System.out.println("End of method: salary=" + x.getSalary());
}
public static void swap(Employee x, Employee y) {
Employee temp = x;
x = y;
y = temp;
System.out.println("End of method: x=" + x.getName());
System.out.println("End of method: y=" + y.getName());
}
}
class Employee // simplified Employee class
{
private String name;
private double salary;
public Employee(String n, double s) {
name = n;
salary = s;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
}
4.6 对象构造
4.6.1 重载
方法名相同,方法参数不同产生了重载。
Java允许重载,方法名和参数类型组成了方法的签名(signature);
4.6.2 默认域初始化
构造器中没有显示地给域赋予初值,那么就会自动的赋值:数值为0、布尔值为false、对象引用为null。
4.6.3 无参数的构造器
当类没有提供任何的构造器的时候,系统才会提供一个默认的无参构造器。如果类中提供了有参构造器,那么必须显示提供一个无参构造器,否者不能调用无参构造器。
4.6.4 显示域初始化
class Employee{
private static int nextId;
private int id = assignId();
private static int assignId()
{
int r = nextId;
nextId++;
return r;
}
}
4.6.5 参数名
public Employee(String name,double salary)
{
this.name = name;
this.salary =salary;
}
4.6.6 调用另一个构造器
关键字this引用方法的隐私参数。this还可以放在构造方法的第一行,表示这个构造器将调用同一个类的另一构造器。
public Employee(double s){
this("Employee #"+nextId,s);//只能放在构造方法的第一行,表示引用另一个同名构造器
nextId++;
}
4.6.7 初始化块
两种初始化数据域的方法:
- 在构造器中设置值
- 在声明中赋值
初始化块
{
id = nextId;
nextId++
}
调用构造器的具体处理步骤:
1.所有数据域被初始化为默认值(0、flase或null)
2. 按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块。
3. 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体。
4. 执行这个构造器的主体。
对静态域进行初始化,可以使用静态代码块。
static
{
Random generator = new Random();
nextId = generator.nextInt(10000)
}
在类第一次加载的时候,将会进行静态域的初始化。
所有的静态初始化语句以及静态初始化块都将依照类定义的顺序执行。
package constructor;
import java.util.Random;
/**
* This program demonstrates object construction.
* @version 1.01 2004-02-19
* @author Cay Horstmann
*/
public class ConstructorTest
{
public static void main(String[] args)
{
// fill the staff array with three Employee objects
Employee[] staff = new Employee[3];
staff[0] = new Employee("Harry", 40000);
staff[1] = new Employee(60000);
staff[2] = new Employee();
// print out information about all Employee objects
for (Employee e : staff)
System.out.println("name=" + e.getName() + ",id=" + e.getId() + ",salary="
+ e.getSalary());
}
}
class Employee
{
private static int nextId;
private int id;
private String name = ""; // instance field initialization
private double salary;
// static initialization block
static
{
Random generator = new Random();
// set nextId to a random number between 0 and 9999
nextId = generator.nextInt(10000);
}
// object initialization block
{
id = nextId;
nextId++;
}
// three overloaded constructors
public Employee(String n, double s)
{
name = n;
salary = s;
}
public Employee(double s)
{
// calls the Employee(String, double) constructor
this("Employee #" + nextId, s);
}
// the default constructor
public Employee()
{
// name initialized to ""--see above
// salary not explicitly set--initialized to 0
// id initialized in initialization block
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public int getId()
{
return id;
}
}
对象析构与finalize方法
实际应用中,不用使用finalize方法回收当前任何应用的资源。
4.7 包
使用包是为了确保类名的唯一性,可以嵌套包。
4.7.1 类的导入
一个类可以使用所属包中的所有类,以及其他包中的公有类。
import java.util.*;//导入所有类
类名冲突时导入所有类
4.7.2 静态导入
可以导入静态方法和静态域:
import static java.lang.System.*;
可以使用System类的静态方法和静态域,而不必加类名前缀:
4.7.3 将类放入包中
将包名放在源文件头。
package com.horstmann.corejava;
源文件没有指出类名,则java源码被放在default
包中
4.7.4 包作用域
标记为public的部分可以被任意的类使用,标记为private的部分只能被定义它们的类使用。如果没有指定public或private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问。
4.8 类路径
类存储在文件系统的子目录中。类的路径必须与包名匹配。
共享类的方法:
1.把类放到一个目录中,为包的根目录
2.将jar 文件放在一个目录中。
3.设置类路径。类路径是所用包含类文件的路径的集合。设置CLASSPATH集合。
4.9 文档注释
4.9.1注释才插入
4.9.2 类注释
类注释放在import之后,类定义之前。
/*
*
*/
4.9.3 方法注释
- @param 变量描述
- @return 描述
- @throws 类描述
4.9.4 域注释
只需要对公有域建立文档
/**
*The "Hearts " card suit
*/
public static final int HEARTS =1;
4.10 类设计技巧
-数据私有
-数据初始化
-不要在类中使用过多的基本类型
-不是所有的域都需要独立的域访问器和域更改器
-将职责过多的类进行分解
-类名和方法名要能够体现它们的职责