java核心技術 第四章 物件與類
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 類設計技巧
-資料私有
-資料初始化
-不要在類中使用過多的基本型別
-不是所有的域都需要獨立的域訪問器和域更改器
-將職責過多的類進行分解
-類名和方法名要能夠體現它們的職責