顯示具有 Java 標籤的文章。 顯示所有文章
顯示具有 Java 標籤的文章。 顯示所有文章

2010年10月12日 星期二

Overloading VS. Overriding

剛學物件導向程式設計的人常常把Overriding與Overloading兩個詞搞混,不僅是因為這兩個單字看起來很像,連使用方式都很類似。於是愛用國貨的中文書市場為了造福國內廣大英文辨識力不良族群,推出了以下絕妙的通俗翻譯:
  • Overloading=多載。
  • Overriding=覆載。
這翻譯絕妙的地方在於繼承了英文單字看起來很類似的特性,從字面來看也很難明白其中意涵,於是還是讓人看得霧煞煞,令人不禁想豎起大拇指大讚「這就是物件導向啊!」
就在這個時候,國內的大學生發現這可能是教授為了能當更多人而玩的文字遊戲,又或者是當初的翻譯者害怕太多人學會之後飯碗不保,刻意翻得很奇怪,於是提出更明確的翻譯方式以自救:
  • Overloading = 給予太多工作 (load多到太over)。
  • Overriding =忤逆家長 (也就是ride在家長頭上太over的意思)。

2010年1月30日 星期六

用Java盡大學生的義務

最近總是出現一堆新聞在報導時下大學生的醜態,舉凡遲到、翹課、上課吃雞腿、考試作弊等等。每次看到這類新聞,都不禁為這些大學同學感到惋惜。但是,請不要誤會,我寫這篇文章的目的絕不是要炫耀自己是個努力用功人見人愛的乖學生,而是這些醜態我在大學前就全都幹過了。我惋惜的不是他們和我一樣糟,而是可憐他們運氣差,剛好符合媒體的胃口而被爆料。
身為一個大學生,我大約知道公認的義務,這些義務不外乎:
  1. 記得上課
  2. 但是不要遲到
  3. 過程中保持清醒
  4. 再餓也不能吃雞腿
  5. 沒雞腿吃也要認真聽課
  6. 快被二一,仍保持考試不作弊
  7. 就算作業完全不會寫,也不能抄襲,而且要準時交
大學生簡直是聖人了!

對我這個糟糕大學生而言,只有兩件事情是必須做的:
  1. 註冊
  2. 選課
這麼說或許漠視了全天下負責繳學費的父母與全天下努力教書的老師們的期望。確實,每個父母都希望孩子成龍成鳳;每個教授都希望學生認真聽課;每個公民都希望學生(尤其是那些念到台大醫科的)不要浪費社會資源。然而,我認為大學不僅是一個鑽研學術的機構,更是個學習如何抉擇的小社會:除了註冊與選課外,其他義務應是學生了解做的理由後才選擇去做的,否則,我們的教育還是在製造同樣思維的人,與我們一直要擺脫的填鴨式教育並無什麼不同。

看到這裡,一定有人開始懷疑本文與主題的關係。但是,別著急,按照慣例,接下來的文章一定會努力拗回正題的!
我真的要說的是,每次我在執行「選課」這項大學生的義務時,總是會遇到相當多競爭者要搶同一門課,而且好死不死每次運氣都很差,抽籤的結果總是沒選到,只好在加退選階段等選上的人退選。然而加退選階段搶課的人還是很多,每次為了選到一門課,都得坐在電腦前不斷按加選按鈕,浪費了相當多時間。為了剷除那些妨礙我盡大學生義務的絆腳石,上次選課時,我終於下定決心寫出一個滑鼠連點程式(然而,當程式一完成,開始讀秒準備發射時,我才發現想選的課已經選上了..囧rz)。

本文要教大家做的就是如何以Java實作滑鼠連點程式。程式碼十分短,大約只要15行。
在此先介紹Java AWT裡的一個稱為Robot的類別,大家可別看到名字就以為是給搜尋引擎用來做網路蜘蛛(Web Spider)的,事實上我有個不懂裝懂的同學就這麼想;也不要認為它是用來做機器人行動規畫的工具,它和真實的機器人一點關係也沒有。它其實是個用來模擬圖形介面使用者動作的類別。換句話說,它可以模擬使用者滑鼠移動、按下鍵盤或滑鼠按鍵,並且可以擷取目前螢幕的畫面。

Robot類別提供以下幾個方法來模擬滑鼠動作:
  • public void mouseMove(int x, int y); // x,y為螢幕像素位置

  • public void mousePress(int buttons);
    /* buttons可以是以下的值
    * java.awt.event.InputEvent.BUTTON1_MASK 代表左鍵
    * java.awt.event.InputEvent.BUTTON2_MASK 代表中鍵
    * java.awt.event.InputEvent.BUTTON3_MASK 代表右鍵*/

  • public void mouseRelease(int buttons); //同上

  • public void mouseWheel(int wheelAmt);
    /* wheelAmt為滑鼠滾輪的轉動量,
    * 正值為向前轉,負值為向後轉。*/

注意按下(press)滑鼠按鍵後,必須放開(release)才是一次點擊(click)。若忘了把按鍵放開,按鍵會一直被按著。

連點程式全部的程式碼只有這樣:
import java.awt.*;
import java.awt.event.*;
class RobotTest{
public static void main(String[] arg)throws AWTException,
InterruptedException{
Robot robot = new Robot();
while(true){
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
Thread.sleep(3000); // 讓執行緒睡3秒
}
}
}

注意在每次點擊必須讓執行緒暫停一段時間,否則滑鼠會不停點擊,到時連這個連點程式都很難關掉。

這裡只介紹到Robot類別的一種應用,事實上他能夠實現相當多功能,諸如螢幕放大鏡、螢幕畫面擷取器之類的小程式。
有興趣深入的人可以參考Java API的介紹:Robot。

2009年7月23日 星期四

What is “this”?

What is “this”?這是許多物件導向程式語言初學者的疑問。
在許多物件導向語言中都存在著「this」關鍵字,舉凡Java、C++、C#都有這樣一個謎一般的關鍵字,在VB.Net與Ruby中則是以「self」之名存在著。某些非物件導向的程式語言如Javascript,甚至也提供「this」來模擬物件導向的特性。
究竟「this」是天使的化身,還是地獄的使者?是什麼樣的力量促使他在物件導向的世界裡屹立不搖?其實,它沒那麼偉大,充其量只是個「第一人稱代名詞」。
在解釋它存在的必要之前,我們必須先為一個人生難題找出解答:如何在不說「我」的情況下,用合理的文法表達「教授把我當了」?如果你說「教授把XXX當了」,這樣的句子很奇怪,像是說另一個和你同名同姓還被你爆料的可憐同學。如果你說「教授當了」,請問教授到底當了誰?如果你說「被教授當了」,聽起來合理,但這其實是把「我」省略的慣用句型,文法上不合理,因為缺乏主詞。
不知道有沒有人可以找出答案,如果有請留言告訴我。無論如何,代名詞不是我的重點,我只是想證實第一人稱代名詞的必要性:若沒有第一人稱代名詞,我們很難用語言表達自己的存在,也很難做第一人稱敘述。
在物件導向的世界裡也是一樣的。由於一個類別可以產生許多實體,就必須用一個代名詞來讓個別實體表達「我」,以區分和其他實體的不同。一個類別的實體就是用「this」表達「我」。

例如以下程式碼:
class Student{
private int score;
Student(int s){
score = s; //A
}
public void say(){
if( this.score > 59) // B
System.out.println("教授讓我過了!");
else
System.out.println("教授把我當了!");
}
public static void main(String[] arg){
Student 好學生 = new Student(80);
Student 混學生 = new Student(40);
好學生.say();
混學生.say();
}
}

在main中,產生兩個Student類別的實體,兩個實體say的結果不一樣,因為this.score的值不一樣。這樣就可以看出,每個new出來的實體都有自己的this,代表各自的實體。

※B的this是可以省略的,因為是在同一個類別,Java編譯器會在編譯時自動加入this。如A的score也可以改為this.score。

其實看下圖就一目了然:



main方法的Stack Frame中,「好學生」與「混學生」參照分別參考到不同的Student實體,兩個Student實體的this參照也分別參考到自己從屬的實體。如此一來,每個實體就可以用this表達「我」。例如,我的score就是this.score。
那麼你可能會有個疑惑:難道「this」存在的意義只是為了表達像this.score這種可以省略的語法嗎?「this」真正的好處在於,實體可以將自己傳遞給其他實體。

為了解釋這點,我們定義一個Professor類別,並在Student類別裡加入getScore方法,如下:
class Professor{
public void grade(Student student){
student.score = (int)(Math.random()*101);
}
}
class Student{
...
Professor 叫獸 = new Professor();
public int getScore(){
叫獸.grade(this); // C
return score;
}

public static void main(String[] arg){
Student 羔羊 = new Student(0);
羔羊.getScore();
羔羊.say();
}
}

我們先產生一個Student的實體「羔羊」,並呼叫getScore方法。呼叫時,羔羊的實體就會傳入叫獸.grade()方法,並設定score。C的部分,就是Student實體將自身傳入叫獸.grade()方法的部分。其實這行程式碼,翻成中文就是「叫獸給我打分數。」「this」就是「我」!

2009年7月8日 星期三

執行緒同步化之時間怪獸

昨天終於把專題Server端的Database Connection Pool改好了。原本以為是Thread之間的Race condition,造成資料庫記錄取得之前,連線就被回收連線的Thread給close掉。後來才發現是自己沒把 java.sql.Connection的autoCommit設為false,造成Connection在還沒有被commit的狀態下就被放回 Connection Pool裡,時間一到就帶著沒送出的資料一起被回收。問題根本不在Thread的同步,而是我幾乎忘記有autoCommit這東西。
不過,問題改好之後,發現更大的問題:速度超慢。更離譜的是,使用單執行緒的方式執行,竟然還比有快取的多執行緒池快10倍以上。昨天晚上看著 Profiler的分析到凌晨4點,仍然找不出問題在哪,只看到執行緒wait的頻率很高。白天上課一直恍神,完全無法理解老師在上什麼,不過可能也因為恍神太多,讓我在走回宿舍的路上精神飽滿,聽著Claudio Abbado指揮Lucerne Festival Orchestra演出的Mahler交響曲第二號,靈機一動想到了一個可能性:「會不會有人拿著互斥鎖,做一些很耗時間的工作?」回到宿舍看code,果如所料!

試比較下列這兩段程式碼,你比較喜歡哪一種寫法?

Code 1.
synchronized(this){
if(queue.isEmpty()){
if(connectionCount.get()< maxConnectionCount){
conn = new
DBConnection(this,getRawConnection());

connectionCount.incrementAndGet();
}
}
else{...}
}
Code 2.
synchronized(this){
if(queue.isEmpty()){
if(connectionCount.get()< maxConnectionCount){
newConn = true;
connectionCount.incrementAndGet();
}
}
else{...}
}

if(newConn)
conn = new DBConnection(this,getRawConnection());
一般而言,多數人都會選Code 1.,因為兩段程式碼功能一樣,Code1卻顯得比較簡潔直觀。然而,Code1就是我一開始寫的,很花時間的程式碼。

Code2和Code1最大的差別在於Code 2把 conn = new DBConnection(this,getRawConnection()); 從同步區塊取出。這麼做會讓效能增加數倍的原因是getRawConnection()操作太花時間。如果放在同步區塊,代表一次只有1個執行緒能夠呼叫這個方法。這個方法一次得花10秒鐘的時間去和遠端資料庫連繫,如果一次只有1個執行緒呼叫,則20個執行緒至少得花200秒。Code2的getRawConnection()呼叫因為是在同步區塊之外,能夠讓多個執行緒同時取用,就沒有這種問題。
這次的教訓告訴我們,可以放在同步區塊的程式碼越少越好。至少,非必要的話,不要把怪獸放進去。

2009年7月1日 星期三

物件繼承的物件觀點-轉型篇

這篇文章是物件繼承的物件觀點一文的續集,將用物件的功能觀點圖來說明物件的強制轉型何時會成功。
首先,我們先釐清一些有關於物件轉型的概念。
在Java中,所有子類別的物件不需要強制轉型就能夠隱含轉換為父類別型態。因此以下程式碼皆能夠通過編譯,也能正常執行。

女人 欣郁 = new 女人();
人類 路人甲 = 欣郁;

人類 阿呆 = new 男人();