书名:Java和Android开发学习指南(第2版)
ISBN:978-7-115-41753-4
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
• 著 [加] Budi Kurniawan
译 李 强
责任编辑 陈冀康
• 人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
• 读者服务热线:(010)81055410
反盗版热线:(010)81055315
本书是Java语言学习指南,特别针对使用Java进行Android应用程序开发展开了详细介绍。
全书共50章。分为两大部分。第1部分(第1章到第22章)主要介绍Java语言基础知识及其功能特性。第2部分(第23章到第50章)主要介绍如何有效地构建Android应用程序。
本书适合任何想要学习Java语言的读者阅读,特别适合想要成为Android应用程序开发人员的读者学习参考。
欢迎阅读本书。
本书是针对那些想要学习Java语言,特别是想要进行Android应用程序开发的人编写的。本书包含两个部分,第1部分主要介绍Java,第2部分介绍如何有效地构建Android应用程序。
本书中关于Java的内容并非每一项Java技术都讲到(在一本书里,无论如何也不可能涵盖所有的内容,这也是为什么大多数Java图书都专注于一项技术)。但是,本书介绍了最重要的Java编程主题,这些主题是你自学其他技术所必须掌握的。特别是第1部分介绍了一名专业的Java程序员所必须掌握的3个主题:
构建一门高效的Java课程的难点在于,这3个部分是彼此独立的。一方面,Java是一门OOP语言,因此,如果你了解OOP的话,其语法很容易学习。另一方面,像继承、多态和数据封装这样的OOP功能,最好是和现实世界的例子一起来讲解。遗憾的是,理解现实世界的Java编程需要具备Java核心库的知识。
由于这种相关性,这3个主题并没有分为3个独立的部分。相反,介绍一个主题的章节和另一个主题的章节是相互交织的。例如,在介绍多态之前,本书确保你熟悉某些Java类,以便可以给出现实世界的例子。此外,如果不能全面理解某种类的话,是不能有效地讲解诸如泛型这样的语言特性的,因此,本书在讨论了支持类之后才介绍泛型。
还有一些情况是,可能会在一个或多个地方找到一个主题。例如,for语句是一种基本的语言功能,应该在较早的章节中介绍,同时for也可以用于遍历一个集合对象,只能在教授了集合框架之后才能介绍这种功能。因此,for是在第3章中初次介绍的,在第14章再次介绍。
本书第2部分介绍了Android框架,以及一个Java程序员开发App所需要掌握的工具。然后,介绍了进行Android编程的基本话题,包括Android用户界面、位图和图形处理、动画、音频/视频录制,以及任务同步。
下面的内容从一个较高的高度介绍了Java,介绍了OOP并且简单描述了本书中每章的内容。
Java不仅是一种面向对象编程语言,也是一组技术,它使得软件开发更加快速,并且使得应用程序更加健壮和安全。多年来,Java已经是一种技术选择,因为它提供了如下的一些优点:
Sun Microsystems公司于1995年引入了Java,并且Java已经成为了通用性的语言,而它最初为人们所熟知,还是作为一种编写applet这种较小的、在Web浏览器中运行并为静态Web添加交互的语言。对于Java早期的成功,互联网的发展功不可没。
正如我们所说,applet并不是使Java发光的唯一因素,Java吸引人的另一个因素是平台无关性的承诺,由此而产生的口号是“一次编写,处处运行”。这意味着,你编写的完全一样的程序,将会在Windows、UNIX、Mac、Linux以及其他的操作系统上运行。而这是其他一些编程语言所无法做到的。那个时候,C和C++还是开发应用程序最常用的语言。Java一诞生,就抢了它们的风头。
这就是Java#1.0版。
1997年,Java 1.1发布了,添加了更好的事件模型、Java Beans以及国际化等重要的功能。
1998年12月,Java 1.2发布了。在其发布3天之后,版本号改为2,这标志着一次巨大的市场活动的开始,从1999年开始,Java作为“下一代”技术而发售。Java 2有4个版本销售,分别为标准版(J2SE)、企业版(J2EE)、Micro版(J2ME)和Java Card(在其品牌名中没有出现2)。
2000年发布的下一个版本是1.3,也就是J2SE 1.3。两年以后出现版本1.4,即J2SE 1.4。2004年发布了J2SE 1.5版。然而,Java 2的1.5版本的名字改为了Java 5。
2006年11月13日,在Java 6正式发布前的1个月,Sun Microsystems公司宣布将Java开源。Java SE 6是Sun公司邀请外部开发者设计代码并帮助修复bug的第一个版本。Sun公司之前确实也接受非雇员设计的代码,例如Doug Lea对于多线程方面的工作,但是,这是Sun公司第一次发出公开的邀请。Sun公司承认自己的资源有限,而外界的贡献者能够帮助他们更快地完成开发任务。
2007年5月,Sun公司将Java源代码作为免费软件开放给了OpenJDK社群。随后,IBM公司、Oracle公司和Apple公司加入了OpenJDK社群。
2010年,Oracle公司收购了Sun公司。
2011年7月发布了Java 7。2014年3月发布了Java 8,这都是通过OpenJDK进行开源协作的结果。
你可能听到过术语“平台无关性”或“跨平台”,这意味着你的程序可以在多种操作系统上运行。这是对Java的流行贡献最大的功能。但是,是什么使得Java能够与平台无关呢?
在传统的编程中,源代码编译为可执行的代码。可执行代码只能在它所针对的平台上运行。换句话说,针对Windows编写和编译的代码,只能够在Windows上运行,针对Linux编写的代码,只能够在Linux上运行,以此类推,如图I.1所示。
图I.1 传统编程范型
Java程序则编译为字节码。字节码本身不能运行,因为它不是原生代码。字节码只能够在Java虚拟机(JVM)上运行。JVM是一个原生应用程序,它负责解释字节码。通过使用JVM可用在众多的平台上,Sun公司将Java变为了跨平台的语言。如图I.2所示,完全相同的字节码,可以在已经开发了JVM的任何操作系统上运行。
图I.2 Java编程模型
当前,JVM对于Windows、UNIX、Linux、Free BSD,以及世界上主流操作系统均可用。
前面提到Java程序必须要编译。实际上,任何编程语言都需要编译才可用。编译器是将程序源代码转换为一种可执行格式(字节码、本地代码或者其他形式)的程序。在开始用Java编程之前,你需要下载一个Java编译器。Java编译器是名为javac的程序,是Java compiler的缩写。
尽管javac可以把Java源代码编译为字节码,但是要运行字节码,你需要一个Java虚拟机。此外,由于要使用Java核心库中的各种类,还需要下载这些库。Java运行时环境(Java Runtime Environment,JRE)包含了一个JVM和类库。你可能猜到了,针对Windows的JRE和针对Linux的JRE是不同的,和针对其他操作系统的JRE也不同。
Java软件以两种形式可用:
概括起来,JVM是运行字节码的本地应用程序。JRE是包含了JVM和Java类库的环境。JDK包括JRE以及其他工具,包括一个Java编译器。
JDK的第1个版本是1.0。其后的版本是1.1、1.1、1.2、1.3、1.4、1.5、1.6、1.7和1.8。对于较小的发布,在版本号的后面再添加另外一个数字。例如,1.8.1是1.8版本的第一个较小的升级。
JDK 1.8比JDK 8更为知名。包含在一个JDK中的JRE,其版本和JDK的版本相同。因此,JDK 1.8包含了JRE 1.8。这个JDK通常也叫作SDK(软件开发工具箱)。
除了JDK,Java程序员还需要下载说明了核心库中的类、接口和枚举类型的Java文档。你可以从提供JRE和JDK的相同的URL来下载文档。
Sun Microsystems公司对于推动Java做了很好的工作。其市场策略的一部分是,创造了Java 2这个名字,而实际上它是基于JDK 1.2的。Java 2有3个版本。
第5版中的名称变了。J2SE变成了Java Platform,Standard Edition 5(Java SE 5)。此外,J2EE和J2ME中的2已经去掉了。企业版当前的版本是Java Platform,Enterprise Edition 7(Java EE 7)。J2ME现在叫作Java Platform,Micro Edition(Java ME,不带版本号)。在本书中,Java 8通常写作Java SE 8。
和Sun公司作为产品推出的第一个Java版本J2SE 1.4不同,Java SE 5及其后的Java版本都是一组规范,定义了需要实现的功能。这个软件自身叫作一个参考实现。Oracle、IBM和其他的公司与OpenJDK一起工作,提供了Java SE 8参考实现,以及Java的下一个版本的参考实现。
Java EE 6和Java EE 7也是一组规范,包括了servlets、JavaServer Pages、JavaServer Faces、Java Messaging Service等技术。要开发并运行Java EE应用程序,需要一个Java EE应用程序服务器。任何人都可以实现一个Java EE应用程序服务器,这就解释了为什么市场上有各种应用程序服务器可供使用,包括很多开源的产品。如下是Java EE 6和Java EE 7应用程序服务器的示例:
在如下网址中可以找到完整的列表。
http://www.oracle.com/technetwork/java/javaee/overview/compatibility-jsp-136984.html
JBoss、GlassFish、WildFly、Geronimo和TomEE都是开源的Java EE服务器。它们有各自不同的许可,因此,在决定使用该产品之前要确保先阅读其许可。
Java持续成为占有统治地位的技术,这和Sun公司的很多策略有关,Sun公司纳入了其他产业的从事者来确定Java的未来。通过这种方式,很多人感到他们拥有Java。很多大型的公司,例如IBM、Oracle、Google、Fujitsu等,都在Java中投资颇多,因为它们都可以提出一种技术的规范,并且推动Java技术的下一个版本成为它们想要看到的样子。协作的努力采取了JCP程序的形式。其Web站点的URL是http://www.jcp.org。
JCP程序所产生的规范,叫作Java Specification Requests(JSR)。例如,JSR 337指定了Java SE 8。
面向对象编程(object-oriented programming,OOP)通过基于现实世界的对象来建模应用程序而起作用。OOP的3大原理是封装、继承和多态。
OOP的好处是实实在在的。这也是大多数现代编程语言(包括Java),都采用面向对象范型的原因。我甚至可以引用语言变迁中的两个知名的例子来说明OOP所得到的支持:C语言演变为C++,而Visual Basic升级为Visual Basic.NET。
下面介绍OOP的好处,并且介绍学习OOP的容易之处和难处。
OOP的好处包括代码易于维护、代码复用以及扩展能力。下面更为详细地介绍这些好处。
1.易于维护。现代软件应用程序倾向于变得很大。一个较大的系统可能曾包含数千行的代码。而现在,即便是那些数百万行代码的程序,也不能算是大程序了。C++之父Bjarne Stroustrup曾经说过,当系统变得越来越大的时候,就会给开发者带来问题。无论如何,一个较小的程序可以用任何语言编写。即便不是很容易的话,最终也可以让它工作。但是一个较大的程序则完全不同。如果你没有使用良好的编程技术,你刚修改完旧的错误,就会出现新的错误。
之所以出现这种情况,是因为较大的程序中存在相互依赖的情况。当修改了程序中的一部分的内容时,你可能不会意识到这个修改会影响到其他的部分。OOP很容易让应用程序模块化,并且模块化会降低维护的难度。模块化是OOP内在的特性,因为作为对象的模板,一个类自身就是模块化的。好的设计应该允许一个类包含类似的功能和相关的数据。OOP中经常使用的一个重要的术语是耦合(coupling),它表示两个模块之间相互作用的程度。各个部分之间的松耦合,使得代码更容易复用,而代码复用正是OOP的另一个好处。
2.复用性。复用性表示之前编写的代码,可以由代码的作者或其他需要使用最初代码所提供的相同功能的人重复使用。这并不会令人吃惊,因为OOP语言常常带有一组准备好的库。在Java中,该语言带有数百个类库或应用程序接口(application programming interfaces,API),都经过了仔细的设计和测试。编写和发布你自己的库也很容易。在编程平台中,支持可复用性是非常吸引人的,因为它缩短了开发时间。
类的可复用性的主要的挑战之一是要为类库创建好的文档。一个程序员有多快才能找到他想要的功能的类?查找这样一个类更快,还是从头开始编写一个新的类更快?好在Java核心API和扩展API带有详尽的文档。
可复用性并不只是适用在编码阶段复用类或其他的类型,当设计一个OO系统的应用程序的时候,OO设计问题的解决方案也可以复用。这些解决方案叫作设计模式(design pattern)。为了使得引用每个解决方案更加容易,需要给每个模式一个名称。可复用的设计模式的最早的目录,可以在Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides所编写的经典图书《Design Patterns: Elements of Reusable Object-Oriented Software》中找到。
3.可扩展性。每个应用程序都是独特的,它有自己的需求和规范。就可复用性而言,有时候,你不会发现一个已有的类提供了你的应用程序所需的确切功能。然而,你可能会发现有一两个类提供了部分功能。可扩展性意味着,你仍然可以通过扩展它们以满足你的需求,从而使用这些类。你仍然可以节省时间,因为你不必从头开始编写代码。
在OOP中,可扩展性是通过继承来实现的。你可以扩展一个已有的类,为其添加一些方法或数据,或者修改你不喜欢的方法的行为。如果你知道一些基本功能在很多情况下都要使用,但是你不想自己的类提供非常具体的功能,你可以提供一个泛型类,随后可以扩展这个类来为应用程序提供具体的功能。
Java程序员需要掌握OOP。然而,如果你曾经使用一种过程式语言,如C或Pascal的话,那么,掌握OOP很有意义。在这一点上,有坏消息,也有好消息。
先来看坏消息吧。
研究者已经争论过在学校教授OOP的最好的方法,一些人认为最好是在介绍OOP之前教授过程式编程。在很多课程中,当学生接近其大学时期的最后一年的时候,才教授OOP课程。
然而,最近的研究表明,一些具备过程式编程技能的人,其思考范型与那些OOP程序员的视角以及解决问题的方式非常不同。当这类人学习OOP的时候,他们所面临的最大的挑战是,必须经过范型迁移。也就是说,要花6~18个月的时间将思维方式从过程式范型过渡到面向对象范型。另一项研究表明,那些没有学习过过程式编程的学生,则不会认为OOP有那么难。
现在来看好消息。
Java可以算得上最容易学习的OOP语言了。例如,你不需要担心指针,不必花费宝贵的时间来解决由于没有成功销毁未使用的对象而导致的内存泄露问题等。最后,Java带有非常充足的类库,在其早期的版本中,这些类库的bug也很少。一旦你了解了OOP的边边角角的知识,使用Java编程真的很容易。
下面我们来看看本书中每一章的内容。
第1章介绍了如何下载和安装一个JDK,其目标是让你对使用Java有一些感觉。此部分内容包括编写一个简单的Java程序,使用javac工具来编译它,以及使用java程序运行它。此外,对于编码惯例给出了一些建议,还介绍了集成开发环境。
第2章介绍了Java语言的语法。你将会了解诸如字符集、基本类型、变量以及操作符等主题。
第3章介绍了Java语句,包括for、while、do-while、if、if-else、switch、break和continue等。
第4章是本书中的OOP内容的开始。首先介绍了Java对象是什么,以及如何存储在内存中。然后,继续讨论类、类成员以及两个OOP概念(抽象和封装)。
第5章介绍了Java核心库中的重要的类,包括java.lang.Object、java.lang.String、java.lang.StringBuffer、java.lang.StringBuilder、包装类和java.util.Scanner。这是很重要的一章,因为本章所介绍的这些类是Java中最常用的类。
第6章讨论了数组,这是Java的一种特殊的语言功能,得到了广泛的使用。本章还介绍了操作数组的工具类。
第7章讨论了支持代码扩展的OOP特性。本章教你扩展一个类、影响子类的可见性、覆盖方法等。
第8章介绍了错误处理机制。毫无疑问,错误处理在任何语言中都是一项重要的功能。作为一门成熟的语言,Java有非常强大的错误处理机制,可以帮助预防bug。
第9章介绍了操作数字时的3个问题:解析、格式化和操作。本章介绍了能够帮助你完成这些任务的Java类。
第10章介绍了接口,它不仅仅是没有实现的类。接口定义了服务提供者和客户之间的一个协议。本章介绍了如何使用接口和抽象类。
第11章介绍了多态,并且给出了有用的示例。多态是OOP的主要支柱之一。当一个对象的类型在编译的时候还不知道,多态就特别有用。
第12章介绍了枚举类型,这是从Java 5之后增加的一种类型。
第13章介绍了如何使用Java 8新添加的日期和时间API,以及在老的Java版本中使用的旧API。
第14章介绍了如何使用java.util包的成员来组织对象并操作它们。
第15章详细地介绍了泛型,它是Java中非常重要的一项功能。
第16章介绍了流的概念,并且介绍了如何使用Java IO API中的4种类型的流来执行输入/输出操作。此外,还介绍了对象序列化和反序列化。
第17章介绍了注解,讲解了JDK所带有的标准注解、通用注解、元注解和定制注解。
第18章介绍了如何在另一个类中编写一个类,以及为什么OOP功能很有用。
第19章介绍了Java中的多线程编程,这不只是专家程序员才能使用的技术了。线程是操作系统分配处理器时间的一个基本处理单位,并且在进程中也可以有多个线程来执行代码。
第20章是讨论多线程编程的另外一章,介绍了使得编写多线程程序更加容易的接口和类。
第21章介绍了Java程序员所能够使用的技术。如今,能够在不同的国家和地区部署的软件应用程序已经很常见了。这样的应用程序需要在设计的时候就牢记国际化。
第22章介绍了在网络编程中使用的类。给出了一个示例的Web服务器应用程序,以说明如何使用这些类。
第23章介绍了Android框架。
第24章包含了下载和安装开发App的工具的说明。
第25章介绍了活动及其生命周期。活动是Android编程中的最重要的概念之一。
第26章介绍了最为重要的UI组件,包括微件、Toast和AlertDialog。
第27章介绍了如何在Android应用程序中布局UI组件,以及使用Android中可用的内建布局。
第28章介绍了如何创建一个监听器以处理事件。
第29章介绍了如何向操作栏添加项,以及如何使用它驱动应用程序导航。
第30章详细介绍了Android菜单。菜单是很多图形化用户界面(GUI)系统中常见的功能,其主要角色是提供某些操作的快捷方式。
第31章介绍了ListView,它会显示可以滚动的列表项并且从一个ListAdapter获取其数据源的一个视图。
第32章介绍了GridView微件,这是和ListView类似的一个视图。和ListView不同的是,GridView在栅格中显示其项。
第33章介绍了两个重要的主题,它们直接关系到App的视觉体验。
第34章教你如何操作位图图像。即便你不能编写一个图像编辑器应用程序,本章所介绍的技术也很有用。
第35章介绍了如何创建一个定制视图以及在画布上绘制形状。Android SDK带有很广泛的视图,可以在应用程序中使用它们。如果这些都不符合你的需要,你可以创建一个定制视图并且在其上绘制。
第36章介绍了片段,这是可以添加到活动中的组件。片段有自己的生命周期,当其进入生命周期的某个阶段的时候,会调用的相应方法。
第37章介绍了如何针对不同的屏幕大小使用不同的布局,例如,手机和平板电脑。
第38章介绍了Android中最新的动画API属性动画,并给出了示例。
第39章介绍了如何使用Preference API来存储应用程序设置并将其读回。
第40章介绍了如何使用Android应用程序中的Java File API。
第41章介绍了Android Database API,可以使用它来连接SQLite数据库。SQLite是每一个Android设备所附带的默认的关系数据库。
第42章介绍了如何使用内建的Camera和Camera API来获取静态的图像。
第 43 章介绍了两种方法来为应用程序提供拍摄视频的功能,分别是使用内建的意图和使用Media- Recorder类。
第44章介绍了如何记录音频。
第45章介绍了Handler类,可以使用它来调度将来要执行的一个Runnable。
第46章介绍了如何在Android中处理异步任务。
第47章介绍了如何创建后台服务,即便当启动它们的应用程序已经结束了,它们还会运行。
第48章介绍了用于接收广播的另一种Android组件。
第49章介绍了如何使用AlarmManager来调度任务。
第50章介绍了另一个应用程序组件类型,它用来封装数据并且跨应用程序共享。
附录A、附录B和附录C分别介绍了javac、java和jar工具。
附录D和附录E分别给出了NetBeans和Eclipse的简短教程。
可以通过如下的链接,从出版商的站点下载本书的配套程序示例。
http://books.brainysoftware.com
可以下载为单个的ZIP文件,或者将其作为一个Git项目导入。
Budi编写计算机编程图书有15年的经验,以清晰的写作风格著称。他编写的一本Java教程,最近被德国斯图加特传媒学院的一组计算机科学教授选作该大学的主教材,他们是在把Budi的书与其他类似图书进行比较后做此决定的。
Budi有20年担任软件架构师和开发者的经历,这为他的写作提供了支撑。他为世界各地的很多企业提供咨询服务,包括芬兰的手机厂商、英国的投资银行以及美国和加拿大的创业企业。
Budi编写过诸如基于Web的文档管理软件CreateData,这是一款商业软件,目前在撰写Java虚拟机的一篇研究文章。他编写的图书还包括《How Tomcat Works》《Servlet & JSP: A Tutorial and Struts 2 Design and Programming》等。
本书特色
作者是Java编程的高手,以清晰的写作风格而著称。他编写的Java教程,最近被的德国斯图加特传媒学院的计算机科学教授团队选作教材,以便学校能够按照其类似图书进行教学创新。
本书将Java开发的基础知识和Android平台开发的必备技能结合,通过丰富、易于理解的实例进行讲解,简单易学
要使用Java编程,需要Java SE开发工具包(Java SE Development Kit,JDK)。因此,本章的第1节将介绍如何下载和安装JDK。开发Java程序,涉及编写代码,将其编译为字节码,以及运行字节码。在Java程序员的职业生涯中,这是一个一次又一次重复的过程,并且,它对于你适应这个职业至关重要。因此,本章的主要目标是让你体验用Java进行软件开发的过程。
编写的代码不仅要能够工作,还要容易阅读又便于维护,这一点很重要,因此本章将向你介绍Java编码惯例。聪明的开发者总是使用集成开发环境(integrated development environment,IDE),因此,本章的最后一部分将针对Java IDE给出建议。
在开始编译和运行Java程序之前,需要下载和安装JDK,并且配置一些系统环境变量。你可以从Oracle的Web站点,下载针对Windows、Linux和Mac OS X的JRE和JDK:
http://www.oracle.com/technetwork/java/javase/downloads/index.html
如果单击页面上的Download链接,将会转到一个页面,允许你针对自己的平台(Windows、Linux、Solaris或Mac OS X)选择一个安装程序。相同的链接还提供了JRE。然而,要进行开发,不能只有JRE,还要有JDK,JRE只是帮助运行编译后的Java类。JDK包含了JRE。
下载了JDK之后,需要安装它。在各个操作系统上的安装是不同的。以下各节详细地介绍了安装过程。
在Windows上的安装很容易。在Windows资源管理器中找到已下载的文件,双击可执行文件,并且按照指示进行安装。图1.1展示了安装向导的第一个对话框。
图1.1 在Windows上安装JDK 8
在Linux平台上,JDK有两种安装格式。
如果你使用RPM,按照如下步骤进行:
1.使用su命令成为root用户。
2.解压缩下载的文件。
3.将目录更改为下载文件所在的位置,并且输入:
chmod a+x rpmFile
其中,rpmFile是RPM文件。
4.运行RPM文件:
./rpmFile
如果使用自解压的二进制安装程序,按照如下步骤进行:
1.解压缩下载的文件。
2.使用chmod来赋予文件执行许可:
chmod a+x binFile
这里,binFile是针对你的平台下载的bin文件。
3.把目录修改为想要安装文件的位置。
4.运行自解压的二进制文件。执行下载的文件,在其前面带上路径。例如,如果文件在当前文件夹中,在其前面加上“./“:
./binFile
要在Mac OS X系统上安装JDK 8,需要一台基于Intel的计算机,运行OS X 10.8(Mountain Lion)或以后的版本。你还需要管理员的权限。安装很简单:
1.在所下载的.dmg文件上双击。
2.在出现的Finder窗口中,双击该包的图标。
3.在出现的第一个窗口上,单击Continue。
4.出现Installation Type窗口。单击Install。
5.将会出现一个窗口显示“Installer is trying to install new software. Type your
password to allow this.”。输入你的管理员密码。
6.单击Install Software开始安装。
安装了JDK之后,可以开始编译和运行Java程序了。然而,你只能从javac和java程序的位置调用编译器和JRE,或者通过在命令中包含安装路径来调用。为了使得编译和运行程序更容易,在计算机上设置PATH环境变量便可以从任何目录调用javac和java,这一点很重要。
要在Windows系统上设置PATH环境变量,执行如下的步骤:
1.单击Start,Settings,Control Panel。
2.双击System。
3.选择Advanced标签并且单击Environment Variables。
4.在User Variables或System Variables面板中,找到Path环境变量。Path的值是分号隔开的一系列的目录。现在,到Java安装目录的bin目录下的完整路径,将其添加到已有的Path值的末尾。该目录看上去如下所示:
C:\Program Files\Java\jdk1.8.0_<version>\bin
5.单击Set,OK或Apply。
在这些操作系统上设置路径变量,取决于你所使用的shell。对于C shell,在~/.cshrc文件的末尾添加如下内容:
set path=(path/to/jdk/bin $path)
而path/to/jdk/bin是你的JDK安装目录下的bin目录。对于Bourne Again shell,在~/.bashrc或~/.bash_profile文件的末尾添加如下这一行:
export PATH=/path/to/jdk/bin:$PATH
这里,path/to/jdk/bin是JDK安装目录下的bin目录。
要想确认是否已经正确地安装了JDK,在你的计算机的任何目录下,在命令行上输入javac。如果看到这条指令正确地运行javac,那么,你就成功地安装了它。相反,如果只能够从JDK安装目录的bin目录下运行javac,说明没有正确地配置PATH环境变量。
在使用Java编程的时候,你总是要使用来自核心库的类。即使是资深程序员,在编码的时候,也需要查看这些库的文档。因此,应该从如下地址下载这个文档。
http://www.oracle.com/technetwork/java/javase/downloads/index.html
(你需要向下滚动,直到看到“Java SE 8 Documentation.”)
API文档也可以通过下面的网址在线查阅:
http://download.oracle.com/javase/8/docs/api
本小节将强调Java开发中的步骤,即编写程序,将其编译为字节码以及运行字节码。
可以使用任何文本编辑器来编写Java程序。打开一个文本编辑器,并且输入代码清单1.1中的代码。或者,如果你已经下载了本书配套的程序示例,只要将它复制到你的文本编辑器中就可以了。
代码下载
如果你还没有下载代码,现在就可以从异步社区的Web站点下载示例。在前言的最后部分中,给出了下载地址。
代码清单1.1 一个简单的Java程序
class MyFirstProgram {
public static void main(String[] args) {
System.out.println("Java rocks.");
}
}
现在应该可以讲,Java代码必须驻留在一个类中。此外,确保你将代码清单1.1中的代码保存为MyFirstProgram.java文件。所有的Java源代码文件,其扩展名必须为.java。
我们使用了JDK安装目录中的bin目录下的javac程序来编译Java程序。假设你在计算机中已经编辑过了PATH环境变量(如果还没有的,参见前面的1.1节),应该能够从任何的目录调用javac。要编译代码清单1.1中的MyFirstProgram类,按照如下的步骤进行:
1.打开一个终端或命令提示符,并且将目录修改为保存MyFirstProgram.java文件的目录。
2.输入如下的命令:
javac MyFirstProgram.java
如果一切在正常,javac将会在你的工作目录中创建一个名为MyFirstProgram.class的文件。
注意
javac工具有很多的功能,可以通过传递选项来使用它们。例如,可以告诉它你想要在哪里创建所生成的类文件。附录A更清楚详细地介绍了javac。
要运行Java程序,使用作为JDK的一部分的java程序。或者,如果添加了PATH环境变量,你应该能够从任何目录调用java。从你的工作目录中,输入如下命令并按下Enter键。
java MyFirstProgram
注意,在运行Java程序的时候,不要包含class扩展名。你将会在控制台看到如下内容。
Java rocks
恭喜你。你已经成功地编写了第一个Java程序。由于本章的目的是让你熟悉编写代码和编译的过程,我将不会解释程序是如何工作的。
你也可以给一个Java程序传递参数。例如,如果有一个名为Calculator的类,并且想要传递两个参数给它,可以像下面这样做:
java Calculator arg-1 arg-2
这里,arg-1是第1个参数,arg-2是第2个参数。你可以传递任意多个参数。Java程序将会让这些参数作为字符串的数组来供Java程序使用。我们将会在第6章学习如何处理参数。
注意
java工具是一个高级程序,你可以通过传递选项来配置。例如,可以设置其进行内存分配的数量。附录B会介绍这些选项。
注意
java工具也被用来运行打包到一个.jar文件中的Java类。请阅读附录C的C.4节。
编写能够运行正确的Java程序是很重要的。然而,编写易于阅读和维护的程序也很重要。据统计,一款软件的80%的生命周期都花在了维护上。此外,程序员的变动率很高,很有可能是由其他的人在你的代码的生命周期内来维护它。那些继承你的代码的人,会因为清晰而易于阅读的程序源代码而对你感激涕零。
使用一致的编码惯例,是使得你的代码易于阅读的一种方法(其他的方式包括,良好的代码组织和充分的注释)。代码惯例包括文件名、文件组织、缩进、注释、声明语句、空格和命名惯例。
类声明以关键字class开头,后面跟着一个类名和开始花括号{。你可以将开始花括号放在类名的同一行,如代码清单1.1所示,或者,可以将花括号放在下一行,如代码清单1.2所示。
代码清单1.2 使用不同编码惯例编写的MyFirstProgram
class MyFirstProgram
{
public static void main(String[] args)
{
System.out.println("Java rocks.");
}
}
代码清单1.2中的代码,和代码清单1.1中的代码都很好。只不过它采用了不同的惯例来编写类。你应该针对所有的编程元素采用一种一致的样式。如何定义你自己的代码惯例,取决于你自己,但是,Sun Microsystems公司发布了一个文档,总结了其雇员应该遵守的标准。可以通过如下链接查看该文档(当然,这个文档现在也是Oracle.com的一部分)。
http://www.oracle.com/technetwork/java/codeconvtoc-136057.html
本书中的程序示例都遵守这个文档所推荐的惯例。我还想鼓励你在自己职业生涯的第一天就养成遵守这些惯例的习惯,以便在以后的日子里,能够自然而然地编写出清晰的代码。
关于风格的第一堂课,就是缩进。缩进的单位必须是4个空格。如果使用制表符来代替空格,每个制表符必须设置为8个空格(而不是4个)。
你可以使用一个文本编辑器来编写Java程序。然而,使用集成开发环境(Integrated Development Environment,IDE)将会更有帮助。IDE不仅会检查代码的语法错误,还可以自动提示代码、调试和跟踪程序。此外,当你录入的时候,编译会自动进行,并且,运行一个Java程序也只需要单击一下按钮。最终,你将用更短的时间进行开发。
可用的Java IDE曾经有数十种之多,但是如今,只剩下3种常用的。好在,前两种完全是免费的:
最流行的两种Java IDE是NetBeans和Eclipse,在过去的几年中,你曾经看到二者之间争夺不断,都想要成为老大。NetBeans和Eclipse都是开源项目,背后有强大的支持者。Sun Microsystems公司在2000年收购了捷克公司Netbeans Ceska Republika,随后就发布了NetBeans。Eclipse是由IBM发起的,是NetBans的竞争者。
关于哪一个IDE更好这个问题,不同的人有不同的答案,但是,它们都很流行,因此,也使得其他的软件厂商都放弃了自己的IDE。即便是Microsoft公司,其.NET技术是Java最强有力的竞争者,也随波逐流,不在为其Visual Studio.NET的Express Editions收费。
本书的附录D和附录E分别提供了NetBeans和Eclipse的简短教程。请考虑使用IDE,因为它用途很大。
本章介绍了如何下载和安装JDK,并帮助你编写第一个Java程序。你会使用一个文本编辑器来编写程序,使用javac来将其编译为一个类文件,并且使用java工具来运行类文件。
随着程序变得更加复杂,且项目变得更大,IDE将会帮助你加快应用程序的开发。
Java是一种面向对象编程语言,因此,理解OOP非常重要。第4章是本书的第一个关于OOP内容的一章。然而,在了解OOP功能和技术之前,应该先学习Java语言的基础知识。
传统上,英语国家的计算机只使用美国信息交换标准代码(American Standard Code for Information Interchange,ASCII)字符来表示字母和数字字符。ASCII中的每个字符都用7位来表示。因此,这个字符集中有128个字符。其中包括小写和大写的拉丁字母、数字和标点符号。
ASCII字符集后来扩展了,包括了另外的128个字符,例如,德语字符ä、ö、ü和英国货币符号£。这个字符集叫作扩展了的ASCII,并且每个字符使用8位来表示。
ASCII和扩展的ASCII只是可用的众多字符集中的两个。另一个流行的字符集由国际标准化组织(International Standards Organization,ISO)标准化了,即ISO-8859-1,也称之为Latin-1。ISO-8859-1中的每一个字符也用8位来表示。这个字符集包含了很多西方语言(如德语、丹麦语、荷兰语、法语、意大利语、西班牙语、葡萄牙语,当然也包括英语)编写文本所需的所有字符。每个字符占8位的字符集便于使用,因为一个字节也是8位的长度。因此,用一个8位的字符集来存储和传输文本,也更有效率。
然而,并不是每种语言都使用Latin字母。中文和日文是使用不同的字符集的两个例子。例如,中文中的每个字符表示一个字,而不是一个字母。这样的字符有数千个,8位不足以表示字符集中的所有字符。日文也使用一种不同字符集。全部算起来,全世界的语言中,有数以百计的不同的字符集。为了统一所有字符集,创建了一个叫作Unicode的计算标准。
Unicode是由一个叫作Unicode联盟(Unicode Consortium,www.unicode.org)的非营利的组织开发的。这个实体试图将全世界所有语言的所有字符,都包含到一个单个的字符集中。Unicode中的一个唯一的编号,只表示1个字符。Unicode当前的版本8,用于Java、XML、ECMAScript和LDAP等。
一开始,一个Unicode字符用16位来表示,这足够表示65 000多个不同的字符。65 000字符足以表示世界上主要语言中的大多数字符了。然而,Unicode联盟计划支持100万个以上的字符编码。根据这个数量,可能还需要另外的16位才能表示每个字符。实际上,32位系统被认为是存储Unicode字符的一种方便的方式。
现在,你已经看到了一个问题。尽管Unicode为所有语言中的所有字符提供了足够的空间,但是,存储和传输Unicode文本并不像存储和传输ASCII或Latin-1字符那样高效。在互联网世界中,这是一个大问题。想象一下,你要传输的数据是ASCII文本的4倍那么多。
好在字符编码可以使得存储和传输Unicode文本更加高效。你可以把字符编码看作是和数据压缩类似。并且,如今有很多类型的字符编码可用。Unicode联盟支持如下3种:
ASCII字符仍然在软件编程中扮演主要的角色。Java对于几乎所有的输入元素都使用ASCII,除了注释、标识符以及字符和字符串内容之外。对于后者,Java支持Unicode字符。这意味着,你可以用英语以外的语言来编写注释、标识符和字符串。
Java使用某些字符作为分隔符。这些特殊的字符见表2.1。熟悉这些符号和名称很重要,但是,如果你现在还不理解“说明”栏中的术语,也不要担心。
表2.1 Java分隔符
符号 |
名称 |
说明 |
---|---|---|
( ) |
圆括号 |
用于:1.在方法签名中,用来包含参数列表;2.在表达式中,用来提高操作符优先级;3.窄转换;4.在循环中,用来包含要求值的表达式 |
{} |
花括号 |
用于:1.类型声明;2.语句块;3.数组初始化 |
[ ] |
方括号 |
用于:1.数组声明;2.数组值解引用 |
< > |
尖括号 |
用于向参数化类型传递参数 |
; |
分号 |
用于结束语句,以及在for语句中,用于将初始化代码、表达式和更新代码分隔开来 |
: |
冒号 |
在for语句中,用来遍历一个数组或一个集合 |
, |
逗号 |
用于将方法声明中的参数分隔开 |
. |
句点 |
用于将包名和子包、数据类型分隔开来,并且用于将文件或方法和一个引用变量区分开来 |
当我们编写一个面向对象应用程序的时候,就会创建和现实世界相似的对象模型。例如,一个工资支付应用程序有Employee对象、Tax对象、Company对象等。然而,在Java中,对象并非唯一的数据类型。还有另一种叫作基本类型的数据类型。Java中有8种基本类型,其中每一种都有特定的格式和大小。表2.2列出了Java的基本类型。
表2.2 Java基本类型
基本类型 |
说明 |
范围 |
---|---|---|
byte |
字节长度的整数(8位) |
从−128 (−27)到127 (27−1) |
short |
短整数(16位) |
从−32 768 (−215) 到32 767 (−215−1) |
int |
整数(32位) |
从−2 147 483 648 (−231) 到2 147 483 647 (−231−1) |
long |
长整数(64位) |
从−9 223 372 036 854 775 808 (−263) 到9 223 372 036 854 775 807 (263−1) |
float |
单精度浮点数(32位) |
最小的非零正值:14e−45 |
double |
双精度浮点数(64位) |
最小的非零正值4.9e−324 |
char |
Unicode字符 |
参见Unicode 6规范 |
boolean |
布尔值 |
true或false |
前6种基本类型(byte、short、int、long、float和double)表示数字。每一种都有不同的大小。例如,byte可以包含−128到127之间的任意整数。要搞清楚一个整数类型所包含的在最小数字和最大数字,可以看一下位数。一个byte是8位的长度,因此,有28即256个可能的值。前128个值是从−128~−1,0还要占一个位置,剩下了127个正值。因此,一个byte的范围是−128~127。
如果你需要一个占位符来存储数字1 000 000,那么,需要使用一个int类型。long甚至会更大,你可能会问,如果long可以包含比byte和int更大的一组数字,为何不总是使用long呢?这是因为,long占了64位,比byte和int消耗更多的内存。因此,为了节省空间,总是要使用数据大小尽可能小的基本类型。
基本类型byte、short、int和long只能够保存整数,对于小数来说,你需要使用float或者double类型。float是32位的值,遵守IEEE标准754。double是一个64位的值,也遵从相同的标准。
char可以包含单个的Unicode字符,例如“a”、“9”或“&”。使用Unicode,允许char包含那些在英语字母中不存在的字符。一个boolean类型包含两个可能的状态(false或true)之一。
注意
Java不将一切内容都表示为对象,是考虑到速度的原因。和基本类型相比,创建和操作对象的代价更加昂贵。在编程语言中,如果一项操作对资源需求很大,并且要占用很多的CPU周期才能完成,我们就说该操作很昂贵。
既然了解了Java中的两种数据类型(基本类型和对象),让我们来继续学习如何使用基本类型。我们从变量开始。
变量是数据占位符。Java是一种强类型的语言,因此,每个变量必须有一个声明的类型。Java中有两种数据类型:
Java如何存储整数值
你一定听说过计算机使用二进制数字,即只包含0和1的数字。本节对此提供了一个概要,当你学习操作符的时候可能用的上。
一个字节占8个位,这表示要分配8个位来存储一个字节。最左边的位是一个符号位。0表示正值,1表示负值。0000 0000是0的字节表示,0000 0001表示1,0000 00010表示2,0000 0011表示3,并且0111 1111表示127,127是byte所能保存的最大的正值。
那么,如何得到一个负数的二进制表示呢?很简单。先获取其对应的正数的二进制表示,然后将所有的位都取反,并且加上1。例如,要得到−3的二进制表示,首先从3开始,它是0000 0011。将所有的位都取反,得到
1111 1100
加1以后,得到
1111 1101
这就是−3的二进制表示。
对于int类型,规则是相同的。例如,最左边的是符号位。唯一的区别是,一个int类型占32位。要表示一个int类型中的−1的二进制形式,我们从1开始,它是
0000 0000 0000 0000 0000 0000 0000 0001
将所有的位都取反后,得到:
1111 1111 1111 1111 1111 1111 1111 1110
加1以后,得到了想要的数字(−1)。
1111 1111 1111 1111 1111 1111 1111 1111
除了数据类型,Java变量还有名称和标识符。在选择标识符的时候,有如下几条规则:
1.标识符是Java字母和数字的一个长度没有限制的序列。标识符必须以一个Java字母开头。
2.标识符必须不是Java关键字(表2.3中给出),不能是一个布尔字面值,也不能是空字面值。
3.标识符必须在其作用域内是唯一的。第4章将会介绍作用域。
表2.3 Java关键字
abstract |
continue |
for |
new |
switch |
---|---|---|---|---|
assert |
default |
if |
package |
synchronized |
boolean |
do |
goto |
private |
this |
break |
double |
implements |
protected |
throw |
byte |
else |
import |
public |
throws |
case |
enum |
instanceof |
return |
transient |
catch |
extends |
int |
short |
try |
char |
final |
interface |
static |
void |
class |
finally |
long |
strictfp |
volatile |
const |
float |
native |
super |
while |
Java字母包括大写的和小写的ASCII Latin字母A到Z(\u0041-\u005a,注意,\u表示一个Unicode字符)和a到z(\u0061-\u007a),由于历史的原因,还包括ASCII下划线(_或\u005f)和美元符号($或\u0024)。$字符只能在机器生成的源代码中使用,极少数情况下,用来访问遗留系统中已经存在的名称。
Java数字包括ASCII数字0~9(\u0030-\u0039)。
以下是一些合法的标识符:
salary
x2
_x3
row_count
以下是一些不合法的标识符:
2x
java+variable
2x不合法,是因为它以数字开头;java+variable不合法,是因为它包含加号。
还要注意,标识符的名称是区分大小写的。x2和X2是两个不同的标识符。
你可以这样声明一个变量:先写类型,后面跟着名称加上一个分号。如下是变量声明的几个例子:
byte x;
int rowCount;
char c;
在上面的例子中,我们声明了3个变量:
x、rowCount和c是变量名或标识符。
还可以在同一行声明具有相同类型的多个变量,两个变量之间用逗号隔开。例如:
int a, b;
这等同于:
int a;
int b;
然而,在同一行声明多个变量的做法,我们不推荐,因为这降低了程序的可读性。
最后,可以在声明一个变量的同时给变量赋一个值:
byte x = 12;
int rowCount = 1000;
char c = 'x';
变量名应该简短而有含义。它们应该是混合大小写的且以小写字母开头。后续的单词都以一个大写的字母开头。变量名不应该使用下划线_或美元符号$开头。例如,如下是与Sun的编码惯例一致的几个变量名的例子:userName、count和firstTimeLogin。
在Java中,常量是一旦赋值之后,其值不能修改的变量。使用关键字final来声明一个常量。按照惯例,常量名都是大写的,单词之间用下划线隔开。
如下是常量或final变量的例子:
final int ROW_COUNT = 50;
final boolean ALLOW_USER_ACCESS = true;
很多时候,我们需要给程序中的变量赋值,例如,将数字2赋给一个int型变量,或者将字符“c”赋给一个char型变量。为此,需要按照Java编译器能够理解的格式来书写值的表示形式。表示一个值的源代码叫作字面值。有3种类型的字面值:基本类型的字面值、字符串字面值和空字面值。本章只介绍基本类型的字面值。第4章将介绍空字面值,第5章将介绍字符串字面值。
基本类型的字面值有4种子类型:整数字面值、浮点数字面值、字符字面值和布尔字面值。下面分别介绍这些子类型。
整数字面值可以写为十进制(以10为基数,这是我们所习惯使用的)、十六进制(以16为基数)和八进制(以8为基数)。例如,100可以表示为100。如下的整数字面值都是十进制的:
2
123456
作为另一个示例,如下的代码将10赋值给int类型变量x。
int x = 10;
使用前缀0x或0X表示十六进制的整数。例如,十六进制的数字9E写作0X9E或0x9E。八进制的整数使用数字0作为前缀。例如,如下是八进制的数字567:
0567
整数字面值用于将值赋给byte、short、int和long类型的变量。请注意,我们所赋值的值不能超出了一个变量的存储范围。例如,一个byte的最大的值是127。因此,如下的代码将会产生一个编译错误,因为200对于byte类型来说太大了。
byte b = 200;
要将一个值赋给long类型,在数字的后面带上一个字母L或l作为后缀。L是首选,因为它很容易和数字1区分开来。一个long类型,可以包含的值在-9223372036854775808L到9223372036854775807L (263)之间。
Java初学者常常会问,为什么需要使用后缀l或L,因为即便没有后缀,就像如下的代码一样,程序仍然能够编译。
long a = 123;
并不完全是这样的。没有后缀L或l的一个整数字面值,会被看作是int类型。因此,如下的代码将会产生一个编译错误,因为9876543210超出了一个int的存储能力:
long a = 9876543210;
为了解决这个问题,需要在数字的末尾添加一个L或l,如下所示:
long a = 9876543210L;
long、int、short和byte也可以表示为二进制形式,只要使用前缀字母0B或0b就可以了。例如:
byte twelve = 0B1100; // = 12
如果一个整数字面值太长了,可读性会受到影响。为此,从Java 7开始,我们可以在整数字面值中使用下划线来将数字分隔开。例如,如下两条语句具有相同的含义,但是第2条显然更容易阅读。
int million = 1000000;
int million = 1_000_000;
将下划线放在什么位置无关紧要。可以每3个数字使用一个下划线,就像上面的例子所示,或者任意多个数字使用一个。如下给出更多的例子:
short next = 12_345;
int twelve = 0B_1100;
long multiplier = 12_34_56_78_90_00L;
像0.4、1.23、0.5e10这样的数字都是浮点数。浮点数有如下几个部分:
以1.23为例。对于这个浮点数,整数部分是1,小数部分是23,没有可选的指数。在0.5e10中,0是整数部分,5是小数部分,10是指数。
在Java中,有两种类型的浮点数:
在float和double类型中,0的整数部分是可选的。换句话说,0.5可以写成.5。此外,指数部分可以表示为e或E。
要表示浮点数字面值,可以使用如下的格式之一:
Digits . [Digits] [ExponentPart] f_or_F
. Digits [ExponentPart] f_or_F
Digits ExponentPart f_or_F
Digits [ExponentPart] f_or_F
注意,方括号之间的部分是可选的。
f_和F_部分使得浮点数字面值是float类型。如果没有这一部分,该浮点数字面值将是double类型。要明确地表示一个double类型的字面值,可以加D或d后缀。要表示一个double类型字面值,使用如下的格式之一:
Digits . [Digits] [ExponentPart] [d_or_D]
. Digits [ExponentPart] [d_or_D]
Digits ExponentPart [d_or_D]
Digits [ExponentPart] [d_or_D]
在float和double类型中,ExponentPart定义为如下的形式:
ExponentIndicator SignedInteger
其中ExponentIndicator是e或者E,而SignedInteger是
Signopt Digits
Sign是+或者-,加号是可选的。
float字面值的示例如下:
2e1f
8.f
.5f
0f
3.14f
9.0001e+12f
如下是double字面值的示例:
2e1
8.
.5
0.0D
3.14
9e-9d
7e123D
布尔类型有两个值,字面值分别为true和false。例如,如下的代码声明了一个布尔变量includeSign,并且为其分配了一个true值。
boolean includeSign = true;
字符字面值是一个Unicode字符,或者是单引号括起来的一个转义序列。转义序列是无法使用键盘输入的Unicode字符或者在Java中具有特殊作用的Unicode字符的一种表示方法。例如,回车字符和换行字符用于终止一行,并且没有任何可视化的表示。要表示一个换行字符,需要对其转义,即写出其字符表示。此外,单引号字符需要转义,因为单引号用于将字符括起来。
如下是字符字面值的一些示例:
'a'
'Z'
'0'
'ü'
如下是作为转义序列的字符字面值:
'\b' 回退字符
'\t' 制表字符
'\\' 反斜杠
'\'' 单引号
'\"' 双引号
'\n' 换行
'\r' 回车
此外,Java允许我们对一个Unicode字符转义,以便能够使用ASCII字符的一个序列来表示一个Unicode字符。例如,字符£的Unicode代码是00A3。你可以编写如下的字符字面值来表示字符:
'£'
然而,如果你没有什么办法来使用键盘输入这个字符,可以使用将其转义的方式:
'\u00A3'
在处理不同的数据类型的时候,我们常常需要进行转换。例如,将一个变量的值赋给另一个变量,就涉及转换。如果两个变量具有相同的类型,赋值总是会成功。从一种类型到相同类型的转换,叫作等同转换(identity conversion)。例如,如下的转换保证能够成功:
int a = 90;
int b = a;
然而,向不同的类型转换则无法保证成功,甚至不一定能够那么做。基本类型转换还有另外两种形式,即加宽转换(widening conversion)和收窄转换(narrowing conversion)。
当从一种基本类型向另一种基本类型转换的时候,如果后者的大小和前者相同或者更大,就会发生加宽转换;例如,从int(32位)到long(64位)的转换。在如下情况中,进行加宽转换:
从一种整数类型向另一种整数类型的加宽转换,不会有信息丢失的风险。同样的,从float向double的转换也会保留所有的信息。然而,从int或long向float的转换,可能会导致精度丢失。
加宽的基本类型转换是隐式地发生的,不需要在代码中做任何事情。例如:
int a = 10;
long b = a; // widening conversion
收窄转换发生在从一种类型到另一种拥有较小的大小的类型的转换中,例如,从long(64位)到int(32位)的转换。通常,收窄转换在如下的情况中发生:
和加宽基本类型转换不同,收窄基本类型转换必须是显式的。需要在圆括号中指定目标类型。例如,如下是从long向int的收窄转换。
long a = 10;
int b = (int) a; // narrowing conversion
第2行中的(int)告诉编译器,应该发生收窄转换。如果被转换的值比目标类型的容量还要大的话,收窄转换可能导致信息丢失。前面的例子并不会导致信息丢失,因为10对一个int类型来说足够小。然而,在如下的转换中,由于9876543210L对一个int类型来说太大了,会导致一些信息丢失。
long a = 9876543210L;
int b = (int) a; // the value of b is now 1286608618
有可能导致信息丢失的收窄转换,在你的程序中将会引入一个缺陷。
计算机程序是实现某一功能的操作汇集在一起的一个集合。有很多种类型的操作,包括加法、减法、乘法、除法和位移。在本小节中,我们将学习各种Java操作。
一个操作符会对一个、两个或三个操作数执行操作。操作数是操作的目标,而操作符则是表示动作的一个符号。例如,如下是加法操作:
x + 4
在这个例子中,x和4是操作数,+是操作符。
一个操作符可能返回一个结果,也可能不返回结果。
注意
操作符和操作数的任何合法的组合,叫作表达式(expression)。例如,x+4是一个表达式。一个布尔表达式会得到真或假;一个整数表达式会得到一个整数;浮点数表达式的结果是一个浮点数。
只需要一个操作数的操作符叫作一元操作符(unary operator),Java中有几个一元操作符。二元操作符(binary operator)接受两个操作数,这是Java操作符中最常见的类型。还有一个三元操作符(ternary operator)? :,它需要3个操作数。
表2.4列出了Java的操作符。
表2.4 Java操作符
= | > | < | ! | ~ | ? | : | instanceof | |||
== | <= | >= | != | && | || | ++ | −− | |||
+ | − | * | / | & | | | ^ | % | << | >> | >>> |
+= | −= | *= | /= | &= | |= | ^= | %= | <<= | >>= | >>>= |
在Java中,操作符分为6类:
下面分别介绍每一种操作符。
一元操作符在一个操作数上起作用。有6个一元操作符,本节中将一一介绍。
一元减操作符返回其操作数的负值。操作数必须是一个数字值,或者是一个数值基本类型的变量。例如,在下面的代码中,y的值是-4.5;
float x = 4.5f;
float y = -x;
一元加操作符返回其操作数的值。操作数必须是一个数值类型,或者是一个数值类型的变量。例如,在如下的代码中,y的值是4.5。
float x = 4.5f;
float y = +x;
这个操作符没有那么重要,因为不使用它也不会有什么差别。
自增操作符将其操作数增加1。操作数必须是一个数值基本类型的变量。操作符可以出现在操作数之前或之后。如果操作符出现在操作符之前,它叫作前缀自增操作符。如果它写在操作数之后,叫作后缀自增操作符。
作为例子,下面展示了一个前缀自增操作符:
int x = 4;
++x;
在++x之后,x的值变为5。前面的代码等同于:
int x = 4;
x++;
在x++之后,x的值为5。
然而,如果在同一个表达式中,自增操作符的结果需要赋值给另一个变量,那么,前缀操作符和后缀操作符之间就存在差异了。考虑如下的例子:
int x = 4;
int y = ++x;
// y = 5, x = 5
在赋值之前,使用了前缀自增操作符。x自增到5,并且随后将其值复制给y。
查看一下如下的后缀自增操作符的用法。
int x = 4;
int y = x++;
// y = 4, x = 5
使用后缀自增操作符,在将操作数(x)的值赋值给另一个变量(y)之后,才将操作数的值加1。
注意,自增操作符对于int类型来说是最常用的。它对于其他的数字基本类型(例如float和long)来说,也是适用的。
自减操作符将操作数的值减去1。操作数必须是数值基本类型的一个变量。和自增操作符类似,它也有前缀自减操作符和后缀自减操作符。例如,如下的代码将x的值自减,并将其赋值给y。
int x = 4;
int y = --x;
// x = 3; y = 3
在如下的示例中,使用了后缀自减操作符:
int x = 4;
int y = x--;
// x = 3; y = 4
逻辑取反操作符只适用于一个布尔基本类型或者java.lang.Boolean的一个实例。如果操作数是false,这个操作符的值为true;如果操作数为true,这个操作符的值为false。例如:
boolean x = false;
boolean y = !x;
// at this point, y is true and x is false
位取反操作符的操作数必须是一个整数基本类型或者整数基本类型的一个变量。其结果是对操作数的按位取反。例如:
int j = 2;
int k = ~j; // k = -3; j = 2
要理解这个操作符是如何工作的,需要将操作数转换为一个二进制数,并且将所有的位都取反。整数2的二进制形式是:
0000 0000 0000 0000 0000 0000 0000 0010
对其按位取反之后,得到
1111 1111 1111 1111 1111 1111 1111 1101
而这是整数-3的二进制表示。
有5种类型的算术操作符,分别是加法、减法、乘法和除法,以及模除。下面将分别介绍这5种操作符。
加法操作符将两个操作数相加。操作数的类型必须可以转换为一个数值基本类型。例如:
byte x = 3;
int y = x + 5; // y = 8
要确保接受加法结果的变量有足够的容量。例如,在如下的代码中,k的值是-294967296而不是40亿。
int j = 2000000000; // 2 billion
int k = j + j; // not enough capacity. A bug!!!
如下的代码则像预期的那样工作:
long j = 2000000000; // 2 billion
long k = j + j; // the value of k is 4 billion
减法操作符在两个操作数之间执行减法。操作数的类型必须可以转换为一个数值类型。例如:
int x = 2;
int y = x – 1; // y = 1
乘法操作符在两个操作数之间执行乘法。操作数的类型必须能够转换为一种数值基本类型。例如:
int x = 4;
int y = x * 4; // y = 16
除法操作符在两个操作数之间执行除法。左操作数除以右操作数。除数和被除数都必须是能够转换一种数值基本类型的类型。例如:
int x = 4;
int y = x / 2; // y = 2
注意,在运行时,如果除数为0的话,将会导致一个错误。使用/操作符的除法的结果,总是一个整数。如果除数不能够将被除数除尽,余数将会被忽略。例如:
int x = 4;
int y = x / 3; // y = 1
第5章将会介绍java.lang.Math类,它能够执行更为复杂的除法操作。
模除操作符执行两个操作数之间的除法,但是返回余数。左操作数是被除数,右操作数是除数。被除数和除数都必须是能够转换为数值基本类型的一种类型。例如,如下操作的结果是2。
8 % 3
有两种相等操作符,==(相等)和!=(不相等),它们都可以作用于两个整数、浮点数、字符或布尔类型的操作数。相等操作符的结果也是一个布尔类型。
例如,如下的比较,结果为真。
int a = 5;
int b = 5;
boolean c = a == b;
又如
boolean x = true;
boolean y = true;
boolean z = x != y;
比较之后,z的值为false,因为x等于y。
一共有5种关系操作符:<、>、<=、>=以及instanceof。前4个操作符都将在本节中介绍。第7章将会介绍instanceof。
<、>、<=和>=操作符作用于两个操作数之上,它们的类型必须能够转换为一种数值基本类型。关系操作符返回一个布尔值。<操作符计算左边操作数的值是否小于右边的操作数的值。例如,如下的操作返回假:
9 < 6
>操作符计算左操作数的值是否大于右操作数的值。例如,如下的操作返回真:
9 > 6
<=操作符测试左操作数的值是否大于或等于右操作数的值。例如,如下的操作结果为假:
9 <= 6
>=操作符测试左操作数的值是否大于或等于右操作数的值。例如,如下操作返回真:
9 >= 9
一共有3个条件操作符:AND操作符&&、OR操作符||以及?:操作符。下面详细地介绍每一种操作符。
&&操作符接受两个表达式作为操作数,并且这两个表达式都必须返回一个值,而这个值必须能够转换为一个布尔类型。如果两个操作数的结果都为真,那么它返回真。否则,它返回假。如果左操作数为假,右操作数就不必计算了。例如,如下的例子返回假:
(5 < 3) && (6 < 9)
||操作符接受两个表达式作为操作数,并且这两个表达式都必须返回一个值,而这个值必须能够转换为一个布尔类型。如果两个操作数有一个结果为真,那么||返回真。如果左操作数为真,右操作数就不必计算了。例如,如下的例子返回真:
(5 < 3) || (6 < 9)
? :操作符有3个操作数。其语法如下:
expression1 ? expression2 : expression3
这里,expression1必须返回一个能够转换为布尔类型的值。如果expression1的结果为真,返回expression2,否则的话,返回expression3。
例如,如下的表达式返回4。
(8 < 4) ? 2 : 4
位移操作符接受两个操作数,操作符类型必须能够转换为一个整数基本类型。左操作数是要位移的值,右操作数表示位移的距离。如下是3种类型的位移操作符:
向左位移操作符会把一个数字向左位移,右边的位用0来补充。n << s的值是将n向左移动s位。相当于将操作数乘以2的s次幂。
例如,将一个值为1的int向左位移3个位置(1<<3),结果是8。为了搞清楚这一点,需要将操作数转换为一个二进制数。
0000 0000 0000 0000 0000 0000 0000 0001
向左位移3位后,得到:
0000 0000 0000 0000 0000 0000 0000 1000
其结果等于8(等同于1*23)。
另一条规则是,如果左操作数是一个int类型,只有位移距离的前5位会使用。换句话说,位移距离必须在0~31之间。如果传递的数字大于31,只有前5位会使用。也就是说,如果x是一个int类型,x<<32等同于x<<0;x<<33等同于x<<1。
如果左操作数是long类型,位移距离中只有前6位会使用。换句话说,实际使用的位移距离的范围在0~63之间。
向右位移操作符>>将左边的操作数向右位移右边操作数所指定的位数。n>>s是将n向右移动s位。结果是n/2s。
例如,16 >> 1等于8。为了证实这一点,写出16的二进制表示如下。
0000 0000 0000 0000 0000 0000 0001 0000
然后,将其向右移动1位,得到:
0000 0000 0000 0000 0000 0000 0000 1000
结果等于8。
n>>>s的值取决于n是正数还是负数。对于一个正数n,其结果值和n>>s相同。
如果n是负值,其结果取决于n的类型。
如果n是一个int类型,结果的值是(n>>s)+(2<<~s)。如果n是一个long类型,结果的值是(n>>s)+(2L<<~s)。
一共有12个赋值操作符,如下所示:
= += -= *= /= %= <<= >>= >>>= &= ^= |=
左操作数必须是一个变量。例如:
int x = 5;
除了赋值操作符=以外,其他的操作符都以相同的方式工作,而且,你应该看到,其中的每一个都由两个操作符组成。例如,+=实际上是+和=。赋值操作符<<=也有两个操作符,分别是<<和=。
由两部分组成的赋值操作符是这样工作的,对两个操作数都应用第一个操作符,然后,将结果赋值给左操作数。例如,x+=5等同于x = x + 5。
x -= 5等同于x = x - 5
x <<= 5等同于x = x << 5
x &= 5得到的结果和x = x & 5相同
位操作符& | ^在两个操作数上执行位操作,操作数的类型必须能够转换为整数。&表示一个AND操作,|表示一个OR操作,^表示一个异或操作。例如:
0xFFFF & 0x0000 = 0x0000
0xF0F0 & 0xFFFF = 0xF0F0
0xFFFF | 0x000F = 0xFFFF
0xFFF0 ^ 0x00FF = 0xFF0F
逻辑操作符& | ^在两个操作数上执行逻辑操作,这两个操作数能够转换为布尔类型。&表示一个AND操作。|表示一个OR操作,^表示一个异或操作。例如:
true & true = true
true & false = false
true | false = true
false | false = false
true ^ true = false
false ^ false = false
false ^ true = true
在大多数程序中,一个表达式中常常会出现多个操作符,例如:
int a = 1;
int b = 2;
int c = 3;
int d = a + b * c;
在代码执行之后,结果是多少?如果你说是9,那么你错了。实际结果是7。
乘法操作符*的优先级比加法操作符+的优先级高。因此,乘法将会在加法之前执行。如果你想要先执行加法,可以使用圆括号:
int d = (a + b) * c;
后者将会把9赋值给d。
表2.5按照优先级顺序列出了所有的操作符。同一行中的操作符,具有相同的优先级。
表2.5 操作符优先级
操作符 |
|
---|---|
postfix operators |
[ ] . (params) expr++ expr-- |
unary operators |
++expr --expr +expr -expr ~ ! |
creation or cast |
new (type)expr |
multiplicative |
* / % |
additive |
+ - |
shift |
<< >> >>> |
relational |
< > <= >= instanceof |
equality |
== != |
bitwise AND |
& |
bitwise exclusive OR |
^ |
bitwise inclusive OR |
| |
logical AND |
&& |
logical OR |
|| |
conditional |
? : |
assignment |
= += -= *= /= %= &= ^= |= <<= >>= >>>= |
注意,圆括号具有最高的优先级。圆括号也可以使得表达式更为清晰。例如,考虑如下的代码:
int x = 5;
int y = 5;
boolean z = x * 5 == y + 20;
比较之后的z值为真。然而,这个表达式还远不够清晰,你可以使用圆括号重新编写最后一行。
boolean z = (x * 5) == (y + 20);
这并不会改变结果,因为*和+拥有比==更高的优先级,但是,这会使得表达式更为清晰。
一些一元操作符(例如+、−和~)和二元操作符(例如,+、−、*和/),会导致自动提升(automatic promotion),例如,演变为一种更宽的类型,如从byte类型到int类型。考虑如下的代码:
byte x = 5;
byte y = -x; // error
第2行会莫名其妙地导致一个错误,即便一个byte类型可以容纳-5。其原因是,一元操作符-导致-x的结果提升为int。要修正这个问题,要么将y修改为int,要么像下面这样执行一次显式的收窄转换:
byte x = 5;
byte y = (byte) –x;
对于一元操作符来说,如果操作数的类型是byte、short或char,结果提升为int类型。
对于二元操作符来说,提升规则如下:
例如,如下的代码将会导致一个编译错误:
short x = 200;
short y = 400;
short z = x + y;
可以通过将z修改为int,或者对x+y执行一次显式的收窄转换,从而修正这个问题。
short z = (short) (x + y);
注意,包围x+y的圆括号是必须的,否则的话,只有x被转换为int,并且一个short和一个int相加的结果将会是int。
在整个代码中编写注释,充分地说明一个类提供了什么函数,一个方法做些什么,一个字段包含什么等,这是一种好的做法。在Java中,有两种类型的注释,它们都和C和C++中的注释有类似的语法。
例如,这里的一个注释描述了一个方法:
/*
toUpperCase capitalizes the characters of in a String object
*/
public void toUpperCase(String s) {
如下是一个单行注释:
public int rowCount; //the number of rows from the database
传统注释不能嵌套,这意味着
/* /* comment 1 */
comment 2 */
是无效的,因为第一个/*之后的第一个*/就结束了该注释。同样,上面的注释多出了一个额外的comment 2 */,这将会导致编译器错误。
另外,单行注释可以包含任何内容,包括字符/*和*/的序列,如下所示:
// /* this comment is okay */
本章介绍了Java语言的基础知识,包括继续学习高级内容之前应该掌握的基本概念和话题。讨论的话题包括字符集、变量、基本数据类型、字面值、操作符、操作符优先级以及注释。
第3章将继续介绍语句,这是Java语言的另一个重要的主题。