列表怎么有限的初始化为零_《零基础学习Android开发》第五课 类与面向对象编程1-1...
視頻:《零基礎學習Android開發》第五課 類與面向對象編程1-1
類的定義、成員變量、構造方法、成員方法
一、從數據與邏輯相互關系審視代碼
通過前面的課程,我們不斷接觸Java語言的知識,不斷增加自己的語言表達能力。到現在為止,我已經可以高興地向大家宣布你的語言能力其實已經可以讓完成一個完整的程序了!可是,有的同學會問,那不對啊!我現在還知道怎么操作圖形界面,不知道文件怎么讀寫,不知道數據庫如何訪問,不知道網絡如何連接……這個不用急,因為這些并不屬于語言的范圍,而是關于如何使用平臺資源的事。并且這些使用資源的知識你們只是不知道該如何找,找到了學起來其實是非常簡單的。再說,這些內容我們后面的課也會講的。但是,有一個問題。那就是我們現在所有的代碼都是寫在一個文件里的,也就是在MainActivity.java里。如果要做的事多了,復雜了,肯定要把代碼分到不同文件里,這樣才使代碼文件有一個比較好的組織結構,今后維護也方便些。那該依據什么邏輯原則來劃分文件呢?
我們重新看一下現在已經寫了的代碼,就會發現,代碼就是由數據結構與算法組成的。一部分代碼就是講這是什么數,這些數是怎么組織的。另一部分代碼就是講數該如何算。再看一下就會發現,有些數據結構與一些邏輯代碼之間的聯系很緊密,而與另一些邏輯代碼的關系則不大。比如說,
int[] pokers = new int[52]; // 一副牌組成的數組 for(int i = 0; i < 52; i++){ pokers[i] = i + 1; // 初始化牌數組,序號從0~51,點數從1到52 } pokers = shuffle(pokers); // 調用洗牌方法可以看到pokers這個數組與初始化數組、洗牌算法的關系比較緊密,在后面的代碼中,只在發牌的時候才引用了一次。而表示每個玩家手里的牌的數組則是與叫牌、拿牌、求和、亮牌等邏輯代碼相關度較高,而且因為有兩個玩家,所以類似的邏輯代碼有兩處。這樣就給了我們一個印象,那就是在代碼中,一些數據和一些邏輯的關系是緊密的,是應該在一起的。我們通過前面的學習知道了數據與數據是可以組合在一起成為數據結構的,比如數組。那有沒有可能把數據與處理它的邏輯代碼組合在一起也做成一種結構呢?有的。這就是“類”。在介紹類的基本概念之前,我們可以試著想想按剛才所說的數據和邏輯的組合要構造類的話應該包括哪些內容。首先說第一組。這可以概括成一副撲克牌,它的內容包括:
根據以上分析,我先把撲克牌的“類”寫出來。在IDE左側的項目工具窗口中右擊“app>java>com.example.helloworld”,在彈出菜單中選擇“New>Java Class”,在對話框的Name一欄中填入類名Pokers,這樣就新建了一個Pokers.java文件,在該類文件中敲如下代碼:
package com.example.helloworld;public class Pokers { private int[] pokers; // 成員變量,一副牌的數組 private int index; // 成員變量,表示當前發牌的序號 // 構造方法 public Pokers(){ index = 0; this.pokers = new int[52]; // 創建成員變量數組 for(int i = 0; i < 52; i++){ pokers[i] = i + 1; // 初始化牌數組,序號從0~51,點數從1到52 } pokers = shuffle(pokers); // 洗牌 } // 成員方法。洗牌 private int[] shuffle(int[] nums) { java.util.Random rnd = new java.util.Random(); for (int i = nums.length - 1; i > 0; i--) { int j = rnd.nextInt(i + 1); int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } return nums; } // 成員方法。發一張牌,即給調用者返回一張牌的數值 public int getNextPoker(){ int poker = pokers[index]; index++; // 發完一張牌后序號加1 return poker; }}同學們可以看到,在這個類Pokers里,有數據pokers和index,還有邏輯代碼,即對這些數據進行處理的方法Pokers、shuffle和getNextPoker,分別完成初始化、洗牌與發牌的功能。這就是我們構建的第一個類。
二、類
1. 類的基本組成
從上面的Pokers類可以看到,組成一個類的基本部分是4個,分別是類聲明、成員變量、構造方法與成員方法。
在Pokers類的聲明public class Pokers中,public是訪問修改符。類的訪問修飾符共有4種,我們先不講有何區別,我們都寫為public就行。class是關鍵詞,表明這是一個類。Pokers是類名,類名的第一個字母一般大寫,如果是由多個單詞組合成的,則每個單詞第一個字母都大寫。
pokers和index都是成員變量,英文是field(也有的翻譯成“域”或“字段”),成員變量在整個類的范圍內都能進行訪問,因此它是不同于方法內部聲明的變量(那個叫部局部變量)的,也可以說成員變量的作用域是整個類。在它們之前的private是訪問修飾符,這表明它是私有的,意思是只能在本類的內部才能訪問,而不能被類之外的調用者訪問。如果將修飾符改為public就可以讓類外的調用者訪問,但是最好不要使用這種方式。因為這種方式使得類內部的數據可以被隨意改變,這很容易失控。類里的數據應該通過類的成員方法來訪問,這樣取值的時候可以根據需要對數值經過某種轉換再給出,比如我們的程序里數組里存的是牌的序號,可以將其轉為實際點數再給出。改變數值的時候,也可以加入某些限制免得超出范圍。比如我們的數組中存入的值就應該在1~52之間。
與類名相同的方法是構造方法,英文是constructor,在類的變量被創建(或者叫“初始化”。大家記得吧,類是引用類型,因此創建類的對象要用new)時首先被調用。構造方法沒有返回值,參數列表可以有也可以沒有。構造方法可以是多個。如果沒有給出構造方法,編譯時會自動生成一個不帶參數列表的默認構造方法。構造方法訪問修飾符有public、protected、private3種,可以從中任選一種,我們現在暫時只用public,也就是公共的,可以讓外部調用的。private的成員變量沒有默認值,必須被初始化,在構造方法里對成員變量進行初始化,是比較合適的,當然也可以進行其它操作。
方法在上一課已經講了,在類的內部就叫成員方法。這里主要講一下它的訪問修飾符,這里在shuffle前面是private,意思是私有的,只能本類中被調用,而getNextPoker之前是public,意思是公共的,可以在其它類中被調用。
以上類的4個組成部分,除了類聲明是必須的,其它都是可選的。可以只要成員變量,比如我們聲明一個學生類:
public class Student{ public int index;// 學號 public String name;// 姓名}這樣就是一個純數據結構,其中的成員變量必須是public的,不然外部訪問不到就沒有意義。同學們可以看到,這種數據結構就可以把不同類型的數據組織在一起。上一課中方法輸出時也如果把sum與text兩個變量組織成一個類,就可以輸出兩種數據。
但是這種在成員變量之前加public的用法沒有把類的優勢發揮出來。如果只是想用類來組合一個數據結構,最好也是將成員變量設為private,而用成員方法來對成員變量進行存取。上面的Student可以改為如下代碼:
public class Student{ private int index; // 學號,私有的成員變量,外部不可訪問 private String name; // 姓名,私有的成員變量,外部不可訪問 public Student(){ // 私有成員變量沒有默認值,必須初始化 this.index = 0; this.name = ""; } public int getIndex() { // 獲得學號,公共的成員方法,外部可訪問 return this.index; } public void setIndex(int index) { // 設置學號,公共的成員方法,外部可訪問 this.index = index; } public String getName() { // 獲得姓名,公共的成員方法,外部可訪問 return this.name; } public void setName(String name) { // 設置姓名,公共的成員方法,外部可訪問 this.name = name; }}在存與取的方法里都可以加上一些對數據進行限制或轉換的代碼,這樣可以保證數據的安全,也可以增強數據的外部可讀性。
這里可以看到訪問成員變量時,用的是“this.”的方式,this表明的是本類的對象,“.”是成員訪問操作符。通過在index與name前面加上“this.”就表明是成員變量,就可以與參數列表里的參數變量進行區別。我給大家舉這個例子呢,目的是讓大家不要對類的概念產生陌生感,你完全可以把類就看成一個數據結構,但是這個數據結構里不僅放了數據,還放了處理這些數據的方法。這樣程序文件就有了很好的組織性。
也可以不要成員變量,只要成員方法。比如:
public class ToolsClass{ public void doSth(){ } public int getNum(){ return 0; }}內部只有方法的類一般作為工具類,因為其成員方法的代碼與成員變量無關,可以在方法前上static,靜態的,這就成為靜態方法。上面的類可以改為:
public class ToolsClass{ public static void doSth(){ } public static int getNum(){ return 0; }}這樣的好處是調用時不需要先用new來創建類的對象,而是可以直接通過類名來調用,如ToolsClass.doSth()、ToolsClass.getNum()就可以。static不僅可以加到成員方法前面,也可以加到成員變量前,至于靜態與非靜態成員的區別,我在后面再講。
2. 創建類的對象并訪問其成員
因為類是引用類型,因此創建類的變量(也稱為對象Object、實例Instance)是用new,創建出對象后,就可以通過該對象加“.”來訪問其公共成員方法或變量了。比如上面聲明的Student類:
Student s = new Student(); s.setIndex(1); s.setName("Tom"); int index = s.getIndex(); String name = s.getName();三、用類來重新組織代碼
我們重新看一遍已經建立的Pokers類,它是將對一副撲克牌相關的數據與邏輯都移入該類之中(術語叫“封裝”,英文為Encapsulation)。現在我們把與它相關的代碼變換為使用Pokers的對象來進行。原來的代碼為:
int[] pokers = new int[52]; // 一副牌組成的數組 for(int i = 0; i < 52; i++){ pokers[i] = i + 1; // 初始化牌數組,序號從0~51,點數從1到52 } pokers = shuffle(pokers); // 調用洗牌方法調整后的代碼為:
Pokers pokers = new Pokers();因為我們把順序點數放牌數組及洗牌的動作全放在構造函數里了,因此當Pokers類的對象pokers被創建時,我們就有了一副已經洗好的牌了。在調用者的角度,屏蔽了很多細節,同時如果發現與撲克牌相關的數據與邏輯有問題的話,也能很快定位到Pokers類中,不用去其它地方找Bug。這就是封裝的好處。后面發牌的動作,也用pokers.getNextPoker()一條語句就可以了。調用者不用考慮具體的實現細節,只要知道要調用類對象中的哪個方法能得到自己想要的東西就行。按照這個思路,程序的主要邏輯就可以變得非常的簡潔。主要邏輯不用操心細節,就需要知道把工作“分類”,不同的工作分給不同的類,再找這些類的對象要東西就行。是不是有點領導的感覺了?事來了,這個事是業務部的,那個事是財務部,那個事是人事部的,通通分下去,再找業務部安排的小李、財務部的小劉和人事的小張,跟他們要東西,這事就辦妥了。
我們接著往下看代碼。我們看一下原來的代碼:
int[] pokersA = new int[4]; // 玩家手里的牌 int[] pokersB = {7, 8, 0, 0}; // 電腦手里的牌 for(int i = 0; i < 4; i++){ pokersA[i] = pokers.getNextPoker();// 改為通過Pokers類對象獲得下一張牌 // 獲得當前玩家手中牌的總點數 String[] texts = {""}; int sum = getSum(pokersA, texts); // 在玩家手中牌點數大于電腦時停止叫牌 if(sum > 15){ break; } } // 顯示玩家手里的牌及總數 String[] texts = {"你手中的牌分別是:"} ; int sumA = getSum(pokersA, texts); texts[0] += "總數是:" + sumA + "。對家手里的牌是:"; // 顯示電腦手里的牌及總數 int sumB = getSum(pokersB, texts);這里是否還有數據與邏輯相關似很強呢?有的,數組pokersA與發牌、結算的邏輯相關性強,數組pokersB沒有發牌邏輯,是因為我們做了簡化,其實是應該有的,它也有結算邏輯。pokersA與pokersB代表的是兩個玩家手里的牌,我們是否需要建立兩個類呢?答案是不需要,因為雖然是兩個玩家,但是每個玩家數據的結構以及操作數據的邏輯是完全相同的,唯一不同的是數據的值。而數據的值是在類的對象并創建之后再通過各種方法去設置的,因此值的差異只是對象之間的差異,而不是類的差異。就像int類型的兩個變量,它們的值不同,但它們都是int型的。如果它們的值相同,那也是同類型的不同變量。相同的,同一類的不同對象,它們的成員變量的值不同,但都是同一類的。就算他們的成員變量值相同,也是同一類的不同變量。這也好比人就是一個類,人與人之間的很多數據的值是不一樣的,高矮胖瘦、年齡姓名、生活經歷,但是基本功能是相同的,能跑能跳能吃能睡。只是因為其數據值的不同,造成其功能運行時的差異。在這個游戲里,兩個玩家的功能(即行為邏輯)是相同的,出現差異的原因是其成員變量的值不同造成的。因此,它們是同類的不同對象。我們歸納一下玩家類的包括哪些內容:
根據以上信息,我們可以寫出下面的“玩家類”:
public class Player { private int[] pokers; private int index; public Player(){ this.pokers = new int[12]; // 最極端情況下拿到4個A,4個2,4個3,因此長度設為12即可 this.index = 0; } // 要一張牌 public boolean wantPoker(int num){ if (this.index >= 12) { // 限制性代碼,避免數組序號出錯 return false; } this.pokers[index] = num; index++; // 序號加1 return true; } // 獲得當前的點數之和 public int getSum(){ int sum = 0; for (int i = 0; i < this.pokers.length; i++){ if (pokers[i] == 0){ break; } sum += (pokers[i] - 1) % 13 + 1; // 獲得真正點數 } return sum; } // 獲得牌局結算信息 public String getStatString(){ String txt = ""; for (int i = 0; i < this.pokers.length; i++){ if (pokers[i] == 0){ break; } switch ((pokers[i] - 1) / 13){ // 根據真正的花色的值進行選擇 case 0:{ txt += "黑桃"; break; } case 1:{ txt += "紅桃"; break; } case 2:{ txt += "梅花"; break; } case 3:{ txt += "方片"; break; } } txt += ((pokers[i] - 1) % 13 + 1) + ","; // 獲得真正點數的字符串 } return txt; }}上面這個Player類就實現了我們對它的要求。但是看這段代碼我們可以發現一個問題:原來我們說所有與撲克牌實現的細節是被封裝在Pokers類里面的,但是在Player類的getSum與getStatString兩個方法里,卻要知道關于牌的很多細節,要知道真正的點數值與花色值如何求出,還要知道如何將花色值翻譯成字符表達,這是與我們上面表達的原則是相違背的。因此,這兩個方法里的很多實現的細節應該被放到Pokers類里去。我們對Pokers類進行擴充,加幾個方法:
// 獲得真正點數public static int getCount(int num){ return (num - 1) % 13 + 1;}//從牌的序號中得出花色值public static int getColor(int num){ return (num - 1) / 13; }// 從牌的序號中得出花色值的文字表達public static String getColorString(int num){ String txt = ""; switch (getColor(num)){ case 0:{ txt = "黑桃"; break; } case 1:{ txt = "紅桃"; break; } case 2:{ txt = "梅花"; break; } case 3:{ txt = "方片"; break; } } return txt;}這樣關于撲克牌的實現細節就真正地全部封裝在Pokers類里了。因為這3個方法并不是對Pokers的成員變量進行操作,也就不需要成為對象方法(對象方法需要通過實例化的類的對象才能調用),我們在前面加上static修飾符,使其成為靜態方法,這樣調用時直接使用類名就可以調用了。再重構Player類為如下代碼:
public class Player { private int[] pokers; private int index; public Player(){ this.pokers = new int[12]; // 最極端情況下拿到4個A,4個2,4個3,因此長度設為12即可 this.index = 0; } // 要一張牌 public boolean wantPoker(int num){ if (this.index >= 12) { // 限制性代碼,避免數組序號出錯 return false; } this.pokers[index] = num; index++; // 序號加1 return true; } // 獲得當前的點數之和 public int getSum(){ int sum = 0; for (int i = 0; i < this.pokers.length; i++){ if (pokers[i] == 0){ // 為0時表示當前已經沒牌了 break; } sum += Pokers.getCount(pokers[i]); // 獲得真正點數 } return sum; } // 獲得牌局結算信息 public String getStatString(){ String txt = ""; for (int i = 0; i < this.pokers.length; i++){ if (pokers[i] == 0){ break; } txt += Pokers.getColorString(pokers[i]) + Pokers.getCount(pokers[i]) + ","; } return txt; }}現在我們已經建立了管牌的Pokers類和玩牌的Player類,那么一個牌局就可組起來了。讓我們開干,把主程序的調用代碼改了:
Pokers pokers = new Pokers(); pokers.getNextPoker(); Player p1 = new Player(); Player p2 = new Player(); for(int i = 0; i < 4; i++){ p1.wantPoker(pokers.getNextPoker()); p2.wantPoker(pokers.getNextPoker()); } String text = "player1:" + p1.getStatString() + "總數:" + p1.getSum() + ",player2:" + p2.getStatString() + "總數:" + p2.getSum(); TextView txtResult = (TextView)findViewById(R.id.txtResult); txtResult.setText(text);運行以后,效果如下:
同學們可以看到變為使用類以后,主程序的邏輯變得清晰簡單了(因為我們還沒有做是否要牌的決策方法,就用每個玩家發4張牌來代替),每個具體的工作都有專門的類來管理與執行,主邏輯只需要指揮(調用)對應的對象就能把整個任務完成好。這看起來是不是就很像我們真實社會運行的狀態了呢?我們可以看到我們的代碼發生重大的變化,這是因為我們對代碼的組織方式發生了變化,而這個新的方式就是“面向對象編程”。
總結
以上是生活随笔為你收集整理的列表怎么有限的初始化为零_《零基础学习Android开发》第五课 类与面向对象编程1-1...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java web 实现视频在线播放的常用
- 下一篇: android 选择 播放器,Andro