本系列文章經補充和完善,已修訂整理成書《Java程式設計的邏輯》(馬俊昌著),由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買:京東自營連結
上節我們通過類Point介紹了類的一些基本概念和語法,類Point中只有基本資料型別,但類中的成員變數的型別也可以是別的類,通過類的組合可以表達更為複雜的概念。
程式是用來解決現實問題的,將現實中的概念對映為程式中的概念,是初學程式設計過程中的一步跨越。本節通過一些例子來演示,如何將一些現實概念和問題,通過類以及類的組合來表示和處理。
我們先介紹兩個基礎類String和Date,他們都是Java API中的類,分別表示文字字串和日期。
基礎類
String
String是Java API中的一個類,表示多個字元,即一段文字或字串,它內部是一個char的陣列,它提供了若干方法用於方便操作字串。
String可以用一個字串常量初始化,字串常量用雙引號括起來(注意與字元常量區別,字元常量是用單引號),例如,如下語句宣告瞭一個String變數name,並賦值為”老馬說程式設計”
String name = "老馬說程式設計";
複製程式碼
String類提供了很多方法,用於操作字串。在Java中,由於String用的非常普遍,Java對它有一些特殊的處理,本節暫不介紹這些內容,只是把它當做一個表示字串的型別來看待。
Date
Date也是Java API中的一個類,表示日期和時間,它內部是一個long型別的值,它也提供了若干方法用於操作日期和時間。
用無參的構造方法新建一個Date物件,這個物件就表示當前時間。
Date now = new Date();
複製程式碼
日期和時間處理是一個比較長的話題,我們留待後續章節詳解,本節我們只是把它當做表示日期和時間的型別來看待。
圖形類
擴充套件 Point
我們先擴充套件一下Point類,在其中增加一個方法,計算到另一個點的距離,程式碼如下:
public double distance(Point p){
return Math.sqrt(Math.pow(x-p.getX(), 2)
+Math.pow(y-p.getY(), 2));
}
複製程式碼
線 – Line
在型別Point中,屬性x,y都是基本型別,但類的屬性也可以是類,我們考慮一個表示線的類,它由兩個點組成,有一個例項方法計算線的長度,程式碼如下:
public class Line {
private Point start;
private Point end;
public Line(Point start, Point end){
this.start= start;
this.end = end;
}
public double length(){
return start.distance(end);
}
}
複製程式碼
Line由兩個Point組成,在建立Line時這兩個Point是必須的,所以只有一個構造方法,且需傳遞這兩個點,length方法計算線的長度,它呼叫了Point計算距離的方法獲取線的長度。可以看出,在設計線時,我們考慮的層次是點,而不考慮點的內部細節。每個類封裝其內部細節,對外提供高層次的功能,使其他類在更高層次上考慮和解決問題,是程式設計的一種基本思維方式。
使用這個類的程式碼如下所示:
public static void main(String[] args) {
Point start = new Point(2,3);
Point end = new Point(3,4);
Line line = new Line(start, end);
System.out.println(line.length());
}
複製程式碼
這個也很簡單。我們再說明一下記憶體佈局,line的兩個例項成員都是引用型別,引用實際的point,整體記憶體佈局大概如下圖所示:
start, end, line三個引用型變數分配在棧中,儲存的是實際內容的地址,實際內容儲存在堆中,line的兩個例項變數還是引用,同樣儲存的是實際內容的地址。
電商概念
接下來,我們用類來描述一下電商系統中的一些基本概念,電商系統中最基本的有產品、使用者和訂單:
- 產品:有產品唯一Id、名稱、描述、圖片、價格等屬性。
- 使用者:有使用者名稱、密碼等屬性。
- 訂單:有訂單號、下單使用者、選購產品列表及數量、下單時間、收貨人、收貨地址、聯絡電話、訂單狀態等屬性。
當然,實際情況可能非常複雜,這是一個非常簡化的描述。
這是產品類Product的程式碼:
public class Product {
//唯一id
private String id;
//產品名稱
private String name;
//產品圖片連結
private String pictureUrl;
//產品描述
private String description;
//產品價格
private double price;
}
複製程式碼
我們省略了類的構造方法,以及屬性的getter/setter方法,下面大部分示例程式碼也都會省略。
這是使用者類User的程式碼:
public class User {
private String name;
private String password;
}
複製程式碼
一個訂單可能會有多個產品,每個產品可能有不同的數量,我們用訂單條目OrderItem這個類來描述單個產品及選購的數量,程式碼如下所示:
public class OrderItem {
//購買產品
private Product product;
//購買數量
private int quantity;
public OrderItem(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
}
public double computePrice(){
return product.getPrice()*quantity;
}
}
複製程式碼
OrderItem引用了產品類Product,我們定義了一個構造方法,以及計算該訂單條目價格的方法。
下面是訂單類Order的程式碼:
public class Order {
//訂單號
private String id;
//購買使用者
private User user;
//購買產品列表及數量
private OrderItem[] items;
//下單時間
private Date createtime;
//收貨人
private String receiver;
//收貨地址
private String address;
//聯絡電話
private String phone;
//訂單狀態
private String status;
public double computeTotalPrice(){
double totalPrice = 0;
if(items!=null){
for(OrderItem item : items){
totalPrice+=item.computePrice();
}
}
return totalPrice;
}
}
複製程式碼
Order類引用了使用者類User,以及一個訂單條目的陣列orderItems,它定義了一個計算總價的方法。這裡用一個String類表示狀態status,更合適的應該是列舉型別,列舉我們後續文章再介紹。
以上類定義是非常簡化的了,但是大概演示了將現實概念對映為類以及類組合的過程,這個過程大概就是,想想現實問題有哪些概念,這些概念有哪些屬性,哪些行為,概念之間有什麼關係,然後定義類、定義屬性、定義方法、定義類之間的關係,大概如此。概念的屬性和行為可能是非常多的,但定義的類只需要包括哪些與現實問題相關的就行了。
人 – Person
上面介紹的圖形類和電商類只會引用別的類,但一個類定義中還可以引用它自己,比如我們要描述人以及人之間的血緣關係,我們用類Person表示一個人,它的例項成員包括其父親、母親、和孩子,這些成員也都是Person型別。
下面是程式碼:
public class Person {
//姓名
private String name;
//父親
private Person father;
//母親
private Person mother;
//孩子陣列
private Person[] children;
public Person(String name) {
this.name = name;
}
}
複製程式碼
這裡同樣省略了setter/getter方法。對初學者,初看起來,這是比較難以理解的,有點類似於函式呼叫中的遞迴呼叫,這裡面的關鍵點是,例項變數不需要一開始都有值。我們來看下如何使用。
public static void main(String[] args){
Person laoma = new Person("老馬");
Person xiaoma = new Person("小馬");
xiaoma.setFather(laoma);
laoma.setChildren(new Person[]{xiaoma});
System.out.println(xiaoma.getFather().getName());
}
複製程式碼
這段程式碼先建立了老馬(laoma),然後建立了小馬(xiaoma),接著呼叫xiaoma的setFather方法和laoma的setChildren方法設定了父子關係。記憶體中的佈局大概如下圖所示:
目錄和檔案
接下來,我們介紹兩個類MyFile和MyFolder,分別表示檔案管理中的兩個概念,檔案和資料夾。檔案和資料夾都有名稱、建立時間、父資料夾,根資料夾沒有父資料夾,資料夾還有子檔案列表和子資料夾列表。
下面是檔案類MyFile的程式碼:
public class MyFile {
//檔名稱
private String name;
//建立時間
private Date createtime;
//檔案大小
private int size;
//上級目錄
private MyFolder parent;
//其他方法 ....
public int getSize() {
return size;
}
}
複製程式碼
下面是MyFolder的程式碼:
public class MyFolder {
//資料夾名稱
private String name;
//建立時間
private Date createtime;
//上級資料夾
private MyFolder parent;
//包含的檔案
private MyFile[] files;
//包含的子資料夾
private MyFolder[] subFolders;
public int totalSize(){
int totalSize = 0;
if(files!=null){
for(MyFile file : files){
totalSize+=file.getSize();
}
}
if(subFolders!=null){
for(MyFolder folder : subFolders){
totalSize+=folder.totalSize();
}
}
return totalSize;
}
//其他方法...
}
複製程式碼
MyFile和MyFolder,我們都省略了構造方法、settter/getter方法,以及關於父子關係維護的程式碼,主要演示例項變數間的組合關係,兩個類之間可以互相引用,MyFile引用了MyFolder,而MyFolder也引用了MyFile,這個是沒有問題的,因為正如之前所說,這些屬性不需要一開始就設定,也不是必須設定的。另外,演示了一個遞迴方法totalSize(),返回當前資料夾下所有檔案的大小,這是使用遞迴函式的一個很好的場景。
一些說明
類中定義哪些變數,哪些方法是與要解決的問題密切相關的,本節中並沒有特別強調問題是什麼,定義的屬性和方法主要用於演示基本概念,實際應用中應該根據具體問題進行調整。
類中例項變數的型別可以是當前定義的型別,兩個類之間可以互相引用,這些初聽起來可能難以理解,但現實世界就是這樣的,建立物件的時候這些值不需要一開始都有,也可以沒有,所以是沒有問題的。
類之間的組合關係,在Java中實現的都是引用,但在邏輯關係上,有兩種明顯不同的關係,一種是包含,另一種就是單純引用。比如說,在訂單類Order中,Order與User的關係就是單純引用,User是獨立存在的,而Order與OrderItem的關係就是包含,OrderItem總是從屬於某一個Order。
小結
對初學程式設計的人來說,不清楚如何用程式概念表示現實問題,本節通過一些簡化的例子來解釋,如何將現實中的概念對映為程式中的類。
分解現實問題中涉及的概念,以及概念間的關係,將概念表示為多個類,通過類之間的組合,來表達更為複雜的概念以及概念間的關係,是計算機程式的一種基本思維方式。
正所謂,道生一,一生二,二生三,三生萬物,如果將二進位制表示和運算看做一,將基本資料型別看做二,基本資料型別形成的類看做三,那麼,類的組合以及下節介紹的繼承則使得三生萬物。
類之間的關係除了組合,還有一種非常重要的關係,那就是繼承,讓我們下節來探索。
未完待續,檢視最新文章,敬請關注微信公眾號“老馬說程式設計”(掃描下方二維碼),深入淺出,老馬和你一起探索Java程式設計及計算機技術的本質。用心原創,保留所有版權。