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

2021年6月28日 星期一

JAVA 物件導向的概念-11 覆寫、隱藏父類別中的方法,過載

  本篇故事:愛未未在逃亡的過程中,見證到太多無辜的女人被汙衊為魔女而遭到殺害,這一切逼使著她投入黑暗,於是暗黑愛未未出現了! 她要復仇!

  這一篇要練習的覆寫和隱藏父類別的方法,前陣子很紅的廣告詞:開局就送魔關羽!有魔關羽就有一般的關羽,他們一定極為相似,魔關羽繼承了關羽的大部份屬性,也一定有所不同,魔關羽的技能可能更帶有邪氣!

  本篇我所做的就是使者與暗黑使者兩個類別,其中暗黑使者類別繼承了使者類別,同時修改(隱藏或覆寫)了它所繼承的方法,只要子類別內的方法和父類別內的方法同名、同參數列、同回傳值形態,就會修改!之所以要都相同,理由之一是因為 JAVA 還有一個功能叫過載,即使方法名稱相同,只要傳入的參數不同,JAVA 會自動選擇適合的方法。過戴會在本篇最下方再介紹一次。

父類別:

/* 使者類別 */
// 編寫人:Lancelot Cheng
public class Envoy {
public String name, brithday, blood;
public String profession, weapon;
public int level, exp, oID;
//private Contract envoyDemon;

// 建構子
public Envoy (int oID, String nn, String bD, String bb,
String pro, int lev, int ex, String wea) {
name = nn;
brithday = bD;
blood = bb;
this.oID = oID;
profession = pro;
level = lev;
exp = ex;
weapon = wea;
}

public static void surrender(){
System.out.println("投降");
}

public void normalattack () {
System.out.println( weapon + "一般攻擊!");
}
}

子類別:注意這裡有兩個方法的名稱與父類別相同,同內容不同,此即為修改。

/* 暗黑使者類別 */
// 編寫人:Lancelot Cheng
public class DarkEnvoy extends Envoy{

public DarkEnvoy(int oID, String nn, String bD, String bb
, String pro, int lev, int ex, String wea) {
super(oID, nn, bD, bb, pro, lev, ex, wea);
}

public static void surrender(){ // 與父類別同名:隱藏
System.out.println("自爆!");
}

public void attack () { // 與父類別同名:覆寫
System.out.println(weapon + "暗黑屬性攻擊!");
}
}

主程式:創造了一個愛未未使者和暗黑愛未未使者,都用它們的方法。

/* 主程式 */
// 編寫人:Lancelot Cheng
public class Main {
public static void main( String[] args){

// 創造一個愛未未使者物件。
Envoy ai = new Envoy( 0, "愛未未", "0712", "AB",
"魔術師", 10, 200, "撲克牌");

System.out.print("愛未未攻擊:");
ai.normalattack();
System.out.print("愛未未投降:");
ai.surrender();

System.out.println();

// 創造一個暗黑愛未未使者物件。
DarkEnvoy darkAi = new DarkEnvoy(0, "愛未未", "0712", "AB",
"魔術師", 10, 200, "撲克牌");

System.out.print("暗黑愛未未攻擊:");
darkAi.attack();
System.out.print("暗黑愛未未投降:");
darkAi.surrender();
}
}

結果:兩個物件的方法名稱相同,但結果不同,意思是子類別內的方法的確修改了。

愛未未攻擊:撲克牌一般攻擊!
愛未未投降:投降

暗黑愛未未攻擊:撲克牌暗黑屬性攻擊!
暗黑愛未未投降:自爆!

Process finished with exit code 0


  這個練習滿簡單的,但不是一個好的寫法,我認為最好的寫法是使者類別與暗黑使者類別有共同的父類別,因為最好物件都是由子類產生,原因是子類別只能繼承到父類別中公開的變數,於是裡面所有的變數都必須設為公開,造成的結果是父類別所產生的物件,其成員可以被任意讀取與更動。都是修改方法,為什麼一個叫隱藏一個叫覆寫,我不知道原因。

  關於過載,在同一個類別中,定義同名的方法,當你在使用該類別產生的物件之方法時, JAVA 可以以你輸入的參數類型、數量判斷要用哪一個方法。舉個應用的例子,我們要計算三角形面積,依照所拿到的條件是邊長、邊長、邊長或角度、邊長、角度還是邊長、角度、邊長,會用使用不同的方法,但這些方法的目的都相同,就是算出面積,對我們來說,因為方法不同而去記憶不同的方法名字很麻煩,所以名字都取一樣,讓 JAVA 依輸入參數屬性幫我們判斷使用哪個方法就好,這就是過載。其中輸入的參數也可以是我們自己定義的物件。在這邊的算三角形面積例子有點不太好,因為角度、邊長都可以是浮點數。

  之後會再介紹介面與抽象類別,期待下一次的惡魔使者愛未未。


2021年6月26日 星期六

JAVA 物件導向的概念-10 結合類別

  本篇故事:在中古世紀,表演魔術為生的愛未未被當成魔女而遭受教會組織的追殺,逃亡的過程中進入了一間詭異的圖書館,不小心招喚出詐欺惡魔伊莉莎白,並與之締結了契約,愛未未以生命為代價,借得了惡魔的力量,成為了惡魔使者,終於有了與教會組織對抗的能力。

  簡單的說,兩個類別間的結合是透過第三個類別完成,則第三個類別稱為結合類別。僅僅只是第三個類別分別與第一和第二個類別有結合關係,概念上很簡單,只是[類別間的關係之結合關係]那篇文中的事情做兩次而已,如果您還沒看過,也許能去看一下[JAVA 物件導向的概念-06],以方便理解本篇中的範例。

  在本範例中,我會先創造愛未未使者、伊莉莎白惡魔兩個物件,然後再創造c_01契約物件,建立時同時結合前面兩個物件。結合後,愛未未可以使用伊莉莎的的方法。

使者的類別:
/* 使者類別 */
// 編寫人:Lancelot Cheng
public class Envoy {
private String name, brithday, blood;
private String profession, weapon;
private int level, exp, oID;
private Contract envoyDemon;

// 建構子
public Envoy (int oID, String nn, String bD, String bb,
String pro, int lev, int ex, String wea) {
name = nn;
brithday = bD;
blood = bb;
this.oID = oID;
profession = pro;
level = lev;
exp = ex;
weapon = wea;
}

public void toDemonContrant (Contract eD) {
envoyDemon = eD;
}
// *** 上一行重點,接契約物件。

public void powerOfDemon(){
if(envoyDemon != null) {
System.out.print(name + " 以自己的生命為代價,使用惡魔的絕招——");
envoyDemon.dem.useStunt(); // 透過契約使用惡魔的絕招。
}
else{
System.out.println("愛未未 沒有和任何惡魔締結契約,只是嚇嚇對方!");
}
}

}

惡魔的類別:
/* 惡魔類別 */
// 編寫人:Lancelot Cheng
public class Demon {
private String name;
private String title, stunt;
private int level, exp, oID;
private Contract envoyDemon;

// 建構子
public Demon (int oID, String nn, String tit, int lev, int ex, String stu) {
this.oID = oID;
name = nn;
title = tit;
level = lev;
exp = ex;
stunt = stu;
}

public void useStunt(){
System.out.println(stunt);
}

public void toEnvoyContrant (Contract eD) {
envoyDemon = eD;
}

}

契約的類別:在這裡扮演結合類別的角色。
/* 契約類別 */
// 結合類別:結合使者與惡魔。
// 透過契約,使者可以借得惡魔的力量,但也必須付出相應的代價。
// 編寫人:Lancelot Cheng
public class Contract {
int iD;
public Envoy env;
public Demon dem;

// 建構子。
public Contract( int iD, Envoy env, Demon dem) {
this.iD = iD;
this.env = env;
this.dem = dem;
env.toDemonContrant(this); // 把自己丟給使者物件。
dem.toEnvoyContrant(this); // 把自己丟給惡魔物件
}

// 解約的方法。
public void deContract(){
iD = 0;
env.toDemonContrant(null); // 把 null 丟給使者物件。
dem.toEnvoyContrant(null); // 把 null 丟給惡魔物件
env = null;
dem = null;
} // 注意上面的順序和建構子的不同,可以思考一下為什麼。

}

主程式:
/* 主程式 */
// 這個程式主要示範結合類別。
// 編寫人:Lancelot Cheng
public class Main {
public static void main(String[] args) {

// 產生 愛未未使者 物件。
Envoy ai = new Envoy(271828,"愛未未", "0912","O",
"魔術師", 99, 10000,"樸克牌");

// 產生 伊莉莎白惡魔 物件。
Demon elizabeth = new Demon(416, "伊莉莎白", "詐欺惡魔",
71, 8000, "虛幻的攻擊");

// 產生契約,同時將 愛未未使者 與 伊莉沙白惡魔 兩個物件丟給契約,結合兩個物件。
Contract c_01 = new Contract( 1, ai, elizabeth);


// 此時已經結合完成,所以可以使用愛未未使者物件中與惡魔有關的方法。
ai.powerOfDemon();

// 使用合約中,解除契約的方法。
c_01.deContract();

// 令契約容為 null,此動作或許可以讓電腦知道後面用不到它了,進而釋放記憶體。
c_01 = null;

// 再次使用與惡魔用關的方法,看結果可以知道用不出來了。
ai.powerOfDemon();

}
}

結果:
愛未未 以自己的生命為代價,使用惡魔的絕招——虛幻的攻擊
愛未未 沒有和任何惡魔締結契約,只是嚇嚇對方!

Process finished with exit code 0


  我手邊的書說結合類別使用在在多對一或多對多的結合關係,另外上網查,也有人表示非必要少用結合類別。所以我也不確定它的重要性,不過我還是寫了這個範例。我的範例仍然是一對一的結合,如果想要多對多,或多對一,在使者與惡魔類別中的契約物件宣告要宣告成陣列。

  再補充,範例中,我用契約結合了愛未未使者和伊莉莎白惡魔,但其實也是可以解約的,解除結合的方法我沒有寫在這邊,如果真的有人感興趣,請留言,我會補上。

  雖然使者和惡魔都有名字、等級和經驗,可以提出去創造一個共同的父類別,但這裡為了讓程式看起來不要太複雜,所以還是各別寫了。

  預告一下,我覺得惡魔使者愛未未的故事還不錯,之後會以這個故事為主題來寫 JAVA 程式,目標之一是寫出戰鬥的程式,可能是回合制或是時間軸制的。不過目前我還沒有使用圖的能力,所以只能是文字顯示了。

2021/6/29 :我新增了解除結合的方法於契約類別中,同時為了程式碼比較簡便,刪除了使者類別與惡魔類別的資料顯示方法。

2021年6月24日 星期四

JAVA 物件導向的概念 09- 類別間的相依關係(dependency)

  這個也滿簡單,一個類別的值或分法都與其他類別無關,具有獨立性,就稱為獨立類別,如下的例子:

薪水類別:

/* 薪水 */
// 編寫人:Lancelot Cheng
public class Salery {
public double basic = 10000.0; // 基本薪資
public double extraUnit = 999.0; // 獎金基本單位

}

上面是薪水規定的類別,基本薪水 10000 元,額外獎金單位是 999 元。

  那麼一個類別的值或方法與其他類別相關,就稱為相依類別。前面說過繼承的概念,下面三個類別各別是人類、公務人員、圓桌武士,設計邏輯是公務人員屬於人類,所以繼承人類;而圓桌武士屬於公務人員,所以繼承公務人員。

人類類別:

/* 人類 */
// 編寫人:Lancelot Cheng
public class Human {
public String name, brithday, blood;

// 建構子
public Human(String name, String brithday, String blood){
this.name = name;
this.brithday = brithday;
this. blood =blood;
}
}

公務人員類別:

/* 公務人員 */
// 編寫人:Lancelot Cheng
public class GoverEmployee extends Human {

public GoverEmployee(String name, String brithday, String blood) {
super(name, brithday, blood);
}

public double getBasic(Salery sa){
return sa.basic;
}

public double getExtraUnit(Salery sa, double score) {
return sa.extraUnit*score;
}
}

大中土 在公務人員類別中,設定了接收薪水物件的方法與計算額外獎金的方法,它的方法結果和薪水類別的成員變數有關,這便是相依。


圓桌武士類別:

/* 圓桌武士 */
// 編寫人:Lancelot Cheng
public class KOTRT extends GoverEmployee {
private String title, weapon;
private double score;

public KOTRT(String name, String brithday, String blood, String tt, String ww, double sco) {
super(name, brithday, blood);
title = tt;
weapon = ww;
score = sco;
}

public double getScore() {
return score;
}

public void setScore(double score) {
this.score = score;
}
}

接下來是主程式,新增政府給薪物件和藍斯洛特圓桌武士物件,並顯示出他的薪水:

/* 主程式 */
// 編寫人:Lancelot Cheng
public class Main {

public static void main(String[] args) {

Salery goverSal = new Salery(); // 創造政府薪水物件 gover

KOTRT lancelot = new KOTRT("藍斯洛特", "1001", "B",
"騎士之花", "阿隆黛特", 120);

System.out.println( "藍斯洛特的基本薪資與額外加給獎金:" + lancelot.getBasic(goverSal) +
";" + lancelot.getExtraUnit(goverSal, lancelot.getScore()));
}

}

結果:

藍斯洛特的基本薪資與額外加給獎金:10000.0;119880.0

Process finished with exit code 0

說明:因為藍斯洛特圓桌武士是公務人員,所以薪水按照公務人員的方法給定。

  寫這些文章也許可以賺一些廣告費,但目前為止不知道我不合規定的地方是什麼,所以只好各位面都試看看了。未來會減少曾寫過的程式碼,然後程式碼會試著更精簡,讓讀者更容易看懂。

  忽然間發現自己學到的東西己經可以重現以前文字遊戲的回合制戰鬥了,如果能搭配圖形遊戲性會更好,雖然能做出來的東西會很粗糙,不過就當個練習吧,之後會寫出來。

JAVA 實用程式分享-01 計算台灣電費的程式

   這邊難得練習了一個比較實用的程式,目的是輸入度數與是否夏季,得到電費。下面有兩個類別,一個是營業用電,一個是分營業用電。 

類別:家用,即非營業用電費。

// 編寫人:Lancelot Cheng
public class HomeUseBill{
public double degree;
private double moneyPD, bill, aa;
private String season;

public HomeUseBill(String season){
//super(degree);
this.season = season;
}

public double notSummer(double degree){
aa = 0;
if (degree <= 120*2){
moneyPD = 1.63;
aa = degree*moneyPD;
}
if (degree > 120*2 && degree <= 330*2){
moneyPD = 2.1;
aa = 195.6*2 + ( degree - 120*2 )*moneyPD;
}
if (degree > 330*2 && degree <= 500*2){
moneyPD = 2.89;
aa = 636.6*2 + ( degree - 330*2 )*moneyPD;
}
if (degree > 500*2 && degree <= 700*2){
moneyPD = 3.94;
aa = 1127.9*2 + ( degree - 500*2 )*moneyPD;
}
if (degree > 700*2 && degree <= 1000*2){
moneyPD = 4.6;
aa = 1915.9*2 + ( degree - 700*2 )*moneyPD;
}
if (degree > 1000*2){
moneyPD = 5.03;
aa = 3295.9*2 + ( degree - 1000*2 )*moneyPD;
}
return aa;
}

public double summer(double degree){
aa = 0;
if (degree <= 120*2){
moneyPD = 1.63;
aa = degree*moneyPD;
}
if (degree > 120*2 && degree <= 330*2){
moneyPD = 2.38;
aa = 195.6*2 + ( degree - 120*2 )*moneyPD;
}
if (degree > 330*2 && degree <= 500*2){
moneyPD = 3.52;
aa = 695.4*2 + ( degree - 330*2 )*moneyPD;
}
if (degree > 500*2 && degree <= 700*2){
moneyPD = 4.8;
aa = 1293.8*2 + ( degree - 500*2 )*moneyPD;
}
if (degree > 700*2 && degree <= 1000*2){
moneyPD = 5.66;
aa = 2253.8*2 + ( degree - 700*2 )*moneyPD;
}
if (degree > 1000*2){
moneyPD = 6.41;
aa = 3951.8*2 + ( degree - 1000*2 )*moneyPD;
}
return aa;
}

public double fee(double degree){
switch (season) {
case "非夏":
//System.out.println("非夏季用電");
bill = notSummer(degree);
break;
case "夏":
//System.out.println("夏季用電");
bill = summer(degree);
break;
default:
System.out.println("不合規定的輸入文字");
}
return bill;
//System.out.println(bill);
}
}


類別:商用,即營業用電費。

// 編寫人:Lancelot Cheng
public class BusiUseBill{
public double degree;
private double moneyPD, bill, aa;
private String season;

public BusiUseBill(String season){
//super(degree);
this.season = season;
}

public double notSummer(double degree){
aa=0;
if (degree <= 330*2){
moneyPD = 2.12;
aa = degree*moneyPD;
}
if (degree > 330*2 && degree <= 700*2){
moneyPD = 2.91;
aa = 699.6*2 + ( degree - 330*2 )*moneyPD;
}
if (degree > 700*2 && degree <= 1500*2){
moneyPD = 3.44;
aa = 1776.3*2 + ( degree - 700*2 )*moneyPD;
}
if (degree > 1500*2 ){
moneyPD = 5.05;
aa = 4528.3*2 + ( degree - 1500*2 )*moneyPD;
}
return aa;
}

public double summer(double degree){
aa=0;
if (degree <= 330*2){
moneyPD = 2.53;
aa = degree*moneyPD;
}
if (degree > 330*2 && degree <= 700*2){
moneyPD = 3.55;
aa = 834.9*2 + ( degree - 330*2 )*moneyPD;
}
if (degree > 700*2 && degree <= 1500*2){
moneyPD = 4.25;
aa = 2148.4*2 + ( degree - 700*2 )*moneyPD;
}
if (degree > 1500*2 ){
moneyPD = 6.43;
aa = 5548.4*2 + (degree - 1500*2 )*moneyPD;
}
return aa;
}

public double fee(double degree){
//System.out.println(degree);

switch (season) {
case "非夏":
//System.out.println("非夏季用電");
bill = notSummer(degree);
break;
case "夏":
//System.out.println("夏季用電");
bill = summer(degree);
break;
default:
System.out.println("不合規定的輸入文字");
}

return bill;
//System.out.println("電費 = " + degree + " * " + moneyPD + " = " + bill);
}
}


主程式:宣告你要的物件,並實作輸入"夏"或是"非夏"的字串,使用物件的 fee 方法,輸入度數。

// 編寫人:Lancelot Cheng
public class Main {
public static void main(String[] args) {

// 非營業用
BusiUseBill lancelotHome = new BusiUseBill( "非夏");
// 營業用
HomeUseBill lancelotStore = new HomeUseBill( "非夏");

System.out.println("非營業用電費:" + lancelotHome.fee(330));
System.out.println("營業用電費:" + lancelotStore.fee(170));
}
}

結果:

非營業用電費:636.6
營業用電費:360.40000000000003

  我寫完後發現有更好的寫法,不過也懶得改了,下一次寫程式之前,會多想一想。