我還是覺得這是基礎中的基礎。

算是 BBS 上的舊文重 PO 吧,畢竟這是自己的一些想法,留在部落格上好像比較好一點。

原先這篇文章是回應 aMaa 網友的這一篇『類別的方法中為什麼可以建立本身類別的物件』的問題,看起來好像沒啥關聯,但我覺得這還是很重要的,至於後續還有一些發展以及想法,我會另外用專文來寫。

簡單的講,我還是覺得這些看似不相干的基礎是很重要的。

當然,如果你寫程式的目的只是為了好玩,只是想做玩具,只想當成手邊解決問題的工具,那麼這些確實可以不用去弄懂它。

但如果你想靠這行吃飯,我覺得還是認命一點,把基礎搞好吧……

就像,你可以拿起鎚子、釘子、木板自己蓋出一間狗屋,但你應該不會想要去住沒有建築基礎專業知識的設計師、建築師蓋出來的房子吧?


【離題】

說實話,這個討論串讓我想到之前在自己的部落格上寫到的這一篇雜記裡關於程式語言學習的部份,有問到到底在學習程式式語言的時候,要從何處入手。

這一串討論看下來,看來看去,只有一個想法:會有這種疑問,根本就是因為不了解整個 von Neumann架構/程式語言/編譯器/機器語言/虛擬機器之間的關連所造成的嘛……

以下,可能很長,可能很多看似無關緊要的東西(就算你不懂,你的程式還是可以動),可是我自己認為這是值得去了解的基礎,如果你真的用心了解下面所講的東西,基本上就不會再出現類似的疑惑了。

【回題】

其實原 PO 的問題本質上和另一個問題一樣:為什麼像是下面的遞迴程式裡,第 6 行的時候,明明 sum 還明有定義完,卻可以呼叫自己呢?

// 用遞迴計算 1 + 2 + ... + n
int sum (int n) {
   if (n == 1) {
       return 1;
   }

   return n + sum(n-1);
}

為什麼下面的程式裡,Node 明明還沒定義完,裡頭卻又出現另一個 Node 呢?

class Node {
   private Node next;
}

我真的很想大叫:不要鬧了!大家到底知不知道 Java 程式語言 / Bytecode / Virtual Machine / von Neumann 電腦架構 / 原生可執行檔 / 機器語語之間的關係,到底知不知道一個 Java 程式是怎麼執行的啊?!

請問你自己一個問題:Java 程式執行的時候,是執行你寫的原始碼嗎?

(答:不是,實際上執行的是 bytecode,也就是 javac 翻譯出來的 .class 檔)

請問你自己第二個問題:你在執行你的 Java 程式 (Bytecode) 的時候,你知道 Bytecode 到底是什麼東西,做什麼用的嗎?

(答:可以將 Bytecode 視為一套虛擬的『類機器語言』,將其交由 Virtual Machine 解譯後,可以產生電腦 CPU 真正能夠理解的機器語言指令)

請問你自己第三個問題:你知道什麼叫 von Neumann 架構嗎?你知道一個『原生電腦程式』是如何在 von Neumann 架構上執行的嗎?

(答:如果不知道,請參閱『小人電腦』,裡面是簡化版的說明,但基本上目前的所有電腦都不脫離這個架構。)

如果你看完了上面的小人電腦,請你問你自己第四個問題:你在小人電腦裡,有看到『資料結構』、『函數』、『物件』、『類別』這種東西嗎?

(答:沒有,只有記憶體/指令/暫存器/Program Counter ,而且記憶體位置好像也都知能存數字【註】)

(謎之音:那我的函數、物件、資料夾構在哪裡?提示:我們還有可執行檔以及 Bytecode 這兩個東西沒講到。)

【註】嚴格來說這並不正確

請再問你自己第五個問題:你知道我們剛剛『定義』出來的計算總合的函式,如果翻譯成小人電腦的機器語言,用上述網頁中的機器語言表示出來會長什麼樣子嗎?

(答:你會看到 sum(n-1) 被翻成一個 CALL 指令,其參數 XX 的部份會是此函數的開頭,而 return 會被翻成 RETURN ,如果你不知道這是什麼意思,請把小人電腦再認真看一次,並注意最後一個函數呼叫的例子)

以上,是回答為什麼函數可以呼叫自己。

接下來,請再問你自己:記憶體 / 指標 / 參考 / 物件之間,究竟是什麼關係,你知道第二個 Node 的 Java 程式碼,被載入到記憶體後,到底長什麼模樣嗎?

(答:指標和參考本質上都是相同的,都是『記憶體位置』)

這不就很明顯了嗎?private Node next,說的是『next 是一個記憶體位置,而且這個記憶體位置應該要指到一個長得像 Node 的物件』。

但 next 真的必需指到 Node 物件嗎?(提示:多型/強制轉型/執行期錯誤)他不過就是個數字,來表示記憶體位置,誰管他指到什麼地方啊!

所以說,這有什麼好訝異的?不過就是『在 Node 的這個物件裡,有一個欄位是一個數字,他指到某一個記憶體的位置,而這個記憶體位址上的內容,應該要是另一個Node 物件(但不必然是)。』

這很直覺,很正常啊!

最後,回到原 PO 的問題,如果再把原 PO 的問題更簡化,可以問另一個問題,那就是以下的 Java 程式碼是否合法,如果它合法的話,執行這段 Java 程式碼裡的 test() 到底會發生什麼事,記憶體裡產生了哪些變化?

class Hello
{
    public void test ()
    {
        Hello hello = new Hello();
    }
}

public class Test
{
    public static void main (String [] args)
    {
        Hello hello = new Hello ();
        hello.test ();
    }
}

接下來,你要問你自己,Java 裡 new 出來的物件,到底在是住在記憶體的哪裡?而指到各物件的參考又活在哪裡?是在 Stack 還是在 Heap ?指標裡存的又是什麼?

再來, hello.test() 這一行會翻譯成什麼小人電腦的組合語言?(提示:Method call 和 function call 本質上是一樣的東西)

如果,你能回答出以上的問題,基本上就不會有『為什麼函數沒有定義完還可以呼叫自己,為什麼物件可以 new 自己,為什麼明明就還沒定義完,Node 裡卻可以有 Node』 的這種疑問了。

謎之音:所以說,這篇『爪哇學校的危害』還是有他的道理在的啊!

回響