《Java白皮书1996自译》02:简单和友好的

詹姆斯·高斯林

第二章 简单和友好的

在设计上达到完美,不是无法再增加什么,而是不需要再删减任何东西。

安东尼·圣艾修伯里(法国作家)

罗伯特·海莱因在他的科幻小说《滚石》中评论到:

每一项技术都要经历三个阶段:首先是一个极其简单、令人不满意的小玩意;第二,是一组极为复杂的小工具,其设计目的是克服原来的缺点,从而通过极其复杂的折衷办法取得某种令人满意的性能;第三,最后一个合理的设计。

海莱因的评论可以很好地描述许多编程语言的演变。Java为编程语言的发展提供了一种新的观点——创建一种小型和简单的语言,这种语言仍然足够全面,可以处理各种各样的软件应用程序开发。虽然Java在表面上类似于C和c++,但Java之所以简单,是因为它从前辈那里系统地删除了一些特性。本章讨论了Java的两个主要设计特性,即简单(从删除特性开始)和熟悉(因为它看起来像C和c++)。下一章将更详细地讨论Java的面向对象特性。在这一章的末尾,你会发现关于在Java进化过程中从C和c++中删除的特性的讨论。

设计目标

简单是Java最重要的设计目标之一。简单性和从它的C和c++祖先那里移除许多不确定的“特性”使Java相对较小,并减少了程序员在生成可靠应用程序方面的负担。为此,Java设计团队检查了“现代”C和c++语言的许多方面,从而确定在现代面向对象编程环境中可以消除的特性。

另一个主要的设计目标是,对于个人计算机和工作站领域中的大多数程序员来说,Java看起来很熟悉,其中有很大一部分系统程序员和应用程序程序员熟悉C和c++。因此,Java“看起来像”c++。熟悉C、Objective C、c++、Eiffel、Ada和相关语言的程序员应该会发现他们的Java语言学习曲线相当短——大约几周。

为了说明Java简单和熟悉的方面,我们遵循了一系列优秀编程书籍的传统,向您展示HelloWorld程序。它是你能写的最简单的程序,它能做一些事情。这是用Java实现的HelloWorld。

image

这个例子声明了一个名为HelloWorld的类。类将在面向对象编程的下一章中讨论,但通常我们假设读者熟悉对象技术并了解类、对象、实例变量和方法的基础知识。

在HelloWorld类中,我们声明一个名为main()的方法,该方法反过来包含一个方法调用,以在标准输出上显示字符串“Hello world!”,打印“Hello world!”的语句通过调用out对象的println方法来实现这一点。out对象是系统类中的一个类变量,它对文件执行输出操作。这就是HelloWorld的所有含义。

2.1、Java编程语言的主要特性

Java™编程语言在某种程度上遵循c++,这使得许多程序员对它很熟悉。本节描述Java编程语言的基本特性,并指出该语言与它的祖先C和c++的区别。

2.1.1、基本数据类型

除了这里讨论的基本数据类型之外,Java编程语言中的所有内容都是对象。如果需要,甚至可以将原始数据类型封装到库提供的对象中。Java编程语言在其基本数据类型集中紧跟C和c++,但有几个小的例外。基本数据类型只有三种,即数字类型、字符类型和布尔类型。

数字数据类型

整数数字类型是8位字节、16位短字节、32位int和64位长字节。Java中的8位字节数据类型已经取代了旧的C和c++字符数据类型。Java对char数据类型进行了不同的解释,如下所述。

在Java中,整数数据类型没有无符号类型说明符。

真正的数字类型是32位浮点型和64位双精度浮点型。真正的数字类型及其算术运算由IEEE 754规范定义。浮点文字值,如23.79,默认情况下被认为是双精度的;如果您希望将其赋值给float变量,则必须显式地将其强制转换为float。

字符数据类型

Java语言字符数据与传统的c语言不同。Java的char数据类型定义了16位Unicode字符。Unicode字符是无符号的16位值,它们定义0到65,535范围内的字符代码。如果你写一个声明,如

image

您将得到一个初始化为字符q的Unicode值的Unicode(16位无符号值)类型。通过对Java语言应用程序的字符数据类型采用Unicode字符集标准,Java语言应用程序可以适应国际化和本地化,极大地扩展了全球应用程序的市场。

布尔数据类型

Java添加了一个布尔数据类型作为基本类型,默认了现有的C和c++编程实践,开发人员在其中为真和假、YES和NO或类似的结构定义关键字。Java布尔变量假定值为真或假。Java编程语言boolean是一种独特的数据类型;与常见的C实践不同,Java编程语言布尔类型不能转换为任何数字类型。

2.1.2、算术和关系运算符

所有熟悉的C和c++操作符都适用。Java编程语言没有无符号数据类型,因此在该语言中添加了>>>操作符,以指示无符号(逻辑)右移。Java还使用+运算符进行字符串连接;下面关于字符串的讨论将讨论串联。

2.1.3、数组

与C和c++相比,Java编程语言数组是一流的语言对象。Java编程语言中的数组是具有运行时表示的真实对象。可以声明和分配任何类型的数组,还可以分配数组的数组以获得多维数组。

你用这样声明一个数组,比如说,点(你在别处声明的类):

image

这段代码声明myPoints是一个未初始化的点数组。此时,为myPoints分配的唯一存储是一个引用句柄。在未来的某个时间,您必须分配所需的存储量,例如:

image

将包含十个引用的数组分配给初始化为null引用的点。注意,数组的这种分配实际上并不为您分配Point类的任何对象;你还需要分配点对象,像这样:

image

对myPoints元素的访问可以通过普通的c风格的索引来执行,但是所有的数组访问都被检查,以确保它们的索引在数组的范围内。如果索引超出数组的范围,将生成异常。

数组的长度存储在特定数组的length实例变量myPoints中。length包含myPoints中元素的数量。例如,代码片段:

image

将值10赋给howMany变量。

C数组的内存指针的概念元素,和,任意指针运算导致不可靠的C代码不再你能离开一个数组的末尾,可能破坏记忆和导致了著名的“delayed-crash”综合症,出现一个内存访问违反今天表现后几小时或几天。程序员可以确信,Java中的数组检查将导致更健壮和可靠的代码。

2.1.4、字符串

字符串是Java编程语言对象,而不是c语言中的伪字符数组。实际上有两种字符串对象:string类用于只读(不可变)对象。StringBuffer类用于您希望修改的字符串对象(可变字符串对象)。

虽然字符串是Java编程语言对象,但是Java编译器遵循C语言的传统,提供了C程序员使用C风格的字符串所享有的语法便利,也就是说,Java编译器理解用双引号括起来的字符字符串将被实例化为字符串对象。因此,声明:

image

在后台实例化String类的对象,并使用包含Unicode字符表示“Hello world!”的字符串字符串初始化它。

Java技术扩展了+运算符的含义,表示字符串连接。因此,你可以写这样的语句:

image

这个代码片段将字符串“There are”与数字值num转换为字符串的结果连接起来,并将其与字符串“文件中的字符”连接起来。然后在标准输出上打印这些连接的结果。

String对象提供了一个length()访问器方法来获取字符串中的字符数。

2.1.5、多级中断

Java编程语言没有goto语句。要中断或继续多重嵌套循环或开关结构,您可以将标签放在循环和开关结构上,然后断开或继续按标签命名的块。下面是来自Java编程语言内置字符串类的一小段代码:

image

continue测试语句位于嵌套在另一个for循环中的for循环中。通过引用标签测试,continue语句将控制传递给外部for语句。在传统C语言中,continue语句只能继续立即包围的块;要继续或退出外部块,程序员传统上要么使用辅助布尔变量,其唯一目的是确定外部块是继续还是退出;或者,程序员(mis)使用goto语句退出嵌套块。Java编程语言中标记块的使用大大简化了编程工作并大大减少了维护。

标记块的概念可以追溯到20世纪70年代中期,但在现代编程语言中并没有得到很大的普及。Perl是另一种实现标记块概念的现代编程语言。Perl的下一个标签和最后一个标签等价于Java中的continue label和break label语句。

2.1.6、内存管理和垃圾收集

C和c++程序员现在已经习惯了显式管理内存的问题:分配内存、释放内存以及跟踪什么内存可以在什么时候释放。显式内存管理已被证明是bug、崩溃、内存泄漏和性能低下的有效来源。

Java技术完全消除了程序员的内存管理负载。不存在c风格的指针、指针算术、malloc和free。自动垃圾收集是Java及其运行时系统不可或缺的一部分。虽然Java技术提供了一个新的操作符来为对象分配内存,但是没有显式的空闲函数。一旦分配了对象,运行时系统就会跟踪对象的状态,并在对象不再使用时自动回收内存,从而释放内存供将来使用。

Java技术的内存管理模型基于对象和对对象的引用。Java技术没有指针。相反,对已分配存储的所有引用(实际上意味着对对象的所有引用)都是通过符号“句柄”进行的。Java技术内存管理器跟踪对对象的引用。当对象没有更多引用时,该对象是垃圾收集的候选对象。

Java技术的内存分配模型和自动垃圾收集使您的编程任务更容易,消除了整个类的bug,并且通常比通过显式内存管理获得的性能更好。下面是一个代码片段,它说明了什么时候发生垃圾收集:

image

变量dest在执行reverseIt方法时用作临时对象引用。当dest超出作用域(reverseIt方法返回)时,对该对象的引用已经消失,然后它就成为垃圾收集的候选对象。

2.1.7、后台垃圾收集器

Java技术垃圾收集器通过在与HotJava TM浏览器等软件应用程序交互时利用用户行为的本质来实现高性能。典型交互应用程序的典型用户有许多自然的停顿,在这些停顿中,他们在思考面前的场景或思考下一步要做什么。Java运行时系统利用这些空闲时间,并在没有其他线程竞争CPU周期时以低优先级线程运行垃圾收集器。垃圾收集器收集并压缩未使用的内存,增加了在大量交互使用期间需要足够的内存资源的可能性。

使用线程运行垃圾收集器只是从Java技术的集成多线程功能中获得的协同作用的众多示例之一,否则一个棘手的问题将以简单而优雅的方式得到解决。

2.1.8、集成的线程同步

Java技术支持多线程,在语言(语法)级别和通过其运行时系统和线程对象的支持。虽然其他系统提供了多线程的工具(通常通过“轻量级进程”库),但是在语言本身中构建多线程支持为程序员提供了一种更简单、更强大的工具,可以轻松创建线程安全的多线程类。多线程将在第7章中更详细地讨论。

2.2、从C和c++中删除的特性

本章的前一部分集中讨论了Java的主要特性。本节讨论在Java发展过程中从C和c++中删除的特性。

第一步是从C和c++中消除冗余。在许多方面,C语言演变成一组重叠的特性,提供了太多的方式来表达相同的内容,而在许多情况下却没有提供所需的特性。在试图添加“C中的类”时,c++只是增加了更多的冗余,同时保留了C的许多固有问题。

2.2.1、不再需要类型定义、定义或预处理器

用Java编写的源代码很简单。没有预处理器,没有#define和相关功能,没有typedef,没有这些特性,不再需要头文件。Java语言源文件提供其他类及其方法的声明,而不是头文件。

C和c++的一个主要问题是您需要理解另一个程序员代码的上下文量:您必须阅读所有相关的头文件、所有相关的#定义和所有相关的类型定义,然后才能开始分析程序。从本质上说,使用#define和typedefs编程会导致每个程序员都发明一种新的编程语言,除了它的创建者之外,任何人都无法理解这种语言,从而破坏了良好编程实践的目标。

在Java中,您可以通过使用常量来获得#define的效果。通过声明类可以获得typedef的效果——毕竟,类有效地声明了一个新类型。您不需要头文件,因为Java编译器将类定义编译成二进制形式,该形式通过链接时间保留所有类型信息。

通过删除所有这些行李,Java变得非常无上下文。程序员可以阅读和理解代码,更重要的是,修改和重用代码更快更容易。

2.2.2、不再需要结构或联合体

Java没有复杂数据类型的结构或联合体。当你有类的时候,你不需要结构和联合;您可以通过使用适当的实例变量声明一个类来实现相同的效果。

下面的代码片段声明了一个名为Point的类。

image

下面的代码片段声明了一个名为Rectangle的类,它使用Point类的对象作为实例变量。

image

在C语言中,您将这些类定义为结构。在Java中,只需声明类。您可以随意将实例变量设置为私有或公共,这取决于您希望向其他对象隐藏实现细节的程度。

2.2.3、不再需要枚举

Java没有枚举类型。通过声明一个类,您可以获得与enum类似的东西,该类存在的惟一理由是保存常量。你可以像这样使用这个功能:

image

你现在可以引用,比如说,South常量使用direction。South这个符号。

以这种方式使用类来包含常量比C的枚举类型有一个主要优点。在C(和c++)中,枚举中定义的名称必须是惟一的:如果您有一个名为HotColors的枚举,其中包含红色和黄色的名称,则不能在任何其他枚举中使用这些名称。例如,您不能定义另一个名为TrafficLightColors的枚举也包含红色和黄色。

使用Java中的类到容器常量技术,您可以在不同的类中使用相同的名称,因为这些名称由包含的类的名称限定。从上面的例子中,您可能希望创建另一个名为CompassRose的类:

image

没有歧义,因为包含类的名称充当常量的限定符。在第二个示例中,您将使用CompassRose符号。西北地区访问相应值。Java有效地为您提供了限定枚举的概念,所有这些概念都在现有的类机制中。

2.2.4、不再需要函数

Java没有函数。面向对象编程取代了函数式和过程式编程风格。混合这两种风格只会导致混淆并稀释面向对象语言的纯粹性。使用函数可以做的任何事情都可以通过定义类并为该类创建方法来完成。从上面考虑Point类。我们添加了一些公共方法来设置和访问实例变量:

image

如果x和y实例变量是该类私有的,则访问它们的唯一方法是通过该类的公共方法。下面是如何从Point类的对象(比如矩形类的对象)中使用Point类的对象:

image

这并不是说函数和过程本身就是错误的。但是对于给定的类和方法,我们现在只剩下一种表示给定任务的方法了。通过消除函数,程序员的工作大大简化了:只处理类及其方法。

2.2.5、不再需要多继承

多重继承——以及它产生的所有问题——从Java中被丢弃。多继承的理想特性由接口提供——在概念上类似于目标C协议。

接口不是类的定义。相反,它是一个或多个类将实现的一组方法的定义。接口的一个重要问题是它们只声明方法和常量。变量不能在接口中定义。

2.2.6、不再需要goto语句

Java没有goto语句。研究表明,后藤被(mis)使用的频率比“因为它在那里”要高。消除goto会简化语言——例如,对于将goto放到for语句中间的效果没有规则。对大约10万行C代码的研究表明,大约90%的goto语句纯粹是为了获得跳出嵌套循环的效果。如上所述,多级中断和继续删除goto语句的大部分需要。

2.2.7、不再需要操作符重载

程序员无法提供重载标准算术运算符的方法。同样,通过声明类、适当的实例变量和操作这些变量的适当方法,也可以轻松地实现操作符重载的效果。消除操作符重载会大大简化代码。

2.2.8、不再需要自动强制

Java禁止C和c++风格的自动强制。如果希望将一种类型的数据元素强制转换为会导致精度损失的数据类型,则必须通过强制转换显式地这样做。考虑以下代码片段:

image

将myFloat赋值给myInt会导致编译器错误,指示可能丢失精度,并且必须使用显式转换。因此,您应该将代码片段重写为:

image

2.2.9、不再需要指针

大多数研究都认为指针是程序员向代码中注入bug的主要特性之一。由于结构已经消失,数组和字符串是对象,因此不再需要指向这些结构的指针。因此,Java没有指针数据类型。任何需要C语言中的数组、结构和指针的任务都可以通过声明对象和对象数组更容易、更可靠地执行。您可以通过数组的算术索引访问数组,而不是对数组指针进行复杂的指针操作。Java运行时系统检查所有数组索引,以确保索引在数组的范围内。

由于指针不正确,您不再有悬空指针和内存垃圾,因为Java中没有指针。

2.3、概要

综上所述,Java是:

简单——要完成工作,您需要理解的语言结构的数量是最少的。

友好的——Java看起来像C和c++,但却忽略了这些语言的巨大复杂性。

既然您已经了解了Java是如何通过删除其前辈的特性来简化的,那么请阅读下一章来讨论Java的面向对象特性。


Familiar /fə’mɪlɪə/ adj. 熟悉的;常见的;亲近的 n. 常客;密友

注:

我个人认为,对于c或者c++程序员来说,Java借鉴过来的那些特性确实不陌生。但是,更重要的是,Java抛弃了c和c++的那些令人费解和头疼的特性,这大大降低了学习Java的难度和开发效率。所以,我觉得Familiar翻译为”友好“更为贴切。


好好学习,天天向上!继续下一章…


发布了31 篇原创文章 · 获赞 30 · 访问量 5780

猜你喜欢

转载自blog.csdn.net/goldentec/article/details/104776478