面向信息学实践的 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);
- ↑ 结构化语法是一种编写代码的线性方式。程序通常从程序代码的第一行开始解释,直到它到达结尾。你不能将程序的后面部分连接到前面的部分。流程遵循从上到下的线性方法。