77集程序底层执行
初识Java
1.1计算机语言发展史以及未来
计算机语言经历了三代:第一代是机器语言,第二代是汇编语言,第三代是高级语言。
·第一代语言:机器语言(相当于人类的原始阶段)
机器语言通常由数字串组成(最终被简化成01),对于人类来说,机器语言过于繁琐。使用机器语言,人类无法编出复杂的程序。如下为一段典型的机器码:
1. 0000,0000,000000010000 代表 LOAD A, 16
2. 0000,0001,000000000001 代表 LOAD B, 1
3. 0001,0001,000000010000 代表 STORE B, 16
·第二代语言:汇编语言(相当于人类的手工业阶段)
为了编程的方便,以及解决更加复杂的问题。程序员开始改进机器语言,使用英文缩写的助记符来表示基本的计算机操作。这些助记符构成了汇编语言的基础。如下是一些常见的汇编语言助记符(单词)比如:LOAD、MOVE之类,这样人更容易使用了。识别几百、几千个单词,感觉要比几百几千个数字,美妙多了。汇编语言相当于人类的手工业社会,需要技术极其娴熟的工匠,但是开发效率也非常低。
汇编语言虽然能编写高效率的程序,但是学习和使用都不是易事,并且很难调试。另一个复杂的问题,汇编语言以及早期的计算机语言(Basic、Fortran等)没有考虑结构化设计原则,而是使用goto语句来作为程序流程控制的主要方法。这样做的后果是:一大堆混乱的调转语句使得程序几乎不可能被读懂。对于那个时代的程序员,能读懂上个月自己写的代码都成为一种挑战。
汇编语言仍然应用于工业电子编程领域、软件的加密解密、计算机病毒分析等。
·第三代:高级语言(相当于人类的工业阶段)
对于简单的任务,汇编语言可以胜任。但是随着计算机的发展,渗透到了工作生活的更多的方面,一些复杂的任务出现了,汇编语言就显得力不从心(应该说是程序员使用汇编语言解决复杂问题出现了瓶颈)。于是,出现了高级语言。像我们熟知的C、C++、Java等等都是高级语言。
高级语言允许程序员使用接近日常英语的指令来编写程序。例如,实现一个简单的任务:A+B=C , 使用机器语言、汇编语言和高级语言的的实现如下图1-2所示.
从上面这个简单的加法计算,可以看出越到高级语言,越接近人的思维,人使用起来就越方便。
高级语言的出现,尤其是面向对象语言的出现,相当于人类的工业社会,高级语言极其易用,编程门槛和难度大大降低,大量的人员进入软件开发行业,为软件爆发性的增长提供了充足的人力资源。目前以及可预见的将来,计算机语言仍然处于“第三代高级语言”阶段。
·未来30年必将是软件人才的世界
未来30年的世界必将是软件人才的世界;除了普通软件的应用,大批的人工智能应用也将出现;未来自动驾驶、自动翻译、机器人保姆甚至机器人女友都会进入我们的生活,甚至计算机编程和基因工程结合,长生不老都能实现;有兴趣的童鞋,推荐大家看《未来简史》这本书,把握一下未来的脉搏。
1.2常见编程语言介绍
1.C语言
C语言诞生于1972年,可以称之为现代高级语言的鼻祖,由著名的贝尔实验室发明。C语言是人们追求结构化、模块化、高效率的“语言之花”。在底层编程,比如嵌入式、病毒开发等应用,可以替代汇编语言来开发系统程序。在高层应用,也可以开发从操作系统(Unix/Linux/Windows都基于C语言开发)到各种应用软件。
2.C++语言
作为C语言的扩展,C++是贝尔实验室于80年代推出的。C++是一种混合语言,既可以实现面向对象编程,也可以开发C语言面向过程风格的程序。
C语言让程序员第一次可以通过结构化的理念编写出易于理解的复杂程序; 尽管C语言是一个伟大的语言,但是程序的代码量达到30000行以上时,程序员就不能很好的从总体上把握和控制这个程序。 因此,在80年代初期,很多软件项目都面临无法解决的问题而不能顺利进行。 1979年,贝尔实验室发明了C++。 C++最初的名字叫做“带类的C”,后来才改名叫C++。国内通用叫法:“C加加”,国际通用读法“C plus plus”。
C++语言在科学计算、操作系统、网络通讯、系统开发、引擎开发中仍然被大量使用。
3.Java语言
由美国SUN公司发明于1995年,是目前业界应用最广泛、使用人数最多的语言,连续多年排名世界第一,可以称之为“计算机语言界的英语”。
Java广泛应用于企业级软件开发、安卓移动开发、大数据云计算等领域,几乎涉及IT所有行业。关于Java的发展历史和特性,将在后面专门介绍。
4.PHP语言
PHP原始为Personal Home Page的缩写,已经正式更名为 “PHP: Hypertext Preprocessor”。PHP语言,一般用于WEB开发领域;大量的中小型网站以及某些大型网站使用PHP开发。
5.Object-C和Swift语言
Object-C通常写作Objective-C或者Obj-C或OC,是根据C语言所衍生出来的语言,继承了C语言的特性,是扩充C的面向对象编程语言。OC主要用于苹果软件的开发。
Swift,苹果于2014年WWDC(苹果开发者大会)发布的新开发语言,可与OC共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序。
6.JavaScript语言
JavaScript是一种脚本语言,已经被广泛用于Web应用开发;应用范围越来越大,重要性越来越高。目前,流行的H5开发的核心其实就是JavaScript语言。
7.Python语言
Python发明于1989年,语法结构简单,易学易懂;Python具有丰富和强大的库。它常被昵称为胶水语言,能够把用其他语言制作的各种模块(尤其是C/C++)很轻松地联结在一起。Python广泛应用于:图形处理、科学计算、web编程、多媒体应用、引擎开发;尤其是在未来大热方向机器学习和人工智能上有非常大的潜力。
8.C#语言
C#是微软公司发布的一种面向对象的、运行于.NET Framework之上的高级程序设计语言。C#在基于windows操作系统的应用开发这一领域在取代C++,占据主导地位。“成也萧何败也萧何”,C#的微软身份,也成为了发展的阻力,在其他IT领域应用较少。
9.Fortran语言
世界上第一种高级语言, IBM公司在1954年提出的,主要用在需要复杂数学计算的科学和工程领域。现在仍然被广泛使用,尤其是工程领域。Fortran虽然适合编写科学计算方面的程序,但是不适于编写系统程序。
10.Basic语言
虽然易学,但功能不够强大,应用到大程序的有效性令人怀疑。已经逐步退出历史舞台。
11.COBOL语言
于1959年提出。主要用于大量精确处理数据的商业领域中,比如金融、银行。今天,仍然有超过一半的商业软件使用COBOL编写。有将近100万人使用COBOL编程。
12.Pascal语言
Pascal的名称是为了纪念十七世纪法国著名哲学家和数学家Blaise Pascal而来的,它由瑞士Niklaus Wirth教授于六十年代末设计并创立的。Pascal语言语法严谨,层次分明,程序易写,可读性强,是第一个结构化编程语言。由于没有大厂商和政府的支持,只是限于大学教育这一块。
1.3.1Java发展简史
1991年,James Gosling在SUN公司的工程师小组想要设计这样一种小型计算机语言。该语言主要用于像电视盒这样的消费类电子产品。另外,由于不同的厂商选择不同的CPU和操作系统,因此,要求该语言不能和特定的体系结构绑在一起,要求语言本身是中立的,也就是跨平台的。所以,将这个语言命名为“Green”,类似于绿色软件的意思。后来,改名为Oak,橡树的意思。改名后发现已经有一种语言叫这个名字了,再改名叫Java。Java语言发展到今天经历了一系列的过程:
1991年,SUN公司的Green项目,Oak
1995年,推出Java测试版
1996年,JDK1.0
1997年,JDK1.1
1998年,JDK1.2,大大改进了早期版本缺陷,是一个革命性的版本,更名为Java2。
2004年,J2SE 5.0 (1.5.0) Tiger老虎
2006年,J2SE 6.0 (1.6.0) Mustang野马
2011年,JavaSE7.0 Dolphin海豚
2014年,JavaSE8.0
17年会出9.0
11.0
与互联网的发展息息相关。2010年以后移动互联网。20年之后物联网。
1.3.2 Java核心优势
Java为消费类智能电子产品而设计,但智能家电产品并没有像最初想象的那样拥有大的发展。然而90年代,Internet却进入了爆发式发展阶段,一夜之间,大家都在忙着将自己的计算机连接到网络上。这个时侯,遇到了一个大的问题。人们发现连接到Internet的计算机各式各样,有IBM PC、苹果机、各种服务器等等,不仅硬件CPU不同,操作系统也不同,整个的网络环境非常复杂。这个时候,程序员们希望他们编写的程序能够运行在不同的机器,不同的环境中,这需要一种体系中立的语言(即跨平台)。Java的研发小组忽然发现他们用于小范围的语言也可以适应Internet这个大环境。
跨平台是Java语言的核心优势,赶上最初互联网的发展,并随着互联网的发展而发展,建立了强大的生态体系,目前已经覆盖IT各行业的“第一大语言”,是计算机界的“英语”。
虽然,目前也有很多跨平台的语言,但是已经失去先机,无法和Java强大的生态体系抗衡。Java仍将在未来几十年成为编程语言的主流语言。
JAVA虚拟机是JAVA实现跨平台的核心。事实上,基于JAVA虚拟机(JVM)的编程语言还有很多种:
企业/安卓/大数据/云计算
基于JAVA生态建立的产品将会越来越多;基于JAVA虚拟机的编程语言也将会越来越多;生态系统的强大,是JAVA能长盛不衰的根本。
1.3.3 Java各版本的含义
JavaSE(Java Standard Edition):标准版,定位在个人计算机上的应用。这个版本是Java平台的核心,它提供了非常丰富的API来开发一般个人计算机上的应用程序,包括用户界面接口AWT及Swing,网络功能与国际化、图像处理能力以及输入输出支持等。在上世纪90年代末互联网上大放异彩的Applet也属于这个版本。Applet后来为Flash取代,Flash即将被HTML5取代。
JavaEE(Java Enterprise Edition):企业版,定位在服务器端的应用。
JavaEE是JavaSE的扩展,增加了用于服务器开发的类库。如:
JDBC是让程序员能直接在Java内使用的SQL的语法来访问数据库内的数据;
Servlet能够延伸服务器的功能,通过请求-响应的模式来处理客户端的请求;
JSP是一种可以将Java程序代码内嵌在网页内的技术;
JavaME(Java Micro Edition):微型版,定位在消费性电子产品的应用上
JavaME是JavaSE的内伸,包含J2SE的一部分核心类,也有自己的扩展类,增加了适合微小装置的类库:javax.microedition.io.*等。该版本针对资源有限的电子消费产品的需求精简核心类库,并提供了模块化的架构让不同类型产品能够随时增加支持的能力。
1.3.4 Java的特性和优势
·跨平台/可移植性
这是Java的核心优势。Java在设计时就很注重移植和跨平台性。比如:Java的int永远都是32位。不像C++可能是16,32,可能是根据编译器厂商规定的变化。这样的话程序的移植就会非常麻烦。
·安全性
Java适合于网络/分布式环境,为了达到这个目标,在安全性方面投入了很大的精力,使Java可以很容易构建防病毒,防篡改的系统。
·面向对象
面向对象是一种程序设计技术,非常适合大型软件的设计和开发。由于C++为了照顾大量C语言使用者而兼容了C,使得自身仅仅成为了带类的C语言,多少影响了其面向对象的彻底性!Java则是完全的面向对象语言。
·简单性
Java就是C++语法的简化版,我们也可以将Java称之为“C+±”。跟我念“C加加减”,指的就是将C++的一些内容去掉;比如:头文件,指针运算,结构,联合,操作符重载,虚基类等等。同时,由于语法基于C语言,因此学习起来完全不费力。
·高性能
Java最初发展阶段,总是被人诟病“性能低”;客观上,高级语言运行效率总是低于低级语言的,这个无法避免。Java语言本身发展中通过虚拟机的优化提升了几十倍运行效率。比如,通过JIT(JUST IN TIME)即时编译技术提高运行效率。 将一些“热点”字节码编译成本地机器码,并将结果缓存起来,在需要的时候重新调用。这样的话,使Java程序的执行效率大大提高,某些代码甚至接待C++的效率。
因此,Java低性能的短腿,已经被完全解决了。业界发展上,我们也看到很多C++应用转到Java开发,很多C++程序员转型为Java程序员。
·分布式
Java是为Internet的分布式环境设计的,因为它能够处理TCP/IP协议。事实上,通过URL访问一个网络资源和访问本地文件是一样简单的。Java还支持远程方法调用(RMI,Remote Method Invocation),使程序能够通过网络调用方法。
·多线程
多线程的使用可以带来更好的交互响应和实时行为。 Java多线程的简单性是Java成为主流服务器端开发语言的主要原因之一。
·健壮性
Java是一种健壮的语言,吸收了C/C++ 语言的优点,但去掉了其影响程序健壮性的部分(如:指针、内存的申请与释放等)。Java程序不可能造成计算机崩溃。即使Java程序也可能有错误。如果出现某种出乎意料之事,程序也不会崩溃,而是把该异常抛出,再通过异常处理机制加以处理。
1.3.5 Java应用程序的运行机制
计算机高级语言的类型主要有编译型和解释型两种,而Java 语言是两种类型的结合。
Java首先利用文本编辑器编写 Java源程序,源文件的后缀名为.java;
再利用编译器(javac)将源程序编译成字节码文件,字节码文件的后缀名为.class;
最后利用虚拟机(解释器,java)解释执行。
程序不和操作系统直接打交道,而是通过和JRE交互。
1.3.6 JVM、JRE和JDK
JVM(Java Virtual Machine)就是一个虚拟的用于执行bytecode字节码的”虚拟计算机”。他也定义了指令集、寄存器集、结构栈、垃圾收集堆、内存区域。JVM负责将Java字节码解释运行,边解释边运行,这样,速度就会受到一定的影响。
.class文件就是喂给JVM解释执行,JVM与操作系统打交道。针对每一个操作系统,有不同版本的虚拟机。
不同的操作系统有不同的虚拟机。Java 虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,随处运行”。 Java虚拟机是实现跨平台的核心机制。
Java Runtime Environment (JRE) 包含:Java虚拟机、库函数、运行Java应用程序所必须的文件。
Java Development Kit (JDK)包含:包含JRE,以及增加编译器和调试器等用于程序开发的文件。
JDK、JRE和JVM的关系如图
·如果只是要运行Java程序,只需要JRE就可以。JRE通常非常小,其中包含了JVM。
·如果要开发Java程序,就需要安装JDK。
1.4.1 JDK下载和安装
·下载JDK
- 下载地址,点击进入:
www.oracle.com/technetwork/java/javase/downloads/index.html
点击下载JDK,会出现下载列表界面。首先,点击”Accept License Agreement”,然后选择对应的版本,下载即可。
注:如果您无法确定您的windows版本,可以右键点击“计算机属性”,即可查看系统信息:
1.32位操作系统只能安装32位JDK;
2.64位操作系统可安装32位JDK,也可以安装64位JDK。
·安装JDK
安装过程和普通软件安装没什么区别,过程中会让你选择JDK和JRE安装目录,采用默认即可。也就是说,通通点击“下一步”就能完成安装。
JDK安装成功。
我们再进入JDK的安装目录C:\Program Files\Java\jdk1.8.0_131,可以看到如下的结构:
其中:
bin目录是存储一些可执行的二进制文件;
lib存储相关jar包;
src.zip是JDK相关JAVA类的源代码。
1.4.2 环境变量Path的配置
环境变量是在操作系统中一个具有特定名字的对象, 它包含了一个或者多个应用程序所将使用到的信息。
Path是一个常见的环境变量,它告诉操作系统,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下寻找此程序外,还应到哪些目录下寻找。
·设置Path环境变量的步骤如下
- 右键计算机——>属性——>高级系统设置。
- 单击环境变量按钮,进入环境变量操作主界面:
- 单击新建按钮,新建JAVA_HOME变量(用于说明JDK的安装目录),填入jdk路径。
4.修改系统环境变量Path,在最前面追加%JAVA_HOME%\bin 并以;和原路径分隔。再增加:“.;”。这是表示当前目录的意思。
1.此处一定是英文分号而不是中文分号! 大家以后设置相关配置时也要注意中英文符号的区别。
注:classpath配置问题:如果使用JDK1.5以上就不需要配置这个环境变量!JRE会自动搜索当前路径下的类文件及相关jar文件。
1.4.3 测试JDK安装成功
进入命令行窗口,开始菜单搜索框输入“cmd”即可。在窗口中输入命令“java -version ”,回车。出现如下结果,则说明JDK安装成功。
1.5.1 开发第一个Java程序
1.使用记事本,编写代码
【示例1-1】使用记事本开发第一个Java程序
public class Welcome{
public static void main(String[] args){
System.out.println("Hello Java!我是尚学堂学员,程许愿");
}
}
可在d盘下建立文件夹mycode,用于保存学习的代码。保存路径建议为:”d:/mycode”。保存为:Welcome.java (文件名必须为Welcome,大小写也必须一致)。
注意:
1.代码中的引号、分号必须为英文引号和分号,不能是中文全角的引号和分号
2.注意大小写
2.编译(编译器创建class字节码文件)
打开命令行窗口,进入Java文件所在目录;执行命令:javac Welcome.java,生成class文件。
上图编译Java源文件
3.解释并运行阶段
执行:”java Welcome”(就是运行编译生成的Welcome.class文件),输出执行结果。
上图解释并运行程序
注意:修改完java源文件,一定要重新编译才能生效
1.5.2 第一个程序常见错误
·常见问题-1:提示“javac不是内部或外部命令……”
出错原因:Path变量配置错误导致没有找到javac命令
解决方案:参考1.4.2 <环境变量Path的配置>,细心重新设置Path,配置完成后需要重新输入cmd打开命令行窗口。
·常见问题-2:文件夹中可以看到Welcome.java文件,在编译时出现’javac 找不到文件……’
出错原因:可能是操作系统显示设置的问题,隐藏了文件的扩展名。
解决方案:打开我的电脑窗口,选择【组织】【文件夹和搜索选项】【查看】,如图1-24所示,去掉“隐藏已知文件夹类型的扩展名”复选框中的对勾,看到文件的实际名称为Welcome.java.txt,将后缀后.txt去掉。
·常见问题-3:NoSuchMethodError:main
出错原因:找不到main方法,应该是main方法书写有误。
解决方案:检查public static void main(String [] args){}是否有误。
1.5.3 第一个Java程序的总结和提升
1.Java对大小写敏感,如果出现了大小写拼写错误,程序无法运行。
2.关键字public被称作访问修饰符(access modifier),用于控制程序的其它部分对这段代码的访问级别。
3.关键字class 的意思是类。Java是面向对象的语言,所有代码必须位于类里面。
4.一个源文件中至多只能声明一个public的类,其它类的个数不限,如果源文件中包含一个public 类,源文件名必须和其中定义的public的类名相同,且以“.java”为扩展名。
5.一个源文件可以包含多个类class。
6.正确编译后的源文件,会得到相应的字节码文件,编译器为每个类生成独立的字节码文件,且将字节码文件自动命名为类的名字且以“.class”为扩展名。
7.main方法是Java应用程序的入口方法,它有固定的书写格式:
8.public static void main(String[] args) {…}
9.在Java中,用花括号划分程序的各个部分,任何方法的代码都必须以“{”开始,以“}”结束, 由于编译器忽略空格,所以花括号风格不受限制。
10.Java中每个语句必须以分号结束,回车不是语句的结束标志,所以一个语句可以跨多行。
·编程时,一定要注意缩进规范;
·在写括号、引号时,一定是成对编写,然后再往里插入内容。
1.5.4 最常用DOS命令
1.6 常用Java开发工具
入门学习时,进行简单的Java程序编写,我们可以使用文本编辑器,比如:记事本。但是,记事本功能不够强大,我们可以考虑使用“更加强大的记事本”,常见的有如下三种软件:
1.Notepad++
2.UltraEdit
3.EditPlus
在我们真正学习开发中,包括以后在企业中从事软件开发时,一般使用集成开发环境(IDE), 如下三种软件是最常见的,尤其是eclipse。 我们只选一个作为入门使用,其他两种大家自行举一反三,使用很简单。
eclipse官方网址:http://www.eclipse.org
IntelliJ IDE官方网址: http://www.jetbrains.com/idea/
NetBeans 官方网址:https://netbeans.org/
1.7.1 下载和安装eclipse
下载地址:http://www.eclipse.org/downloads/eclipse-packages/
选择Eclipse IDE for Java Developers,根据自己安装的JDK, 决定是下载32位还是64位。下载完成后,直接解压。进入目录,双击eclipse.exe即可使用。
启动时会先出现设置工作空间界面,如图1-26所示。工作空间指Java项目存储的目录,一般采用默认工作空间目录即可。
关闭欢迎界面,即可进入开发主界面。
1.7.2 在eclipse中创建Java项目
在界面左侧的“Package Explorer”视图中右键单击,在快捷菜单中依次选择new——> Java Project,开始新建Java项目。输入项目名称MyPro01,点击Finish 完成即可。新建项目完成,其中的src文件夹用于存放源代码。显示JDK版本是JavaSE-1.8。
1.7.3 使用eclipse开发和运行Java程序
·使用eclipse开发第一个程序
我们在上一节建好的Java项目中,开始开发Java程序。首先,新建一个Java类。在src目录上右键单击,建立一个Java类。出现新建类的开始界面。我们只需输入类名即可,其他不需做任何设置:
点击Finish后,新建Java类成功。
在src下面出现了Welcome.java文件。点击该文件后,出现该文件的代码编辑,我们开发Eclipse下的第一个Java程序。
public class Welcome {
public static void main(String[] args) {
System.out.println(“我是尚学堂的高淇!”);
}
}
运行该程序: 在代码上单击右键后,点击Run as Java application。也可以使用快捷键“ctrl+F11”,直接实现运行。或者直接点击工具栏的中运行按钮。
界面下方的控制台(console)出现运行结果。
至此,我们成功在eclipse中开发了我们的第一个Java程序!
·eclipse自动编译
eclipse会自动执行javac进行编译,并且会将编译错误直接给出提示,一目了然,非常便于我们调试。
·Java项目的src目录和bin目录
src用于存放源代码,bin用于存放生成的class文件。
在eclipse视图里我们只看到了src目录,eclipse隐藏了bin目录。进入我的电脑,打开Java项目目录,我们可以看到src和bin目录:
数据类型和运算符
2.1 注释
为了方便程序的阅读,Java语言允许程序员在程序中写上一些说明性的文字,用来提高程序的可读性,这些文字性的说明就称为注释。 注释不会出现在字节码文件(.class)中,即Java编译器编译时会跳过注释语句。 在Java中根据注释的功能不同,主要分为单行注释、多行注释和文档注释。
单行注释: 使用“//”开头,“//”后面的单行内容均为注释。
多行注释: 以“/”开头以“/”结尾,在“/”和“/”之间的内容为注释,我们也可以使用多行注释作为行内注释。但是在使用时要注意,多行注释不能嵌套使用。
文档注释: 以“/**”开头以“*/”结尾,注释中包含一些说明性的文字及一些JavaDoc标签(后期写项目时,可以生成项目的API)
/**
* Welcome类(我是文档注释)
* @author 高淇
* @version 1.0
*/
public class Welcome {
//我是单行注释
public static void main(String[] args/*我是行内注释 */) {
System.out.println("Hello World!");
}
/*
我是多行注释!
我是多行注释!
*/
}
2.2 标识符
标识符是用来给变量、类、方法以及包进行命名的,如Welcome、main、System、age、name、gender等。标识符需要遵守一定的规则:
1标识符必须以字母、下划线_、美元符号
开
头
。
2
标
识
符
其
它
部
分
可
以
是
字
母
、
下
划
线
“
”
、
美
元
符
“
开头。 2标识符其它部分可以是字母、下划线“_”、美元符“
开头。2标识符其它部分可以是字母、下划线“”、美元符“”和数字的任意组合。
3Java 标识符大小写敏感,且长度无限制。
4标识符不可以是Java的关键字。
标识符的使用规范:
1表示类名的标识符:每个单词的首字母大写,如Man, GoodMan
2表示方法和变量的标识符:第一个单词小写,从第二个单词开始首字母大写,我们称之为“驼峰原则”,如eat(), eatFood()
【注意】:Java不采用通常语言使用的ASCII字符集,而是采用Unicode这样标准的国际字符集。因此,这里字母的含义不仅仅是英文,还包括汉字等等。但是不建议大家使用汉字来定义标识符!
合法的标识符:
int a = 3;
int _123 = 3;
int $12aa = 3;
int 变量1 = 55; //合法,但是不建议使用中文命名的标识符
不合法的标识符:
int 1a = 3; //不能用数字开头
int a# = 3; //不能包含#这样的特殊字符
int int = 3; //不能使用关键字
/**
* 测试标识符的用法
* @author 高淇
*
*/
public class TestIdentifer {
//能力是练出来的,不是看书看出来的。对于初学者来说,再简单的代码也一定要敲一下!
public static void main(String[] args) {
int a123 = 1;
//int 123abc = 2; //数字不能开头
int $a = 3;
int _abc = 4;
//int #abc = 5;
int 年龄 = 18; //可以使用汉字,但是一般不建议
//int class = 2; //关键字不能作为标识符
}
}
2.3 Java中的关键字/保留字
Java关键字是Java语言保留供内部使用的,如class用于定义类。 关键字也可以称为保留字,它们的意思是一样的,我们不能使用关键字作为变量名或方法名。
2.4.1 变量的本质
变量本质上就是代表一个”可操作的存储空间”,空间位置是确定的,但是里面放置什么值不确定。我们可通过变量名来访问“对应的存储空间”,从而操纵这个“存储空间”存储的值。
Java是一种强类型语言,每个变量都必须声明其数据类型。变量的数据类型决定了变量占据存储空间的大小。 比如,int a=3; 表示a变量的空间大小为4个字节。
变量作为程序中最基本的存储单元,其要素包括变量名,变量类型和作用域。变量在使用前必须对其声明, 只有在变量声明以后,才能为其分配相应长度的存储空间。
变量的声明
格式为:
type varName [=value][,varName[=value]...];
//[]中的内容为可选项,即可有可无
数据类型 变量名 [=初始值] [,变量名 [=初始值]…];
声明变量:
double salary;
long earthPopulation;
int age;
不同数据类型的常量会在内存中分配不同的空间:
注意事项:
1每个变量都有类型,类型可以是基本类型,也可以是引用类型。
2变量名必须是合法的标识符
3变量声明是一条完整的语句,因此每一个声明都必须以分号结束
在一行中声明多个变量:
int i ,j; // 两个变量的数据类型都是int
不提倡这种"一行声明多个变量"风格,逐一声明每一个变量可以提高程序可读性。
可以将变量的声明和初始化放在同一行中:
int age = 18;
double e = 2.718281828;
2.4.2 变量的分类
从整体上可将变量划分为局部变量、成员变量(也称为实例变量)和静态变量。
成员变量和静态变量不是目前重点,不要过多纠结理解与否。我们学习面向对象时,再重点讲解成员变量和静态变量
局部变量(local variable)
方法或语句块内部定义的变量。生命周期是从声明位置开始到到方法或语句块执行完毕为止。局部变量在使用前必须先声明、初始化(赋初值)再使用。
【示例2-7】局部变量
public void test() {
int i;
int j = i+5 ; // 编译出错,变量i还未被初始化
}
public void test() {
int i;
i=10;
int j = i+5 ; // 编译正确
}
成员变量(也叫实例变量 member variable)
方法外部、类的内部定义的变量。从属于对象,生命周期伴随对象始终。如果不自行初始化,它会自动初始化成该类型的默认初始值。
【示例2-8】实例变量的声明
public class Test {
int i;
}
静态变量(类变量 static variable)
使用static定义。 从属于类,生命周期伴随类始终,从类加载到卸载。 (注:讲完内存分析后我们再深入!先放一放这个概念!)如果不自行初始化,与成员变量相同会自动初始化成该类型的默认初始值,如表 2-3所示。
课堂练习1:变量的声明并赋值
public class LocalVariableTest {
public static void main(String[ ] args) {
boolean flag = true; // 声明boolean型变量并赋值
char c1, c2; // 声明char型变量
c1 = '\u0041'; // 为char型变量赋值
c2 = 'B'; // 为char型变量赋值
int x; // 声明int型变量
x = 9; //为int型变量赋值
int y = x; // 声明并初始化int型变量
float f = 3.15f; // 声明float型变量并赋值
double d = 3.1415926; //声明double型变量并赋值
}
}
课堂代码:
/**
* 测试变量
*
* @author 高淇
*
*/
public class TestVariable {
int a; //成员变量, 从属于对象; 成员变量会自动被初始化
static int size; //静态变量,**从属于类**
public static void main(String[] args) {
{
int age; //局部变量,**从属于语句块;**
age = 18;
}
int salary = 3000; //局部变量,**从属于方法**
int gao = 13;
System.out.println(gao);
int i;
// int j = i + 5; // 编译出错,变量i还未被初始化。局部变量必须先声明并且初始化,才能使用
}
}
2.5 常量(Constant)
常量通常指的是一个固定的值,例如:1、2、3、’a’、’b’、true、false、”helloWorld”等。
在Java语言中,主要是利用关键字final来定义一个常量。 常量一旦被初始化后不能再更改其值。
声明格式为:
final type varName = value;
【示例2-9】常量的声明及使用
public class TestConstants {
public static void main(String[] args) {
final double PI = 3.14;
// PI = 3.15; //编译错误,不能再被赋值!
double r = 4;
double area = PI * r * r;
double circle = 2 * PI * r;
System.out.println("area = " + area);
System.out.println("circle = " + circle);
}
}
为了更好的区分和表述,一般将1、2、3、’a’、’b’、true、false、”helloWorld”等称为字面常量,而使用final修饰的PI等称为符号常量。
注意:
1变量和常量命名规范(规范是程序员的基本准则,不规范会直接损害你的个人形象):
2所有变量、方法、类名:见名知意
3类成员变量:首字母小写和驼峰原则: monthSalary
4局部变量:首字母小写和驼峰原则
5常量:大写字母和下划线:MAX_VALUE
6类名:首字母大写和驼峰原则: Man, GoodMan
7方法名:首字母小写和驼峰原则: run(), runRun()
2.6 基本数据类型(primitive data type)
Java是一种强类型语言,每个变量都必须声明其数据类型。 Java的数据类型可分为两大类:基本数据类型(primitive data type)和引用数据类型(reference data type)。
Java中定义了3类8种基本数据类型
1数值型- byte、 short、int、 long、float、 double 1、2、4、8、4、8个字节
2字符型- char 2个字节
3布尔型-boolean 1位
引用数据类型是引用对象,代表了对象地址。占4个字节。
注意事项:
1引用数据类型的大小统一为4个字节,记录的是其引用对象的地址!
2本章只讲解基本数据类型。引用数据类型在后续数组和面向对象章节讲解。
2.6.1 整型变量/常量
整型用于表示没有小数部分的数值,它允许是负数。整型的范围与运行Java代码的机器无关,这正是Java程序具有很强移植能力的原因之一。与此相反,C和C++程序需要针对不同的处理器选择最有效的整型。
1个字节是8位,因为可以表示负数,所以拿出一位来表示符号。为什么还要减一,因为中间有0。
Java 语言整型常量的四种表示形式
1十进制整数,如:99, -500, 0
2八进制整数,要求以 0 开头,如:015
3十六进制数,要求 0x 或 0X 开头,如:0x15
4二进制数,要求0b或0B开头,如:0b01110011
Java语言的整型常数默认为int型,声明long型常量可以后加‘ l ’或‘ L ’ 。
【示例2-10】长整型常数的声明
long a = 55555555; //编译成功,在int表示的范围内(21亿内)。
long b = 55555555555;//不加L编译错误,已经超过int表示的范围。
我们修改成long类型的常量即可:
long b = 55555555555L;
2.6.2 浮点型变量/常量
带小数的数据在Java中称为浮点型。浮点型可分为float类型和double类型。
float类型又被称作单精度类型,尾数可以精确到7位有效数字,在很多情况下,float类型的精度很难满足需求。
而double表示这种类型的数值精度约是float类型的两倍,又被称作双精度类型,绝大部分应用程序都采用double类型。浮点型常量默认类型也是double。
Java浮点类型常量有两种表示形式:
1十进制数形式,例如:3.14 314.0 0.314
2科学记数法形式,如314e2 314E2 314E-2
e2就是10的2次方 e-2就是10的-2次方
【示例2-11】使用科学记数法给浮点型变量赋值
double f = 314e2; //314*10^2-->31400.0
double f2 = 314e-2; //314*10^(-2)-->3.14
float类型的数值有一个后缀F或者f ,没有后缀F/f的浮点数值默认为double类型。也可以在浮点数值后添加后缀D或者d, 以明确其为double类型。
若这样写float a=3.14;则会报错。因为3.14是浮点数,默认为double型的为8个字节,所以这样的话你直接到float里面放,只有4个字节就放不进去,所以会报错。所以需要加个才能让它不报错。
【示例2-12】float类型赋值时需要添加后缀F/f
float f = 3.14F;
double d1 = 3.14;
double d2 = 3.14D;
注意:
浮点类型float,double的数据不适合在不容许舍入误差的金融计算领域。如果需要进行不产生舍入误差的精确数字计算,需要使用BigDecimal类。
【示例2-13】浮点数的比较一
float f = 0.1f;
double d = 1.0/10;也是0.1
System.out.println(f==d);//结果为false,
【示例2-14】浮点数的比较二
float d1 = 423432423f;//4亿多
float d2 = d1+1;
if(d1==d2){
System.out.println("d1==d2");//输出结果为d1==d2,明明不相等。所以浮点数不精确。
}else{
System.out.println("d1!=d2");
}
运行以上两个示例,发现示例2-13的结果是“false”,而示例2-14的输出结果是“d1==d2”。这是因为由于字长有限,浮点数能够精确表示的数是有限的,因而也是离散的。 浮点数一般都存在舍入误差,很多数字无法精确表示(例如0.1),其结果只能是接近, 但不等于。二进制浮点数不能精确的表示0.1、0.01、0.001这样10的负次幂。并不是所有的小数都能可以精确的用二进制浮点数表示。
java.math包下面的两个有用的类:BigInteger和BigDecimal,这两个类可以处理任意长度的数值。BigInteger实现了任意精度的整数运算。BigDecimal实现了任意精度的浮点运算。
注意:
不要使用浮点数进行比较!很多新人甚至很多理论不扎实的有工作经验的程序员也会犯这个错误!需要比较请使用BigDecimal类
【示例2-15】使用BigDecimal进行浮点数的比较
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal bd = BigDecimal.valueOf(1.0);
bd = bd.subtract(BigDecimal.valueOf(0.1));//减0.1
bd = bd.subtract(BigDecimal.valueOf(0.1));//减0.1
bd = bd.subtract(BigDecimal.valueOf(0.1));//减0.1
bd = bd.subtract(BigDecimal.valueOf(0.1));//减0.1
bd = bd.subtract(BigDecimal.valueOf(0.1));//减0.1
System.out.println(bd);//0.5
System.out.println(1.0 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1);//0.5000000000000001
//使用精确浮点数计算
BigDecimal bd2=BigDecimal.valueOf(1.0);
BigDecimal bd3=BigDecimal.valueOf(1.0/10.0);
System.out.println(bd2.equals(bd3));//true
}
}
浮点数使用总结
1默认是double类型
2浮点数存在舍入误差,数字不能精确表示。如果需要进行不产生舍入误差的精确数字计算,需要使用BigDecimal类。
3避免比较中使用浮点数,需要比较请使用BigDecimal类
2.6.3 字符型变量/常量
字符型在内存中占2个字节,在Java中使用单引号来表示字符常量。例如’A’是一个字符,它与”A”是不同的,”A”表示含有一个字符的字符串。
char 类型用来表示在Unicode编码表中的字符。Unicode编码被设计用来处理各种语言的文字,它占2个字节,可允许有65536个字符。
【示例2-16】字符型举例
char eChar = 'a';
char cChar ='中';
Unicode具有从0到65535之间的编码,他们通常用从’\u0000’到’\uFFFF’之间的十六进制值来表示(前缀为u表示Unicode)
\u0061表示的是a
【示例2-17】字符型的十六进制值表示方法
char c = '\u0061';
Java 语言中还允许使用转义字符 ‘\’ 来将其后的字符转变为其它的含义。常用的转义字符及其含义和Unicode值如表2-6所示。
【示例2-18】转义字符
char c2 = '\n'; //代表换行符
注意事项:
以后我们学的String类,其实是字符序列(char sequence)。
课堂代码
/**
* 测试字符类型
* @author 高淇
*
*/
public class TestPrimitiveDataType3 {
public static void main(String[] args) {
char a = 'T';
char b = '尚';
char c = '\u0061';
System.out.println(c);
//转义字符
System.out.println('a'+'b');//195 遇到加号,char类型自动转换成数字类型
System.out.println(""+'a'+'b');//ab 为了让加号变成连接符,我们要在前面加一个空字符串
System.out.println(""+'a'+'\n'+'b'); a换行b
System.out.println(""+'a'+'\t'+'b'); a制表符b
System.out.println(""+'a'+'\''+'b'); //a'b
//String就是字符序列
String d = "abc";
}
}
2.6.4 boolean类型变量/常量
boolean类型有两个常量值,true和false,在内存中占一位(不是一个字节),不可以使用 0 或非 0 的整数替代 true 和 false ,这点和C语言不同。 boolean 类型用来判断逻辑条件,一般用于程序流程控制 。
【示例2-19】boolean类型
boolean flag ;
flag = true; //或者flag=false;
if(flag) {
// true分支
} else {
// false分支
}
注意:
Less is More!!请不要这样写:if ( flag == true ),只有新手才那么写。关键也很容易写错成if(flag=true),这样就变成赋值flag 为true而不是判断!老鸟的写法是if ( flag )或者if ( !flag)
2.7 运算符(operator)
计算机的最基本用途之一就是执行数学运算,作为一门计算机语言,Java也提供了一套丰富的运算符来操作变量。
2.7.1 算术运算符
算术运算符中+,-,*,/,%属于二元运算符,二元运算符指的是需要两个操作数才能完成运算的运算符。其中的%是取模运算符,就是我们常说的求余数操作。
二元运算符的运算规则:
整数运算:
1. 如果两个操作数有一个为Long, 则结果也为long。
2. 没有long时,结果为int。即使操作数全为short,byte,结果也是int。
报错,因为结果为int。
报错,因为结果为long。
浮点运算:
3. 如果两个操作数有一个为double,则结果为double。
4. 只有两个操作数都是float,则结果才为float。
对的
对的
只要有一个数为double,结果就是double。
取模运算:
1.其操作数可以为浮点数,一般使用整数,结果是“余数”,“余数”符号和左边操作数相同,如:7%3=1,-7%3=-1,7%-3=1。
算术运算符中++,–属于一元运算符,该类运算符只需要一个操作数。
【示例2-20】一元运算符++与–
int a = 3;
int b = a++; //执行完后,b=3。先给b赋值,再自增。
System.out.println("a="+a+"\nb="+b);//a=4 b=3
a = 3;
b = ++a; //执行完后,c=5。a先自增,再给c赋值
System.out.println("a="+a+"\nb="+b);//a=4 b=4
2.7.2 赋值及其扩展赋值运算符
示例2-21】扩展运算符
int a=3;
int b=4;
a+=b;//相当于a=a+b;
System.out.println("a="+a+"\nb="+b);
a=3;
a*=b+3;//相当于a=a*(b+3)
System.out.println("a="+a+"\nb="+b);
运行该程序,执行结果
2.7.3 关系运算符
关系运算符用来进行比较运算,如表所示。关系运算的结果是布尔值:true/false;
注意事项
1=是赋值运算符,而真正的判断两个操作数是否相等的运算符是==
2==、!= 是所有(基本和引用)数据类型都可以使用
3> 、>=、 <、 <= 仅针对数值类型(byte/short/int/long, float/double。以及char可以自动转化为数字)
2.7.4 逻辑运算符
Java中的逻辑运算符如表所示。逻辑运算的操作数和运算结果都是boolean值。
运算符 说明
逻辑与 &( 与) 两个操作数为true,结果才是true,否则是false
逻辑或 |(或) 两个操作数有一个是true,结果就是true
短路与 &&( 与) 只要有一个为false,则直接返回false
短路或 ||(或) 只要有一个为true, 则直接返回true
逻辑非 !(非) 取反:!false为true,!true为false
逻辑异或 ^(异或) 相同为false,不同为true
短路与和短路或采用短路的方式。从左到右计算,如果只通过运算符左边的操作数就能够确定该逻辑表达式的值,则不会继续计算运算符右边的操作数,提高效率。
【示例2-22】短路与和逻辑与
//1>2的结果为false,那么整个表达式的结果即为false,将不再计算2>(3/0)
boolean c = 1>2 && 2>(3/0);
System.out.println(c);
//1>2的结果为false,那么整个表达式的结果即为false,还要计算2>(3/0),0不能做除数,//会输出异常信息
boolean d = 1>2 & 2>(3/0);
System.out.println(d);
2.7.5 位运算符
位运算指的是进行二进制位的运算,常用的位运算符如表
【示例2-23】左移运算和右移运算
int a = 3*2*2;
int b = 3<<2; //相当于:3*2*2;
int c = 12/2/2;
int d = 12>>2; //相当于12/2/2;
雷区
- &和|既是逻辑运算符,也是位运算符。如果两侧操作数都是boolean类型,就作为逻辑运算符。如果两侧的操作数是整数类型,就是位运算符。
- 不要把“^”当做数学运算“乘方”,是“位的异或”操作。
2.7.6 字符串连接符
“+”运算符两侧的操作数中只要有一个是字符串(String)类型,系统会自动将另一个操作数转换为字符串然后再进行连接。
如果是字符,会用ascii码
【示例2-24】连接符“+”
int a=12;
System.out.println("a="+a);//输出结果: a=12
2.7.7 条件运算符
语法格式:x ? y : z
其中 x 为 boolean 类型表达式,先计算 x 的值,若为true,则整个运算的结果为表达式 y 的值,否则整个运算结果为表达式 z 的值。
【示例2-25】三目条件运算符
int score = 80;
int x = -100;
String type =score<60?"不及格":"及格";
int flag = x > 0 ? 1 : (x == 0 ? 0 : -1);
System.out.println("type= " + type);
System.out.println("flag= "+ flag);
2.7.8 运算符优先级的问题
建议:
1大家不需要去刻意的记这些优先级,表达式里面优先使用小括号来组织!!
2逻辑与、逻辑或、逻辑非的优先级一定要熟悉!(逻辑非>逻辑与>逻辑或)。如:
a||b&&c的运算结果是:a||(b&&c),而不是(a||b)&&c
2.8.1 自动类型转换
自动类型转换指的是容量小的数据类型可以自动转换为容量大的数据类型。如图所示,黑色的实线表示无数据丢失的自动类型转换,而虚线表示在转换时可能会有精度的损失。
并不是说字节容量大小,而是容量大小。
可以将整型常量直接赋值给byte、 short、 char等类型变量,而不需要进行强制类型转换,只要不超出其表数范围即可。
【示例2-26】自动类型转换特例
short b = 12; //合法
short b = 1234567;//非法,1234567超出了short的表数范围
2.8.2 强制类型转换
强制类型转换,又被称为造型,用于显式的转换一个数值的类型。在有可能丢失信息的情况下进行的转换是通过造型来完成的,但可能造成精度降低或溢出。
语法格式:(type)var
运算符“()”中的type表示将值var想要转换成的目标数据类型。
【示例2-27】强制类型转换
double x = 3.14;
int nx = (int)x; //值为3,只留整数部分
char c = 'a';
int d = c+1;
System.out.println(nx);
System.out.println(d);
System.out.println((char)d);//字符类型
当将一种类型强制转换成另一种类型,而又超出了目标类型的表数范围,就会被截断成为一个完全不同的值。
【示例2-28】强制类型转换特例
int x = 300;
byte bx = (byte)x; //值为44
新手雷区:不能在布尔类型和任何数值类型之间做强制类型转换
2.8.3 基本类型转化时常见错误和问题
1.操作比较大的数时,要留意是否溢出,尤其是整数操作时。
可以把其中一个数事先转化为long类型。
【示例2-29】常见问题一
int money = 1000000000; //10亿
int years = 20;
//返回的total是负数,超过了int的范围
int total = money*years;
System.out.println("total="+total);
//返回的total仍然是负数。默认是int,因此结果会转成int值,再转成long。但是已经发生//了数据丢失
long total1 = money*years; //计算的时候就溢出了,所以之后再赋给一个大的也没用了
System.out.println("total1="+total1);
//返回的total2正确:先将一个因子变成long,整个表达式发生提升。全部用long来计算。
long total2 = money*((long)years);
System.out.println("total2="+total2);
2.L和l 的问题:
不要命名名字为l的变量,l容易和1混淆。long类型使用大写L不要用小写。
【示例2-30】常见问题二
int l = 2; //分不清是L还是1,
long a = 23451l;//建议使用大写L
System.out.println(l+1);
2.9 简单的键盘输入和输出
为了我们能写出更加复杂的程序,可以让我们的程序和用户可以通过键盘交互,我们先学习一下简单的键盘输入和输出。
【示例2-31】使用Scanner获取键盘输入
import java.util.Scanner;
/**
* 测试获得键盘输入
* @author 高淇
*
*/
public class TestScanner {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);//创建Scanner对象
System.out.println("请输入名字:");
String name = scanner.nextLine();
System.out.println("请输入你的爱好:");
String favor = scanner.nextLine();
System.out.println("请输入你的年龄:");
int age = scanner.nextInt();
System.out.println("###############");
System.out.println(name);
System.out.println(favor);
System.out.println("来到地球的天数:"+age*365);
System.out.println("离开地球的天数:"+(72-age)*365);
}
}
第二章 总结
1.注释可以提高程序的可读性。可划分为
2.单行注释 //
3.多行注释 /…/
4.文档注释 /**…/
5.标识符的命名规则:
6.标识符必须以字母、下划线_、美元符号
开
头
。
7.
标
识
符
其
它
部
分
可
以
是
字
母
、
下
划
线
“
”
、
美
元
符
“
开头。 7.标识符其它部分可以是字母、下划线“_”、美元符“
开头。7.标识符其它部分可以是字母、下划线“”、美元符“”和数字的任意组合。
8.Java 标识符大小写敏感,且长度无限制。
9.标识符不可以是Java的关键字。
10.标识符的命名规范
11.表示类名的标识符:每个单词的首字母大写,如Man, GoodMan
12.表示方法和变量的标识符:第一个单词小写,从第二个单词开始首字母大写,我们称之为“驼峰原则”,如eat(), eatFood()
13.变量的声明格式:
type varName [=value] [,varName[=value]…];
14.变量的分类:局部变量、实例变量、静态变量
15.常量的声名格式
final type varName = value ;
16.Java的数据类型可分为基本数据类型和引用数据类,基本数据类型的分类如下:
17.整型变量:byte、short、int、long
18.浮点型:float、double
19.字符型:char
20.布尔型:boolean,值为true或者false
21.Java语言支持的运算符可分为:
22.算术运算符: +,-,,/,%,++,–
23.赋值运算符 =
24.扩展赋值运算符:+=,-=,*=,/=
25.关系运算符: >,<,>=,<=,==,!= ,instanceof
26.逻辑运算符: &&,||,!
27.位运算符: &,|,^,~ , >>,<<,>>>
28.字符串连接符:+
29.条件运算符 ?:
30.基本数据类型的类型转换可分为:
31.自动类型转换:容量小的数据类型可以自动转换为容量大的数据类型
32.强制类型转换:用于显式的转换一个数值的类型,语法格式:(type)var
33.键盘的输入:Scanner类的使用
第三章 控制语句
本章开始我们需要学习流程控制语句,流程控制语句是用来控制程序中各语句执行顺序的语句,可以把语句组合成能完成一定功能的小逻辑模块。控制语句分为三类:顺序、选择和循环。
“顺序结构”代表“先执行a,再执行b”的逻辑。比如,先找个女朋友,再给女朋友打电话;先订婚,再结婚;
“选择结构”代表“如果…,则…”的逻辑。比如,如果女朋友来电,则迅速接电话;如果看到红灯,则停车;
“循环结构”代表“如果…,则再继续…”的逻辑。比如,如果没打通女朋友电话,则再继续打一次; 如果没找到喜欢的人,则再继续找。
前面两章讲解的程序都是顺序结构,即按照书写顺序执行每一条语句,这并不是我们的重点,因此本章研究的重点是“选择结构”和“循环结构”。
很神奇的是,三种流程控制语句就能表示所有的事情!不信,你可以试试拆分你遇到的各种事情。实际上,任何软件和程序,小到一个练习,大到一个操作系统,本质上都是由“变量、选择语句、循环语句”组成。
这三种基本逻辑结构是相互支撑的,它们共同构成了算法的基本结构,无论怎样复杂的逻辑结构,都可以通过它们来表达。上述两种结构组成的程序可以解决全部的问题,所以任何一种高级语言都具备上述两种结构。
因此,本章是大家真正跨入编程界的“门槛”,是成为“程序猿”的“门票”。 本章后面会附加大量的练习,供大家提升自我。
3.1 选择结构
在还没有知道Java选择结构的时候,我们编写的程序总是从程序入口开始,顺序执行每一条语句直到执行完最后一条语句结束,但是生活中经常需要进行条件判断,根据判断结果决定是否做一件事情,这就需要选择结构。
选择结构用于判断给定的条件,然后根据判断的结果来控制程序的流程。
主要的选择结构有:if选择结构和switch多选择结构。有如下结构:
1if单选择结构
2if-else双选择结构
3if-else if-else多选择结构
4switch结构
3.1.1 if单选择结构
语法结构:
if(布尔表达式){
语句块
}
if语句对布尔表达式进行一次判定,若判定为真,则执行{}中的语句块,否则跳过该语句块。
【示例3-1】if单选择结构
public class Test1 {
public static void main(String[] args) {
//通过掷三个骰子看看今天的手气如何?
int i = (int)(6 * Math.random()) + 1;//通过Math.random()产生随机数
int j = (int)(6 * Math.random()) + 1;
int k = (int)(6 * Math.random()) + 1;
int count = i + j + k;
//如果三个骰子之和大于15,则手气不错
if(count > 15) {
System.out.println("今天手气不错");
}
//如果三个骰子之和在10到15之间,则手气一般
if(count >= 10 && count <= 15) { //错误写法:10<=count<=15
System.out.println("今天手气很一般");
}
//如果三个骰子之和小于10,则手气不怎么样
if(count < 10) {
System.out.println("今天手气不怎么样");
}
System.out.println("得了" + count + "分");
}
}
Math类的使用
1.java.lang包中的Math类提供了一些用于数学计算的方法。
2.Math.random()该方法用于产生一个0到1区间的double类型的随机数,但是不包括1。
int i = (int) (6 * Math.random()); //产生:[0,5]之间的随机整数
新手雷区
1.如果if语句不写{},则只能作用于后面的第一条语句。
2.强烈建议,任何时候都写上{},即使里面只有一句话!
3.1.2 if-else双选择结构
语法结构:
if(布尔表达式){
语句块1
}else{
语句块2
}
当布尔表达式为真时,执行语句块1,否则,执行语句块2。也就是else部分。
【示例3-2】if-else结构
public class Test2 {
public static void main(String[] args) {
//随机产生一个[0.0, 4.0)区间的半径,并根据半径求圆的面积和周长
double r = 4 * Math.random();
//Math.pow(r, 2)求半径r的平方
double area = Math.PI * Math.pow(r, 2);
double circle = 2 * Math.PI * r;
System.out.println("半径为: " + r);
System.out.println("面积为: " + area);
System.out.println("周长为: " + circle);
//如果面积>=周长,则输出"面积大于等于周长",否则,输出周长大于面积
if(area >= circle) {
System.out.println("面积大于等于周长");
} else {
System.out.println("周长大于面积");
}
}
}
条件运算符有时候可用于代替if-else,如示例3-3与示例3-4所示。
【示例3-3】使用if-else
public class Test3 {
public static void main(String[] args) {
int a=2;
int b=3;
if (a<b) {
System.out.println(a);
} else {
System.out.println(b);
}
}
}
【示例3-4】使用条件运算符
public class Test4 {
public static void main(String[] args) {
int a=2;
int b=3;
System.out.println((a<b)?a:b);
}
}
3.1.3 if-else if-else多选择结
语法结构:
if(布尔表达式1) {
语句块1;
} else if(布尔表达式2) {
语句块2;
}……
else if(布尔表达式n){
语句块n;
} else {
语句块n+1;
}
当布尔表达式1为真时,执行语句块1;否则,判断布尔表达式2,当布尔表达式2为真时,执行语句块2;否则,继续判断布尔表达式3······;如果1~n个布尔表达式均判定为假时,则执行语句块n+1,也就是else部分。流程图如图
【示例3-5】if-else if-else多选择结构
public class Test5 {
public static void main(String[] args) {
int age = (int) (100 * Math.random());
System.out.print("年龄是" + age + ", 属于");
if (age < 15) {
System.out.println("儿童, 喜欢玩!");
} else if (age < 25) {
System.out.println("青年, 要学习!");
} else if (age < 45) {
System.out.println("中年, 要工作!");
} else if (age < 65) {
System.out.println("中老年, 要补钙!");
} else if (age < 85) {
System.out.println("老年, 多运动!");
} else {
System.out.println("老寿星, 古来稀!");
}
}
}
3.1.4 switch多选择结构
语法结构:
switch (表达式) {
case 值1:
语句序列1;
[break];
case 值2:
语句序列2;
[break];
… … … … …
[default:
默认语句;]
}
switch语句会根据表达式的值从相匹配的case标签处开始执行,一直执行到break语句处或者是switch语句的末尾。如果表达式的值与任一case值不匹配,则进入default语句(如果存在default语句的情况)。
根据表达式值的不同可以执行许多不同的操作。switch语句中case标签在JDK1.5之前必须是整数(long类型除外)或者枚举,不能是字符串,在JDK1.7之后允许使用字符串(String)。
大家要注意,当布尔表达式是等值判断的情况,可以使用if-else if-else多选择结构或者switch结构,如果布尔表达式区间判断的情况,则只能使用if-else if-else多选择结构。
switch多选择结构的流程图如图
【示例3-6】switch结构
public class Test6 {
public static void main(String[] args) {
char c = 'a';
int rand = (int) (26 * Math.random());
char c2 = (char) (c + rand);
System.out.print(c2 + ": ");
switch (c2) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
System.out.println("元音");
break;
case 'y':
case 'w':
System.out.println("半元音");
break;
default:
System.out.println("辅音");
}
}
}
3.2 循环结构
循环结构分两大类,一类是当型,一类是直到型。
当型:当布尔表达式条件为true时,反复执行某语句,当布尔表达式的值为false时才停止循环,比如:while与for循环。
直到型:先执行某语句, 再判断布尔表达式,如果为true,再执行某语句,如此反复,直到布尔表达式条件为false时才停止循环,比如do-while循环。
3.2.1 while循环
语法结构:
while (布尔表达式) {
循环体;
}
在循环刚开始时,会计算一次“布尔表达式”的值,若条件为真,执行循环体。而对于后来每一次额外的循环,都会在开始前重新计算一次。
语句中应有使循环趋向于结束的语句,否则会出现无限循环–––"死"循环。
while循环结构流程图如图
【示例3-7】while循环结构:求1到100之间的累加和
public class Test7 {
public static void main(String[] args) {
int i = 0;
int sum = 0;
// 1+2+3+…+100=?
while (i <= 100) {
sum += i;//相当于sum = sum+i;
i++;
}
System.out.println("Sum= " + sum);
}
}
3.2.2 do-while循环
语法结构:
do {
循环体;
} while(布尔表达式) ;
do-while循环结构会先执行循环体,然后再判断布尔表达式的值,若条件为真,执行循环体,当条件为假时结束循环。do-while循环的循环体至少执行一次。do-while循环结构流程图如图
【示例3-8】do-while循环结构:1-100之间的累加和
public class Test8 {
public static void main(String[] args) {
int i = 0;
int sum = 0;
do {
sum += i; // sum = sum + i
i++;
} while (i <= 100);//此处的;不能省略
System.out.println("Sum= " + sum);
}
}
【示例3-9】while与do-while的区别
public class Test9 {
public static void main(String[] args) {
//while循环:先判断再执行
int a = 0;
while (a < 0) {
System.out.println(a);
a++;
}
System.out.println("-----");
//do-while循环:先执行再判断
a = 0;
do {
System.out.println(a);
a++;
} while (a < 0);
}
}
从运行效图中可以看出do-while总是保证循环体至少会被执行一次!
3.2.3 for循环
语法结构:
for (初始表达式; 布尔表达式; 迭代因子) {
循环体;
}
for循环语句是支持迭代的一种通用结构,是最有效、最灵活的循环结构。for循环在第一次反复之前要进行初始化,即执行初始表达式;随后,对布尔表达式进行判定,若判定结果为true,则执行循环体,否则,终止循环;最后在每一次反复的时候,进行某种形式的“步进”,即执行迭代因子。
A. 初始化部分设置循环变量的初值
B. 条件判断部分为任意布尔表达式
C. 迭代因子控制循环变量的增减
for循环在执行条件判定后,先执行的循环体部分,再执行步进。
for循环结构的流程图如图
【示例3-10】for循环
public class Test10 {
public static void main(String args[]) {
int sum = 0;
//1.求1-100之间的累加和
for (int i = 0; i <= 100; i++) {
sum += i;
}
System.out.println("Sum= " + sum);
//2.循环输出9-1之间的数
for(int i=9;i>0;i--){
System.out.print(i+"、");
}
System.out.println();
//3.输出90-1之间能被3整除的数
for(int i=90;i>0;i-=3){
System.out.print(i+"、");
}
System.out.println();
}
}
Java里能用到逗号运算符的地方屈指可数,其中一处就是for循环的控制表达式。在控制表达式的初始化和步进控制部分,我们可以使用一系列由逗号分隔的表达式,而且那些表达式均会独立执行。
【示例3-11】逗号运算符
public class Test11 {
public static void main(String[] args) {
for(int i = 1, j = i + 10; i < 5; i++, j = i * 2) {
System.out.println("i= " + i + " j= " + j);
}
}
}
-
无论在初始化还是在步进部分,语句都是顺序执行的。
-
尽管初始化部分可设置任意数量的定义,但都属于同一类型。
-
约定:只在for语句的控制表达式中写入与循环变量初始化,条件判断和迭代因子相关的表达式。
初始化部分、条件判断部分和迭代因子可以为空语句,但必须以“;”分开,如示例3-12所示。
【示例3-12】无限循环public class Test12 {
public static void main(String[] args) {
for ( ; ; ) { // 无限循环: 相当于 while(true)
System.out.println(“北京尚学堂”);
}
}
}
编译器将while(true)与for(;;)看作同一回事,都指的是无限循环。
在for语句的初始化部分声明的变量,其作用域为整个for循环体,不能在循环外部使用该变量。如示例3-13所示。
【示例3-13】初始化变量的作用域
3.2.4 嵌套循环
在一个循环语句内部再嵌套一个或多个循环,称为嵌套循环。while、do-while与for循环可以任意嵌套多层。
【示例3-14】嵌套循环
public class Test14 {
public static void main(String args[]) {
for (int i=1; i <=5; i++) {
for(int j=1; j<=5; j++){
System.out.print(i+" ");
}
System.out.println();//每次外层循环一次换行
}
}
}
【示例3-15】使用嵌套循环实现九九乘法表
public class Test15 {
public static void main(String args[]) {
for (int i = 1; i < 10; i++) { // i是一个乘数
for (int j = 1; j <= i; j++) { // j是另一个乘数
System.out.print(j + "*" + i + "=" + (i * j < 10 ? (" " + i * j) : i * j) + " ");
}
System.out.println();
}
}
}
3.2.5 break语句和continue语句
在任何循环语句的主体部分,均可用break控制循环的流程。break用于强行退出循环,不执行循环中剩余的语句。
【示例3-16】break语句
public class Test16 {
public static void main(String[] args) {
int total = 0;//定义计数器
System.out.println("Begin");
while (true) {
total++;//每循环一次计数器加1
int i = (int) Math.round(100 * Math.random());
//当i等于88时,退出循环
if (i == 88) {
break;
}
}
//输出循环的次数
System.out.println("Game over, used " + total + " times.");
}
}
continue 语句用在循环语句体中,用于终止某次循环过程,即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判定。
注意事项
-
continue用在while,do-while中,continue 语句立刻跳到循环首部,越过了当前循环的其余部分。
-
continue用在for循环中,跳到for循环的迭代因子部分。
【示例3-17】continue语句:把100~150之间不能被3整除的数输出,并且每行输出5个public class Test17 {
public static void main(String[] args) {
int count = 0;//定义计数器
for (int i = 100; i < 150; i++) {
//如果是3的倍数,则跳过本次循环,继续进行下一次循环
if (i % 3 == 0){
continue;
}
//否则(不是3的倍数),输出该数
System.out.print(i + “、”);
count++;//没输出一个数,计数器加1
//根据计数器判断每行是否已经输出了5个数
if (count % 5 == 0) {
System.out.println();
}
}
}
}
3.2.6 带标签的break和continu
goto关键字很早就在程序设计语言中出现。尽管goto仍是Java的一个保留字,但并未在Java语言中得到正式使用;Java没有goto语句。然而,在break和continue这两个关键字的身上,我们仍然能看出一些goto的影子—带标签的break和continue。
“标签”是指后面跟一个冒号的标识符,例如:“label:”。对Java来说唯一用到标签的地方是在循环语句之前。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环,由于break和continue关键字通常只中断当前循环,但若随同标签使用,它们就会中断到存在标签的地方。
在 “goto有害”论中,最有问题的就是标签,而非goto, 随着标签在一个程序里数量的增多,产生错误的机会也越来越多。 但Java标签不会造成这方面的问题,因为它们的活动场所已被限死,不可通过特别的方式到处传递程序的控制权。由此也引出了一个有趣的问题:通过限制语句的能力,反而能使一项语言特性更加有用。
【示例3-18】带标签break和continue:控制嵌套循环跳转(打印101-150之间所有的质数)
public class Test18 {
public static void main(String args[]) {
outer: for (int i = 101; i < 150; i++) {
for (int j = 2; j < i / 2; j++) {
if (i % j == 0){
continue outer;
}
}
System.out.print(i + " ");
}
}
}
3.3 语句块
语句块(有时叫做复合语句),是用花括号扩起的任意数量的简单Java语句。块确定了局部变量的作用域。块中的程序代码,作为一个整体,是要被一起执行的。块可以被嵌套在另一个块中,但是不能在两个嵌套的块内声明同名的变量。语句块可以使用外部的变量,而外部不能使用语句块中定义的变量,因为语句块中定义的变量作用域只限于语句块。
【示例3-19】语句块
public class Test19 {
public static void main(String[] args) {
int n;
int a;
{
int k;
int n; //编译错误:不能重复定义变量n
} //变量k的作用域到此为止
}
}
3.4 方法
方法就是一段用来完成特定功能的代码片段,类似于其它语言的函数。
方法用于定义该类或该类的实例的行为特征和功能实现。 方法是类和对象行为特征的抽象。方法很类似于面向过程中的函数。面向过程中,函数是最基本单位,整个程序由一个个函数调用组成。面向对象中,整个程序的基本单位是类,方法是从属于类和对象的。
方法声明格式:
[修饰符1 修饰符2 …] 返回值类型 方法名(形式参数列表){
Java语句;… … …
}
方法的调用方式:对象名.方法名(实参列表)
方法的详细说明
-
形式参数:在方法声明时用于接收外界传入的数据。
-
实参:调用方法时实际传给方法的数据。
-
返回值:方法在执行完毕后返还给调用它的环境的数据。
-
返回值类型:事先约定的返回值的数据类型,如无返回值,必须显示指定为为void。
【示例3-20】方法的声明及调用public class Test20 {
/ main方法:程序的入口 /
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
//调用求和的方法:将num1与num2的值传给add方法中的n1与n2
// 求完和后将结果返回,用sum接收结果
int sum = add(num1, num2);
System.out.println("sum = " + sum);//输出:sum = 30
//调用打印的方法:该方法没有返回值
print();
}*
/** 求和的方法 /
public static int add(int n1, int n2) {
int sum = n1 + n2;
return sum;//使用return返回计算的结果
}
/* 打印的方法 */
public static void print() {
System.out.println(“北京尚学堂…”);
}
}
return1结束方法运行2返回值
注意事项 -
实参的数目、数据类型和次序必须和所调用的方法声明的形式参数列表匹配。
-
return 语句终止方法的运行并指定要返回的数据。
-
Java中进行方法调用中传递参数时,遵循值传递的原则(传递的都是数据的副本):
-
基本类型传递的是该数据值的copy值。
-
引用类型传递的是该对象引用的copy值,但指向的是同一个对象。
3.5 方法的重载(overload)
方法的重载是指一个类中可以定义多个方法名相同,但参数不同的方法。 调用时,会根据不同的参数自动匹配对应的方法。
雷区
重载的方法,实际是完全不同的方法,只是名称相同而已!
构成方法重载的条件:
1.不同的含义:形参类型、形参个数、形参顺序不同
2.只有返回值不同不构成方法的重载
如:int a(String str){}与 void a(String str){}不构成方法重载
3.只有形参的名称不同,不构成方法的重载
如:int a(String str){}与int a(String s){}不构成方法重载
【示例3-21】方法重载
public class Test21 {
public static void main(String[] args) {
System.out.println(add(3, 5));// 8
System.out.println(add(3, 5, 10));// 18
System.out.println(add(3.0, 5));// 8.0
System.out.println(add(3, 5.0));// 8.0
// 我们已经见过的方法的重载
System.out.println();// 0个参数
System.out.println(1);// 参数是1个int
System.out.println(3.0);// 参数是1个double
}
/** 求和的方法 */
public static int add(int n1, int n2) {
int sum = n1 + n2;
return sum;
}
// 方法名相同,参数个数不同,构成重载
public static int add(int n1, int n2, int n3) {
int sum = n1 + n2 + n3;
return sum;
}
// 方法名相同,参数类型不同,构成重载
public static double add(double n1, int n2) {
double sum = n1 + n2;
return sum;
}
// 方法名相同,参数顺序不同,构成重载
public static double add(int n1, double n2) {
double sum = n1 + n2;
return sum;
}
//编译错误:只有返回值不同,不构成方法的重载
public static double add(int n1, int n2) {
double sum = n1 + n2;
return sum;
}
//编译错误:只有参数名称不同,不构成方法的重载
public static int add(int n2, int n1) {
double sum = n1 + n2;
return sum;
}
}
3.6 递归结构
递归是一种常见的解决问题的方法,即把问题逐渐简单化。递归的基本思想就是“自己调用自己”,一个使用递归技术的方法将会直接或者间接的调用自己。
利用递归可以用简单的程序来解决一些复杂的问题。比如:斐波那契数列的计算、汉诺塔、快排等问题。
递归结构包括两个部分:
1.定义递归头。解答:什么时候不调用自身方法。如果没有头,将陷入死循环,也就是递归的结束条件。
2.递归体。解答:什么时候需要调用自身方法。
【示例3-22】递归:计算n!
public class Test22 {
public static void main(String[] args) {
long d1 = System.currentTimeMillis();
System.out.printf("%d阶乘的结果:%s%n", 10, factorial(10));
long d2 = System.currentTimeMillis();
System.out.printf("递归费时:%s%n", d2-d1); //耗时:32ms
}
/** 求阶乘的方法*/
static long factorial(int n){
if(n==1){//递归头
return 1;
}else{//递归体
return n*factorial(n-1);//n! = n * (n-1)!
}
}
}
递归的缺陷
简单的程序是递归的优点之一。但是递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环慢的多,所以在使用递归时要慎重。
比如上面的递归耗时558ms。但是用普通循环的话快得多,如示例3-23所示。
【示例3-23】使用循环求n!
public class Test23 {
public static void main(String[] args) {
long d3 = System.currentTimeMillis();
int a = 10;
int result = 1;
while (a > 1) {
result *= a * (a - 1);
a -= 2;
}
long d4 = System.currentTimeMillis();
System.out.println(result);
System.out.printf("普通循环费时:%s%n", d4 - d3);
}
}
注意事项
任何能用递归解决的问题也能使用迭代解决。当递归方法可以更加自然地反映问题,并且易于理解和调试,并且不强调效率问题时,可以采用递归;
在要求高性能的情况下尽量避免使用递归,递归调用既花时间又耗内存。