2021年11月28日 星期日

UNITY 設計遊戲雜記

 這篇不是教學,只是記錄我找到的網頁和資訊,未來相同的資訊、心得都會放在這一篇。

最一開始操作 UNITY 遇到的問題是 importing assets 非常慢,問了我買的書的作者,才知道是專案名稱用中文的關係,只要改成英文就好了,預計未來的 UNITY 中文版本會改善這一塊。

然後在此之前,我不知道 C# 的變數可以用中文命名,這點很特別。程式碼的部份,因為我有學過JAVA 並有物件導向的觀念,所以看到書中的範例倒是不會怕。順便請我朋友 Elton 幫我複習了泛型類別的觀念。

成功完成一個範例後,我考慮到的是遊戲中的模型要用個軟體製作,如果用 Blander 做模型,模型可以除了可以用在 UNITY 上,也可以用在 MMD 上,又或者許多用在 MMD 的模型是用 Blander 製作的,而且免費放在網路上給大家用,雖然可能不能用於商業,但練習時總是可以拿來試吧?

https://forum.gamer.com.tw/C.php?bsn=60610&snA=276

2021年9月2日 星期四

JAVA Package 練習

Package 相關類別的集合,這在程式越來越大時,不同的 Package 即使取了相同的變數或方法的名稱也沒有關係,因為 JAVA 可以由 Package 知道我們究竟是指哪一個變數或方法。接下來的範例是主程式引用了 NPC Package,而 NPC Package 的位置就設在 project 下的 src 資料夾中,即在 src 資料夾中設置一個 NPC 資料夾,而 NPC 資料夾內就是 NPC 的相關類別了。

父類別 NPC 共同特徵與方法:下面三行的抽象方法有提醒的功用,其子類別中必須覆寫,不然會出錯。
package NPC;

public abstract class Commn {
public String name, line;

public Commn(String name, String line){
this.name = name;
this.line = line;
}

public abstract void position();
public abstract void say();
public abstract void doing();

}
子類別之行人:
package NPC;

public class Walker extends Commn {

public Walker(String name, String line) {
super(name,line);
this.line = line;
}

public void position(){
System.out.println("【到處亂走!】");
}
public void say(){
System.out.println("台詞:" + line);
}
public void doing(){
System.out.println("== NPC" + name + " 的狀態! ==");
position();
say();
}
}
子類別之商人:
package NPC;

public class Merchant extends Commn{
public Merchant(String name, String line) {
super(name,line);
this.line = line;
}

public void position(){
System.out.println("【停在原地,不移動!】");
}
public void say(){
System.out.println("台詞:" + line);
}
public void doing(){
System.out.println("== NPC" + name + " 的狀態! ==");
position();
say();
}
}
不在 NPC package 中的子類別幽靈:故意測試看看這樣能不能繼承,答案是可以,但要引入。

import NPC.Commn; // 因為此類別不在 NPC 資類夾下,但用到了其中的類別,故需引入。

public class Ghost extends Commn {

public Ghost(String name, String line) {
super(name,line);
this.line = line;
}

public void position(){
System.out.println("【若隱若現的亂飄浮!】");
}
public void say(){
System.out.println("台詞:" + line);
}
public void doing(){
System.out.println("== NPC" + name + " 的狀態! ==");
position();
say();
}
}
主程式:
// 這個程式在試驗 package 的引入。
import NPC.*; // 引入 NPC package 下的所有類別。

public class Main {
public static void main(String[] args){

// 創造各種 NPC,給名字與台詞!
Walker luna = new Walker("露娜", "今晚的月亮(我)很美吧?");
Merchant troy = new Merchant("特洛伊","快來買喔!這些武器都有 CAS 國家認證喔!");
Ghost kate = new Ghost("凱特","我死得好慘啊!");

// 使用各個 NPC doing 方法。
luna.doing();
System.out.println();

troy.doing();
System.out.println();

kate.doing();
System.out.println();
}
}
結果:
== NPC露娜 的狀態! ==
【到處亂走!】
台詞:今晚的月亮(我)很美吧?

== NPC特洛伊 的狀態! ==
【停在原地,不移動!】
台詞:快來買喔!這些武器都有 CAS 國家認證喔!

== NPC凱特 的狀態! ==
【若隱若現的亂飄浮!】
台詞:我死得好慘啊!


Process finished with exit code 0
這可能是最後一個會放上來的 JAVA 練習了,因為書看完了,雖然還有另外買一本,但接下目標是實作了,之後放上來的應該只有成果,不會再有程式碼了。


2021年8月30日 星期一

JAVA 例外處理-02 各種例外

  本程式在 try 區的程式碼只有三種可能會出現的例外,除以 0 、陣列溢位、使用方法的輸入參數不符、物件為 null 的情況,但我 catch 的部份還是多寫了儲存陣列元素型態不符,主要是為了日後查指令時方便。

  主程式中利用亂數決定要用陣列的哪個元素進行轉整數與相除運算,所以每一次出現的情況可能會不同,可能順利的完成,也可能產生例外。

  順道一題, catch 捕捉的物件名稱都一樣,只是型別不同,這跟多載(過載)相同, JAVA 會自動找合適的 catch 去捕捉物件。

程式碼:

public class Main {

// 類別方法,顯示例外訊息。
static void showErr(Exception ecp){
// Exception 是其它例外類別的父類別,所以可以用渣男寫法。
System.out.println("發生例外:" + ecp.getMessage());
//System.out.println("原因如下:");
}

public static void main(String[] args){
String[] text = { null, "44", "22", "11", "0",
"愛未未","恨已已","情深深","仇切切"};

try{ // 這邊寫可能會出現例外的程式碼,執行時若出現例外,會丟出例外物件給 catch

int i,ratio;
double r = Math.random()*10;
i = (int)(r);
System.out.println("亂數 r = " + r + ",小數捨去化為整數 i = " + i);
ratio = Integer.parseInt(text[i])/Integer.parseInt(text[i+1]);
System.out.println("參數比例 = "+ratio);
}
catch (ArithmeticException ecp){ // 捕捉【除以 0 的例外物件】。
showErr(ecp);
}
catch (ArrayIndexOutOfBoundsException ecp){ // 補捉【陣列溢位的例外物件】。
showErr(ecp);
}
catch (ArrayStoreException ecp){ // 捕捉【陣列儲存型態不符的例外物件】。
showErr(ecp);
}
catch (IllegalArgumentException ecp){
// 捕捉【使用的方法時,丟入的參數型態不同的例外物件】。
showErr(ecp);
}
catch (NullPointerException ecp) { // 捕捉【物件值為 null 的例外物件】。
showErr(ecp);
}

finally {
System.out.println("無論有沒有出現例外都會出現這一行!");
}

System.out.println("程式結束了!");

}
}

結果:

亂數 r = 5.228886020103869,小數捨去化為整數 i = 5
發生例外:For input string: "愛未未"
無論有沒有出現例外都會出現這一行!
程式結束了!

Process finished with exit code 0


我手邊的書還有丟出例外與自訂例外類別,暫且就先跳過。執行緒的部份,不想花時間改程式碼以避開著作權,所以決定練習不放上來。

2021年8月26日 星期四

JAVA 例外處理-01 try、catch、finally

例外處理的目的是為了避免程式崩潰,可以處理的例外有數學運算產生的,例如除以 0 、陣列索引在設定之外產生的、儲存陣列元素的型態不符產的、呼叫方法時,參數型態不同產生的、物件值為 null 產生的。

主程式 

public class Main {
public static void main(String[] args){
int i,j;
j=0;

try{
for (i = 2; i > -100; i--){
System.out.println("i= " + i + ",則 10/i = " + 10/i);
j=j+1;
}
// 如果沒有問題的話,程式會跑完迴圈,但這邊故意設了一個會除以 0 的情況,
// 該情況下會出錯,所以 catch 段的程式會執行,而且迴圈只會執行到此斷點。

System.out.println("出現此行,表示迴圈有跑完。");
// 實際上上面這一行不會出現在結果中,因為更前面已經出現例外了。
}
catch (ArithmeticException gg){
// 如果沒有例外,此區塊的程式碼不會被執行。
// 這邊的 gg 是送到 catch 方法裡面的 ArithmeticException 物件名稱,
// 名字可以隨便取。
System.out.println("例外說明:" + gg.getMessage());
// 展示 gg 物件得到的系統訊息。
System.out.println("例外原因:");
gg.printStackTrace();
// 有了上面這一行,出現例外的情況下,程式在執行完後會顯示程式呼叫的執行過程。
}
finally {
System.out.println("例外處理結束!迴圈共跑了 " + j + " 次。");
// 無論有沒有例外,上面這一行都會出現,表示此區程式一定會執行。
}

System.out.println("程式結束!");
}
}

結果:

i= 2,則 10/i = 5
i= 1,則 10/i = 10
例外說明:/ by zero
例外原因:
例外處理結束!迴圈共跑了 2 次。
程式結束!
java.lang.ArithmeticException: / by zero
	at Main.main(Main.java:8)

Process finished with exit code 0

其中 catch 的區塊可以有好幾個,最保險的方式是 try 區塊中的程式可能出現多少種例外,就有多少個 catch 來以確保程式不會崩潰。

2021年8月9日 星期一

c++ 判斷質數練習

  這個是小孩子的作業,其實我以前也用 python 做過類似的練習,那時是找出某個數字以下的所有質數,這次只是判斷一個數字。

======================================================================

#include <iostream>

// 這個程式功用是判斷數字是否為質數,在數字不大的情況下,這應該是簡單度與效率的最好比例了。 

using namespace std;

int main() { 


unsigned int a,b,c,d;

//cout << "請輸入數字 \n";

//cin >> a;

//a=2147483647;

a=4294967291;

b=1;

d= a/2;

c=1;

//cout << c << endl; 

while( b<d and c!=0) 

{

b=b+1;

c=a%b;

}

if(c==0){

cout << "不是質數!\n";

}

else{

cout << "質數!\n";

}

cout << "程式結束!";

    return 0; 

}

================================================================

以下是執行結果:

================================================================

請輸入數字

2147483647

質數!

=================================================================

我對質數有些好奇,但研究的人實在太多了,意即我投入時間研究大概也很難有新發現,但或許未來有空,還是可以探索一下的。所以留下 C++ 讀取與輸出文字檔的範例連結給未來的自己。

讀取:https://shengyu7697.github.io/cpp-read-text-file/

寫入:https://shengyu7697.github.io/cpp-write-text-file/

另外是質數研究的介紹:https://math.ntnu.edu.tw/~horng/letter/hpm16011.pdf

2021年7月22日 星期四

JAVA IO套件-05 讀檔與計算字數

  上次說要做的練習,讀取英文文字檔案並計數裡面出現的每一個字。

讀取類別:將文字檔案讀取後存成一維陣列,

import java.io.*;
public class ReadAsRaw {
String fileName;
int x;

// 字串讀取方法,就是英文單字,這個方法讀取存成一維陣列。
public String[] readStringFile(String fileName,int x) throws IOException{
this.fileName = fileName;
this.x = x;
int total = 0;
String[] stringArry = new String[x];
if( new File(fileName).exists()) {
BufferedReader fileString = new BufferedReader(new FileReader(fileName));
String line;
while ((line = fileString.readLine()) != null) {
String [] unit = line.split("\\W+"); // 用一個或多個非字母字元分割字串。
int size = unit.length;
for (int i = 0; i < size; i++){
stringArry[total + i] = unit[i];
}
total=total+size;
}
fileString.close();
}
else {
System.out.println("沒有 " + fileName + " 登!登!");
}
System.out.println("總字數:"+ total);
return stringArry;
}

}

字數計算類別:計數丟入的一維字串陣列中的每個單字出現次數。

import java.util.HashMap;

public class WordCount {

public void wordsCount(String[] sentence) {
// TODO Auto-generated method stub
String[] words = sentence ;//sentence.split("\\W+");
//新建一個 HashMap
HashMap<String, Integer> hashMap = new HashMap<String, Integer>();
int j = 0;
for (int i = 0; i < words.length; i++) {
if (hashMap.get(words[i]) == null) {
hashMap.put(words[i], 1); //如果 hashMap 中沒有那個單詞,設定值為 1
}
else { //如果 hashMap 中有這個單詞,則將該單詞的值加 1
hashMap.put(words[i], hashMap.get(words[i]) + 1);
j++;
}
}
System.out.println(" " + j + " 個不同的單字");
for(int i = 0; i < j; i++) {
System.out.println(words[i] + " ; " + hashMap.get(words[i]));
}

}
}

主程式:事先必須準備好一個文字檔於在專案資料夾中,例如 test.txt 。

public class Main {
public static void main(String[] args) throws Exception{

ReadAsRaw rW = new ReadAsRaw();
String nameString = "test.txt";
int guessTotal=313; // 猜總字數,可以大於等於,不能小於真實的字數。
String[] sArry = rW.readStringFile(nameString, guessTotal);

WordCount cW = new WordCount();
cW.wordsCount(sArry);
//System.out.println(cW.);
}
}

結果:因為太長,所我中間省略了,前面單字,後面數字。

總字數:313
有 137 個不同的單字
Quantum  ;  3
mechanics  ;  8
is  ;  4
a  ;  7
fundamental  ;  1

中間略

are  ;  2
restricted  ;  1
to  ;  7
discrete  ;  1

Process finished with exit code 0

  這邊用分號是因為試算表可以用分號當分隔符號,方便大家後續的操作。這個程式還有一件事情我沒有做,那就是排序。暫時懶得寫。最近有其他目標,下次練習應該是一段時間後了。

  為了完成這個練習,我所找到的程式參考來源:https://www.itread01.com/p/1432610.html;另外還有一個別人已經寫好,放在網路上分享的字數統計工具:https://blog.qqboxy.com/2016/06/english-word-analyze.html



2021年7月19日 星期一

JAVA IO套件-04 用物件讀數字

  如同前一篇所說的,我想保持主程式的簡單性,於是我把複雜留給類別,清爽留給主程式。又一次要傳的數字是一大堆,所以方法中宣告一個二維陣列去存數字。然後在主程式中也宣告一個二維陣列去接收其位址(程式碼看起來是回傳陣列給主程式,實際上傳的是陣列的位址)。

將讀到的值存成矩陣的類別:
import java.io.*;
public class ReadAsMatrix {
String fileName;
int x,y;

public int[][] readIntFile(String fileName,int x,int y) throws IOException{
this.fileName = fileName;
this.x = x;
this.y = y;
int[][] intgerArry = new int[x][y];
if( new File(fileName).exists()) {
BufferedReader fileInteger = new BufferedReader(new FileReader(fileName));
String line;
int j=0;
while ((line = fileInteger.readLine()) != null) {
String [] unit = line.split("\\s+"); // 用空格分割字串,並指定給字串陣列。
int size = unit.length;
for (int i = 0; i < size; i++){
intgerArry[j][i] = Integer.parseInt(unit[i]);
}
j++;
}
fileInteger.close();
}
else {
System.out.println("沒有 " + fileName + " 登!登!");
}
return intgerArry;
}
}
主程式:
/* 讀寫整數 */
//import java.io.*;
public class Main {

public static void main(String[] args) throws Exception{

// 宣告 rI 是一個讀取檔案值並存為矩陣的物件。
ReadAsMatrix rI = new ReadAsMatrix();

String nameInteger = "整數檔.txt";
int x=10,y=10; // 行數、列數(直行、橫列)。

// 使用 rI 物件的方法,並將結果的矩陣位址傳給 iArry
int[][] iArry = rI.readIntFile( nameInteger, x, y);


// 展現讀取的陣列數值。
for ( int j = 0; j < x; j++){
for (int i = 0; i < y; i++){
System.out.print(iArry[j][i]+";");
}
System.out.println();
}
}
}

  不看主程式後面展現讀取結果的雙迴圈和註解的話,主程式只用了四行來完成讀取。因為我類別內方法的寫法關係,使用這個程式,x、y 的值必須夠大,太小會出錯,太大則沒有給值的陣列元素會是初始值 0 ,所以如果使我的程式,最好確定檔案的行數與列數。

  如果想讀取的是實數、字串,就必須要改變類別中的方法,並不難。我已經寫好了,但示範的程式碼以簡潔為主,就不放上來了。


  如果想要對 String.Split 有更多理解,這裡:String.Split

  接下來我想做的練習是統計字母、單字,沒想到網路上已經有人做了,這裡:字的統計


2021年7月17日 星期六

JAVA IO套件-03 讀取數字

  這一篇是我想到我以前做研究時最需要的基本能力,可以讀取數字檔案,並對其內容做操作,以完成我們想要的分析。我的書上沒看到這部份的語法教學,似乎這個能力對寫 JAVA 的人來說並非必要,所幸網路上找得到教學,應該做數值分析或統計的人都用得到吧!

  建立專案後,把準備好的 整數檔.txt 放到專案資料夾中,我這次放的是一個十十乘法的值。這次我直接在主程式寫,結果整數檔案必須放在專案的資料內,跟我的第一次不同,卻跟第二次相同,這部份我還想不到理由。以下是十十乘法的值,按圖可放大。

主程式:我使用了和前兩次練習相同的方式讀取文字檔案的每一行,所以如果我的前兩次練習在做什麼都有理解,那其實這次的重點就只有包含我做註解的那一行往下六行而已。

/* 讀整數 */
import java.io.*;
public class Main {

public static void main(String[] args) throws IOException{
String nameInteger;
nameInteger = "整數檔.txt";
if( new File(nameInteger).exists()) {
BufferedReader fileInteger = new BufferedReader(new FileReader(nameInteger));
String line;
while ((line = fileInteger.readLine()) != null) {

String [] unit = line.split("\\s+"); // 用一個或多個空格分割字串,並指定給字串陣列。
int size = unit.length;
int[] intgerRow = new int[size];
for (int i = 0; i < size; i++){
intgerRow[i] = Integer.
parseInt(unit[i]);
}

System.
out.println(intgerRow[1] + " + " + intgerRow[2] + " = " + (intgerRow[1]+intgerRow[2]));
}
fileInteger.close()
;
}
else {
System.
out.println("沒有 " + nameInteger + " 登!登!");
}
}
}

結果:看得出來是可以做數值運算的,所以的確有把讀取到的字串轉換成數字。

2 + 3 = 5
4 + 6 = 10
6 + 9 = 15
8 + 12 = 20
10 + 15 = 25
12 + 18 = 30
14 + 21 = 35
16 + 24 = 40
18 + 27 = 45
20 + 30 = 50

Process finished with exit code 0

  我必須說,以讀取的功能來說, fortran 真的是方便多了。上面那些程式碼如果沒物件導向的基本概念,應該不容易看懂,難怪我當時的學校物理系、化學系是教 fortran 而非 JAVA 了。雖然 fortran 處理數字和讀取檔案容易,不過以未來求職的角度來看,還是其他語言比較有優勢。

  最後,寫這麼多行只為了處理一個檔案,主程式顯得很亂,之後我還是把這些處理方式寫在類別裡面吧!

2021年7月16日 星期五

JAVA I/O 套件-02 基本練習,混合中英歌詞

  上一篇是寫入與讀取的練習,利用相同的方法,我寫了一個把英文歌詞和中文歌詞合在一起的程式,最後會產生一個英中對照的文字檔。這次我選的是 You're gonna go far, kid ,歌詞來源是 https://genius.com/The-offspring-youre-gonna-go-far-kid-lyrics ,我把一些不要的刪掉後,存成文字檔,


然後自己翻譯成中文,


把兩個檔案放在 project 的資料夾內,上一個練習是放在 src 資料夾裡面,這次是放與 src 同位置的資料夾,原因是這一次我打算保持主程式的簡潔,所以複雜的流程放在類別裡面了。那為什麼這麼一來文字檔放的位置就不同了呢?我也不曉得,查了好久才發現這個現象。

混合歌詞的類別:

/* 混合兩份歌詞 */
// 編寫人:Lancelot Cheng
import java.io.*; // I/O 套件是在使用的類別中引入,而非在主程式中引入。
public class Mix2Lyrics {
String name1,name2,name3;

public Mix2Lyrics(String name1, String name2, String name3){
this.name1 = name1; // 要混合的第一份(英文)歌詞檔名。
this.name2 = name2; // 要混合的第二份(中文)歌詞檔名。
this.name3 = name3; // 混合後的檔名。
}

public void mixThem() throws Exception{ // 要記得加 throws Exception
BufferedReader readEng = new BufferedReader(new FileReader(name1));
BufferedReader readChi = new BufferedReader(new FileReader(name2));
String song1;
String song2;
BufferedWriter writeCom = new BufferedWriter(new FileWriter(name3));
while(((song1 = readEng.readLine()) != null) && ((song2 = readChi.readLine()) != null)) {
System.out.println(song1);
System.out.println(song2);
writeCom.write(song1+"\n");
writeCom.write(song2+"\n");
}
readEng.close();
readChi.close();
writeCom.close();
}
}

主程式:

/* 主程式 */
// 編寫人:Lancelot Cheng
public class Main {
public static void main(String[] args) throws Exception{ // 要記得加 throws Exception
Mix2Lyrics uRGonnaGoFar = new Mix2Lyrics
("You're gonna go far, kid.txt", "你會功成名就,孩子.txt","英中對照.txt");
uRGonnaGoFar.mixThem();
}
}

結果:

Show me how to lie, you're getting better all the time
秀給我看你是如何欺騙眾人,你技藝總是越來越好。
And turning all against the one is an art that's hard to teach
讓眾人群起攻擊一個人,那可是一門難以教授的藝術啊!
Another clever word sets off an unsuspecting herd
用另一個聰明的字眼,鼓動毫無戒心的群眾,
And as you step back into line, a mob jumps to their feet
而當你退回人群之中,一群暴徒躁動了起來。


Now dance, fucker, dance man, he never had a chance
現在跳舞吧!混蛋!跳舞人們啊!那個受害者未曾有過機會。
And no one even knew it was really only you
甚至沒有人知道,真正的兇手就只有你!
And now you steal away, take him out today
現在你逍遙法外,讓在受害者在今日失去一切。
Nice work you did, you're gonna go far, kid
幹得好,孩子!你必將功成名就!


With a thousand lies and a good disguise
撒上千百謊言與偽裝成好人,
Hit 'em right between the eyes, hit 'em right between the eyes
痛擊他們的眉心!痛擊他們的眉心!
When you walk away, nothing more to say
當你一走,什麼都沒說,
See the lightning in your eyes, see 'em running for their lives
看看你眼中的閃電,看看他們為了生存而奔跑。


Slowly out of line, and drifting closer in your sight
我慢慢地置身於外,卻飄近你的視野之中。
So play it out, I'm wide-awake, it's a scene about me
所以玩開來吧!我非常清楚,這次輪到我了!
There's something in your way and now someone is gonna pay
你的路上有阻礙,而且有人必須付出代價,
And if you can't get what you want, well, it's all because of me
而如果你不能得到你所要的,哈!那全是因為我!


Now dance, fucker, dance, man, I never had a chance
現在跳舞吧!混蛋,跳舞吧!老兄!我未曾有過機會。
And no one even knew, it was really only you
甚至沒有人知道,真正的兇手其實只有你!
And now you'll lead the way, show the light of day
現在你引領著道路,展現日子的光明
Nice work you did, you're gonna go far, kid, trust deceived
幹得好,孩子!你必將功成名就。信任被欺騙!


With a thousand lies and a good disguise
撒上千百謊言與偽裝成好人,
Hit 'em right between the eyes, hit 'em right between the eyes
痛擊他們的眉心!痛擊他們的眉心!
When you walk away, nothing more to say
當你一走,什麼都沒說,
See the lightning in your eyes, see 'em running for their lives
看看你眼中的閃電,看看他們為了生存而奔跑。


Now dance, fucker, dance, he never had a chance
現在跳舞吧!混蛋!跳舞!那個受害者未曾有過機會。
And no one even knew, it was really only you
甚至沒有人知道,真正的兇手就是你!
So dance, fucker, dance, I never had a chance
所以跳舞吧!混蛋,跳舞吧!我未曾有過機會。
It was really only you
真正的兇手其實只有你!


With a thousand lies and a good disguise
撒上千百謊言與偽裝成好人,
Hit 'em right between the eyes, hit 'em right between the eyes
痛擊他們的眉心!痛擊他們的眉心!
When you walk away, nothing more to say
當你一走了之,什麼都沒說,
See the lightning in your eyes, see 'em running for their lives
看看你眼中的閃電,看看他們為了生存而奔跑。


Clever alibis, Lord of the Flies
巧妙的不在場證明,蒼蠅王!
Hit 'em right between the eyes, hit 'em right between the eyes
痛擊他們的眉心!痛擊他們的眉心!
When you walk away, nothing more to say
當你一走了之,什麼都沒說,
See the lightning in your eyes, see 'em running for their lives
看看你眼中的閃電,看看他們為了生存而躲避。

Process finished with exit code 0

產生的檔案~~


如果想聽這首歌的話,網址:https://www.youtube.com/watch?v=ql9-82oV2JE



JAVA I/O 套件-01 寫入與讀取文字

  為了方便其見,我把兩個範例寫在一起~~

主程式:

// 使用輸入與輸出套件
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {

// 輸出文字檔案
BufferedWriter writeSomethings = new BufferedWriter(new FileWriter("我是藍斯洛特.txt"));
writeSomethings.write(" 網球場上 \n\n 詐欺師!\n");
writeSomethings.write("今年我要拿四個大滿冠!\n");
writeSomethings.close(); // 關閉串流。
// 執行後, src 檔案夾內就會出現 我是藍斯落特.txt 的文字檔,

// 讀取文字檔案,先在 src 資料夾內準備一個 txt 文字檔,
if(new File("口是心非.txt").exists()) {
BufferedReader readSomething = new BufferedReader(new FileReader("口是心非.txt"));
String song;
while ((song = readSomething.readLine()) != null)
System.out.println(song);
readSomething.close();
}
else {
System.out.println("找不到檔案喔!");
}
}
}

結果:

張雨生

口是心非

作詞:張雨生
作曲:張雨生
編曲:Koji Sakurai

口是心非 你深情的承諾都隨著西風飄渺遠走
癡人夢話 我鍾情的倚托就像枯萎凋零的花朵
星火燎原 我熱情的眼眸曾點亮最燦爛的天空
晴天霹靂 你絕情的放手在我最需要你的時候

於是愛恨交錯人消瘦 怕是怕這些苦沒來由
於是悲歡起落人靜默 等一等這些傷會自由

口是心非 你矯情的面容都烙印在心靈的角落
無話可說 我縱情的結果就像殘破光禿的山頭
渾然天成 我純情的悸動曾奔放最滾燙的節奏
不可收拾 你濫情的拋空所有晶瑩剔透的感受

Process finished with exit code 0

口詞心非的歌詞檔案取自:https://mojim.com/twy100199x10x2.htm



2021年7月13日 星期二

JAVA 物件導向的概念-14 多型的練習

   上一篇練習的多型,其承接物件時是使用父類別所宣告的物件,這次嚐試用介面所宣告的物件來承接。

  英雄與惡棍都會被世界所記錄,死後也都有葬禮,但方式都不同,所以我抽出記錄與葬禮各自寫成介面。

記錄介面:

// 記錄的介面
public interface Record {
void recordWay();
}

葬禮的介面:

// 葬禮的介面
public interface Burial {
void burialWay();
}

然後再寫出英雄與惡棍的類別,並實作上述兩個介面,而且內容要覆寫介面的方法。

英雄類別:

// 英雄類別
public class Hero implements Burial, Record {

public void burialWay(){
System.out.println("使用國葬!");
}

public void recordWay(){
System.out.println("製造雕像紀念!");
}
}

惡棍類別:

// 惡棍類別
public class Gangster implements Burial, Record {

public void burialWay(){
System.out.println("丟去餵野狗!");
}

public void recordWay(){
System.out.println("寫入罪犯啟示錄中!");
}
}

在主程式中的工作與前一篇相同,

主程式:

// 主程式
public class Main {
    public static void burialDeal(Burial man) {
man.burialWay();
}

public static void recordDeal(Record man) {
man.recordWay();
}
public static void main(String[] args){

Hero ai = new Hero(); // 宣告愛未未是一個英雄物件,並產生她。
Gangster atnans = new Gangster(); // 宣告亞特南斯是一個惡棍物件,並產生他。

System.out.println("英雄愛未未的下場~~");
burialDeal(ai);
recordDeal(ai);

System.out.println();

System.out.println("惡棍亞特南斯的下場~~");
burialDeal(atnans);
recordDeal(atnans);

}

}

結果:

英雄愛未未的下場~~
製造雕像紀念!
使用國葬!

惡棍亞特南斯的下場~~
寫入罪犯啟示錄中!
丟去餵野狗!

Process finished with exit code 0

下個結論~~多型基本上就是模糊化寫法的進階應用!

  上一篇是用方法中宣告的[父類別物件]去承接[子類別物件],這一篇則是用方法中宣告的[介面]去承接[實作該介面的類別產生之物件]。

  物件導向的概念大概就是這樣,實際上我手邊的書還有教巢狀類別、聚合之類的,不過我朋友 Elton 認為那些以後需要再學就好,所以我就果斷跳過了。接下來我要學的就是套件。

  最後說一下,如果你已經到學到這邊,那接下來就請不要放棄,因為接下來就是學用別人的套件,你不需要知道內容原理,只要知道如何用就好了。而這些資源,網路上很多,善用這些資源,你就像是有超能力一樣,可以完成很多事情。簡單說,回報現在才開始。



JAVA 物件導向的概念-13 複習模糊化寫法、過載特性,多型

   在看多型章節時發現多型和模糊化寫法、過載有相似之處,故決定開頭先複習,再介紹多型。

  先複習模糊化寫法,在兩個類別是繼承關係時,我們可以使用模糊化寫法宣告物件,例如父類別是人類 Human,其中一個子類別是騎士 Kinght,我們宣告藍斯洛特是騎士時,可以寫作

Kinght lancelot = new Kinght();// 明確宣告藍斯洛特為一個騎士,口語說法:騎士藍斯洛特是一個騎士。

;可以寫作

Human lancelot = new Kinght();// 模糊宣告藍斯洛特為一個騎士,口語說法:人類藍斯洛特是一個騎士。

,上面這一行就是模糊化寫法,等號左邊只說了藍斯洛是人類,右邊才宣告了他是騎士;除此之外還可以寫作

var lancelot = new Kinght();

。無論上面哪一種寫法所產生出來的物件,都可以使用子類別中的方法,只是模糊寫法的物件在使用子類別的方法時,該物件必須強制轉型。

  再來是過載特性的複習,在一個類別中,有複數個成員方法的名稱相同,但其需要的參數屬性或數量的不同,JAVA 可以自動依所傳的參數判斷要使用哪一個方法,這個特性稱之為過載。

  JAVA 多型正是用到了模糊化寫法,而且擁有過載的特性。

  宣告一個父類別,父類別中擁有它自己的方法,然後其子類別中也有同名的方法,覆寫掉父類別中的同名方法;在主程式中定義的方法指定丟入參數為父類別的物件,但實際上你可以丟入任何有繼承該父類別的子類別所產生的物件,這就是模糊化;而 JAVA 會自動使用該物件在子類別中的同名方法,這就是過載。以下是 Elton 教我多型概念時的範例。

父類別:Shape 形狀,包含了一個它自己的方法 area 面積;

public class Shape {
public double area() {
return 0;
}
}

子類別:Circle 圓形,包含了和父類別同名的方法 area 面積;

public class Circle extends Shape {
int r;
double pi = 3.1415926;

public int getR() {
return r;
}

public void setR(int r) {
this.r = r;
}

public double getPi() {
return pi;
}

public void setPi(double pi) {
this.pi = pi;
}

public double area() {
return pi*r*r;
}

}

子類別:Rectangle 矩形,包含了和父類別同名的方法 area 面積;

public class Rectangle extends Shape {
int length;
int width;

public int getLength() {
return length;
}

public void setLength(int length) {
this.length = length;
}

public int getWidth() {
return width;
}

public void setWidth(int width) {
this.width = width;
}

public double area() {
return length * width;
}

}

主程式:裡面示範了方法的過載和多型兩種方式,註解應該算詳細,所以這裡不再說明。

public class Main {

// 以下是方法的多載(overloading),方法名稱相同,會依傳入的參數型別或數量不同而區別使用哪一方法。
// 萬一今天有多個不同的形狀,例如三角形,梯形,六角形等。下方就要為特定的形狀再多加宣告多組的面積方法,有些繁瑣。
public static double calArea(Circle cir) {
return cir.area();
}

public static double calArea(Rectangle rec) {
return rec.area();
}

// 那麼為了有更簡潔的方式,我們可以使用到多型的觀念。只宣告一組面積方法來算所有形狀的面積。
// 設定承接物件為父類別,模糊化所承接之物。
public static double calAreaBetter(Shape sha) {
return sha.area();
}

public static void main(String[] args) {
Circle cir1 = new Circle();
cir1.setR(12);

Rectangle rec1 = new Rectangle();
rec1.setLength(5);
rec1.setWidth(10);

double cirArea = calArea(cir1);
double recArea = calArea(rec1);
System.out.println("this is an area of circle by cir method:" + cirArea);
System.out.println("this is an area of rectangle by rec method:" + recArea);

cirArea = calAreaBetter(cir1);
recArea = calAreaBetter(rec1);
System.out.println("this is an area of circle by better method:" + cirArea);
System.out.println("this is an area of rectangle by better method:" + recArea);
}
}

結果:不管是用方法的過載或是多型的概念,都得到同樣的面積值。

this is an area of circle by cir method:452.38933440000005
this is an area of rectangle by rec method:50.0
this is an area of circle by better method:452.38933440000005
this is an area of rectangle by better method:50.0

Process finished with exit code 0

這個範例中的父類別是一般的類別,但我買的書,其範例是用抽象類別當父類別,或是用介面讓不同的類別實作,來完成多型的目的,其使用是自由自在的,下一篇會再練習一次多型,應該也是最後一篇物件導向的概念文章了,之後會練習如何使用套件與檔案處理。


2021年7月12日 星期一

JAVA 物件導向的概念-13 介面

  這裡的範例也是 Elton 教我時所寫的,當時寫的註解也相當詳細,但我還用自己的話來解釋一下,雖然 JAVA 不允許多重繼承,但實作多個介面其實就等同於多重繼承。

  當你想要保持類別繼承的簡單性時,介面可能可以幫助你。舉個例子,老鷹和幽浮都會飛,但一個是生物,一個是機械,如果我創造出一個包含了飛行方法的鳥類別來當父類別,讓老鷹類別去繼承鳥類別,這完全符合我們的一般常識;但如果為了讓幽浮類別也擁有飛行的方法而去繼承鳥類別,這就完全不符合常識了,因為幽浮並不是鳥類!

  那麼這時候可以怎麼做呢?我們可以抽出飛行的方法,將之寫成為一個介面,並讓鳥類別與幽浮的類別實作它。以下範例~~

飛行方法的介面:

public interface Flyable {
void fly();

// 介面本身是個特殊的抽像類別,通常用來抽離出不同類別間的共同特性。
// JAVA 的類別只能繼承一個類別,但可以實作多個介面。
// 比方說:它希望鳥類就是自成一個體系,而飛行器又是另外自成一個體系。
// 以避免濫用繼承。
// 但之所以可以實作多個介面,是因為介面是多種物件間的共同特性,故不受此限制。
// 實作其實就是繼承,有 is-a 的關係。
// 同時以程式碼管理的角度,它可以提醒你未實作的程式碼有哪些,不然編譯會出錯。
}

實作飛行方法介面的鳥類別:其中實作的語法是 implement,而覆寫方法如前面的章節所說的同名同回傳值,即可覆寫,不過這裡前面有加上 @Override ,似乎依不同的 IDEA 有的需要加,有的不用。

public class Bird implements Flyable {

@Override
public void fly() {
System.out.println("Fly by the wings");
}
}

實作飛行方法的的幽浮類別:

public class UFO implements Flyable {

@Override
public void fly() {
System.out.println("AntiGravity by an unknow force");
}
}

主程式:

class TestInterface {

public static void main(String[] args) {
Flyable b1 = new Bird(); // 口語的說法:可飛行的 b1 是一隻鳥。
Flyable u1 = new UFO(); // 口語的說法:可飛行的 u1 是一台幽浮。

b1.fly(); // b1 的飛行方法。
u1.fly(); // u1 的飛行方法。
}
}

結果:

Fly by the wings
AntiGravity by an unknow force

Process finished with exit code 0

  實作的意義與繼承相同,但一個類別只可以繼承一個類別,卻可以實作多個介面。如想要你的類別實作多個介面,寫法如下:

class 類別名稱 implements 介面名稱1, 介面名稱2 {

  實作介面1 方法…

  實作介面2 方法…

2021年7月8日 星期四

學習 JAVA 的心得-02 學會物件導向之前與之後

  我發現我寫程式時關注的焦點變了~~

在學會物件導向之前

架構:當我所會的就僅僅只是基本語法時,我關注的重點是程式的架構。要成功解決問題,先要分析問題,想出適合的方法,寫出適合的架構實現自己所想出來的方法,其實光是到這一步就是個門檻了,寫出第一個程式的時候最困難,但之後就會習慣了。如果不是用改別人程式的方式,而是完全由自己想出程式的架構,寫出程式解決問題,應該會對程式架構的重要性有所體悟。我覺得如果只會改程式,是不能算會寫程式的。

簡明的架構:我在學會方法或副程式(JAVA 沒有副程式,比較接近的就是類別中的方法。)之前,為了完成目的,可能會寫出一個龐大的架構流程,但在學會方法或副程式後,其實要多加利用它們 ,可以讓主程式看起來更簡明。不過我是到後期才想到要盡量讓主程式只用來控制簡單的流程,複雜的處理寫在方法或副程式中。

在學會物件導向之後 

類別的闗係:此時我花比較多的時間去思考該如何設計類別間的內容物,這影響到後面類別間關係的設計,所以本質上仍是架構,只是變成了類別之間關係的架構。在學會繼承後,可能為了擁有某項屬性或方法而亂繼承,類別間的關係容易搞得很亂,學會介面後,就可以讓類別間的關係保持簡單。由於有物件導向的概念了,善用它,主程式的複雜度應該可以大幅度的降低。

以上僅個人心得,望不吝賜教! 

2021年7月7日 星期三

JAVA 物件導向的概念-12 抽象類別

  這是很久以前 Elton 教我的範例,由於註釋已經寫得很輕楚了,故本篇沒有太多說明。

抽象類別,同時也是父類別:
public abstract class Shape {
// 抽象類別不能拿來產生新的物件,也常被抽離出來當父類別。
// 抽象類別可以有屬性,也可以沒有。
// 抽象類別可以更節省程式碼。
public abstract double calArea();
// 抽象方法不能有具體作為,在此例子之下,它只是被子類別實作具體行為。
}

子類別之圖形:
public class Circle extends Shape {
int radius;
double pi=3.1415926;

public double calArea() {
return pi*radius*radius;
} // 它覆寫了父類別的抽象方法,有實作具體行為。


public int getRadius() {
return radius;
}

public void setRadius(int radius) {
this.radius = radius;
}
}

子類別之矩形:
public class Rectangle extends Shape {
int length;
int width;

public double calArea(){
return length*width;
} // 它覆寫了父類別的抽象方法,有實作具體行為。


public int getLength() {
return length;
}

public void setLength(int length) {
this.length = length;
}

public int getWidth() {
return width;
}

public void setWidth(int width) {
this.width = width;
}

}

主程式:
public class Main {

public static void main(String[] args) {
Shape c1 = new Circle(); // 模糊寫法,口語的說法是 形狀 c1 是圓形。
Shape r1 = new Rectangle(); // 模糊寫法,口語的說法是 形狀 r1 是方形。


((Circle)c1).setRadius(10); // 這邊要強制轉型,不然電腦認為 c1 仍是 Shape 。
((Rectangle)r1).setLength(10);
((Rectangle)r1).setWidth(5);

System.out.println(c1.calArea()); // 這邊不用強制轉型, Shape 裡面本來就有 calArea。
System.out.println(r1.calArea());

}

}
這邊再次複習前一篇的練習,子類覆寫了父類別中的方法,所以該 shape 物件使用父類別的方法時,效果是覆寫後的。


結果:
314.15926
50.0

Process finished with exit code 0