面向資訊學實踐的 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)。有趣的是,他們試圖如何處理兩種相互矛盾的消費力量。這些是
- 移植到任何已移植 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);
- ↑ 結構化語法是一種編寫程式碼的線性方式。程式通常從程式程式碼的第一行開始解釋,直到它到達結尾。你不能將程式的後面部分連線到前面的部分。流程遵循從上到下的線性方法。