书名:Java经典入门指南
ISBN:978-7-115-52576-5
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 [加] 布迪·克尼亚万(Budi Kurniawan)
译 沈泽刚
责任编辑 吴晋瑜
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
Simplified Chinese translation copyright© 2019 by Posts & Telecom Press Co.,LTD.
All rights reserved.
Java: A Beginner’s Tutorial (5th Edition) ,by Budi Kurniawan.
Copyright©2019.
本书由Budi Kurniawan授权人民邮电出版社有限公司出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。
本书基于新版的Java 11编写,全面系统地介绍Java程序员必须掌握的核心基础知识,这些内容融合在三大主题中——Java语言基础、面向对象编程以及Java核心类库。其中,Java语言基础包括数据类型和运算符、控制结构、数组、类和对象、异常处理、枚举和注解等;面向对象包括封装性、继承性、多态性、接口与抽象类、泛型与集合、多线程与并发编程等;Java核心类库包括日期时间API、输入/输出、JavaFX图形界面、Lambda表达式和Stream的使用、数据库和网络编程。本书还介绍了安全性和Java Web编程基础的相关知识。
本书是为专业级Java程序员打造的理想教程,也可作为高等院校计算机相关专业“面向对象编程”和“Java语言程序设计”等课程的教学用书。
Java自1995年诞生以来,经过20多年的发展,现已成为IT领域最流行的编程语言和开发平台。多年来,Java一直名列流行编程语言排行榜(如TIOBE)首位,尤其是在Java归于Oracle公司后,其发展更是与时俱进,其半年一次的新版本的发布策略,更能适应当今IT领域日新月异的快速发展。
本书主要包括三大主题:Java语言基础、面向对象编程和Java核心类库。作者将这些内容集结成册,以飨读者。本书共30章,包括运算符和表达式、控制结构、类与对象、数组、核心类等语言基础,包括继承、封装、多态、接口与抽象类、枚举、注解和内部类等面向对象特征,包括日期和时间API、泛型与集合框架、输入/输出、数据库与网络编程、线程、Lambda表达式、Stream的使用、模块以及JavaFX图形界面设计,还包括Java安全、Servlet和JSP编程基础。最后,附录介绍了javac、java和jar工具以及Eclipse开发工具(以配套资源形式给出)。
本书是作者为Java程序员和即将成为Java程序员的读者奉献的一场盛宴,将带领读者一步步进入Java领域丰富多彩的殿堂。读者可以按书中给出的章节顺序学习,也可以在学习了前8章的基础知识后,有选择地学习后面的章节。
本书通过大量的案例对基本概念和编程思想进行了解释,读者应该仔细研究这些案例并亲手实践,以提升自己的编程能力。
本书作者是资深的Java技术专家、Java企业级应用架构师,著有多部专业图书,以清晰的写作风格闻名。在本书翻译过程中,我也得到了他本人的指导,在此表示感谢。
本书的出版得到了人民邮电出版社的大力支持,我对他们高效的工作态度、高度的敬业精神和精湛的专业知识表示敬佩。译者在翻译过程中虽竭力准确表达原作者思想,但因水平有限,难免存在不足之外,敬请广大读者不吝指教。
沈泽刚
布迪·克尼亚万(Budi Kurniawan)是Brainy Software的高级开发人员,曾在世界各地的多家机构担任顾问。他著有How Tomcat Works、Servlet and JSP: A Tutorial、Struts 2 Design and Programming等多部图书。他以清晰的写作风格而闻名,他的写作基于20年的软件架构师和软件开发经验。他的Java教程最近被德国斯图加特HDM计算机科学教学团队选中,作为大学的主要教材。
本书涵盖了中级Java程序员日常工作所需掌握的最重要的Java编程技术,其中包括三大主题,它们是一名专业级Java程序员必须熟练掌握的内容:
这三大主题相互依存,却很难组织到一本教程中。一方面,Java是一种OOP语言,所以如果已经了解OOP,那么它的语法很容易学习;另一方面,OOP的特性最好通过实例来讲解。但遗憾的是,理解实际的Java程序还需要Java类库的知识。
鉴于这种相互依存关系,这三大主题的内容并没有分成三个独立的部分,而是经常交织在一起。例如,本书在解释多态性之前,要保证读者已熟悉某些Java类,之后才能讲解真实的案例。此外,像泛型这样的语言特性,如果读者事先不理解某些类,是无法解释清楚的,所以需要先介绍支持类,然后讨论泛型。也会出现这样的情况:一个主题可能在两个或多个地方出现。例如,for语句是一项基本的语言特性,应该在前面的章节中讨论,不过for语句也可以用来迭代数组或集合,因此首先在第3章中介绍for语句,然后在第6章和第14章中再度讨论它。
接下来,本书对Java做高度概括,粗略介绍面向对象编程(OOP)的概念,并简要地描述每一章的主要内容。
Java不仅是一种面向对象的编程语言,还是一系列技术,它可以使软件开发更快捷,使应用程序更具鲁棒性、更安全。多年来,Java一直是首选的编程技术,因为它具有平台独立性、易用性、加速应用程序开发的完整类库、安全性、可伸缩性以及广泛的行业支持等优点。
Sun Microsystems公司于1995年发布了Java。尽管Java从一开始是一种通用语言,但很快就以能编写Applet而闻名。Applet是一种小程序,运行在Web浏览器中,并且为静态网站添加交互性。Applet在这个领域统治了十多年,但随着Flash、Silverlight和HTML5等竞争技术的出现,它的统治地位开始动摇。谷歌浏览器Chrome于2015年放弃对Applet的支持,Firefox也于2017年停止了对它的支持,Applet就此出局。
Java另一个吸引人的特性是其平台独立性的承诺,也就是“一次编写,随处运行”(Write Once, Run Anywhere)的口号。这意味着编写的程序可在Windows、macOS、Linux和其他操作系统上运行。这是其他编程语言无法做到的。当时,C和C++是开发常规应用程序最常用的两种语言。Java从诞生之日起,似乎就抢尽了它们的风头。
那就是Java 1.0版。
1997年,Java 1.1版发布了,其在原来的基础上增加了一些重要的特性,如更好的事件模型、Java Beans以及国际化等。
1998年12月,Java 1.2发布了。在发布3天后,它的版本号被改为2,这标志着一个巨大的营销活动的开始,该活动旨在将Java作为“下一代”技术进行销售。Java 2有3个版本:标准版(J2SE)、企业版(J2EE)、移动版(J2ME)。
2000年发布的下一个版本是1.3,也就是J2SE 1.3。2002年发布1.4版,即J2SE 1.4。J2SE 1.5版于2004年发布。1.5版的Java 2后来被改为Java 5。
2006年11月13日,也就是Java 6正式发布的前一个月,Sun宣布开放Java的源代码。Java SE 6是Sun邀请外部开发人员编写代码并帮助修复程序错误的第一个Java版本。诚然,公司过去接受过非本公司员工的参与,如道格·利(Doug Lea)在多线程方面的工作,但这是Sun第一次公开发出邀请。Sun公司承认他们的资源有限,而外部参与者将帮助他们画上完美的句号。
2007年5月,Sun将其Java源代码作为免费软件发布给OpenJDK社区。IBM、Oracle和Apple后来都加入了OpenJDK社区。
2010年,Oracle收购了Sun,成为Java新的所有者。
Java 7于2011年7月发布,Java 8于2014年3月发布,Java 9于2017年9月发布。从Java 9开始,Oracle将Java发布策略从特征驱动模型更改为基于时间的模型,每6个月发布一个新Java版本,每季度更新一次,每3年发布一个长期支持版本。得益于这个新方案,Java 10于2018年3月发布,Java 11于2018年9月发布。
一个“独立于平台”或“跨平台”的程序可以运行在多种操作系统上,这是使Java流行的主要原因。然而,是什么使Java平台独立呢?
一方面,在传统编程中,源代码被编译成可执行代码。这种可执行代码只能在设计它的平台上运行。换句话说,用Windows编写和编译的代码只能在Windows上运行,用Linux编写的代码只能在Linux上运行,以此类推,如图I.1所示。
图I.1 传统的编程范例
另一方面,Java程序被编译成字节码(bytecode)。字节码本身不能运行,因为它不是本机代码(native code)。字节码只能在Java虚拟机(JVM)上运行。JVM是解释字节码的本机应用程序。JVM在许多平台上可用,从而把Java变成一种跨平台语言。如图I.2所示,完全相同的字节码可以在各种操作系统的JVM上运行。
图I.2 Java编程模式
目前,JVM适用于Windows、macOS、Unix、Linux、Free BSD以及世界上几乎所有其他主流操作系统。
Java程序必须被编译,Java需要一个真正有用的编译器。编译器是一种将程序源代码转换为可执行格式(字节码或本机代码)的程序。在开始用Java编程之前,必须先下载Java编译器,编译器程序名为javac,是Java compiler的缩写。
尽管javac可以将Java源代码编译为字节码,但要执行字节码,还需要Java虚拟机(Java Virtual Machine,JVM)。此外,由于经常使用Java类库中的类,因此还需要下载这些类库。Java运行时环境(Java Runtime Environment,JRE)同时包含JVM和Java类库。当然,Windows的JRE与Linux的JRE不同,也就是一种操作系统的JRE与另一种操作系统的JRE不同。
Java软件有如下两种发行版。
总之,JVM是执行字节码的本机应用程序。JRE是一个包含JVM和Java类库的环境。JDK包括JRE和含Java编译器在内的其他工具。JDK也经常被称为SDK(软件开发工具包)。
Sun在推广Java方面做得很好。它的营销策略之一就是创造了Java 2这个名字。Java 2有如下3个版本。
在版本5中出现了名称的变化。J2SE变成了Java Platform, Standard Edition 5(Java SE 5),而且,J2EE和J2ME中的“2”也被去掉了。企业版的最新版是Java Platform, Enterprise Edition 8(Java EE 8或JEE 8)。J2ME现在称为Java Platform, Micro Edition(Java ME,不带版本号)。
与Sun公司推出的第一个Java版本不同,J2SE 1.4、Java SE 5和Java的后续版本是一系列规范,它们定义了在发布时需要实现的特性。软件本身被称为参考实现。Oracle、IBM和其他公司一起,通过OpenJDK提供了Java SE 11参考实现以及Java后续版本的参考实现。Java EE 6、Java EE 7和Java EE 8也是一系列规范,其中包括Servlet、JavaServer Pages、JavaServer Faces、Java Messaging Service等技术。
到目前为止,一切顺利。然而,到了2017年年初,Oracle显然在Java EE商业化方面几乎没有取得成功。2017年9月,Oracle宣布将把Java EE提交给新的买方Eclipse Foundation。然而,所有权的转让并不包括Java这个名称,因此它现在仍然是Oracle拥有的商标。Eclipse基金会为Java EE取的新名称是Jakarta EE。
要运行Jakarta EE应用程序,需要一个应用服务器。在撰写本书时,我们还不清楚哪些应用程序服务器是兼容的,但可以明确的是满足Java EE 6和Java EE 7等的应用程序服务器包括Oracle WebLogic、IBM WebSphere、GlassFish、JBoss、WildFly、Apache Geronimo、Apache TomEE等。
JBoss、GlassFish、WildFly、Geronimo和TomEE都是开源的Java EE服务器。不过,它们有不同的许可证,所以在决定使用这些产品之前一定要仔细阅读相关内容。
Java之所以能够持续成为首选技术,在很大程度上归功于Sun的策略,即让其他行业人员参与决定Java的未来。这样,很多人觉得他们也拥有Java。许多大公司,如IBM、Oracle、Nokia、Fujitsu等,都在Java上进行了大量的投资,因为他们也可以为一项技术提出一个规范,并提出他们希望在下一个Java技术版本中看到的内容。这种协同工作采用Java社区进程(Java Community Process,JCP)程序的形式。
JCP程序提出的规范称为Java规范请求(Java Specification Request,JSR),例如JSR 386定义了Java SE 12。
JDK增强建议(JDK Enhancement Proposal,JEP)是一个收集改进JDK和OpenJDK建议的过程。JEP比JSR更不正式,它也并不打算取代JCP。此外,JEP仅仅针对JDK特有的新特性。
面向对象编程(Object-Oriented Programming,OOP)通过在现实世界的对象上对应用程序建模来发挥作用。OOP的3个原则是封装性、继承性和多态性。
OOP的优势就是大多数现代编程语言(包括Java)都是面向对象(Object-Oriented, OO)的原因。可以举出两个转而支持OOP的语言的例子:C语言演变为C++,Visual Basic升级为Visual Basic.NET。
接下来将讨论OOP的优势,并对学习OOP的难易程度进行评定。
OOP的优势包括代码易维护、代码可重用和可扩展性。
(1)代码易维护(ease of maintenance)。现代软件应用程序规模日趋庞大。从前,一个“大型”系统包含几千行代码。现在,即使是那些由百万行代码组成的软件也并不被认为是大型系统。当系统变得越来越大时,它就开始给开发人员带来各种问题。C++之父本贾尼·斯特劳斯特卢普(Bjarne Stroustrup)说过,可以用任何语言以任何方式编写一个小程序。如果读者不轻易放弃,最终还是可以让它运行起来。但对于大型项目就是另一回事了。如果不采用“好的编程”方法,旧的错误还没有修复完,就会产生新的错误。这是因为大型程序的各个部分之间是相互依赖的。当修改程序的某处时,读者可能没有意识到该修改可能会影响其他地方。OOP很容易使应用程序模块化,这会让维护变得不那么麻烦。模块化在OOP中本来就存在,因为类本身就是模块,它是对象的模板。一种好的设计应该允许类包含类似的功能和相关数据。OOP中经常使用的一个重要且相关的术语是耦合(coupling),它表示两个模块之间的交互程度。组件之间的松散耦合可以使代码重用更容易实现,这是OOP的另一个优点。
(2)代码可重用(reusability)。可重用性是指之前编写的代码可以被代码作者和其他需要原始代码提供相同功能的人重用。因此,OOP语言通常提供一系列现成的类库就不足为奇了。以Java为例,该语言附带着数百个经过精心设计和测试的类库或应用程序接口(API)。编写和分发自己的类库也很容易。编程平台支持可重用性这点非常有吸引力,因为它可以缩短开发时间。
类可重用性的主要挑战之一是为类库创建良好的文档。作为一名程序员,要找到一个能够为其提供所需功能的类能有多快?是找一个类更快呢,还是从头开始编写一个新类更快呢?幸运的是,Java核心和扩展API都带有大量文档。
可重用性不仅通过类和其他类型的重用应用于编码阶段,在OO系统中设计应用程序时,OO设计问题的解决方案也可以重用。这些解决方案称为设计模式。为了更容易地引用每个解决方案,每种设计模式都有一个名称。可重用设计模式的早期讨论请见经典著作《设计模式:可重用面向对象软件的基础》,该著作由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides合著。
(3)可扩展性(extendibility)。每个应用程序都有自己的需求和规范。就可重用性而言,有时可能找不到提供应用程序所需的精确功能的现成的类。不过,或许可以找到一两个提供部分功能的应用程序。可扩展性的意思就是可以通过扩展这些类来满足需要。这样做仍然可以节省时间,因为不必从头编写代码。在OOP中,可以扩展现有的类,并添加或修改其中的方法或数据。
研究人员一直在讨论在学校讲授OOP的最佳方法。有人认为,在引入OOP之前,最好先讲过程化程序设计。在许多课程教学计划中,都是在学生快大学毕业时才开设OOP课程。然而,最近的研究表明,具有过程化程序设计技能的人的思维模式与OO程序员看待和试图解决问题的方式非常不同。当这个人需要学习OOP时,他所面临的最大困难是必须经历模式转换。据说将一个人的思维模式从过程化转向面向对象需要6~18个月的时间。另一项研究表明,没有学过过程化程序设计的学生并不觉得学习OOP很难。
好消息是Java是一种易于学习的OOP语言。读者不需要担心指针,也不需要花时间解决由于未能销毁无用对象而导致的内存泄露,等等。此外,Java还提供了一个全面的类库。一旦掌握了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中一些最常用的类。
第6章讨论数组,这是Java的一个广泛使用的特性。
第7章讨论OOP中的代码可扩展性。本章教读者如何扩展类、影响子类的可见性和覆盖方法。
错误处理是任何编程语言的一个重要特性。作为一种成熟的语言,Java有一个非常健壮的错误处理机制,有助于防止程序错误的扩散。第8章详细讨论这种机制。
第9章讨论处理数字时涉及的3个问题:解析、格式化和操作。
第10章介绍接口不仅是一个没有实现的类。接口定义了服务提供者和客户之间的一种契约。本章将解释如何使用接口和抽象类。
多态性是面向对象的主要支柱之一。它在程序编译时对象类型未知的情况下非常有用。第11章解释多态性特性,并给出一些有用的例子。
第12章介绍枚举类型,这是自版本5以来添加到Java中的一种类型。
第 13 章讨论添加到Java 8中的新的日期和时间API,以及在旧版本Java中使用的旧API。
第14章展示如何对对象进行分组和操作。
第15章解释泛型,这是Java中的一个重要特性。
第16章介绍流(Stream)的概念,并解释了如何使用Java IO API中的流类型来执行输入/输出操作。
第17章讨论注解。它解释了JDK附带的标准注解、一般注解、标准元注解和自定义注解类型。
第18章解释如何在另一个类中编写类,以及为什么这种OOP特性非常有用。
第19章介绍Lambda表达式的用法。
第20章讨论Stream以及为什么它们在Java编程中扮演着重要的角色。
第21章介绍访问数据库和操作数据的技术。
第22章介绍JavaFX,这是一种用于创建富客户端应用程序的技术。
第23章讨论FXML,这是一种标记语言,可用于分离JavaFX应用程序中的表示层和业务逻辑。
第24章介绍线程。线程是操作系统分配处理器时间的基本处理单元,一个进程中可以有多个线程执行代码。由此可知,在Java中多线程编程并不是只有高级程序员才能做到。
第25章是关于多线程编程的另一章。它讨论了更容易编写多线程程序的类型。
第26章处理可以在网络编程中使用的类。
第27章介绍Java应用程序用户如何限制Java应用程序运行,以及如何使用密码保护应用程序和数据。
第28章探讨Servlet技术和Servlet API,并给出了几个示例。
第29章解释另一种Web开发技术并展示了如何编写JSP页面。
第30章解释Java模块系统,这是Java 9中添加的最新特性。
本书附录以配套资源形式给出,请登录异步社区下载。附录A、附录B和附录C分别介绍javac、java和jar这3个工具的使用方法。附录D提供了当今流行的IDE之一Eclipse的简短教程。
本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。
本书提供为读者提供源代码。要获得以上配套资源,请在异步社区本书页面中单击,跳转到下载界面,按提示进行操作即可。注意:为保证购书读者的权益,该操作会给出相关提示,要求输入提取码进行验证。
如果您是教师,希望获得教学配套资源,请在社区本书页面中直接联系本书的责任编辑。
作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。
如果您发现错误,请登录异步社区,按书名搜索,进入本书页面,单击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,将赠予您异步社区的100积分(积分可用于在异步社区兑换优惠券、样书或奖品)。
我们的联系邮箱是contact@epubit.com.cn。
如果您对本书有任何疑问或建议,请发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。
如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/selfpublish/submission即可)。
如果您来自学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。
如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请将疑似有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。
“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。
“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。
异步社区
微信服务号
用Java编程,需要用到Java SE开发工具包(JDK),因此1.1节先介绍如何下载和安装JDK。
开发Java程序包括编写代码、将其编译为字节码以及执行字节码。Java程序员在其职业生涯中,将不断重复这个过程。由此可见,适应该过程是至关重要的。因此,本章的主要目的是让读者体验使用Java进行软件开发的过程。
编写出来的代码不仅要能够执行,更重要的是还要易于阅读和维护,因此本章将介绍Java编码规范。此外,明智的开发人员会选择使用集成开发环境(IDE)。本章的最后一节将给出关于Java IDE的使用建议。
Java源代码可在OpenJDK官方网站下载。代码库包含组成JDK的项目,包括类库、虚拟机(代号为HotSpot)、Java编译器和其他工具。要使用此源代码,必须将其构建到Java开发所需的软件套件中。尽管可以自己构建JDK,但这并不容易,即使对于经验丰富的Java程序员也是如此。优选的方法是下载一个可用的JDK构建。Oracle、AdoptOpenJDK开源社区和其他提供商都提供免费下载的JDK构建版本。这些构建具有不同的许可协议,但通常都可以免费用于开发。
Oracle JDK是当今最流行的JDK。读者可从下面的网址下载适用于Windows、Linux和macOS的JRE和JDK。
在打开的页面中单击DOWNLOAD链接,会跳转到一个新页面,在此选择一个安装平台:Windows、Linux、Solaris或macOS。这一链接也提供了JRE下载,但是开发者需要的是JDK而不是JRE——JRE只适合运行编译后的Java类。JDK包含JRE。
下载JDK之后还需要安装它。安装因操作系统而异,详细过程见1.1.1节。
注意 JDK安装目录通常称为$JAVA_HOME或$JDK_HOME。
在Windows上安装JDK很容易。只需在Windows资源管理器中双击下载的可执行文件,并按说明操作即可。图1.1所示的是安装向导的第一个对话框。
图1.1 在Windows上安装JDK
在Linux平台上,JDK有以下两种安装文件格式:一种是RPM,支持RPM包管理系统的Linux平台,如Red Hat和SuSE;另一种是自解压包,其中包含安装包的压缩文件。
如果使用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
要在macOS上安装JDK 11,需要一台基于英特尔的计算机运行macOS,还需要管理员特权。安装的具体步骤如下。
(1)双击下载的.dmg文件。
(2)在出现的Finder窗口中,双击包图标。
(3)在出现的第一个窗口中,单击Continue按钮。
(4)出现安装类型窗口,单击Install按钮。
(5) 这时会出现一个窗口,显示“Installer is trying to install new software. Type your password to allow this”,输入读者的Admin密码。
(6)单击Install Software按钮启动安装。
安装JDK之后,就可以编译和运行Java程序了。但是,现在只能从javac和java程序的位置调用编译器和JRE,或者在命令中包含安装路径。在计算机上设置PATH环境变量很重要,这样就可以使编译和运行程序更容易,可以从任何目录调用javac和java。
要在Windows上设置PATH环境变量,请按下面的步骤操作。
(1)如果使用的是Windows 10,请在工具栏上的搜索框中输入“environment”,然后单击Windows找到的第一个搜索结果。
(2)如果使用的是Windows 7或Windows 8,请依次选择Start>Settings>Control Panel>System。
(3)如果还没有打开,选择Advanced选项卡,然后单击Environment Variables按钮。
(4)在用户变量或系统变量窗口中找到PATH环境变量。PATH值是由分号分隔的一系列目录。现在,单击Edit按钮将$JAVA_HOME/bin(Java安装目录下bin目录的完整路径)添加到PATH现有值的末尾,或者在资源管理器中找到该目录——类似于如下样式:
C:\Program Files\Java\jdk-11\bin
(5)单击Set、OK或Apply按钮。
在UNIX和Linux操作系统上设置PATH环境变量取决于所使用的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编程,一定会用到Java类库中的类。即便是经验丰富的程序员,在编写代码时也要查找这个库的文档。读者可以从如下链接下载文档:
http://www.oracle.com/technetwork/java/javase/downloads/index.html
在下载界面中,需要向下拖动滚动条,直至看到“Java SE 11Documentation”。
以下网址还提供了最新的在线API:
http://download.oracle.com/javase/11/docs/api
本节重点介绍Java程序的开发步骤:先编写程序,接着将其编译为字节码,最后执行字节码。
可以用任何文本编辑器编写Java程序。打开文本编辑器并输入清单1.1所示的代码。或者,如果下载了本书附带的程序示例,可以简单地将其复制到文本编辑器中。
代码下载
如需使用本书示例代码,请从人民邮电出版社异步社区图书详情页的“配套资源”处下载。
清单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环境变量(关于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 rocks.
注意 在执行Java程序时不包含class扩展名。
至此,读者已成功地编写了第一个Java程序。由于本章只是帮助读者了解编写和编译程序的过程,因此这里不解释程序是如何工作的。
还可以给Java程序传递参数,例如,如果有一个名为Calculator的类,想给它传递两个参数,可以这样执行程序:
java Calculator arg-1 arg-2
其中,arg-1是第一个参数,arg-2是第二个参数。可以传递任意多的参数,java工具会将这些参数作为字符串数组提供给要执行的Java程序。处理参数的方法详见第6章。
注意 java工具是一个高级程序,提供了很多配置选项,例如可以设置分配给它的内存大小。相关内容参见附录B。
注意 java工具还可以用来运行打包在jar文件中的Java类。相关内容参见附录C。
编写正确的、可以运行的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发布了一份文档,规定了其员工应该遵循的标准。该文档的详细内容可从下面的网址获得。
http://www.oracle.com/technetwork/java/codeconvtoc-136057.html
本书中的程序示例将遵循本文档中推荐的规范。读者应从学习编程的第一天开始就养成遵循这些规范的习惯,这有助于编写出清晰的代码。
关于样式的编码规范首先是有关缩进的。缩进的单位必须是4个空格。如果用制表符替代空格,Tab键必须设置成8个空格(而不是4个)。
确实可以仅用文本编辑器编写Java程序。不过,使用集成开发环境(IDE)会更方便。IDE不仅可以检查代码的语法,还可以完成代码的自动编写、调试和跟踪程序。此外,编译可以在输入时自动进行,要运行Java程序,只需按下一个按钮。因此,用IDE可以大大缩短开发时间。
过去有几十个Java IDE可用,但是现在只有Eclipse和IntelliJ IDEA这两个主要的。Eclipse是完全免费和开源的,IntelliJ IDEA有免费和付费两个版本。IntelliJ IDEA可能是当今最流行的Java IDE,它包含了许多Eclipse用户必须作为插件单独安装的优秀工具。但是,对于初学者来说,Eclipse是一个更好的工具,因为它很简单。
本书推荐使用Eclipse,相关内容参见附录D。
JShell是Java 9的一个新特性,它是一个环境,称为REPL(Read-Eval-Print Loop)或语言外壳,用于读取用户输入、计算输入并打印输出结果。可以使用JShell输入一小段程序并在没有编译的情况下运行它。在实际应用中,通常用JShell执行一些琐碎的任务,比如测试一个方法或做简单的数学计算。
要使用JShell,需要执行以下操作。
(1)打开Windows命令提示符或Linux终端。
(2)如果还没有将JDK_HOME/bin添加到PATH路径中,请将目录更改为JDK_HOME/bin。JDK_HOME是JDK安装的目录。
(3)输入“jshell”。
下面是使用JShell的一个简单示例。其中以粗体字显示的是在Windows命令提示符下输入的内容。
c:\Program Files\Java\jdk-10.0.1\bin>jshell
| Welcome to JShell -- Version 10.0.1
| For an introduction type: /help intro
jshell> int i = 5; var j = 8;
i ==> 5
j ==> 8
jshell> System.out.println(i + j)
13
本章给出了下载和安装JDK的指导方法,并引导读者编写了第一个Java程序。读者可以用文本编辑器编写程序,用javac将程序编译为类文件,并用java工具执行类文件。
随着程序变得越来越复杂,项目变得越来越大,使用IDE有助于加快应用程序的开发。
1.什么是编译器?
2.Java编程与传统编程有何不同?
3.字节码是什么?
4.JRE和JDK之间的区别是什么?
5.如果使用另一个不同的名称保存清单1.1中的代码,如whatever.java,它能被编译吗?
6.如果在保存清单1.1的代码时使用了java以外的文件扩展名,如MyFirstProgram.txt,它能被编译吗?
7.下面这些Java类名是有效的吗?
FirstJava, scientificCalculator, numberFormatter
8.如何向控制台输出信息?
9.编写一个名为HelloWorld的Java类,使其输出“Hello World”。
由于Java是一种面向对象的编程(OOP)语言,因此理解OOP至关重要。但是,在研究OOP特性和技术之前,读者应该先学习Java语言基础。
传统上,使用英语国家的计算机只使用ASCII(美国信息交换标准代码)字符集来表示字母、数字、字符。ASCII中的每个字符由7位二进制数表示。因此,这个字符集有128个字符,包括小写拉丁字母、大写拉丁字母、数字和标点符号等。
ASCII字符集后来扩展到包含另外的128个字符,比如德语字符ä 、ö、ü和英国货币符号£。这个字符集称为扩展ASCII,每个字符由8位二进制数表示。
ASCII和扩展ASCII只是可用的许多字符集中的两种。还有一个流行的字符集是ISO(国际标准化组织)标准化的ISO-8859-1,也称为Latin-1。ISO-8859-1中的每个字符也用8位二进制数表示,该字符集包含许多西欧语言编写文本所需的所有字符,这些语言包括德语、丹麦语、荷兰语、法语、意大利语、西班牙语、葡萄牙语等,当然还有英语。因为每个字节的长度也是8个二进制位,所以这样的字符集使用起来很方便。因此,存储和传输以8位字符集编写的文本是最高效的。
然而,并不是每种语言都使用拉丁字母,汉语和日语就是使用不同字符集的语言的例子。例如,汉语中的每个字符代表一个单词,而不是一个字母,这些字符的个数成千上万,8位二进制数不足以表示字符集中的所有字符。日语同样如此,也使用了不同的字符集。总的来说,世界各国语言包含数百种不同的字符集,为了统一所有这些字符集,一个名为Unicode的计算标准应运而生。
Unicode是一个由非营利性组织Unicode Consortium开发的字符集。它试图将世界上所有语言中的所有字符包含到一个字符集中。在Unicode中,每个字符使用唯一编码表示。Unicode目前的版本是11,用在Java、XML、ECMAScript、LDAP等语言中。
最初,Unicode字符用16位二进制数表示,这足以表示超过65000个不同的字符,这些字符足以为世界上主要语言中的大多数字符编码。然而,Unicode联盟计划允许对多达100万个字符进行编码。对这个数量级的字符数,需要使用超过16位二进制数来表示每个字符。事实上,32位系统被认为是一种存储Unicode字符的方便方法。
现在,问题出现了。虽然Unicode为所有语言中使用的所有字符提供了足够的空间,但是存储和传输Unicode文本的效率不如ASCII或Latin-1字符高。在互联网世界,这是一个巨大的问题。想象一下,对于同样的数据量,它的传输时间是ASCII文本的4倍!
幸运的是,字符编码可以提高存储和传输Unicode文本的效率。可以认为字符编码与数据压缩类似;而且,现在有很多类型的字符编码。Unicode联盟支持以下3种。
(1)UTF-8。其在HTML和将Unicode字符转换为可变长度字节编码的协议中非常流行。这种字符编码的优点有,与熟悉的ASCII字符集对应的Unicode字符具有与ASCII相同的字节数,并且Unicode字符转换成UTF-8后可以与许多现有软件一起使用。大多数浏览器支持UTF-8字符编码。
(2)UTF-16。在这种字符编码中,所有常用的字符都通过一个16位代码单元存放,而其他较不常用的字符还可以通过一对16位代码单元进行编码。
(3)UTF-32。这种字符编码对每个字符使用32位编码,这显然不是互联网应用程序的选择,至少目前不是。
ASCII字符在软件编程中仍然占主导地位。Java也对几乎所有输入元素使用了ASCII,除了注释、标识符及字符和字符串的内容(在计算机编程中,字符串是一段文本或字符序列)。对于后者,Java支持Unicode字符。这意味着,完全可以用英语以外的语言编写注释、标识符、字符和字符串。
传统上,Java使用UTF-16表示字符串,这说明任何单个字符都需要由16位二进制数来表示。从版本9开始,Java默认使用压缩字符串,这意味着Latin-1字符集是默认字符。Latin-1字符集中的字符只用8位二进制数表示。只有当字符串包含非Latin -1字符时,Java才会使用UTF-16来表示。大多数Java字符串都使用Latin-1字符集来表示,这大大节省了内存空间,显著提升了性能。
Java使用某些字符作为分隔符,这些特殊字符如表2-1所示。
表2-1 Java分隔符
符号 |
名称 |
用途说明 |
---|---|---|
() |
圆括号 |
(1)方法签名,用于包含参数列表 |
{} |
大括号 |
(1)类型声明 |
[] |
方括号 |
(1)数组声明 |
<> |
尖括号 |
将参数传递给参数化类型 |
; |
分号 |
结束语句,以及在for语句中分隔初始化代码、表达式以及更新代码 |
: |
冒号 |
在for语句中迭代数组或集合 |
, |
逗号 |
在方法声明中分隔参数 |
. |
句号 |
将包名称与子包及类型名称分开,以及将字段或者方法与引用的变量分开 |
对于这些符号和名称,读者应做到了然于胸,但是如果读者现在不理解说明栏中的术语,也不必担心。我们将在后续章节中给出介绍。
在编写面向对象(OO)的应用程序时,我们将创建一个类似于真实世界的对象模型,例如,工资单应用程序将包含Employee对象、Tax对象、Company对象等。然而,在Java中,对象不是唯一的数据类型,还有另一种称为基本类型(primitive)的数据类型。Java中有8种基本类型,每种类型都有特定的格式和大小,如表2-2所示。
表2-2 Java基本类型
基本类型 |
说明 |
范围 |
---|---|---|
byte |
字节整型(8位) |
−128(−27)~127(27−1) |
short |
短整型(16位) |
−32768(−215)~32767(215−1) |
int |
整型(32位) |
−2147483648(−231)~2147483647(231−1) |
long |
长整型(64位) |
−9223372036854775808(−263)~9223372036854775807(263−1) |
float |
单精度浮点型(32位) |
最小非零正值:14e−45,最大非零正值:3.4028234e38 |
double |
双精度浮点型(64位) |
最小非零正值:4.9e−324,最大非零正值:1.7976931348623157e308 |
char |
Unicode字符 |
[请参阅Unicode 6规范] |
boolean |
布尔值 |
true或false |
前6种基本类型(byte、short、int、long、float和double)表示数字,它们的大小各不相同。例如,一个byte可以包含−128和127之间的任何整数。要想获得整数的最小的数和最大的数,可以参考它的位数。一个字节有8位长,所以有28(256)个可能的值。前128个值留给−128到−1,然后0占一位,还有127个正值。因此,一个字节的范围是−128到127。
如果需要一个占位符来存储数字1000000,那么需要一个int型数,long可以用于存储更大的数字,读者可能会问,既然long可以包含比byte和int型数更大的数字,为什么不总是使用long呢?这是因为long需要64位,相比byte或int,这会占用更多的内存空间。因此,为了节省空间,需要选择合适的基本类型。
基本类型byte、short、int和long只能保存整数,对于带有小数点的数字,需要float或double。float数是一个32位的值,符合电气电子工程师学会(Institute of Electrical and Electronics Engineer,IEEE)标准754。double数是符合相同标准的64位的值。
char类型可以包含一个Unicode字符,比如‘A’‘9’或‘&’。Unicode允许使用英语字母表中没有包含的字符。boolean类型的值可以是false或true。
注意 在Java中,不是所有东西都是对象,其原因是速度问题。对象的创建和操作比基本类型开销更大。在编程中,如果某个操作是资源密集型的,或者需要消耗大量CPU周期才能完成,那么这个操作的开销就会很大。
现在已经知道Java有两种数据类型——基本类型和对象,接下来我们继续探讨基本类型的使用方法,先从变量开始讨论。
变量(variable)是数据占位符。由于Java是一种强类型语言,因此每个变量必须有一个声明的类型。Java中有以下两种数据类型:引用类型——引用类型的变量提供一个对象的引用;基本类型——基本类型的变量保存一个基本类型。
除了数据类型,Java变量还要有名称或标识符。选择标识符时有一些基本规则。
(1)标识符是由Java字母和Java数字组成的序列,其长度没有限制,但必须以一个Java字母开头。
(2)标识符不能是Java关键字(见表2-3),但是var可以用作标识符。
(3)在其作用域内标识符必须是唯一的。作用域的相关内容参见第4章。
表2-3 Java关键字
abstract |
default |
goto |
package |
this |
assert |
do |
if |
private |
throw |
boolean |
double |
implements |
protected |
throws |
break |
else |
import |
public |
transient |
byte |
enum |
instanceof |
return |
true |
case |
extends |
int |
short |
try |
catch |
false |
interface |
static |
var |
char |
final |
long |
strictfp |
void |
class |
finally |
native |
super |
volatile |
const |
float |
new |
switch |
while |
continue |
for |
null |
synchronized |
Java如何存储整数值
读者一定听说过计算机是通过二进制数工作的,二进制数是由0和1组成的数字。本节给出一个概述,为读者学习算术运算符做铺垫。
一个字节占8位,意思是保存一个字节要给它分配8位空间。最左边的位是符号位,0表示正数,1表示负数。0000 0000是0的二进制表示,0000 0001是1的二进制表示,0000 0010是2的二进制表示,0000 0011是3的二进制表示,0111 1111是127的二进制表示(它是一个字节可以表示的最大正数)。
那么,如何得到负数的二进制表示呢?很容易。首先需要得到它的正数的二进制表示,然后把所有位反转再加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字母包括大写的ASCII拉丁字母A到Z(\u0041—\u005a——注意,\u表示Unicode字符)和小写的ASCII拉丁字母a到z(\u0061—\u007a),由于历史原因,它还包括ASCII下画线(_或\u005f)和美元符号($或\u0024)。$字符只能在机器生成的源代码中使用,或者偶尔用于访问遗留系统已有的名称。Java数字包括ASCII数字0~9(\u0030—\u0039)。
下面是一些合法的标识符:
salary
x2
_x3
row_count
下面是一些无效的变量:
2x
java+variable
2x是无效的,因为它是以一个数字开始的。java+variable也是无效的,因为它包含一个“+”号。
还要注意,Java标识符是区分大小写的,即x2和X2是两个不同的标识符。
声明变量时,要先写类型,再写名称和分号。下面是几个变量声明的例子:
byte x;
int rowCount;
char c;
在上面的例子中,我们声明了3个变量:byte类型的变量x、int类型的变量rowCount以及char类型的变量c。其中,x、rowCount和c就是变量名或标识符。
也可以在同一行声明多个具有相同类型的变量,用逗号将这些变量分隔开,例如:
int a, b;
它与以下声明的效果相同:
int a;
int b;
但是,不建议在同一行声明多个变量,因为那样会降低可读性。
最后,可以在变量声明的同时给变量赋值:
byte x = 12;
int rowCount = 1000;
char c = 'x';
变量名应该简短并有意义。变量名应该以一个首字母小写的单词开头,整个变量名中大小写字母混合。后面的单词的首字母应大写。变量名不应该以下画线_或美元符号$字符开头。userName、count、firstTimeLogin是符合Sun公司编码规范的变量名。
Java 10增加了局部变量类型推断,这一特性允许程序员使用新的关键字var代替类型,并允许编译器推断(猜测)类型,也就是说,下面的代码:
int numCars = 200;
char code = 'a';
可以写成:
var numCars = 200;
var code = 'a';
使用var的目的是提供一种快捷方式并节省一些录入时间,虽然在本例中表现得并不明显。但是,如果有一个下面这样的复杂变量:
Map<List<Integer>, String> result = doSomething();
像下面这样使用局部变量类型推断,确实会节省时间:
var result = doSomething();
尽管var被提升为关键字,但仍然可以将其作为变量名,这是为了不破坏向后兼容性。换句话说,为Java 10之前的编译器编写的碰巧使用var作为标识符的Java程序,仍然可以用Java 10及以后的编译器编译。
在Java中,常量(constant)是指一旦赋值,其值就不能再更改的变量。使用关键字final声明常量。按照惯例,常量名称都是大写的,单词之间用下画线分隔。下面是一些常量或final变量的例子:
final int ROW_COUNT = 50;
final boolean ALLOW_USER_ACCESS = true;
在程序中,我们经常需要给变量赋值,例如,将数字2赋给int型变量,将字符‘c’赋给char型变量。为此,我们需要以Java编译器能够理解的格式书写值的表示形式。值的这种源代码表示形式称为字面值(literal)。字面值有3种类型:基本类型的字面值、字符串字面值和null字面值。本章只讨论基本类型的字面值,第4章将讨论null字面值,第5章将讨论字符串字面值。
基本类型的字面值又分为4种子类型:整数字面值、浮点字面值、布尔字面值和字符字面值。下面我们逐一介绍这些子类型。
整数字面值可以写成十进制(基数为10,这是我们所习惯的)、十六进制(基数为16)或八进制(基数为8)。以下是十进制整数:
2
123456
作为另一个例子,下面的代码将10赋值给int类型的变量x:
int x = 10;
十六进制整数的前缀为0x或0X。例如,十六进制数字9E可以写成0X9E或0x9E。八进制整数是在数字前面加上0来表示。例如,下面是一个八进制数567:
0567
整数字面值用于为byte、short、int和long类型的变量赋值。但请注意,不能将超过各个类型的容量的值赋给变量。例如,一个字节的最大值是127,下面的代码会产生编译错误,因为200对于一个字节来说太大了:
byte b = 200;
要给一个long型变量赋值,请在数字后面加字母L或l作为数字的后缀。建议使用大写的字母L,因为小写的字母 l 很容易与数字1混淆。long型变量可包含−9223372036854775808L~9223372036854775807L(263)的值。
Java初学者经常会问为什么需要使用后缀L或l,即便没有它,下面的代码不也可以编译吗?
long a = 123;
这么说只是部分正确。没有后缀L或l的整数字面值将被视为int类型,当值超出int型的范围时,如9876543210,就会产生编译错误:
long a = 9876543210;
要纠正这个问题,可以在数字后面加上一个L或l:
long a = 9876543210L;
long、int、short和byte也可以用二进制表示,只需在其二进制表示法之前加上0B或0b,例如:
byte twelve = 0B1100; // = 12
如果一个整数字面值太长,其可读性就会受到影响。因此,从Java 7开始,可以使用下画线分隔整型字面值中的数字。例如,下面两行代码的含义相同,但是第二种写法读起来显然更容易:
int million = 1000000;
int million = 1_000_000;
下画线放在哪里并不重要,只要在两个数之间即可。可以每 3 个数字之间使用一个下画线,就像上面的例子一样,或者任意数量的数字之间。又如:
short next = 12_345;
int twelve = 0B1_100;
long multiplier = 12_34_56_78_90_00L;
但是,下面的代码将无法编译,因为下画线不在两个数字之间:
int twelve = 0B_1100;
long multiplier = 1234567890_L;
像0.4、1.23、0.5e10这样的数字都属于浮点数。浮点数包括以下几部分:整数部分、小数点、小数部分和可选的指数。以1.23为例,这个浮点数的整数部分是1,小数部分是23,没有可选的指数。再以0.5e10为例,0是整数部分,5是小数部分,10是指数。
在Java中,有如下两种类型的浮点数。
(1)单精度浮点数(float),32位,最大的正单精度浮点数为3.40282347e38,最小的正有限非零单精度浮点数为1.40239846e−45。
(2)双精度浮点数(double),64位,最大的正双精度浮点数为1.79769313486231570e308,最小的正有限非零双精度浮点数为4.94065645841246544e−324。
在上述两种类型的浮点数中,当整数部分为0时,这个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_or_F部分表示浮点字面值是单精度浮点数。没有这一部分,浮点数就变成了双精度浮点数。要显式地表示双精度浮点数字面值,可以加上后缀D或d。
要编写双精度浮点数字面值,请使用以下格式之一:
Digits . [Digits] [ExponentPart] [d_or_D]
. Digits [ExponentPart] [d_or_D]
Digits ExponentPart [d_or_D]
Digits [ExponentPart] [d_or_D]
在单精度浮点数和双精度浮点数中,ExponentPart的定义如下:
ExponentIndicator SignedInteger
其中,ExponentIndicator为e或E,SignedInteger是指:
Signopt Digits
其中,Sign是“+”或“−”,若是“+”则是可选的。
单精度浮点数字面值的例子包括:
2e1f
8.f
.5f
0f
3.14f
9.0001e+12f
下面是双精度浮点数字面值的例子:
2e1
8.
.5
0.0D
3.14
9e-9d
7e123D
布尔类型有两个值,分别用true和false表示。例如,下面的代码声明了一个布尔型变量includeSign,并将true赋给它:
boolean includeSign = true;
字符字面值是一个用单引号括起来的Unicode字符或转义序列。转义序列(escape sequence)是指无法用键盘输入的Unicode字符的表示形式,或者在Java中具有特殊功能的Unicode字符的表示形式。例如,回车和换行符用于终止行,并且它们都没有可视化表示。要表示换行字符,需要对它进行转义,即写出它的字符表示形式。此外,单引号字符也需要转义,因为单引号用于括住字符。
下面是一些字符字面值的例子:
'a'
'Z'
'0'
'ü'
下面是转义序列的字符字面值:
'\b' 退格符
'\t' 制表符
'\\' 反斜线
'\'' 单引号
'\"' 双引号
'\n' 换行符
'\r' 回车符
此外,Java允许转义Unicode字符,以便可以使用ASCII字符序列来表示Unicode字符。例如,字符£的Unicode代码是00A3,可以用以下字面值来表示该字符:
'£'
然而,如果无法通过键盘输入这个字符,也可以这样转义:
'\u00A3'
在处理不同的数据类型时,通常需要执行转换。例如,将一个变量的值赋给另一个变量,就涉及转换。如果两个变量的类型相同,则赋值总会成功。同类型之间的转换称为恒等转换。例如,以下操作一定会成功:
int a = 90;
int b = a;
但是,不同类型之间的转换有时不一定会成功,甚至是不能转换的。有两种基本类型的转换:加宽转换和缩窄转换。
加宽转换(widening conversion)是指一种类型转换为另一种大小与它相同或比它更大的类型,例如从int(32位)转换为long(64位)。在下列情况下,允许加宽转换。
(1)从byte转换为short、int、long、float或者double。
(2)从short转换为int、long、float或者double。
(3)从char转换为int、long、float或者double。
(4)从int 转换为long、float或者double。
(5)从long转换为float或者double。
(6)从float 转换为double。
一种整数类型到另一种整数类型的加宽转换不会有信息丢失的危险。同样,从float转换为double也不会发生信息丢失。但是,从int或long转换为float可能会导致信息的丢失。
基本类型的加宽转换是隐式执行的,不需要在代码中执行任何操作,例如:
int a = 10;
long b = a; // 加宽转换
缩窄转换(narrowing conversion)是指从一种类型转换为另一种位数较少的类型,例如从long类型(64位)转换为int类型(32位)。一般来说,下面的情况会发生缩窄转换。
(1)从short转换为byte或者char。
(2)从char转换为byte或者short。
(3)从int转换为byte、short或者char。
(4)从long转换为byte、short或者char。
(5)从float转换为byte、short、char、int或者long。
(6)从double转换为byte、short、char、int、long或者float。
与基本类型的加宽转换不同,缩窄转换必须是显式的。读者需要在括号中指定目标类型,例如从long类型到int类型的缩窄转换:
long a = 10;
int b = (int) a; // 缩窄转换
第二行中的(int)告诉编译器这是一个缩窄转换。
如果转换值大于目标类型的容量,则缩窄转换可能导致信息丢失。前面的例子没有发生信息丢失,因为10对于int足够小。但是,在接下来的转换中,9876543210L对于int太大,从而导致一些信息丢失:
long a = 9876543210L;
int b = (int) a; // 现在b的值为1286608618
导致信息丢失的缩窄转换会在程序中引入一些缺陷。
计算机程序是一组操作的集合,这些操作共同实现某种功能。操作有很多种,包括加法、减法、乘法、除法和移位。本节将学习Java的各种运算符(见表2-4)。
运算符(operator)可以对一个、两个或3个操作数执行操作。操作数是操作的对象,运算符是表示操作的符号,例如,下面是一个加法运算:
x + 4
在本例中,x和4是操作数,加号(+)是运算符。
运算符可能返回结果,也可能不返回结果。
注意 运算符和操作数的任何合法组合称为表达式(expression),例如x + 4是一个表达式。布尔表达式的结果是true或false;整数表达式产生一个整数;浮点表达式的结果是一个浮点数。
只需要一个操作数的运算符称为一元运算符。在Java中,有许多一元运算符。Java运算符中最常见的是二元运算符,它需要两个操作数。还有三元运算符,即“?: ”运算符,它需要3个操作数。
表2-4 Java运算符
= |
> |
< |
! |
~ |
? : |
instanceof |
||||
== |
<= |
>= |
!= |
&& |
|| |
++ |
-- |
|||
+ |
- |
* |
/ |
& |
| |
^ |
% |
<< |
>> |
>>> |
+= |
-= |
*= |
/= |
&= |
|= |
^= |
%= |
<<= |
>>= |
>>>= |
在Java中,有几类运算符,即一元运算符、算术运算符、关系运算符、条件运算符、移位和逻辑运算符、赋值运算符以及其他运算符。
下面将详细介绍这些运算符。
一元运算符操作一个操作数,有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),之后再递增。
注意 递增运算符最常应用于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
此运算符只能应用于boolean基本类型或java.lang.Boolean实例。如果操作数为false,则该运算符的结果为true;如果操作数为true,则结果false。示例如下:
boolean x = false;
boolean y = !x;
// 此时,y为true,x为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种类型:加法、减法、乘法、除法及取模。下面我们逐一讨论每个算术运算符。
加法运算符将两个操作数相加。操作数的类型必须可转换为数值基本类型。示例如下:
byte x = 3;
int y = x + 5; // y = 8
一定要确保接收加法结果的变量具有足够大的容量。例如,在下面的代码中,k的值是−294967296,而不是40亿:
int j = 2000000000; // 20亿
int k = j + j; // 结果超出范围,这是一个Bug!!!
然而,以下代码可以正常工作:
long j = 2000000000; // 20亿
long k = j + j; // k的值是40亿
该运算符将两个操作数相减。操作数的类型必须可转换为数值基本类型。示例如下:
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
注意,在运行时,如果除数为零,则除法操作将引发错误。
使用/运算符进行整数除法运算的结果总是整数,如果被除数不能被除数整除,余数部分将被忽略。示例如下:
int x = 4;
int y = x / 3; // y = 1
java.lang.Math类(将在后面讨论)可以执行更加复杂的除法运算。
取模运算符在两个操作数之间执行除法运算并返回余数。左边的操作数是被除数,右边的操作数是除数。被除数和除数都必须可转换为数值基本类型,例如,下面运算的结果是2:
8 % 3
有两个相等运算符:==(等于)和!=(不等于),它们都在两个操作数上执行运算,这两个操作数可以是整数、浮点数、字符或布尔值。相等运算符的结果是一个布尔值。例如,下面的代码c的值为true:
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个操作符。instanceof的相关内容参见7.8节。
<、>、<=和>=运算符在两个操作数上进行运算,这两个操作数的类型必须可转换为数值基本类型。关系运算返回一个boolean值。
<运算符计算左操作数的值是否小于右操作数的值,例如,下面操作的结果返回false:
9 < 6
>运算符计算左操作数的值是否大于右操作数的值,例如,下面操作的结果返回true:
9 > 6
<=运算符计算左操作数的值是否小于或等于右操作数的值,例如,下面操作的结果返回false:
9 <= 6
>=运算符计算左操作数的值是否大于或等于右操作数的值,例如,下面操作的结果返回true:
9 >= 9
条件运算符有 3 个:与运算符(&&)、或运算符(||)以及(?:)运算符。下面将详细介绍这3个运算符。
该运算符接收两个表达式并把它们作为操作数,这两个表达式都必须返回一个可转换为boolean的值。如果两个操作数的值都为true,则&&返回true;否则,返回false。如果左操作数的计算结果为false,则不再计算右操作数。例如,下面的表达式将返回false:
(5 < 3) && (6 < 9)
该运算符接收两个表达式并把它们作为操作数,这两个表达式都必须返回一个可转换为boolean的值。如果其中一个操作数的值为true,则||返回true;如果左操作数的值为true,则不再计算右操作数。例如,下面的表达式将返回true:
(5 < 3) || (6 < 9)
该运算符可以操作3个操作数,语法如下:
expression1 ? expression2 : expression3
其中,expression1必须返回一个可转换为boolean的值。如果expression1的计算结果为true,则返回expression2的值;否则,返回expression3的值。例如,下面的表达式将返回4:
(8 < 4) ? 2 : 4
移位运算符接收两个操作数,操作数的类型必须可转换为整数基本类型。左边的操作数表示要移位的值,右边的操作数表示移位的距离。移位运算符有3种类型:左移运算符(<<)、右移运算符(>>)和无符号右移运算符(>>>)。
左移运算符将一个数字按位向左移动,用0填充右边移入的位。n << s的值是n的二进制位向左移s位,这个数值等于n乘以2的s次方。
例如,将一个值为1的int数左移3位(1<<3)。结果是8。同样,要算出这个值,需要将操作数转换为二进制数。
0000 0000 0000 0000 0000 0000 0000 0001
将1向左移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除以2的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也会产生相同的结果。
位运算符&、|和^对两个操作数执行按位操作,这两个操作数的类型必须可转换为int型。&表示与运算,|表示或运算,^表示异或运算。示例如下:
0xFFFF & 0x0000 = 0x0000
0xF0F0 & 0xFFFF = 0xF0F0
0xFFFF | 0x000F = 0xFFFF
0xFFF0 ^ 0x00FF = 0xFF0F
逻辑运算符&、|和^对两个操作数执行逻辑操作,这两个操作数的类型必须可转换为boolean型。&表示逻辑与运算,|表示逻辑或运算,^表示逻辑异或运算。示例如下:
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;
这段代码执行后d的值是多少呢?如果读者说是9,就错了,实际上它的答案是7。
由于乘法运算符*优先于加法运算符+,因此乘法在加法之前执行。但是,如果希望先执行加法,可以使用括号。
int d = (a + b) * c;
赋值运算符右边的整体运算结果为9,将把9赋给d。
表2-5列出了所有运算符的优先顺序。同一栏中的运算符具有相同的优先级。
表2-5 运算符的优先级
运算符 |
优先级(由高到低) |
---|---|
后缀操作符 |
|
一元运算符 |
|
创建或转换 |
|
乘、除、求余 |
|
加减法 |
|
移位 |
|
关系 |
|
相等 |
|
按位与 |
|
按位异或 |
|
按位或 |
|
逻辑与 |
|
逻辑或 |
|
条件 |
|
赋值 |
|
注意,圆括号的优先级最高。圆括号可以使表达式更清晰,例如:
int x = 5;
int y = 5;
boolean z = x * 5 == y + 20;
比较后z的值为true,但这种表达方式还远远不够清晰。
可以利用圆括号重写最后一行:
boolean z = (x * 5) == (y + 20);
结果保持不变,因为*和+的优先级比==高,但是这样就使表达式清晰多了。
有些一元运算符(如+、−和~)和二元运算符(如+、−、*和/)会造成类型自动提升,即提升到更宽的类型,如从byte提升到int。考虑以下代码:
byte x = 5;
byte y = -x; // 出错
即使一个字节可以容纳−5,令人意外的是,第二行也会出现错误,原因是一元运算符−导致−x的结果被提升为int类型。要避免这个问题,要么将y的类型改为int,要么像下面这样执行显式的缩窄转换。
byte x = 5;
byte y = (byte)–x;
对于一元运算符,如果操作数的类型是byte、short或char,则结果将提升为int。
对于二元运算符,提升规则如下。
(1)如果任何一个操作数的类型都为byte或short,那么这两个操作数都将转换为int,结果将为int。
(2)如果任何操作数的类型为double,则另一个操作数转换为double,结果将为double。
(3)如果任何一个操作数的类型为float,则另一个操作数转换为float,结果将为float。
(4)如果任何一个操作数的类型为long,则另一个操作数转换为long,结果将为long。
例如,以下代码会导致编译错误:
short x = 200;
short y = 400;
short z = x + y;
可以通过将z更改为int或显式执行x + y的缩窄转换来解决这个问题,例如:
short z = (short) (x + y);
注意,x + y的圆括号是必需的,否则只有x将被转换成short,一个short加上一个int的结果还是int。
在代码中,添加注释是一种很好的实践,它可以充分解释类有什么功能、方法做什么、字段包含什么等。
Java中有如下两种注释类型,它们的语法都类似于C和C++中的注释。
例如,下面是一个描述方法的注释:
/*
toUpperCase将String对象中的字符转换成大写
*/
public void toUpperCase(String s) {
下面是行尾注释:
public int rowCount; // 数据库的行数
传统的注释不能嵌套,下面的注释是非法的:
/*
/* 注释 1 */
注释 2 */
因为第一个*/表示注释结束。因此,上面的注释多出了“注释2 */”的部分,这将产生编译错误。
另外,行尾注释可以包含任何内容,包括/*和*/的字符序列,像下面这样:
// /*这个注释是合法的*/
本章介绍了Java语言的基本原理、基本概念以及在学习更高级的主题之前应该掌握的内容。讨论的主题包括字符集、变量、基本类型、字面值、运算符、运算符优先级和注释。
第3章将讨论语句,这是Java语言的另一个重要主题。
1.ASCII表示什么?
2.Java使用的是ASCII字符还是Unicode字符?
3.什么是引用类型变量?什么是基本类型变量?
4.常量是如何在Java中实现的?
5.什么是表达式?
6.若需要将英镑符号赋给char变量,但是键盘上没有£键,如果知道它的Unicode码是00A3,如何赋值呢?
7.列出Java中至少10个运算符。
8.什么是Java中的三元运算符?
9.什么是运算符优先级?
10.考虑以下代码,result1和result2的值是多少?为什么会有这样的差异呢?
int result1 = 1 + 2 * 3;
int result2 = (1 + 2) * 3;
11.说出两种类型的Java注释。