# 戰鬥營第二個月上課筆記 --- ## 一、小知識 ### 1.版控系統-git 1. Github 開放原始碼 2. GitLab (網頁版、安裝版) : 免費版好用,公司版也好用 ← 將很多東西整合 3. Bitbucket 私人(五人以內免費) 4. 紅色 減 字號 代表文件被刪除 5. 黃色 三個點 代表文件被修改 6. 問號 代表文件新創建 7. master 這條支線 代表使用者在用的版本,基本上只會是完全沒問題,發布時使用 8. develop 基本上會有無限多個分支,每個功能都會開一個feature / 功能 , 實務上有時候會在feature 完成後在該分支關閉,會在開發到一個段落時合併回develop\\ 9. release 去做測試,測試沒問題才會到master 10. hotfixes 是master 如果有遇到BUG要做時拉出來的 11. git 裡面的readMe 都是MarkDown 檔案 12. 可看第6周的簡報-版本控制管理 補充資料:[terminal上傳新的專案](https://rommelhong.medium.com/%E4%B8%83%E5%88%86%E9%90%98%E5%AD%B8%E6%9C%83gitlab-ecdcbcb42b9c) ### 2.電腦的圖片概念 1. 電腦螢幕是一顆一顆的顆粒組成,假設800*600,代表橫的有800個點,直的有600個點,因此如果螢幕越大,看起來的密度很小,所以顆粒感會很重,但如果是在螢幕很小的情況,密度就很大,還是會很清楚,而每一顆顆粒,都會儲存一個色碼,以此將圖片顯示出來。 2. 圖片格式有三種: jpg png bmp = bitmap ( 也就是二維陣列的概念) 3. 色碼的fffffd 是16進位的表示法。 4. 圖片的bmp格式也就是一個二維陣列的概念,也就是800 * 600 = 二維陣列(800 * 600)每一格代表一個色碼,而因為ARGB的存在(255 * 255 * 255 * 255),每一格會以4Byte存取,因此圖片最小也會用到1.92MB (但因為有些壓縮的情況存在,因此有可能小於此數值)。 5. jpg的壓縮,是先透過將相同的顏色取出,EX:只有用到600種顏色,就可以用比較少的Byte數去存取每個顏色,然後再將其存成圖片內容,在圖片內容上方會放入編碼(也就是原本第幾個顏色= 擁有的顏色) 6. 圖片放大會失真的原因,就是在於他只能將原先擁有的顏色等比例給予,因此會破壞原本的比例。 - 示意圖 ![](https://i.imgur.com/ys5x6C9.png) ### 3.基礎觀念補充 1. String 跟其他參考資料型態的差異是編譯時期時字串會存到String pool 執行時期的不會 2. lambda產生方法,匿名內部類別產生類別 3. 內部類別與靜態內部類別的差異,有沒有依賴外部類別的實體 4. String s1 = "runoob"; String s2 = "runoob"; 字串 "s1 == s2 is:" + s1 == s2 ,要注意程式的順序,程式的順序從左至右, 會先做"s1 == s2 is:" + s1 才做跟s2 的比較,因此會是flase。 5. bulider,除了自己外也有可能用另外一個類別作參數,構造細節細化,可利於封裝與擴充,因為有些數值可選,還是有可能會有情況用到建構子創建(EX: bulider裡面再利用,或是可選擇要不要用bulider) 6. A == B 只要是同個類型,都可以自動轉型 EX: int,short,byte,long,float,double。 7. 不同值經過Hash 出來的結果,還是有機率會相同,或是經過碰撞後相同 --- ## 二、設計模式(Design Pattern) ### 1.單例模式 1. 為了要讓所有的檔案可以統一管理,讓其他地方拿到的都是同一個物件實體,以達到此功能,利用static 進行。 2. 還是要注意不能濫用單例模式,否則程式會更加混亂。 3. 方式: 在該類別中直接放入一個static 的自己,並讓外面可以拿到裡面的成為屬性,並且讓判斷為(如果這個物件還沒產生,是null 那就讓自己 = 物件),否則傳回物件。 4. 但要特別注意要傳回的是物件的變數,還是static的變數,因為單例模式,還是可以有其他非static 的成員屬性。 - 範例code ```java package test; public class Test { //單例模式 private static Test test; private int a; public static Test instance(){ if (test == null){ test = new Test(); } return test; } public int a(){ // 因為一定是有創建了Test 才會有a ,如果變數宣告成static // 會導致a一定要一直有值,就不會是當前操作的物件的值了 return a; } } ``` ### 2.迭代器模式 1. 建立一個公開介面 Iterator 走訪一個集合地所有節點的設計模式。 2. Iterator 一定要有: 1.什麼時候走完→確認是否有下一個節點可以尋訪(確認終點):(boolean hasNext() ) 2.找下一個(拿出當前的,依照找的邏輯指到下一個)→找出下一個節點: `<T> next()` T 看你的陣列是什麼資料型態。 3. ```java ArrayList EX: current = int public <T> next(){ <T> tmp = arr[current]; current ++; return tmp; -> return arr[current++]; } public boolean has next{ if( current >= count){ count = 0; return false; } return true; } linkedlist EX: current = Node <-代表位置 public <T> next(){ <T> -> data的資料型態 current = current.next; return current.data; } public boolean has next{ if( current.next == null){ current = root; return false; } return true; } ``` 3. [範例code-Notion](https://www.notion.so/0929-5a1190cf52584f1a9fccca3a27bbf89d) --- ## 三、資料結構-LinkedList 1. LinkedList取得資料時 要耗費較大記憶體,因為要從第一個開始找,除非要找全部,那就可以以一個變數暫存,這樣只需要抓這個暫存的下個即可,這樣迴圈就會跟ArrayList跑一樣的次數。 2. LinkedList 特點: 1. 不需要將元素儲存在一塊連續的記憶體中,可儲存在任一位置,建立link將元素相連(不用doubleArr) 2. 插入、刪除省資源(不用移動大量數據) 3. 可以動態的分配元素 4. Node ( Data & Link(pointer)-儲存下一個Node的參考) 5. 會有一個參考變數,指向第一格元素 3. LinkedList,在創建、插入、刪除時,很省記憶體,因為只需要將位置指向變換就行,不須更改記憶體長度,但是不論做上述哪個功能都還是要找到那個位置,所以還是會耗部分記憶體的,但如果是新增時可以讓成員屬性多一個暫存結尾的位置,這樣新增就可以直接進行了(current.next),可是插入跟刪除還是需要找,但LinkedList能實現的變化比較大 4. LinkedList 跟 ArrayList的差異: LinkedList:不是一個連續的記憶體,在尋找時的for迴圈會多一圈(index + 1)才會到正確要的索引值,因為第一個是起始位置,。 優點:插入、刪除快速,新增時不用更改記憶體空間,彈性較高。 缺點:取得較慢,取出時需要從源頭開始找起。 Arraylist:是一個連續的記憶體。 優點:取得速度快,可以直接利用索引值取出。 <font color='#399'>缺點:</font>新增、插入、刪除時,都需要調整記憶體,速度較慢 5. Circular Linked List (環狀串列) 最後一個節點的連結在接回開頭的節點,形成循環, 6. Doubly LInked LIst(雙向鏈結串列)擁有兩個link(next)屬性,一個指向前一個節點,一個指向後一個節點,可以前後移動的搜尋指標的方向。 7. 範例code在二-2.迭代器模式裡 --- ## 四、資料結構-Stack & Queue 1. 只要是集合,就必須可以被迭代 Iterable ( 參考迭代器模式的簡報) 2. 有序的集合: List ; 沒有順序的集合: Set ; Queue :佇列 ; 3. PriorityQueue : 用在資源分配 ; Vector :執行序 ; Hash 跟Map 相關。 4. 堆疊: 有順序性的資料,會有一個Top端(尾端),跟一個Bottom端(底端) 從頂部(尾端)新增元素 Pop() : 從頂部(尾端)取得元素並從堆疊中移除 EX : 系統呼叫方法、上一頁、下一頁、復原、返回、可以用在ResController 回到上個場景 所以錯誤資訊跑出來時,才會有哪一行,就像 System Stack 5. 佇列: 有順序性的資料,會有一個尾端,跟一個前端 從佇列尾端放入資料 Pop() : 從前端取得並移除元素 EX: 網路請求連線、購票系統、需要有順序性的東西就會用Queue ,只有Queue 才可以確保資訊有順序性(EX:鍵盤的輸入 CommandSolver) 用LinkedList 較好實現(java內建有實現) 環狀佇列:標籤到一定數字後,會回歸指到0(mod) 6. Queue 佇列 先進先出 FIFO (First in First out) 排隊 Stack 堆疊 先進後出 FILO (First in Last out) 7. 堆疊及佇列 最常用到的方法 : * push , insert(新增 ): statck & queue - 都是新增在尾端 * pop ,delete (取出並移動標籤): Stack 在用的pop - 從尾端拿出 * poll ,delete (取出並移動標籤): Queue 在用的pop - 從前端拿出 - peek (只看資料): 只取得,不刪除 (pop & poll 會取出後刪除) 8. java 的push 是實現stack 也就是放資料在尾端 push就像add --- ## 五、雜湊(Hash) ### 1.觀念 1. ★★★hash 重要特性: 1. 不可逆:因為不能用hash後的位置,得到他的值,因為不知道hash function且 hashing很複雜,所以基本上不可能用hashcode反推值。 2. key相同時,值就會相同:因為同樣的數字通過hash後算出來的值在碰撞前一定一樣。(目前市面上大概是百萬分之一的機率才會碰撞到); 所以如果hashcode 不同,值一定不同,但hashcode相同,值不一定不同。 3. hash絕對沒有順序,因為雜湊後絕對不會是造著順序排列的 2. 雜湊:將你的值,轉換成一個位置,也就是hashing的公式,通過雜湊實現的集合,只是一種應用,並不是雜湊本身。 排序都是n的平方次,雜湊則可以直接得到:讓我要找的值,就在我的位置上 EX: O O 3 4 5 O 7 O 9 ,這樣就可以讓我想要的位置上就是我要的值 5. Hash function : 把值丟進來(key) 經過 hashing 變成位置(i)(將key轉換為hash table 中的bucket) Hashing:定義一個Function h 且 h (Key) = i ,稱h 為 hash function Hash function 分為兩種: 1. Static hashing ( 陣列範圍不變) 2. Dynamic hashing( 陣列動態變大) - 示意圖 ![](https://i.imgur.com/0pQurPm.png) 8. 因為會直接把值的數字變成位置,所以要設定hashing的方式, java 本身會有Hash Table,在java的hashcode 就是透過java 的 hash function 放到 Hash Table中。 9. Hash Table:指在static hashing中,keys 會儲存在固定大小的表格。 是大小為0 ~ n-1(Ex: 0~7, n = 8) 長度為8的Array,共有8格 ,每一格是一個bucket,每一個bucket擁有s個 slots(通常s = 1)除非bucket裡面是放arr才有辦法多放資料,而每個slots只能對應一個bucket。 10. 理想的Hash function 每一個key 的pair 儲存在自己的位置(home bucket) ,也就是隨機選擇一個key 計算出的bucket機率為 1/b。 (b = size) Home bucket:key經過hashing後得出的位置即為其Home bucket,如果Home bucket的位置上已經有別的資料了,就會發生碰撞(但如果該bucket還有空格(slots)時,就可以繼續放資料),因此理想狀態就是每個人都不會互相碰撞。 如果是理想的狀況,search,insert,delete 只需要花 O(1) 即可得到資訊 11. 溢出 (Overflow):因為每一個bucket,都會存放一個資料,如果這個資料是一個arr,那就可能會讓key可以重複放到指定的位置,不會馬上發生碰撞,但當home bucket沒有空格給新的pair存放時,就會發生Overflow 12. 碰撞(Collision):當一個新的pair的home bucket已經有另一個pair(Key不同) 13. 溢出(Overflow) 發生時,一定會有碰撞, 當bucket只能存取一筆資料時,溢出跟碰撞會同時發生。 14. hash 最常用在 密碼加密、簽名 ,常見MDS、sha-256 EX: RSA 公鑰私鑰,通常憑證(token)都是用hash做出來的,為了避免token 被盜用,且會設定憑證的有效時間(EX: 網路郵局的登入時間),因為hash function 只有伺服器有,所以最好把更改資訊跟更改密碼分開(改密碼會利用信件、簡訊較複雜的方式),才不會發生有token就可以竄改資料。 token 會至少包裝3個資訊: 帳號 , 過期日 , Hash ( 帳號, 過期日) ### 2.Hash function(Hashing) 最主要的目的及方式 1. 如何降低碰撞,讓機率發生的極低。 (Hashing) 1. ★Hashing by Division (取除數) : 最常被使用的方式,將key除一個數字後,餘數即為bucket的位址(k % D) ,理想情況D 是一個質數 除數要選奇數、質因數要夠大(至少大於20),除出來的餘數才比較不會固定, 如果除偶數,會造成出來的結果比較固定,而質因數小的時候,容易發生偏差。 可以用兩個很大的質因數相乘當除數,因為當質因數越大,影響越小。 2. Hashing by Mid-Square (取中間) 取key的平方轉為二進位,並取中間適當位數的值,如果是奇數的話,通常往左邊偏移 (使用中間r bits,值域會落在0~2的r次方 -1) ex : r = 6 (取中間6個值) Binary = 1 0 ,0 1 1 1 0 0 ,0 1 1 ,0 0 1 1 1 0 , 0 1 會取出 0 1 1 1 0 0 如果是9位數 -> ,0 0 1 1 1 0 - 二進位 轉換 ![](https://i.imgur.com/atc1gn4.png) 3. Hashing by Folding (切割):將key 切割成數個部分,在將每個部份相加後成為位置: Shift folding :單純切割 Folding at the boundaries :切割後,將數值正反正反正的後才相加 當key為字串型態時,就先轉成數字在處理即可 - 示意圖![](https://i.imgur.com/S3GF73e.png) 2. 如何處理Overflow 1. Overflow 處理:當bucket滿了後, 通常使用以下方式: * Open addressing: 於未滿的bucket找出空位填入。 EX: Linear probing, Quadratic probing, * Random probing Chaining: 每一個 bucket 存放對應的list 來儲存所有home bucket EX: Chain, Array linear list 2. Open addressing - Linear Probing :假如home bucket 有資料,就線性往後找,直到有空格(如果到底就從頭開始,直到繞一圈),刪除很麻煩(因為還要確認他是否在home bucket上,如果不在才往前移),比較少用 3. Open addressing - Rehashing: 有碰撞到就在找一個hash function 再撞一次,直到找到為止 4. Chaining (較常用)- Chain:利用linkedList連接所有相同home bucket的值,且可以增加bucket的容量(s (bucket 的空格)會無限放大)。 亦即每一個bucket有一個list儲存所有相同home bucket的pair,後方接續的list可為array或chain。 3. 如何設定hash table 的大小。 4. 基本上hash function 不用自己實現,因為目前已有的方法都很強大了。 ### 3.Map 1. 有hash就是雜湊完的集合,且有hash就代表沒有順序,EX:hashSet hashMap Map 集合的一種,是利用雜湊做出來的類,不是雜湊本身:成對的集合(key, pair)。 2. HashSet : 沒有順序的集合,不能做排序,塞一堆資料給他,只需要一步就拿到,但很少用到。 * HashMap : 是Map的子類 Map: 左邊是key , 右邊是value ,稱為值鍵對, 型態可以自己定義(也可以是Array、Map),取出時是輸入key,不是索引值,所以key可能會是int String... 。 EX: Map<Integer, String> map = new HashMap(); 3. 同一個key 只能讀一個value 所以同一個key後面如果有放value 會覆蓋掉前面的value,因此要讓程式更快找到,就應該是搜尋key (containsKey)而不是value才不會有重複 - EX: Resource Controller 4. Map進階用法,可以用一個interFace 或 map當作value,就可以無限延伸下去。 ```java package linklistanditerator; import java.awt.Image; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; public class ImgController { // 內容 private Map<String, Image> imgMap; public ImgController() { imgMap = new HashMap<>(); } public Image tryGetImage(String path) { if(imgMap.containsKey(path)){ return imgMap.get(path); } return addImage(path); } private Image addImage(String path) { try { Image img = ImageIO.read(getClass().getResource(path)); imgMap.put(path, img); return img; } catch (IOException e) { e.printStackTrace(); } return null; } public void clear(){ imgMap.clear(); } } ``` --- ## 六、檔案處裡 ### 1.寫檔、讀檔 1. 寫檔案的過程,會動到硬碟,也就是I/O,所以到那邊程式就會變慢,因此如果程式內頻繁的在寫檔,就會跑得很慢,單機:一旦進IO,硬碟的速度決定I/O的速度;網路遊戲,就相關的是網速,因為是到伺服器。 2. 不管任何格式,都可以拆成0 跟 1,讀檔時都是讀串流stream(byte) 3. 副檔名的用途,是一種標記,表示這個標記的檔案裡面存的資料訊息是依照什麼格式放置的,其實就是一種格式。 4. 會建議大家用公開的格式,才可以讓大家都可以通用,EX: txt 、 zip 5. 開檔時很消耗效能,所以通常會在主記憶體用緩衝區(取決讀的頻率跟讀的檔案大小),暫存部分磁碟的資料,以避免頻繁做I/O。 6. 寫入時,也是一樣道理,會先用緩衝區暫存,在一次放進去磁碟中,但為避免緩衝區內資料還沒滿就沒要寫,要在手動呼叫一次存入磁碟,並且如果因為緩衝區內資料還沒存入硬碟時,就發生程式崩潰,因為資料沒進到硬碟所以會爆掉。 7. 寫檔通常不會跟資源(resource)連在一起,讀檔才有可能。 8. 用預設的方式開檔案,就會被覆蓋,另外一個方式就是另外存,但基本上會以覆蓋為主,因為比較簡單。 9. 寫檔時(BufferedWriter、FileWriter) 如果輸入的檔案不存在時,FileWriter會自動幫你建立,但如果希望在某個資料夾的話,沒有這個資料夾他不會自動建立,只會自動建立檔案。 10. 建出來的檔案,如果直接編輯也會導致讀取時會有資料,但如果存檔是覆寫的話,資料又會被蓋掉,要別注意。 11. 讀檔時(BufferedReader、FileReader),大部分都是以行在讀取(readLine()),除非有特別需求才會用特定的方式去讀檔(read() - 可以指定長度),大部分情況會是readLine搭配ready做。 12. 讀檔時如果找不到檔案,會直接錯誤。 13. * readLine() 每次讀完一行時,光標都會自動到下一行的開頭; * ready() 判斷光標是否到底(最後一行) ; * reset() 讓光標回到前面; * skip() 跳過一定數量 14. 副檔名.csv 的資料是用逗點做分隔(每個欄位),excel開過的csv檔,格式可能會跑掉,因此要特別注意,但csv檔系統會建議用excel開啟,然後excel會用逗點做分隔變成一格一格的欄位。 寫csv一樣要換行 15. 通常read() 會在做處理(因為系統不會因為副檔名就幫你做處哩,要自己處裡)EX: .csv 是用逗號區隔,所以我們在讀取時,要把該行的資料,做splt 切割每欄應該有的資訊。 16. 不要在一個while做兩次readLine() 因為這=每次會讀兩行,萬一行數剛好是奇數時,最後一行資料可能會讀不出來或報錯(因為ready() 判斷可能為false,或是讀取不到資料)。 17. 通常會建立資料類別來做存取,及利用interface寫各自的檔案型別,以達到彈性。(EX: date、FileIoInterface。),會用interface 是因為可能會有不同的資料類別,為了要讓對應的資料類別可以做處裡(行為上的不同)。 ### 2.序列化 1. java才有,現在比較少被使用,但常被問。 2. java自己提供的寫檔案格式,用ObjectOutputStream 讀取 3. 只要implements Serializable 不用其他事情就可以把它變成序列化,但是所有裡面的成員屬性的資料型態要是可以被序列化才行(基本資料型態、String、或一些java有先寫的類,或是有被實現Serializable的類別才可以)。 4. 實現時,java會自動給予一個隱藏的屬性serialVersionUID-給予一串long的整數,如果你有改資料時,系統自動給予的也會變化,因此如果沒有重寫資料,serialVersionUID不同是就會報錯,因此可以自己創建一個serialVersionUID屬性,自己去做控制,數值是很大的Long整數,如果只是要小幅修改資料的話就可以這樣用,有大改時再更改版號。 `private static final long serialVersionUID = XXXXL` 5. 序列化還是有可能被修改,但比較難一點。 6. 序列化後,要改類別之前都要些把資料放進來再讀進去,不然移動位置後序列會錯誤。 [範例code-Notion](https://www.notion.so/1013-317d5dc3d7ba4f5b86a658b3e91265d5) --- ## 七、泛型 1. 泛型是多型的一種延伸。 2. 泛型與多型主要差異: 多型是執行時期才能決定他的型態,對物件來說。 泛型的使用時期是編譯時期做的事情,對模板來說。 3. ?型態通配字元不能等於Object,是一種未知型態。 ### 1.基礎觀念 1. 泛型是為了降低過多重複使用的程式碼,增加程式碼使用上的彈性,泛型可以在創建型別/使用函數時,指定使用的型別(替換其定義於其中的標示符號) 2. 泛型就是編寫模板代碼來適應任意類型,就像是為類別定義一種模板(類別型態-參考資料型態),然後讓這一個類別裡的東西通通都是這個資料型態,泛型的好處是使用時不必對類型進行強制轉換,它通過編譯器對類型進行檢查。 泛型通常用在要建立模組工具時才會用,平常比較會用到泛型而不是用他來建立方法 3. 如果泛型沒有給型態時,就會自動給Object型態 4. 泛型的宣告方式: 1. 創建類別時: `public class Node<T>{}` 2. 泛型類別裡的function,需回傳資料時 : public T get(int index){ return x;} ; public void setData(T data){} 3. 成員屬性: private T num; private T[] arr ,泛型的陣列,要先用Object[ ] 創建 然後再強轉T[ ] 即可 4. 創建實體物件時: 類別名稱<參考資料型態EX: String. Integer > 變數名稱 = new 類別名稱<參考資料型態EX: String. Integer >(); =號後面的String.Integer可以省略,只寫<> 5. 泛型方法(非一般使用) 修飾詞後面 `private static <T> void swap(T[] arr, T i1, T i2)` 泛型方法在塞通配字元是有點硬要的方式,偶爾會用。 5. T可以使用於類別中作為一種型態去定義變數、參數、回傳值等,T只是一個代名詞~ 可以隨意更改,但由於為了好辨別,通常會使用有意義的大寫字母,可參考後方表格。 T也可以是任何class(EX:String, Float, Integer, Person),class只能是"參考資料型態",不能是基本資料型態(int,boolean)。 6. 泛型類型`<T>`不能用於靜態方法,但是靜態類別可以。 7. 泛型在定義時,可以有兩個以上的標示符號,EX: 內建的Map就是(K,V) 兩個標示符號,去對應兩個不同型態。 8. T extends Class 亦即T = 該Class 或 該Class的子類, 但該Class的父類是不行的。 9. 內部類別如果是泛型`<T>`外部類別是泛型`<T>`內部的T會=外部的T,不管代名詞是否相同。 10. 在定義標示符號時,通常會使用有意義的大寫字母 E:Element,常用在java Collection裡,如: `List<E>,Iterator<E>,Set<E>` K,V:Key,Value,代表Map的鍵值對 N:Number,數字 T:Type,類型,如String,Integer等等 11. 在T還沒做任何處理之前,內部內容,只能用到Object 除非T有先繼承其他類別,才有辦法拿到該類的方法,且在裡面基本上不太能直接給予定值(因為還無法準確判斷是什麼資料型態,只能用方法,且方法只能用該繼承的class方法(因為只能確定一定是那個class,不一定是他的子類))。 - 範例code ```java package iopratice; // 寫在class定義一個T型態(表示未知的型態) public class Node <T extends Number> { // <T extends Number>亦即T 一定是Number 或 Number的子類 T data; //使用一個T型態的資料 ,在T還沒做任何處理之前,只能用到Object 除非T有先繼承其他類別,才有辦法拿到該類的方法 Node<T> next; //如果這邊沒有放入<T>,會導致Node型態不一 public Node(T data){ setData(data); } public void setData(T data){ this.data = data; } public T getData(){ return data; } public final void setNode(Node<T> node){ this.next = node; } public Node<T> getNode(){ return next; } } ``` ### 2.型態通配字元 1. 「?」是泛型的萬用字元(通配字元),表示任意的物件型態。 跟型態參數`<T>`的差異是,T代表的是同一種型態, ? 則沒有這個約束。 " ? " 使用時機 :允許傳入的泛型類別可以指定一定範圍的類別型態,?代表只出不進,因為只能確定是object。 2. 上界通配(? extends Shape) :只出不進,讓他知道可以的型態天花板是哪個class,還沒辦法確認外面是什麼類別被放進來,只能確定他最多到哪個類別,因此只能使用到該類別的方法,簡單說,set的功能會失效,只剩下get的功能。 3. 下界通配( ? super Rect):只進不出,知道最下面的類別是誰,只有他及他的父類可以,但是 get 會得到Object,因為一定符合多型,可以塞入Rect 跟 Square,因此只要一個Compare即可通用所有人,簡單說set正常,get部分失效。 所有的父類別可以被創建,但只有子類別可以被往內存。 4. 泛型的extends (繼承通配符號): 簡單來說,假設先宣告 < Number > 型態,但後面想要用Integer( 是Number的子類別),如果直接寫`<Integer>` 編譯器無法讀取,需要寫< ? extends Number> 編譯器才會去判度該? 是不是 後面的資料型態的子類別。 5. 使用類似`<? extends Number>`通配符作為方法參數時表示: 方法內部可以調用獲取`Number`引用的方法,例如:`Number n = obj.getFirst();` 方法內部無法調用傳入`Number`引用的方法(`null`除外),例如:`obj.setFirst(Number n);`。 → 因為如果方法是要給Integer使用的的話 即一句話總結:使用`extends`通配符表示可以讀,不能寫。 6. 使用類似`<T extends Number>`定義泛型類時表示:泛型類型限定為Number以及 Number的子類。 7. 隱含轉換,沒做強轉時,自動幫忙向下轉型 8. 向上轉型-多型;向下轉型-強制轉換或隱含/隱式轉換。 9. ?通配符號,用在不需要定義其形態(無法確定),只會說上面是什麼或下面是什麼,因此通常用在方法上,下界取出來是一個類似Object的物件(不是Object)。T定義一種型態,因此通常class要用T。 10. 泛型彈性->自己的彈性越低,外面的彈性越高。 11. kotlin的泛型有點不一樣,是用*字元,沒辦法隱轉,kotlin用with實現,不用寫builder ### 3.進階觀念 1. 使用ArrayList時,如果不定義泛型類型時,泛型類型實際上就是Object。 2. 泛型除了用在類別以外,也可以用在介面上,只是一定要實現介面裡的方法,這樣就可以達成不同calss共用interface的特定功能了(Ex: 有兩種class動物、植物, 但都需要做排序處理,如果是原本的interface 就只能做兩個compare的interface 分別傳入動物以及植物,無法直接做比較。 3. 泛型的實現方法是擦拭法,其實在java虛擬基看到的code跟我們在打的時候一樣,但因為泛型是在編譯時期,讓編譯器先幫忙安全的強制轉型,將我們所寫的標示符號替換成對應的類別型態,EX: T = String; 所以全部的T都變成String;也因此泛型會有一些限制。 1. 不能是基本資料型態,因為Object 不能擁有基本資料型態。 ex:`int` 2. 不能取得泛型物件實體的class,因為所有使用泛型的class 都會指向同一個物件實體。 ex:`ClassPair<String>.class` 3. 相同道理,也無法向下轉型取得物件實體的類型 →使用泛型,並不像是繼承,像是用取代方式將資料型態兌換。例如:`x instanceof Pair<String>` 4. 不能實體化 T(標示符號)例如: ``` T new T() ``` - ex: ```java first = new T(); last = new T(); 擦拭後實際上變成了: first = new Object(); last = new Object(); ---- 如果真的要實體化:就要透過 一個參數反射 public class Pair<T> { private T first; private T last; public Pair(Class<T> clazz) { first = clazz.newInstance(); last = clazz.newInstance(); } } 上述代碼借助Class<T>參數並通過反射來實例化T類型,使用的時候,也必須傳入Class<T>。 例如: Pair<String> pair = new Pair<>(String.class); 因為傳入了Class<String>的實例,所以我們藉助String.class就可以實例化String類型。 ``` 4. 泛型在設計時不能Override Object的方法,所以方法的名稱不能跟Object的方法一樣,不然編譯器不會給過。 5. ArrayList的向上轉型可以變成 List,但是要注意泛型的繼承關係:可以把`ArrayList<Integer>`向上轉型為`List<Integer>`(`T`不能變!),但不能把`ArrayList<Integer>`向上轉型為`ArrayList<Number>`(`T`不能變成父類)。 6. 泛型是可以被繼承的,繼承時會子類別會獲得父類別的資料型態,因為編譯器這樣才知道,只是如果要得到資料型態是什麼的話,資料比較麻煩: - 方法: ```java import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; public class Main { public static void main(String[] args) { Class<IntPair> clazz = IntPair.class; Type t = clazz.getGenericSuperclass(); if (t instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) t; Type[] types = pt.getActualTypeArguments(); // 可能有多个泛型类型 Type firstType = types[0]; // 取第一个泛型类型 Class<?> typeClass = (Class<?>) firstType; System.out.println(typeClass); // Integer } } } class Pair<T> { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { return first; } public T getLast() { return last; } } class IntPair extends Pair<Integer> { public IntPair(Integer first, Integer last) { super(first, last); } } ``` 7. 範例code: - 範例code ```java public class ArrayList<T> { private T[] array; private int size; public void add(T e) {...} public void remove(int index) {...} public T get(int index) {...} } EX: ArrayList<String> 也就是做了 T = String ,因此裡面的就會自動幫你改成對應的格式 // 创建可以存储String的ArrayList: ArrayList<String> strList = new ArrayList<String>(); // 创建可以存储Float的ArrayList: ArrayList<Float> floatList = new ArrayList<Float>(); // 创建可以存储Person的ArrayList: ArrayList<Person> personList = new ArrayList<Person>(); 因此,泛型就是定義一種模板,例如ArrayList<T>,然後在代碼中為用到的類創建對應的ArrayList<类型>: ArrayList<String> strList = new ArrayList<String>(); 由編譯器針對類型作檢查: strList.add("hello"); // OK String s = strList.get(0); // OK strList.add(new Integer(123)); // compile error! Integer n = strList.get(0); // compile error! 這樣一來,既實現了編寫一次,萬能匹配,又通過編譯器保證了類型安全:這就是泛型。 ``` 8. 因為Java引入了泛型,所以實際上Java的類型系統結構如下: ![](https://i.imgur.com/t0XpcgQ.png) ### [泛型範例Code (泛型+LinkedList+ArrayList)](https://www.notion.so/1015-73f4b54b0f8444d4b91d7c00eb0bb1a3) --- ## 八、遊戲專題 ### 1.遊戲專題-1 1. 現在很多桌面應用,可以用網頁寫法,JAVA 這個框架並不是本身拿來做遊戲的框架的。 2. 很多程式語言,都是用Canvas畫布的概念來做為畫面的環境。 3. Graphics類別~ 非常重要,最常用到 java.awt.*; javax.swing.*; 4. JPanel 的重大功能,是可以將視窗切割出來,在畫面的哪個部份顯示不同的東西~,做遊戲的時候只會用到一個JPanel,但目前實作,使用老師自己做出來的GameKernel,因為更將強大及好用。 5. 只要有IO的部分就會特別慢,因為會配合硬碟的速度,所以只要讀取資料就會比較慢,路徑是URI的格式。 6. getResource 的 / 是這個專案的相對路徑,會在/前面補上當前資料位置,src這一層(也就是 /src/開始(所以如果資料放在src資料夾內,就直接/即可抓取,IDE會有差異,可能在src的上一層,(EX: /resources/airplane1.png) 如果不加的話會是絕對路徑,會從C槽開始,但很容易會報錯,因為挪移檔案或更名後會錯誤。 (EX: c/java/game/src/resource/airplane1) 7. JFrame 被叫出來後會先做 paint 此時會觸發所有的 paintComponent <- 畫出裡面的元件長什麼樣子 和 所有的 paint <- 畫自己本身都觸發一次 ★基本上,會以paint作為主要控制,paintComponent最為各自元件畫圖時的細節 JFrame 會觸發paint 這個paint 會在觸發所有的paintComponent <- 會在觸發所有元件的paint 8. fps 是一分鐘畫的次數,電影通常是24FPS 因為每個畫面其實是模糊的 , 遊戲至少要 60FPS 因為遊戲畫面本身不夠模糊。 ping 是網路延遲速度(毫秒) 9. 如果再paint、paintComponent 做讀取圖片的話,就會在每次畫的時候都讀取,也就是一秒內會讀取60次。 10. 在程式中,是以0,0為起點,往右往下去畫,也就是最小就是0,x及y都是正數,越往下越大,可以是負數,就代表超出框外。 11. MouseAdapter 他實現了三個介面 implements MouseListener, MouseWheelListener, MouseMotionListener 並把裡面的方法都設成空方法(因為預設使用者不一定會全部用到),可點擊MouseAdapter觀看要使用的方法在改寫。 Interface 重要觀念:只可以決定他觸發後要幹嘛,但目前先當作不能決定他在何時觸發。 12. Call back function 是一個策略模式,是利用介面來完成,也就是創建的人跟使用的人是分開的,是一種很常用的方法(解耦合的方法),滑鼠處理滑到哪裡,跟有沒有點;而程式才去決定滑鼠移動時跟點擊時的方法。 Call back function最重要的觀念:實現的人跟觸發的人分開(解耦合的重要觀念),是目前非常主流的方法。 **做元件的人控制觸發的時機**:要做的有彈性,並且將細節處裡在自己的元件內,只開放接口給別人,別人即可處裡。 **用元件的人是實現觸發後的方法**:只需要將元件的接口接入後,定義每個接口產生的情況該執行什麼即可。 一個好的插件,就是不用讓其他人知道細節,什麼時候要幹嘛,只需要專心處裡他要的結果(點擊後幹嘛)這樣就行。 13. 電腦的平行多工處理,是因為程式會一直在偵測滑鼠在幹嘛、執行對應指令來回運行,他還是有順序性的,只是太快導致以為是同時進行。 14. 在設計時要將遊戲的邏輯更新次數與畫面的更新次數做分隔,不然會導致遊戲因為效能而產生不合理性(EX: 電競比賽好的電腦因為設備高,而導致邏輯跑得過快,將導致不公),因為Time解決不了,所以才會直接用迴圈做。 15. 所有程式都是無限迴圈再跑,視窗會需要一個無限迴圈 去跑,Timer 也是,因此 Timer.start() 就是讓Timer開始無限迴圈,→ 每經過XX 號秒 重複執行Time的寫進去的Function;因此當main function 如果停止,全部的迴圈都會停止。 後來改寫,將Timer 的概念用迴圈重現,以達到更好的彈性 - 示意圖 ![](https://i.imgur.com/7dWJpSn.png) 16. 迴圈會取奈秒(1000000000 -9個0),是因為會絕對準確(雖然並非真的每一奈秒,但已經是誤差極小),因為每台電腦的毫秒取的時間可能有誤差。 17. 手機不會有main fuction 會直接給你,因為不可能會沒有,因此是會去做創建接口的改寫、更新接口的改寫... 是以生命週期去控制的。 - 範例圖 ![](https://i.imgur.com/ykNTtmB.png) 18. 程式設計的重點之一在於生命週期控制。 Libary - 功能 - 不會影響程式的生命週期 EX: Math.random(); Math ←Libary Framework - 框架- 會影響程式的生命週期控制 EX: JFrame 框架。 19. 在設計程式時,因為paint正常是,每秒會做UPDATE_TIMES_PER_SEC(設定的每秒遊戲邏輯更新次數) 次的方法,所以有用for迴圈做++的務必注意,會變成每秒*60倍,會爆炸 20. 遊戲邏輯的生命週期: input → update → paint 無限迴圈,如果input是平行的話,可能會導致update 或 paint的判斷有誤,進而影響遊戲的運行。 - 示意圖 ![](https://i.imgur.com/LJ1YNim.png) 21. 資料更新+ 畫面處裡 = GI的功用 ### 2.遊戲專題-2 1. 可以把一些常用的方法及參數做成工具包。 2. 遊戲的邏輯一定是加在update 絕對不會加在paint。 3. 基本上要在載入時,將圖片大小改好,不然會造成耗能上的大問題,因為每次在畫圖時,都是跑一個二維陣列(圖片的大小EX:800*600)然後在*每秒畫圖的次數,因此如果大小不先設定好,每次都還要再加跑更改後的次數(EX:要改成400*200),就會超級耗效能(也就是每秒原先會跑畫圖的迴圈數*60次 * 更改後大小的畫圖迴圈數)。 4. 在圖片輸入部分也要注意,盡量達到圖片的重複利用,如果已經有存在的話,就拿取就好不要存取 5. 圖片的單例模式,利用路徑、圖片查詢有無放過(像是查字典一樣,找陣列,沒找到的時候先放進去在取出來,如果找到了,就直接取出來) - 示意圖 ![](https://i.imgur.com/cxGlLCr.png) 6. !圖片在clean後,會有短暫的黑屏,並且有些圖片在下個場景還需要重複利用,不需要被刪除 7. 在設計遊戲時,讀取資料這塊,有兩個方式,一個是寫死在程式中,一個是用讀檔的方式(但這需要用到反射),在主流上是流行讀檔,因為便利於更新,在實務上,圖片、影片等等,肯定是設計部門在處裡,工程師其實是另外有一個程式用來給設計師、PM使用,照設定好的格式依照順序讀出來,大部分都會有各資源、產生地圖的程式(通常會是GUI 會有地圖元件、工具)完成後輸出資料,匯入遊戲中。 - 示意圖 ![](https://i.imgur.com/BCYifJn.png) 8. 路徑的輸入Path,有兩種做法 1.內部類:優點是比較直覺,利用static final 去作定義,在呼叫時要注意只有最後一個是呼叫常數,其他都是選擇他所在的類別名稱。 2.利用內部類繼承:優點是在裡面的資料可以是匿名內部類別,在使用時是一直點方法,但在建構時就比較不直覺。 ```java public Flow Boom(){ return new Flow("/boom"){} } ``` ### 3.遊戲專題-3 1. 有一種進階方法可以動態取得程式碼,(但太高深了當作神話就好)。 2. 可以在Global 訂 Enum 製作上下左右 Direction。 3. 在設計時,最主要的就是對外的接口一致,對內封裝起來,接口訂對,裡面的空間會很好修改 4. 接口: 傳進去的參數以及回傳出去的資料型態是正確的,在使用上的彈性就會很強大。 5. 關卡的話,場景只會有一個,改變的是物件。 可能要多一層做類似場景(也就是關卡的切換),關卡不變。 6. loading 以目前所學,沒辦法使用單例模式做出來,因為程式碼的執行緒目前都在同一條線上-也就是另外一條線上跑,因此Update 一定會先跑完資料的刪除,之後才會進行 7. 因為SceneController 是最為控制場景的控制器,因此要讓其代理場景的功能,也就是說場景所需要的方法,都需要有,透過SceneController 代為執行,之後再彈性擴充會更好 8. 目前是把圖片的資源跟場景的管理做一個整合 ### 4.遊戲碰撞概念 1. 遊戲裡的碰撞,最笨的方法就是判斷像素, 2. 碰撞如果不是拿物件的中心點做處裡,有時候會有問題,但會較複雜 3. 圓形的碰撞判斷,只要兩個圓的圓心點之間距離,小於半徑,就必定重疊。 4. 如果遊戲中有長方形及圓形要判斷碰撞,會需要拉一個Shape作儲存,會產生圓形對圓形、長方形對長方形、圓形對長方形去作設定(因此判斷碰撞上會比較複雜) 5. 分離軸 要查詢碰撞,看影子有無交疊(物件的投影) 6. 彈性碰撞要有質量等等的設定方式 - AABB方式-長方形碰撞 1. 遊戲碰撞方式1- AABB,物件一定要是長方形,且不能被旋轉過,且務必長>寬,兩個交點, 碰撞成立時(兩個長方形重疊的情況)的條件: B.bot ≥ A.top A.bot ≥ B.top B.left ≤ A.right A.left ≤ B.right 因此只要反過來,有任何一個不滿足的話就絕對不可能發生碰撞。 - 示意圖-1 ![](https://i.imgur.com/I7KrXZf.png) - 示意圖-2 ![](https://i.imgur.com/1158FmO.png) ### 5.角色動畫 1. 一個角色基本上是 32 * 32 (看圖片的裁切 , Golbal UNIT_ X . Y 去調整每個的大小) 2. 取出的圖,放到對應的位置上 , drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null); 3. dx : 代表從原始圖片抓出的圖片,要印出的起始點 ( 左上角 dx1 . dy1)、結束點 (右上角 dx2 . dy2),因此dx的大小如果跟sx的大小不同時,出來的圖片size就會有問題。 4. sc : 代表要從原始圖片抓取的範圍,印抓取的起始點(左上角 sx1 . sy 1)、結束點 (右上角 sx2 . sy2) ### 6.各組模組筆記 - 網路模組筆記 1. client 端跟server端是分開兩個專案,client端放在遊戲專案內,server獨立專案 2. CommandSolver 要自定義要的動作 → Command 3. ArrayList 要加上要傳出去的參數 4. 傳完後,會得到字串" id, 自定義指定編號, ArrayList傳出去的參數...." 5. 放入要的update (EX: Scene) 看收到指令後要做什麼事情。 然後把要的參數拿出來。 - 地圖模組筆記 1. 產生器獨立程式 2. set要小心 3. 字串List 會改成類別,所以不用再字串轉數字 4. 用switch 跑物件生成沒錯 - 菜單元件模組 1. labe foucs會鎖住,除非再次點擊 button:只會觸發一次foucs efittext :輸入框, 2. Style : normal :原始在滑鼠沒碰到的時候顯示的畫面 hover :滑鼠碰到時顯示的畫面 fouce : 滑鼠點擊後的畫面 3. setCleanAction 接口設定按下去後要幹嘛 ### [遊戲專題範例code(內含kotlin版)-Notion](https://www.notion.so/DEMO-4083adc78a2b4e458a13ce13d04ff17b) --- ## 九、預習手札 ### 1.StringBuilder && StringJoiner && StringBuffer 1. `StringBuilder`是可變對象,用來高效拼接字符串; 2. `StringBuilder`可以支持鍊式操作,實現鍊式操作的關鍵是返回實例本身; 3. `StringBuffer`是`StringBuilder`的線程安全版本,現在很少使用。 4. `String.join()`,`String`还提供了一个静态方法`join()`,这个方法在内部使用了`StringJoiner`来拼接字符串,在不需要指定“开头”和“结尾”的时候,用`String.join()`更方便: 5. [延伸閱讀極客書](http://tw.gitbook.net/java/lang/java_lang_stringbuilder.html) ``` String[] names = {"Bob", "Alice", "Grace"}; var s = String.join(", ", names); 如果要指定開頭跟結尾的話: var sj = new StringJoiner(", ", "Hello ", "!"); // 分隔符號 , 開頭 , 結尾 ``` ### 2.AutoBoxing && Auto Unboxing 自動裝箱/拆箱 - 泛型小知識 1. 基本資料型態可以跟他的類別資料型態做轉換,這種系統自動轉換的方式稱為自動裝箱/自動拆箱。 2. 自動裝箱和自動拆箱只發生在編譯階段,目的是為了少寫代碼。 3. 裝箱和拆箱會影響代碼的執行效率,因為編譯後的class代碼是嚴格區分基本類型和引用類型的。並且,自動拆箱執行時可能會報NullPointerException: ```java EX: Integer n = 100; int x = n; 為何類別資料型態可以直接變成基本資料型態。 拆解如下: Integer n = 100 -> Integer.valueOf(100); // Auto Boxing 會自動幫int做 Integer.valueOf(100)轉換 int x = n -> n.intValue(); // Auto Unboxing 會自動幫Interger n 做 intValue(); 所以,Java編譯器可以幫助我們自動在int和Integer之間轉型: 這種直接把int變為Integer的賦值寫法,稱為自動裝箱(Auto Boxing), 反過來,把Integer變為int的賦值寫法,稱為自動拆箱(Auto Unboxing)。 ``` ### 3.基本資料型類的類別 小知識 1. [最大整數(無限制)的補充資料 BigInteger](https://www.liaoxuefeng.com/wiki/1252599548343744/1279767986831393) [最大浮點數(無限制)的補充資料 BigDecimal](https://www.liaoxuefeng.com/wiki/1252599548343744/1279768011997217) 2. Integer 是 int 的類別資料型態(基本資料型態的都是這樣),不是參考資料型態唷~,8個基本資料型態的類別資料型態可參考第一個月彙整筆記的基本資料型態表格。 3. 類別資料型態,不能被繼承,變數也不會被改變,因為其內建的宣告都是final ,並且都繼承Number 這個class ```java public final class Integer extends Number{ private final int value; } ``` 4. 類別資料型態之間的比較,不能用 == 要用 equals 5. Integer的進制轉換 Integer.parseInt() 可以把字串解析成一個整數,並且可利用 ( "number", num) 轉換成number 的 num進制。 ```java int x1 = Integer.parseInt("100"); // 100 int x2 = Integer.parseInt("100", 16); // 256,因为按16进制解析 public class Main { public static void main(String[] args) { System.out.println(Integer.toString(100)); // "100",表示为10进制 System.out.println(Integer.toString(100, 36)); // "2s",表示为36进制 System.out.println(Integer.toHexString(100)); // "64",表示为16进制 System.out.println(Integer.toOctalString(100)); // "144",表示为8进制 System.out.println(Integer.toBinaryString(100)); // "1100100",表示为2进制 } } 一些常用的方法 // boolean只有两个值true/false,其包装类型只需要引用Boolean提供的静态字段: Boolean t = Boolean.TRUE; Boolean f = Boolean.FALSE; // int可表示的最大/最小值: int max = Integer.MAX_VALUE; // 2147483647 int min = Integer.MIN_VALUE; // -2147483648 // long类型占用的bit和byte数量: int sizeOfLong = Long.SIZE; // 64 (bits) int bytesOfLong = Long.BYTES; // 8 (bytes) // 向上转型为Number: Number num = new Integer(999); // 获取byte, int, long, float, double: byte b = num.byteValue(); int n = num.intValue(); long ln = num.longValue(); float f = num.floatValue(); double d = num.doubleValue(); ``` 6. 在Java中,並沒有無符號整型(Unsigned)的基本數據類型。C語言才有 例如,byte是有符號整型,範圍是-128~ +127,但如果把byte看作無符號整型,它的範圍就是0~ 255。我們把一個負的byte按無符號整型轉換為int:-1~ -128 就會是128~255。 Java如果要強制使用的話,就要用對應的 類別資料型態.toUnsignedInt("num") 去做轉換。 ### 4.SecoureRandom 真正且安全的隨機數 小知識 1. 一般我們在使用的Math.random() 是一個偽隨機數,而SecoureRandom是通過量子力學原理來獲取的,如果是想要一個無法預測的安全的隨機數,就要使用SecureRandom。 ```java SecureRandom sr = new SecureRandom(); System.out.println(sr.nextInt(100)); ``` 2. `SecureRandom`無法指定種子,它使用RNG(random number generator)算法。JDK的`SecureRandom`實際上有多種不同的底層實現,有的使用安全隨機種子加上偽隨機數算法來產生安全的隨機數,有的使用真正的隨機數生成器。實際使用的時候,可以優先獲取高強度的安全隨機數生成器,如果沒有提供,再使用普通等級的安全隨機數生成器: import java.util.Arrays; import java.security.SecureRandom; import java.security.NoSuchAlgorithmException; Run 3. `SecureRandom`的安全性是通過操作系統提供的安全的隨機種子來生成隨機數。這個種子是通過CPU的熱噪聲、讀寫磁盤的字節、網絡流量等各種隨機事件產生的“熵”。 4. 在密碼學中,安全的隨機數非常重要。如果使用不安全的偽隨機數,所有加密體係都將被攻破。因此,時刻牢記必須使用`SecureRandom`來產生安全的隨機數。 5. 需要使用安全隨機數的時候,必須使用SecureRandom,絕不能使用Random! ###### tags: `CMoney7th-戰鬥營學習筆記`