跳轉到內容

面向資訊學實踐的 XI 年級課程(CBSE)/程式設計基礎

來自華夏公益教科書

注意:基於Java 程式設計/Java 概述章節中的Java 程式設計書籍

在 Java 作為程式語言出現之前,C++ 是行業中的主要參與者。Java 的開發者面臨的首要目標是建立一個能夠處理 C++ 提供的大多數功能的語言,同時擺脫早期語言中一些更繁瑣的任務。

整合到 Java 中的新功能和升級改變了程式設計環境的面貌,併為面向物件程式設計賦予了新的定義。但與它的前身不同,Java 需要與標準功能捆綁在一起,並獨立於宿主平臺。本節將對此進行更詳細的解釋。

建立 Java 語言的首要目標

  • 它很簡單。
  • 它是面向物件的。
  • 它獨立於宿主平臺
  • 它包含用於網路的語言設施和庫。
  • 它旨在安全地執行來自遠端來源的程式碼。

Java 語言引入了一些在 C 和 C++ 等其他語言中不存在的新功能。

這些功能的引入是本頁面的主題。

早期程式語言

[編輯 | 編輯原始碼]

在 1980 年代和 1990 年代,計算機硬體經歷了效能和價格革命。更強大、更快的硬體以更低的價格提供,對大型複雜軟體的需求呈指數級增長。為了滿足需求,新的開發技術應運而生。

Dennis Ritchie於 1972 年開發的 C 語言花了十年時間才成為程式設計師中最受歡迎的語言。但是,隨著時間的推移,程式設計師發現用 C 程式設計由於其結構化語法變得很繁瑣。[1]儘管人們試圖解決這個問題,但後來才引入了一種新的開發理念,名為 *面向物件程式設計*(簡稱為 *OOP*)。使用 OOP,一個人可以編寫一段程式碼,並在以後重複使用,而無需一遍又一遍地重寫程式碼。1979 年,Bjarne Stroustrup開發了C++,它是對 C 語言的增強,其中包含 OOP 基礎知識和功能。

平臺依賴性和零模組化

[編輯 | 編輯原始碼]

然而,隨著時間的推移,人們開始意識到該語言架構中的問題。一方面,該語言在不同的平臺上產生了不同的結果/輸出。C/C++ 應用程式只遵守其目標平臺,而在其他平臺上則無法執行。為特定平臺構建的特定程式只會針對該特定平臺或硬體。這種與硬體的緊密整合帶來了更大的漏洞風險。

請注意,當特定程式碼編譯為可執行格式時,可執行檔案無法動態更改。為了使更改反映在完成的可執行檔案中,需要從更改的程式碼中重新編譯它。在 Java 的前身中,顯然 *沒有* **模組化**(將程式碼劃分為模組)。如果輸出應用程式不是單個可執行檔案,而是以模組的形式存在,則可以輕鬆更改單個模組並檢視應用程式中的更改,但在 C/C++ 中,對程式碼的輕微更改都需要重新編譯整個應用程式。

不安全的程式碼

[編輯 | 編輯原始碼]

由於該語言具有高度的控制能力來操作硬體,因此程式設計師可以訪問系統上的幾乎所有資源,無論是硬體還是軟體。這本來是該語言的優勢之一,但這種靈活性導致了混亂和複雜的程式設計實踐。當程式設計師必須手動嘗試使用系統的記憶體資源時,記憶體洩漏成為經常出現的麻煩。

現在,記憶體資源或緩衝區以一種特殊的方式工作。緩衝區一旦填滿資料,就需要在不再使用其內容後清理。如果程式設計師忘記在其程式碼中清理它,記憶體很容易過載。由於這些非常奇怪的特點,用 C/C++ 語言程式設計變得很繁瑣且不安全,並且用這些語言構建的程式容易出現記憶體洩漏和突然的系統崩潰,有時甚至會損害硬體本身。

非標準化和複雜性

[編輯 | 編輯原始碼]

C++ 建立在 C 語言之上,因此在該語言中,用不同的方法做同樣的事情變得很明顯。例如,在 C++ 中,可以用三種不同的方法建立物件。此外,該語言沒有與編譯器捆綁在一起的標準庫,而是依賴於其他程式設計師建立的資源,這些程式碼很少能很好地融合在一起。

網路功能

[編輯 | 編輯原始碼]

Java 的前身雖然功能強大,但缺乏與其他計算機聯網的標準功能,通常依賴於平臺複雜的聯網功能。由於幾乎所有網路協議都已標準化,Java 技術的建立者希望這成為該語言的一項旗艦功能,同時忠實於早期為標準化遠端過程呼叫所做出的進步。Java 團隊關注的另一個功能是它與全球資訊網和網際網路的整合。

新語言的議程

[編輯 | 編輯原始碼]

牢記上述所有問題,Java 的開發者建立了一系列功能來解決這些問題。在他們看來,Java 應該......

  • ......簡單易用,並收集來自早期語言中的經過測試的基礎知識和功能。
  • ......包含與該語言捆綁在一起的基本和高階功能的標準 API 集。
  • ......擺脫需要直接操作硬體(在這種情況下是記憶體)的概念,使語言安全。
  • ......與平臺無關,可以為每個平臺編寫一次(誕生了WORA 習語)。
  • ......能夠開箱即用地操作網路程式設計。
  • ......可以嵌入到 Web 瀏覽器中,並且......
  • ......能夠讓單個程式同時執行多工,並同時做多件事。
  • 簡單的語法,簡單的功能。那些可能被程式設計師濫用的功能沒有新增到語言中。它們是
    • 運算子過載
    • 多重繼承
    • 記憶體分配(使用自動垃圾回收)
    • 友元類(訪問另一個物件的私有成員)
    • 強制異常處理(程式設計師必須處理異常或宣告使用者必須處理它,有人必須處理它)
    • 自動初始化(提供一致的行為)
    • 顯式型別轉換的限制(與記憶體管理相關)
  • 平臺獨立性(隨處執行的概念)
    • 編譯為由 Java 虛擬機器執行的中間位元組碼
  • 網路程式設計
    • 能夠從遠端伺服器下載程式碼,並在執行時執行該程式碼
    • 建立 Applet 概念(Applet 可以在客戶端瀏覽器程式中執行)
  • 內建執行緒,為該語言提供多工處理能力
    • 易於多工處理(其他語言需要第三方庫才能做到這一點)
  • 與平臺無關的圖形使用者介面 (GUI)
    • AWT(包含大多數作業系統支援的小部件)
    • Swing(後來新增)

Java 於 1995 年釋出,當時網際網路開始對公眾開放。Java 的承諾在於客戶端瀏覽器端。Java 程式碼將被下載並作為 Java applet 在客戶端瀏覽器程式中執行。

然後重點轉向伺服器端。Java 擴充套件被新增到 JDK 中,它被稱為 J2EE

  • Servlet 執行 - (CGI 程式的替代方案)
  • JSP,JavaServer Pages - (在 HTML 中嵌入 Java 程式碼)
  • EJB - (分散式物件執行)

動態類載入

[edit | edit source]

在傳統的語言如 C 和 C++ 中,所有程式碼都必須在執行之前被編譯並連結到一個可執行程式。在 Java 中,類按需編譯。如果一個類在執行階段不需要,那麼該類甚至不會被編譯成位元組碼。

這個特性在網路程式設計中特別有用,因為我們事先不知道將要執行什麼程式碼。一個正在執行的程式可以從檔案系統或遠端伺服器載入類。

這個特性也使得一個 Java 程式理論上可以在執行期間改變自己的程式碼,以進行一些自學習行為。然而,更現實的做法是想象一個 Java 程式在執行之前生成 Java 程式碼,然後執行該程式碼。透過一些反饋機制,下次生成的程式碼可能會有所不同,從而隨著時間的推移而改進。

自動記憶體垃圾回收

[edit | edit source]

在傳統的語言如 C 和 C++ 中,程式設計師必須確保所有分配的記憶體都被釋放。釋放記憶體對於伺服器來說尤為重要,因為它必須連續執行幾天。如果一段記憶體使用後沒有被釋放,而伺服器只是不斷地分配記憶體,那麼這種記憶體洩漏會導致伺服器崩潰。

在 Java 中,釋放記憶體的任務從程式設計師手中解放出來。Java 虛擬機器跟蹤所有使用的記憶體。當記憶體不再使用時,它會被自動釋放。JVM 在後臺執行一個單獨的任務,釋放未引用、未使用的記憶體。這個任務被稱為 "垃圾收集器"。

"垃圾收集器" 始終處於執行狀態。這種自動記憶體垃圾回收功能使編寫健壯的伺服器端 Java 程式變得容易。程式設計師唯一需要關注的是物件建立的速度。如果應用程式建立物件的頻率比 "垃圾收集器" 釋放物件的頻率更快,那麼會導致記憶體問題。根據 JVM 的配置方式,應用程式可能會因為丟擲 NotEnoughMemoryException 異常而耗盡記憶體,或者會暫停以讓 "垃圾收集器" 完成其工作。

錯誤處理

[edit | edit source]

更多資訊請見: Java 程式設計/異常

傳統的錯誤處理方法是讓每個函式返回一個錯誤程式碼,然後讓呼叫者檢查返回值。這種方法的問題是,如果返回值充滿了錯誤檢查程式碼,那麼就會阻礙執行實際工作的原始程式碼,從而降低程式碼的可讀性。

新的錯誤處理方法是,函式/方法不再返回錯誤程式碼。當出現錯誤時,會丟擲一個異常。異常可以透過 catch 關鍵字在 try 塊的末尾進行處理。這樣,執行實際工作的程式碼就不需要被錯誤檢查程式碼混淆,從而提高程式碼的可讀性。這種新的錯誤處理方法被稱為異常處理。

異常處理也被新增到 C++ 中。然而,Java 和 C++ 的異常處理之間存在兩個區別。

  • 在 Java 中,丟擲的異常與 Java 中的任何其他物件一樣是一個 Java 物件。它只需要實現 Throwable 介面。
  • 編譯器檢查異常是否被捕獲。如果丟擲異常沒有捕獲塊,編譯器會給出錯誤。

平臺獨立性

[edit | edit source]

平臺獨立性意味著用 Java 語言編寫的程式必須在不同的硬體上以類似的方式執行。應該能夠編寫一次程式,並在任何地方執行。這是透過將 Java 語言程式碼 "半編譯" 成位元組碼來實現的 - 這是符合一組標準的簡化的虛擬機器指令。然後,程式碼在 Java 虛擬機器 上執行,這是一個用宿主硬體上的原生代碼編寫的程式,它將通用的 Java 位元組碼轉換為宿主硬體上的可用程式碼。此外,還提供了標準化的庫,以統一的方式訪問宿主機器的功能(例如圖形和網路)。Java 語言還支援多執行緒程式 - 這是許多網路應用程式所必需的。

該語言的第一個實現使用瞭解釋型虛擬機器來實現可移植性,許多實現至今仍採用這種方法。這些實現產生的程式執行速度比典型 C++ 編譯器和一些後來的 Java 語言編譯器建立的完全編譯程式慢,因此該語言因產生緩慢程式而聲名狼藉。最近的 Java VM 實現產生的程式執行速度快得多,使用了多種技術。

其中第一個技術是直接編譯成原生代碼,就像更傳統的編譯器一樣,完全跳過位元組碼。這可以實現極高的效能,但代價是可移植性。這在現在已經不再使用。

另一種技術,即 *即時* 編譯器或 "JIT",在程式執行時將 Java 位元組碼編譯成原生代碼,並將編譯後的程式碼儲存起來以供重複使用。更復雜的 VM 甚至使用 *動態重新編譯*,在該技術中,VM 可以分析正在執行程式的行為,並有選擇地重新編譯和最佳化程式的關鍵部分。這兩種技術都允許程式利用原生代碼的速度,而不會失去可移植性。

可移植性是一個在技術上難以實現的目標,Java 在這一目標上的成功存在爭議。雖然確實有可能為 Java 平臺編寫程式,使其在許多宿主平臺上都能一致地執行,但大量平臺上的微小錯誤或不一致性導致一些人嘲笑 Sun 的 "編寫一次,隨處執行" 的口號,將其改為 "編寫一次,到處除錯"。

然而,平臺無關的 Java 在伺服器端應用程式中非常成功,例如 Web 服務、Servlet 或企業 JavaBeans。

Java 在客戶端方面也取得了進展,首先是 抽象視窗工具包(AWT),然後是 Swing,最新的客戶端庫是 標準小部件工具包(SWT)。有趣的是,他們試圖如何處理兩種相互矛盾的消費力量。這些是 

高效、快速的程式碼;移植到最流行的硬體(編寫一次,隨處測試)
使用底層的本地子程式來建立 GUI 元件。這種方法被 AWTSWT 採用。
移植到任何已移植 JVM 的硬體(編寫一次,隨處執行)
為了實現後者的目標,Java 工具包不應該依賴於底層的本地使用者介面。 Swing 採用了這種方法。

有趣的是,這種方法是如何來回切換的。AWT - Swing - SWT。

安全執行遠端程式碼

[edit | edit source]

Java 平臺是最早提供廣泛支援從遠端來源執行程式碼的系統之一。Java 語言的設計考慮了 網路計算

Applet 可以執行在使用者的瀏覽器中,執行從遠端 HTTP 伺服器下載的程式碼。遠端程式碼執行在一個高度受限的 "沙箱" 中,保護使用者免受行為不端或惡意程式碼的侵害;釋出者可以申請證書,用於對 Applet 進行數字簽名,以確保其 "安全",從而允許它們突破沙箱並訪問本地檔案系統和網路,當然是在使用者的控制之下。

面向物件

[edit | edit source]

面向物件 ("OO"),指的是一種程式設計方法和語言技巧。OO 的主要思想是圍繞著它所操作的 "事物"(即物件)來設計軟體,而不是它所執行的操作。

隨著計算機硬體的進步,它帶來了建立更優秀軟體技術的必要性,以能夠建立不斷增長的複雜應用程式。其目的是使大型軟體專案更易於管理,從而提高質量並減少失敗的專案數量。面向物件的解決方案是最新軟體技術。

組合語言
軟體技術從組合語言開始,組合語言接近機器指令,易於轉換成可執行程式碼。每個硬體都有自己的組合語言。組合語言包含低階指令,例如將資料從記憶體移動到硬體暫存器、進行算術運算並將資料移回記憶體。程式設計師必須瞭解計算機的詳細架構才能編寫程式。
過程式語言
在組合語言之後,出現了高階語言。高階語言的編譯器用於將高階語言程式轉換為機器指令,從而減輕了程式設計師記憶計算機硬體架構的負擔。為了促進程式碼複用,並儘量減少GOTO指令的使用,引入了“過程式”程式設計技術。這簡化了軟體控制流的建立和維護,但忽略了資料的組織。當程式中存在大量全域性變數時,除錯和維護程式就會變得非常困難。全域性變數包含可以在應用程式的任何地方修改的資料。
面嚮物件語言
在面嚮物件語言中,資料透過資訊隱藏得到了重視。過程被物件取代。物件包含資料和控制流。我們的思維需要從過程轉向物件之間的互動。

評估

[edit | edit source]

大多數人認為,Java 技術在很大程度上滿足了所有這些目標。然而,該語言也有一些缺點。Java 傾向於比類似語言(如 C++)更高階,這意味著 Java 語言缺少硬體特定資料型別、任意記憶體地址的低階指標或運算子過載等程式設計方法。儘管這些功能經常被程式設計師濫用或誤用,但它們也是強大的工具。然而,Java 技術包含 Java Native Interface (JNI),一種從 Java 語言程式碼呼叫本機程式碼的方式。使用 JNI,仍然可以使用一些這些功能。

一些程式設計師還抱怨它缺乏多重繼承,多重繼承是包括 C++ 在內的幾種面嚮物件語言的一項強大功能。Java 語言將型別和實現的繼承分開,允許透過介面繼承多個型別定義,但僅允許透過類層次結構單一繼承型別實現。這在避免多重繼承的許多風險的同時,提供了多重繼承的大部分好處。此外,透過使用具體類、抽象類以及介面,Java 語言程式設計師可以選擇為定義的物件型別提供完整的、部分的或零實現,從而確保在應用程式設計中的最大靈活性。

有些人認為,對於某些專案來說,面向物件使得工作變得更加困難而不是更輕鬆。這種特別的抱怨並非 Java 語言獨有,也適用於其他面嚮物件語言。

C 程式設計師的說明

[edit | edit source]

存在一些工具可以幫助將現有專案從 C 遷移到 Java。一般來說,自動翻譯工具可以分為兩種不同的型別。

  • 一種型別將 C 程式碼轉換為 Java 位元組碼。它基本上是一個建立位元組碼的編譯器。它與任何其他 C 編譯器具有相同的步驟。另請參見 C 到 Java JVM 編譯器.
  • 另一種型別將 C 程式碼轉換為 Java 原始碼。這種型別更加複雜,使用各種語法規則來建立可讀的 Java 原始碼。對於希望將 C 程式碼遷移到 Java 並留在 Java 中的人來說,此選項是最好的選擇。[需要示例]

從 Java 應用程式呼叫 C 程式

[edit | edit source]

您可以使用 Runtime.exec 方法從執行中的 Java 應用程式呼叫程式。Runtime.exec 還允許您執行與程式相關的操作,例如控制程式的標準輸入和輸出,等待程式完成執行,並獲取其退出狀態。

這是一個簡單的 C 應用程式,它說明了這些功能。這個 C 程式將從 Java 中呼叫

#include <stdio.h>

int main() {
    printf("testing\n");
    return 0;
}

此應用程式將字串“testing”寫入標準輸出,然後以退出狀態 0 終止。要在 Java 應用程式中執行此簡單程式,請編譯 C 應用程式

$ cc test.c -o test

然後使用以下 Java 程式碼呼叫 C 程式

import java.io.*;
import java.util.ArrayList;

public class ExecDemo 
{
    static public String[] runCommand(String cmd) throws IOException 
    {
        // --- set up list to capture command output lines ---
        ArrayList list = new ArrayList();

        // --- start command running
        Process proc = Runtime.getRuntime().exec(cmd);

        // --- get command's output stream and
        // put a buffered reader input stream on it ---
        InputStream istr = proc.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(istr));

        // --- read output lines from command
        String str;
        while ((str = br.readLine()) != null)
            list.add(str);

        // wait for command to terminate
        try {
            proc.waitFor();
        }
        catch (InterruptedException e) {
            System.err.println("process was interrupted");
        }

        // check its exit value
        if (proc.exitValue() != 0)
            System.err.println("exit value was non-zero");

        // close stream
        br.close();

        // return list of strings to caller
        return (String[])list.toArray(new String[0]);
    }

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

            // run a command
            String outlist[] = runCommand("test");

            // display its output
            for (int i = 0; i < outlist.length; i++)
                System.out.println(outlist[i]);
        }
        catch (IOException e) {
            System.err.println(e);
        }
    }
}

該演示呼叫了一個名為 runCommand 的方法來實際執行程式。

 String outlist[] = runCommand("test");

此方法將一個輸入流連線到程式的輸出流,以便它可以讀取程式的輸出並將它儲存到字串列表中。

 
 InputStream istr = proc.getInputStream();
 BufferedReader br = new BufferedReader(new InputStreamReader(istr));  
                
 String str;
 while ((str = br.readLine()) != null)
     list.add(str);
  1. 結構化語法是一種編寫程式碼的線性方式。程式通常從程式程式碼的第一行開始解釋,直到它到達結尾。你不能將程式的後面部分連線到前面的部分。流程遵循從上到下的線性方法。
華夏公益教科書