Java 语言是一门强类型语言。强类型包含两方面的含义: ① 所有的变量必须先声明、后使用; ②指定类型的变量只能接受类型与之匹配的值。强类型语言可以在编译过程中发现源代码的错误,从而保证程序更加健壮。Java 语言提供了丰富的基本数据类型,例如整型、字符型、浮点型和布尔型等。基本类型大致上可以分为两类: 数值类型和布尔类型, 其中数值类型包括整型、字符型和浮点型,所有数值类型之间可以进行类型转换,这种类型转换包括自动类型转换和强制类型转换。
Java 语言还提供了一系列功能丰富的运算符, 这些运算符包括所有的算术运算符,以及功能丰富的位运算符、比较运算符、逻辑运算符, 这些运算符是Java 编程的基础。将运算符和操作数连接在一起就形成了表达式。
3.1 注释
编写程序时总需要为程序添加一些注释,用以说明某段代码的作用,或者说明某个类的用途、某个方法的功能,以及该方法的参数和返回值的数据类型及意义等。
程序注释的作用非常大,很多初学者在开始学习Java 语言时,会很努力地写程序,但不大会注意添加注释,他们认为添加注释是一件浪费时间,而且没有意义的事情。经过一段时间的学习,他们写出了一些不错的小程序,如一些游戏、工具软件等。再经过一段时间的学习,他们开始意识到当初写的程序在结构上有很多不足,需要重构。于是打开源代码,他们以为可以很轻松地改写原有的代码,但这时发现理解原来写的代码非常困难,很难理解原有的编程思路。
为什么要添加程序注释?至少有如下三方面的考虑。
- 永远不要过于相信自己的理解力! 当你思路通畅,进入编程境界时,你可以很流畅地实现某个功能,但这种流畅可能是因为你当时正处于这种开发思路中。为了在再次阅读这段代码时,还能找回当初编写这段代码的思路,建议添加注释!
- 可读性第一, 效率第二! 在那些"古老"的岁月里,编程是少数人的专利, 他们随心所欲地写程序,他们以追逐程序执行效率为目的。但随着软件行业的发展, 人们发现仅有少数技术极客编程满足不了日益增长的软件需求,越来越多的人加入了编程队伍, 并引入了工程化的方式来管理软件开发。这个时候, 软件开发变成团队协同作战,团队成员的沟通变得很重要,因此,一个人写的代码,需要被整个团队的其他人所理解: 而且,随着硬件设备的飞速发展, 程序的可读性取代执行效率变成了第一考虑的要素。
- 代码即文档! 很多刚刚学完学校软件工程课程的学生会以为: 文档就是Word 文档! 实际上,程序源代码是程序文档的重要组成部分, 在想着把各种软件相关文档写规范的同时,不要忘了把软件里最重要的文档一一源代码写规范!
程序注释是源代码的一个重要部分,对于一份规范的程序源代码而言, 注释应该占到源代码的1/3以上。几乎所有的编程语言都提供了添加注释的力法。一般的编程语言都提供了基本的单行注释和多行注释, Java 语言也不例外, 除此之外, Java 语言还提供了一种文档注释。Java 语言的注释一共有三种类型。
- 单行注释。
- 多行注释。
- 文档注释。
3.1.1 单行注释和多行注释
单行注释就是在程序中注释一行代码,在Java 语言中,将双斜线(//)放在需要注释的内容之前就可以了;多行注释是指一次性地将程序中多行代码注释掉, 在Java 语言中,使用" /* "和" */ "将程序中需要注释的内容包含起来, "/*"表示注释开始,而" */ "表示注释结束。
下面代码中增加了单行注释和多行注释。
1 public class CommentTest 2 { 3 /* 4 这里面的内容全部是多行注释 5 Java语言真的很有趣 6 */ 7 public static void main(String[] args) 8 { 9 //这是一行简单的注释 10 System.out.println("Hello World!"); 11 //System.out.println("这行代码被注释了") 12 } 13 }
除此之外,添加注释也是调试程序的一个重要方法。如果觉得某段代码可能有问题,可以先把这段代码注释起来,让编译器忽略这段代码,再次编译、运行,如果程序可以正常执行,则可以说明错误就是由这段代码引起的,这样就缩小了错误所在的范围,有利于排错; 如果依然出现相同的错误,则可以说明错误不是由这段代码引起的,同样也缩小了错误所在的范围。
3.1.2 Java 9 增强文档注释
Java 语言还提供了一种功能更强大的注释形式: 文档注释。如果编写Java 源代码时添加了合适的文档注释,然后通过JDK 提供的javadoc 工具可以直接将源代码里的文档注释提取成一份系统的API文档。
Java 提供了大量的基础类,因此Oracle 也为这些基础类提供了相应的API 文档,用于告诉开发者如何使用这些类,以及这些类里包含的方法。
下载Java 9 的API 文档很简单, 登录http ://www.oracle.comltechnetwork/java/javase/downloads/index.html 站点,将页面上的滚动条向下滚动,找到" Additional Resources"部分,看到如图3 .1所示的页面。单击如图3 .1 所示的链接即可下载得到Java SE 9文档,这份文档里包含了JDK 的API 文档。下载成功后得到一个j dk-9_doc-al l. zip 文件。
将jdk-9_ doc-al l. zip 文件解压缩到任意路径,将会得到一个docs 文件夹,这个文件夹下的内容就是JDK文档, JDK 文档不仅包含API 文档,还包含JDK 的其他说明文档。
进入docs/api 路径下,打开index.html 文件,可以看到JDK 9 API 文档首页,单击该页面上方的"FRAMES" 链接,这个首页就是一个典型的Java API文档首页,如图3.2 所示。
从图3.2 所示的首页中可以看出, API 文档页面被分为三个部分,左上角部分是API 文档"包列表区",在该区域内可以查看Java 类的所有包 ; 左下角是API文挡的"类列表区",用于查看Java 的所有类;右边页面是"详细说明区",默认显示的是各包空间的说明信息。
Java 9 对API 文档进行了增强, Java 9 为API 文档增加了一个搜索框,如图3.2 中右上角所示,用户可以通过该搜索框快速查找指定的Java 类。
Java 9 将API 文档分成3 个子集。
- Java SE: 该子集的API 文档主要包含Java SE 的各种类。
- JDK: 该子集的API 文档主要包含JDK 的各种工具类。
- JavaFX: 该子集的API 文档主要包含Java FX 的各种类。
如果单击"类列表区"中列出的某个类,将看到右边页面变成了如图3 .3所示的格局。
由于文档注释是用于生成API 文档的,而API 文档主要用于说明类、方法、成员变量的功能。因此, javadoc 工具只处理文档源文件在类、接口、方法、成员变量、构造器和内部类之前的注释, 忽略其他地方的文档注释。而且javadoc 工具默认只处理以public 或protected 修饰的类、接口、方法、成员变量、构造器和内部类之前的文档注释。
文档注释以斜线后紧跟两个星号(/** )开始, 以星号后紧跟一个斜线( *!)结束,中间部分全部都是文档注释, 会被提取到API 文档中。
Java 9 的API 文档己经支持HTML 5 规范, 因此为了得到完全兼容HTML5 的API 文挡,必须保证文档注释中的内容完全兼容HTML5 规范。
下面先编写一个JavadocTest 类,这个类里包含了对类、方法、成员变量的文档注释。
1 /** 2 * Description: 3 * Copyright @,2018-2019<br> 4 * This program is protected by copyright laws 5 * Date:<br> 6 * @version 1.0 7 */ 8 public class JavadocTest 9 { 10 /** 11 * 简单测试成员变量 12 */ 13 protected String name; 14 15 /** 16 * 主方法,程序的入口 17 */ 18 public static void main(String[] args) 19 { 20 System.out.println("Hello World !"); 21 } 22 }
编写好上面的Java 程序后,就可以使用javadoc工具提取这两个程序中的文档注释来生成API 文档了。javadoc 命令的基本用法如下:
javadoc 选项Java 源文件|包
javadoc 命令可对源文件、包生成API 文档,在上面的语法格式中, Java 源文件可以支持通配符,例如,使用*.j ava 来代表当前路径下所有的Java 源文件。javadoc 的常用选项有如下几个。
- -d <directory> : 该选项指定一个路径,用于将生成的API 文档放到指定目录下。
- -windowtitle <text> : 该选项指定一个宇符串,用于设置API 文档的浏览器窗口标题。
- -doctitle <html-code> : 该选项指定一个HTML 格式的文本,用于指定概述页面的标题。
- -header <html-code> : 该选项指定一个HTML 格式的文本,包含每个页面的页眉。
除此之外, javadoc 命令还包含了大量其他选项,读者可以通过在命令行窗口执行javadoc -help 来查看javadoc 命令的所有选项。
在命令行窗口执行如下命令来为刚刚编写的Java 程序生成API 文档:
javadoc -d apidoc -windowtitle 测试 -doctitle 学习javadoc工具的测试API文档 -header 我的类*Test .java
如果希望javadoc 工具生成更详细的文档信息,例如为方法参数、方法返回值等生成详细的说明信息,则可利用javadoc 标记。常用的javadoc 标记如下。
- @author: 指定Java 程序的作者。
- @version: 指定源文件的版本。
- @deprecated: 不推荐使用的方法。
- @param: 方法的参数说明信息。
- @retum: 方法的返回值说明信息。
- @see: "参见",用于指定交叉参考的内容。
- @exception: 抛出异常的类型。
- @throws: 抛出的异常,和@exception 同义。
需要指出的是,这些标记的使用是有位置限制的。上面这些标记可以出现在类或者接口文档注释中的有@see 、@deprecated 、@author 、@version 等:可以出现在方法或构造器文档注释中的有@see 、@deprecated 、@param 、@return 、@throws 和@exception 等;可以出现在成员变量的文档注释中的有@see 和@deprecated 等。
下面的JavadocTagTest 程序包含了一个hello 方法, 该方法的文档注释使用了@param 和@return 等文档标记。
1 package test; 2 3 /** 4 * Description: 5 * Copyright (C), 2001-2019<br> 6 * This program is protected by copyright laws.<br> 7 * Program Name:<br> 8 * Date:<br> 9 * @author abc@163.com 10 * @version 1.0 11 */ 12 public class JavadocTagTest 13 { 14 /** 15 * 一个得到打招呼字符串的方法。 16 * @param name 该参数指定向谁打招呼。 17 * @return 返回打招呼的字符串。 18 */ 19 public String hello(String name) 20 { 21 return name + ",你好!"; 22 } 23 }
上面程序中粗体宇标识出使用javadoc 标记的示范。再次使用javadoc 工具来生成API 文档, 这次为了能提取到文档中的@author 和@version 等标记信息,在使用javadoc 工具时增加-author 和-version两个选项,即按如下格式来运行javadoc 命令:
javadoc -d apidoc - windowtitle 测试- doctitle 学习javadoc 工具的测试API 文档- header 我的类 -version - author *Test . java
3.2 标识符和关键字
Java 语言也和其他编程语言一样,使用标识符作为变量、对象的名字,也提供了系列的关键字用以实现特别的功能。本节详细介绍Java 语言的标识符和关键字等内容。
3.2.1 分隔符
Java 语言里的分号(;)、花括号({})、方括号([])、圆括号(()) 、空格、圆点( . )都具有特殊的分隔作用,因此被统称为分隔符。
1. 分号
Java 语言里对语句的分隔不是使用回车来完成的, Java 语言采用分号(;) 作为语句的分隔, 因此每个Java i吾句必须使用分号作为结尾。Java 程序允许一行书写多个语句,每个语句之间以分号隔开即可: 一个语句也可以跨多行,只要在最后结束的地方使用分号结束即可。
例如,下面语句都是合法的Java 语句。
int age=25;String name="Peter"; String hello="Hello"+ "你好";
值得指出的是, Java 语句可以跨越多行书写, 但一个字符串、变量名不能跨越多行。例如, 下面的Java 语句是错误的。
1 //字符串内容不能跨行 2 String name="ddddddd 3 assss"; 4 //变量名不能跨行 5 String s 6 ex="男";
不仅如此,虽然Java 语法允许一行书写多个语旬,但从程序可读性角度来看,应该避免在一行书写多个语句。
2. 花括号
花括号的作用就是定义一个代码块, 一个代码块指的就是" {"和"}"所包含的一段代码, 代码块在逻辑上是一个整体。对Java 语言而言, 类定义部分必须放在一个代码块里,方法体部分也必须放在一个代码块里。除此之外,条件语句中的条件执行体和循环语句中的循环体通常也放在代码块里。
花括号一般是成对出现的, 有一个"{"则必然有一个"}飞反之亦然。
3. 方括号
方括号的主要作用是用于访问数组元素, 方括号通常紧跟数组变量名,而方括号里指定希望访问的数组元素的索引。
例如,如下代码:
1 //定义一个数组,设置其中某个元素数值 2 a[3]=5;
4. 圆括号
圆括号是一个功能非常丰富的分隔符: 定义方法时必须使用圆括号来包含所有的形参声明, 调用方法时也必须使用圆括号来传入实参值; 不仅如此,圆括号还可以将表达式中某个部分括成一个整体, 保证这个部分优先计算:除此之外,圆括号还可以作为强制类型转换的运算符。
关于圆括号分隔符在后面还有更进一步的介绍, 此处不再赘述。
5. 空格
Java 语言使用空格分隔一条语句的不同部分。Java 语言是一门格式自由的语言,所以空格几乎可以出现在Java 程序的任何地方,也可以出现任意多个空格,但不要使用空格把一个变量名隔开成两个,这将导致程序出错。
Java 语言中的空格包含空格符(Space) 、制表符(Tab) 和回车C(Enter ) 等。
除此之外, Java 源程序还会使用空格来合理缩进Java 代码,从而提供更好的可读性。
6. 圆点
圆点(.)通常用作类/对象和它的成员(包括成员变量、方法和内部类)之间的分隔符,表明调用某个类或某个实例的指定成员。关于圆点分隔符的用法,后面还会有更进一步的介绍,此处不再赘述。
3.2.2 Java 9 的标识符规则
标识符就是用于给程序中变量、类、方法命名的符号。Java 语言的标识符必须以字母、下画线(_)、美元符( $)开头,后面可以跟任意数目的字母、数字、下画线( )和美元符($) 。此处的宇母并不局限于26 个英文字母,甚至可以包含中文字符、日文字符等。
由于Java 9 支持Unicode 8.0 字符集,因此Java 的标识符可以使用Unicode 8.0 所能表示的多种语言的字符。Java 语言是区分大小写的,因此abc 和Abc 是两个不同的标识符。
Java 9 规定: 不允许使用单独的下画线(_)作为标识符。也就是说,下画线必须与其他字符组合在一起才能作为标识符。
使用标识符时,需要注意如下规则。
- 标识符可以由字母、数字、下画线(_)和美元符($)组成,其中数字不能打头。
- 标识符不能是Java 关键字和保留字,但可以包含关键字和保留字。
- 标识符不能包含空格。
- 标识符只能包含美元符($) ,不能包含@、#等其他特殊字符。
3.2.3 Java 关键字
Java 语言中有一些具有特殊用途的单词被称为关键宇( keyword ) , 当定义标识符时,不要让标识符和关键字相同, 否则将引起错误。例如,下面代码将无法通过编译。
// 试图定义一个名为boolea口的变量,但boolean 是关键字,不能作为标识符 int boolean;
Java 的所有关键宇都是小写的, TRUE 、FALSE 和NULL 都不是Java 关键字。
Java 一共包含50 个关键字,如表3. 1 所示。
上面的50 个关键字中, enum 是从Java 5 新增的关键字,用于定义一个枚举。而goto 和const 这两个关键字也被称为保留宇( reserved word) ,保留字的意思是, Java 现在还未使用这两个关键字,但可能在未来的Java 版本中使用这两个关键宇;不仅如此, Java 还提供了三个特殊的直接量(literal) : true 、false 和null; Java 语言的标识符也不能使用这三个特殊的直接量。
3.3 数据类型分类
Java 语言是强类型( strongly typed ) 语言,强类型包含两方面的含义: ①所有的变量必须先声明、后使用: ②指定类型的变量只能接受类型与之匹配的值。这意味着每个变量和每个表达式都有一个在编译时就确定的类型。类型限制了一个变量能被赋的值, 限制了一个表达式可以产生的值,限制了在这些值上可以进行的操作, 并确定了这些操作的含义。
强类型语言可以在编译时进行更严格的语法检查,从而减少编程错误。
声明变量的语法非常简单,只要指定变量的类型和变量名即可,如下所示:
type varName [=初始值] ;
上面语法中,定义变量时既可指定初始值,也可不指定初始值。随着变量的作用范围的不同 ,变量还可能使用其他修饰符。但不管是哪种变量,定义变量至少需要指定变量类型和变量名两个部分。定义变量时的变量类型可以是Java 语言支持的所有类型。
Java 语言支持的类型分为两类:基本类型(Primitive Type ) 和引用类型( Reference Type ) 。
基本类型包括boolean 类型和数值类型。数值类型有整数类型和浮点类型。整数类型包括byte 、short 、int 、long 、char , 浮点类型包括float 和double 。
引用类型包括类、接口和数组类型,还有一种特殊的null 类型。所谓引用数据类型就是对一个对象的引用,对象包括实例和数组两种。实际上,引用类型变量就是一个指针,只是Java 语言里不再使用指针这个说法。
空类型(null type ) 就是null 值的类型, 这种类型没有名称。因为null 类型没有名称,所以不可能声明一个null 类型的变量或者转换到null 类型。空引用(null )是null 类型变量唯一的值。空引用(null )可以转换为任何引用类型。
在实际开发中, 程序员可以忽略null 类型,假定null 只是引用类型的一个特殊直接量。
3.4 基本数据类型
Java 的基本数据类型分为两大类: boolean 类型和数值类型。而数值类型又可以分为整数类型和浮点类型,整数类型里的字符类型也可被单独对待。因此常把Java 里的基本数据类型分为4 类,如图3.8所示。
Java 只包含这8 种基本数据类型,值得指出的是,字符串不是基本数据类型, 字符串是一个类,也就是一个引用数据类型。
3.4 .1 整型
通常所说的整型,实际指的是如下4 种类型。
- byte: 一个byte 类型整数在内存里占8 位,表数范围是: - 128(-27) - 127(27一1) 。
- short: 一个short 类型整数在内存里占16 位,表数范围是: - 32768(- 215) - 32767(215 一1) 。
- int: 一个int 类型整数在内存里占32 位, 表数范围是: 一2147483648(-231 ) - 2147483647(231 - 1) 。
- long: 一个long 类型整数在内存里占64 位,表数范围是: (-263 ) -(263 - 1) 。
int 是最常用的整数类型,因此在通常情况下, 直接给出一个整数值默认就是int 类型。除此之外,有如下两种情形必须指出。
- 如果直接将一个较小的整数值(在byte 或short 类型的表数范围内〉赋给一个byte 或short 变量,系统会自动把这个整数值当成byte 或者short 类型来处理。
- 如果使用一个巨大的整数值(超出了int 类型的表数范围)时, Java 不会自动把这个整数值当成long 类型来处理。如果希望系统把一个整数值当成long 类型来处理,应在这个整数值后增加l或者L 作为后缀。通常推荐使用L ,因为英文字母l 很容易跟数字1 搞混。
下面的代码片段验证了上面的结论。
1 // 下面代码是正确的,系统会自动把56当成byte类型处理 2 byte a = 56; 3 /* 4 下面代码是错的,系统不会把9999999999999当成long类型处理, 5 所以超出int的表数范围,从而引起错误 6 */ 7 // long bigValue = 9999999999999; 8 // 下面代码是正确的,在巨大的整数值后使用L后缀,强制使用long类型 9 long bigValue2 = 9223372036854775807L;
Java 中整数值有4 种表示方式: 十进制、二进制、八进制和十六进制,其中二进制的整数以0b 或0B 开头;八进制的整数以0开头:十六进制的整数以0x 或者0X 开头,其中10- 15 分别以a-f ( 此处的a-f 不区分大小写)来表示。
下面的代码片段分别使用八进制和十六进制的数。
1 // 以0开头的整数值是8进制的整数 2 int octalValue = 013; 3 System.out.println(octalValue); 4 // 以0x或0X开头的整数值是16进制的整数 5 int hexValue1 = 0x13; 6 int hexValue2 = 0XaF; 7 8 System.out.println(hexValue1); 9 System.out.println(hexValue2);
在某些时候,程序需要直接使用二进制整数, 二进制整数更"真实",更能表达整数在内存中的存在形式。不仅如此, 有些程序(尤其在开发一些游戏时)使用二进制整数会更便捷。
从Java 7 开始新增了对二进制整数的支持, 二进制的整数以0b 或者0B 开头。程序片段如下。
1 // 定义二个8位的二进制数 2 int binVal1 = 0b11010100; 3 byte binVal2 = 0B01101001; 4 // 定义一个32位的二进制数,最高位是符号位。 5 int binVal3 = 0B10000000000000000000000000000011; 6 System.out.println(binVal1); // 输出212 7 System.out.println(binVal2); // 输出105 8 System.out.println(binVal3); // 输出-2147483645
当定义32 位的二进制整数时,最高位其实是符号位,当符号位是1时,表明它是一个负数,负数在计算机里是以补码的形式存在的,因此还需要换算成原码。
所有数字在计算机底层都是以二进制形式存在的,原码是直接将一个数值换算成二进制数。但计算机以补码的形式保存所有的整数。补码的计算规则: 正数的补码和原码完全相同,负数的补码是其反码加1 ;反码是对原码按住取反,只是最高位(符号位)保持不变。
将上面的二进制整数binVal3 转换成十进制数的过程如图3.9 所示。
正如前面所指出的,整数值默认就是int 类型,因此使用二进制形式定义整数时,二进制整数默认占32 位, 其中第32位是符号位:如果在二进制整数后添加l 或L 后缀,那么这个二进制整数默认占64 位,其中第64 位是符号位。
例如如下程序。
1 /* 2 定义一个8位的二进制,该数值默认占32位,因此它是一个正数。 3 只是强制类型转换成byte时产生了溢出,最终导致binVal4变成了-23 4 */ 5 byte binVal4 = (byte)0b11101001; 6 /* 7 定义一个32位的二进制数,最高位是1。 8 但由于数值后添加了L后缀,因此该整数的实际占64位,第32位的1不是符号位。 9 因此binVal5的值等于2的31次方 + 2 + 1 10 */ 11 long binVal5 = 0B10000000000000000000000000000011L; 12 System.out.println(binVal4); // 输出-23 13 System.out.println(binVal5); // 输出2147483651
上面程序中粗体字代码与前面程序片段的粗体字代码基本相同,只是在定义二进制整数时添加了"L" 后缀,这就表明把它当成long 类型处理,因此该整数实际占64 位。此时的第32 位不再是符号位,因此它依然是一个正数。
至于程序中的byte bin Val4 = (byte )0b11101001;代码,其中0bl1101001 依然是一个32位的正整数, 只是程序进行强制类型转换时发生了溢出,导致它变成了负数。
3.4.2 字符型
字符型通常用于表示单个的宇符, 字符型值必须使用单引号( ')括起来。Java 语言使用16 位的Unicode 字符集作为编码方式,而Unicode 被设计成支持世界上所有书面语言的字符,包括中文字符,因此Java 程序支持各种语言的字符。
字符型值有如下三种表示形式。
- 直接通过单个字符来指定字符型值,例如'A' 、'9'和'0'等。
- 通过转义字符表示特殊字符型值,例如'\n'、'\t'等。
- 直接使用Unicode 值来表示字符型值,格式是'\uXXXX' ,其中xxxx 代表一个十六进制的整数。
Java 语言中常用的转义字符如表3.2 所示。
字符型值也可以采用十六进制编码方式来表示,范围是'\u0000'~'\uFFFF' , 一共可以表示65536 个字符,其中前256 个(‘\u0000'~'\u00FF' ) 字符和ASCII 码中的字符完全重舍。
由于计算机底层保存字符时,实际是保存该字符对应的编号,因此char 类型的值也可直接作为整型值来使用, 它相当于一个16位的无符号整数, 表数范围是0~65535 。
如果把0-65535 范围内的一个int 整数赋给char 类型变量,系统会自动把这个int 整数当成char 类型来处理。
下面程序简单示范了字符型变量的用法。
1 public class CharTest 2 { 3 public static void main(String[] args) 4 { 5 // 直接指定单个字符作为字符值 6 char aChar = 'a'; 7 // 使用转义字符来作为字符值 8 char enterChar = '\r'; 9 // 使用Unicode编码值来指定字符值 10 char ch = '\u9999'; 11 // 将输出一个'香'字符 12 System.out.println(ch); 13 // 定义一个'疯'字符值 14 char zhong = '疯'; 15 // 直接将一个char变量当成int类型变量使用 16 int zhongValue = zhong; 17 System.out.println(zhongValue); 18 // 直接把一个0~65535范围内的int整数赋给一个char变量 19 char c = 97; 20 System.out.println(c); 21 } 22 }
Java 没有提供表示字符串的基本数据类型,而是通过String 类来表示宇符串,由于字符串由多个字符组成,因此字符串要使用双引号括起来。
//下面代码定义了一个s 变量, 它是一个字符串实例的引用,它是一个引用类型的变量 String s = "曾经沧海难为水,除却巫山不是云" ;
3.4.3 浮点型
Java 的浮点类型有两种: float 和double o Java 的浮点类型有固定的表数范围和宇段长度, 宇段长度和表数范围与机器无关。Java 的浮点数遵循IEEE 754 标准,采用二进制数据的科学计数法来表示浮点数, 对于float 型数值,第1 位是符号位, 接下来8 位表示指数, 再接下来的23 位表示尾数:对于double 类型数值,第1位也是符号位,接下来的11 位表示指数, 再接下来的52 位表示尾数。
double 类型代表双精度浮点数, float 类型代表单精度浮点数。一个double 类型的数值占8 字节、64 位, 一个float 类型的数值占4 字节、32 位。
Java 语言的浮点数有两种表示形式。
- 十进制数形式:这种形式就是简单的浮点数,例如5.12 、512 . 0 、. 512 。浮点数必须包含一个小数点,否则会被当成int类型处理。
- 科学计数法形式:例如5.12e2 (5.12x102), 5.l2E2 (也是5 .12x 102)。
必须指出的是,只有浮点类型的数值才可以使用科学计数法形式表示。例如, 51200 是一个int 类型的值, 但512E2 则是浮点类型的值。
Java 语言的浮点类型默认是double 类型,如果希望Java 把一个浮点类型值当成float 类型处理,应该在这个浮点类型值后紧跟f 或F 。例如, 5.12 代表一个double 类型的值,占64 位的内存空间; 5.12f或者5.12F才表示一个float 类型的值, 占32 位的内存空间。当然,也可以在一个浮点数后添加d或D后缀, 强制指定是double 类型,但通常没必要。
Java 还提供了三个特殊的浮点数值:正无穷大、负无穷大和非数,用于表示溢出和出错。例如,使用一个正数除以0 将得到正无穷大,使用一个负数除以0 将得到负无穷大, 0.0 除以0.0 或对一个负数开方将得到一个非数。正无穷大通过Double 或Float 类的POSITIVE_FINITY 表示:负无穷大通过Double 或Float 类的NEGATIVE_INFINITY 表示,非数通过Double 或Float 类的NaN 表示。
必须指出的是,所有的正无穷大数值都是相等的,所有的负无穷大数值都是相等的: 而NaN 不与任何数值相等, 甚至和NaN 都不相等。
下面程序示范了上面介绍的关于浮点数的各个知识点。
1 public class FloatTest 2 { 3 public static void main(String[] args) 4 { 5 float af = 5.2345556f; 6 // 下面将看到af的值已经发生了改变 7 System.out.println(af); 8 double a = 0.0; 9 double c = Double.NEGATIVE_INFINITY; 10 float d = Float.NEGATIVE_INFINITY; 11 // 看到float和double的负无穷大是相等的。 12 System.out.println(c == d); 13 // 0.0除以0.0将出现非数 14 System.out.println(a / a); 15 // 两个非数之间是不相等的 16 System.out.println(a / a == Float.NaN); 17 // 所有正无穷大都是相等的 18 System.out.println(6.0 / 0 == 555.0/0); 19 // 负数除以0.0得到负无穷大 20 System.out.println(-8 / a); 21 // 下面代码将抛出除以0的异常 22 // System.out.println(0 / 0); 23 } 24 }
3.4.4 数值中使用下画线分隔
正如前面程序中看到的,当程序中用到的数值位数特别多时,程序员眼睛"看花"了都看不清到底有多少位数。为了解决这种问题, Java 7 引入了一个新功能:程序员可以在数值中使用下画线,不管是整型数值,还是浮点型数值,都可以自由地使用下画线。通过使用下画线分隔, 可以更直观地分辨数值中到底包含多少位。如下面程序所示。
1 public class UnderscoreTest 2 { 3 public static void main(String[] args) 4 { 5 // 定义一个32位的二进制数,最高位是符号位。 6 int binVal = 0B1000_0000_0000_0000_0000_0000_0000_0011; 7 double pi = 3.14_15_92_65_36; 8 System.out.println(binVal); 9 System.out.println(pi); 10 double height = 8_8_4_8.23; 11 System.out.println(height); 12 } 13 }
3.4.5 布尔型
布尔型只有一个boolean 类型,用于表示逻辑上的"真"或"假"。在Java 语言中, boolean 类型的数值只能是true 或false ,不能用0或者非0 来代表。其他基本数据类型的值也不能转换成boolean 类型。
例如,下面代码定义了两个boolean 类型的变量,并指定初始值。
1 public class BooleanTest 2 { 3 public static void main(String[] args) 4 { 5 boolean b1 = true; 6 boolean b2 = false; 7 // 下面代码将出现错误:字符串不能直接变成boolean型的值 8 // boolean b3 = "true"; 9 // 使用boolean和字符串进行连接运算,boolean会自动转换成字符串 10 String str = true + ""; 11 // 下面将输出true 12 System.out.println(str); 13 } 14 }
字符串"true"和"false"不会直接转换成boolean 类型,但如果使用一个boolean 类型的值和字符串进行连接运算,则boolean 类型的值将会自动转换成字符串。
boolean 类型的值或变量主要用做旗标来进行流程控制, Java 语言中使用boolean 类型的变量或值控制的流程主要有如下几种。
- if 条件控制语句
- while 循环控制语句
- do while 循环控制语句
- for 循环控制语句
除此之外, boolean 类型的变量和值还可在三目运算符(? :)中使用。
3.5 基本类型的类型转换
在Java 程序中,不同的基本类型的值经常需要进行相互转换。Java 语言所提供的7 种数值类型之间可以相互转换,有两种类型转换方式:自动类型转换和强制类型转换。
3.5.1 自动类型转换
Java 所有的数值型变量可以相互转换,如果系统支持把某种基本类型的值直接赋给另一种基本类型的变量,则这种方式被称为自动类型转换。当把一个表数范围小的数值或变量直接赋给另一个表数范围大的变量时,系统将可以进行自动类型转换:否则就需要强制转换。
表数范围小的可以向表数范围大的进行自动类型转换,就如同有两瓶水,当把小瓶里的水倒入大瓶中时不会有任何问题。Java 支持自动类型转换的类型如图3 .1 0 所示。
图3 . 10 中所示的箭头左边的数值类型可以自动类型转换为箭头右边的数值类型。下面程序示范了自动类型转换。
1 public class AutoConversion 2 { 3 public static void main(String[] args) 4 { 5 int a = 6; 6 // int可以自动转换为float类型 7 float f = a; 8 // 下面将输出6.0 9 System.out.println(f); 10 // 定义一个byte类型的整数变量 11 byte b = 9; 12 // 下面代码将出错,byte型不能自动类型转换为char型 13 // char c = b; 14 // 下面是byte型变量可以自动类型转换为double型 15 double d = b; 16 // 下面将输出9.0 17 System.out.println(d); 18 } 19 }
不仅如此,当把任何基本类型的值和字符串值进行连接运算时,基本类型的值将自动类型转换为字符串类型,虽然字符串类型不是基本类型,而是引用类型。因此,如果希望把基本类型的值转换为对应的字符串时,可以把基本类型的值和一个空字符串进行连接。看如下代码。
1 public class PrimitiveAndString 2 { 3 public static void main(String[] args) 4 { 5 // 下面代码是错的,因为5是一个整数,不能直接赋给一个字符串 6 // String str1 = 5; 7 // 一个基本类型值和字符串进行连接运算时,基本类型值自动转换为字符串 8 String str2 = 3.5f + ""; 9 // 下面输出3.5 10 System.out.println(str2); 11 // 下面语句输出7Hello! 12 System.out.println(3 + 4 + "Hello!"); 13 // 下面语句输出Hello!34,因为Hello! + 3会把3当成字符串处理, 14 // 而后再把4当成字符串处理 15 System.out.println("Hello!" + 3 + 4); 16 } 17 }
3.5.2 强制类型转换
如果希望把图3.10 中箭头右边的类型转换为左边的类型,则必须进行强制类型转换,强制类型转换的语法格式是: (targetType )value ,强制类型转换的运算符是圆括号(()) 。当进行强制类型转换时,类似于把一个大瓶子里的水倒入一个小瓶子,如果大瓶子里的水不多还好,但如果大瓶子里的水很多,将会引起溢出,从而造成数据丢失。这种转换也被称为"缩小转换( Narrow Conversion) " 。
下面程序示范了强制类型转换。
1 public class NarrowConversion 2 { 3 public static void main(String[] args) 4 { 5 int iValue = 233; 6 // 强制把一个int类型的值转换为byte类型的值 7 byte bValue = (byte)iValue; 8 // 将输出-23 9 System.out.println(bValue); 10 double dValue = 3.98; 11 // 强制把一个double类型的值转换为int 12 int tol = (int)dValue; 13 // 将输出3 14 System.out.println(tol); 15 } 16 }
经常上网的读者可能会发现有些网页上会包含临时生成的验证字符串,那么这个随机字符串是如何生成的呢?可以先随机生成一个在指定范围内的mt 数字(如果希望生成小写字母,就在97~122 之间),然后将其强制转换成char 类型,再将多次生成的字符连缀起来即可。
下面程序示范了如何生成一个6 位的随机字符串.
1 public class RandomStr 2 { 3 public static void main(String[] args) 4 { 5 // 定义一个空字符串 6 String result = ""; 7 // 进行6次循环 8 for(int i = 0 ; i < 6 ; i ++) 9 { 10 // 生成一个97~122的int型的整数 11 int intVal = (int)(Math.random() * 26 + 97); 12 // 将intValue强制转换为char后连接到result后面 13 result = result + (char)intVal; 14 } 15 // 输出随机字符串 16 System.out.println(result); 17 } 18 }
还有下面一行容易出错的代码:
//直接把5.6 赋值给float 类型变量将出现错误, 因为5.6 默认是doub1e 类型 float a = 5.6
上面代码中的5.6 默认是一个double 类型的浮点数,因此将5.6 赋值给一个float 类型变量将导致错误,必须使用强制类型转换才可以,即将上面代码改为如下形式:
float a = (float)5.6
在通常情况下,字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可以实现把字符串转换成基本类型。例如,把字符串转换成int 类型,则可通过如下代码实现:
String a = "45"; //使用Integer 的方法将一个字符串转换成工川类型 int iVa1ue =Integer.parseInt (a);
Java 为8 种基本类型都提供了对应的包装类: boolean 对应Boolean 、byte 对应Byte 、short 对应Short、int 对应Integer 、long 对应Long 、char 对应Character 、float 对应Float 、double 对应Double , 8 个包装类都提供了一个parseXxx(String str)静态方法用于将字符串转换成基本类型。
3.5.3 表达式类型的自动提升
当一个算术表达式中包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升。Java定义了如下的自动提升规则。
- 所有的byte 类型、short 类型和char 类型将被提升到int 类型。
- 整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型。操作数的等级排列如图3.10 所示,位于箭头右边类型的等级高于位于箭头左边类型的等级。
public class AutoPromote { public static void main(String[] args) { // 定义一个short类型变量 short sValue = 5; // 下面代码将出错:表达式中的sValue将自动提升到int类型, // 则右边的表达式类型为int,将一个int类型赋给short类型的变量将发生错误。 // sValue = sValue - 2; byte b = 40; char c = 'a'; int i = 23; double d = .314; // 右边表达式中在最高等级操作数为d(double型) // 则右边表达式的类型为double型,故赋给一个double型变量 double result = b + c + i * d; // 将输出144.222 System.out.println(result); int val = 3; // 右边表达式中2个操作数都是int,故右边表达式的类型为int // 因此,虽然23/3不能除尽,依然得到一个int整数 int intResult = 23 / val; System.out.println(intResult); // 将输出7 // 输出字符串Hello!a7 System.out.println("Hello!" + 'a' + 7); // 输出字符串104Hello! System.out.println('a' + 7 + "Hello!"); } }
3.6 直接量
直接量是指在程序中通过源代码直接给出的值, 例如在int a = 5 ;这行代码中, 为变量a 所分配的初始值5 就是一个直接量。
3.6.1 直接量的类型
并不是所有的数据类型都可以指定直接量, 能指定直接量的通常只有三种类型: 基本类型、字符串类型和null 类型。具体而言, Java 支持如下8 种类型的直接量。
- int 类型的直接量: 在程序中直接给出的整型数值, 可分为二进制、十进制、八进制和十六进制4 种,其中二进制需要以0B 或0b 开头, 八进制需要以0 开头, 十六进制需要以0x 或0X 开头。例如123 、012 (对应十进制的10 ) 、0x12 (对应十进制的18 ) 等。
- long 类型的直接量: 在整型数值后添加l 或L 后就变成了long 类型的直接量。例如3L 、0x12L(对应十进制的18 L) 。
- float 类型的直接量: 在一个浮点数后添加f 或F 就变成了float 类型的直接量,这个浮点数可以是标准小数形式,也可以是科学计数法形式。例如5.34F 、3. 14E5f。
- double 类型的直接量:直接给出一个标准小数形式或者科学计数法形式的浮点数就是double 类型的直接量。例如5.34 、3.14E5
- boolean 类型的直接量: 这个类型的直接量只有true 和false 。
- char 类型的直接量: char 类型的直接量有三种形式,分别是用单引号括起来的字符、转义字符和Unicode 值表示的字符。例如'a' 、W和'\u0061 ' 。
- String 类型的直接量: 一个用双引号括起来的字符序列就是String 类型的直接量。
- null 类型的直接量: 这个类型的直接量只有一个值,即null 。
在上面的8 种类型的直接量中, null 类型是一种特殊类型,它只有一个值: null ,而且这个直接量可以赋给任何引用类型的变量,用以表示这个引用类型变量中保存的地址为空,即还未指向任何有效对象。
3.7 运算符
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。Java 语言使用运算符将一个或多个操作数连缀成执行性语句,用以实现特定功能。
Java 语言中的运算符可分为如下几种。
- 算术运算符
- 赋值运算符
- 比较运算符
- 逻辑运算符
- 位运算符
- 类型相关运算符
3.7.1 算术运算符
Java 支持所有的基本算术运算符,这些算术运算符用于执行基本的数学运算:加、减、乘、除和求余等。下面是7个基本的算术运算符。
+ :加法运算符。例如如下代码:
double a = 5.2 ; double b = 3.1; double sum = a + b ; // sum 的值为8 . 3 System.out.println(sum);
除此之外, +还可以作为字符串的连接运算符。
-: 减法运算符。例如如下代码:
double a = 5.2 ; double b = 3.1; doub1e sub = a - b ; //sub 的值为2 . 1 System.out.println (sub) ;
*: 乘法运算符。例如如下代码:
double a = 5.2 ; double b = 3.1 ; double multiply = a * b ; //multiply 的值为16.12 System. out.println(mu1tiply) ;
/ : 除法运算符。除法运算符有些特殊,如果除法运算符的两个操作数都是整数类型,则计算结果也是整数, 就是将自然除法的结果截断取整,例如19/4的结果是4 , 而不是5 。如果除法运算符的两个操作数都是整数类型,则除数不可以是0 , 否则将引发除以零异常。
但如果除法运算符的两个操作数有一个是浮点数,或者两个都是浮点数,则计算结果也是浮点数,这个结果就是自然除法的结果。而且此时允许除数是0, 或者0.0,得到结果是正无穷大或负无穷大。看下面代码。
1 public class DivTest 2 { 3 public static void main(String[] args) 4 { 5 double a = 5.2; 6 double b = 3.1; 7 double div = a / b; 8 // div的值将是1.6774193548387097 9 System.out.println(div); 10 // 输出正无穷大:Infinity 11 System.out.println("5除以0.0的结果是:" + 5 / 0.0); 12 // 输出负无穷大:-Infinity 13 System.out.println("-5除以0.0的结果是:" + - 5 / 0.0); 14 // 下面代码将出现异常 15 // java.lang.ArithmeticException: / by zero 16 System.out.println("-5除以0的结果是::" + -5 / 0); 17 } 18 }
% : 求余运算符。求余运算的结果不一定总是整数, 它的计算结果是使用第一个操作数除以第二个操作数, 得到一个整除的结果后剩下的值就是余数。由于求余运算也需要进行除法运算,因此如果求余运算的两个操作数都是整数类型,则求余运算的第二个操作数不能是0 ,否则将引发除以零异常。如果求余运算的两个操作数中有一个或者两个都是浮点数,则允许第二个操作数是0 或0.0 ,只是求余运算的结果是非数: NaN. 0 或0.0 对零以外的任何数求余都将得到0 或0.0 。看如下程序。
1 public class ModTest 2 { 3 public static void main(String[] args) 4 { 5 double a = 5.2; 6 double b = 3.1; 7 double mod = a % b; 8 9 System.out.println(mod); // mod的值为2.1 10 System.out.println("5对0.0求余的结果是:" + 5 % 0.0); // 输出非数:NaN 11 System.out.println("-5.0对0求余的结果是:" + -5.0 % 0); // 输出非数:NaN 12 System.out.println("0对5.0求余的结果是:" + 0 % 5.0); // 输出0.0 13 System.out.println("0对0.0求余的结果是:" + 0 % 0.0); // 输出非数:NaN 14 // 下面代码将出现异常:java.lang.ArithmeticException: / by zero 15 System.out.println("-5对0求余的结果是:" + -5 % 0); 16 } 17 }
++ : 自加。该运算符有两个要点: ①自加是单目运算符,只能操作一个操作数: ②自加运算符只能操作单个数值型( 整型、浮点型都行)的变量, 不能操作常量或表达式。运算符既可以出现在操作数的左边,也可以出现在操作数的右边。但出现在左边和右边的效果是不一样的。如果把++放在左边,则先把操作数加1,然后才把操作数放入表达式中运算:如果把++放在右边,则先把操作数放入表达式中运算,然后才把操作数加1 。看如下代码:
int a = 5 ; //让a 先执行算术运算,然后自加 int b = a++ + 6; //输出a 的值为6 , b 的值为11 System . out.print1n(a + " \ n " + b);
执行完后, a 的值为6 ,而b 的值为11 。当++在操作数右边时,先执行a+6 的运算(此时a 的值为5) ,然后对a 加1 。对比下面代码:
int a = 5 ; //让a 先自加,然后执行算术运算 int b = ++a + 6; //输出a 的值为6 , b 的值为12 Systern.out .print1n(a + " \ n " + b);
执行的结果是a 的值为6 , b 的值为12 。当++在操作数左边时,先对a加1, 然后执行a+6 的运算(此时a 的值为6) ,因此b为12。
一一:自减。也是单目运算符,用法与++基本相似,只是将操作数的值减1。
Java 并没有提供其他更复杂的运算符,如果需要完成乘方、开方等运算,则可借助于java.lang.Math类的工具方法完成复杂的数学运算,见如下代码。
public class MathTest { public static void main(String[] args) { double a = 3.2; // 定义变量a为3.2 // 求a的5次方,并将计算结果赋为b。 double b = Math.pow(a , 5); System.out.println(b); // 输出b的值。 // 求a的平方根,并将结果赋给c double c = Math.sqrt(a); System.out.println(c); // 输出c的值。 // 计算随机数,返回一个0~1之间的伪随机数。 double d = Math.random(); System.out.println(d); // 输出随机数d的值 // 求1.57的sin函数值:1.57被当成弧度数 double e = Math.sin(1.57); System.out.println(e); // 输出接近1 } }
3.7.2 赋值运算符
赋值运算符用于为变量指定变量值,与C 类似, Java 也使用=作为赋值运算符。通常,使用赋值运算符将一个直接量值赋给变量。例如如下代码。
public class AssignOperatorTest { public static void main(String[] args) { String str = "Java"; // 为变量str赋值为Java double pi = 3.14; // 为变量pi赋值为3.14 boolean visited = true; // 为变量visited赋值为true String str2 = str; // 将变量str的值赋给str2 int a; int b; int c; // 通过为a, b , c赋值,三个变量的值都是7 a = b = c = 7; // 输出三个变量的值。 System.out.println(a + "\n" + b + "\n" + c); double d1 = 12.34; double d2 = d1 + 5; // 将表达式的值赋给d2 System.out.println(d2); // 输出d2的值,将输出17.34 } }
3.7.3 位运算符
Java 支持的位运算符有如下7 个。
- & : 按位与。当两位同时为1时才返回1。
- | : 按位或。只要有一位为1即可返回1。
- ~ : 按位非。单目运算符,将操作数的每个位(包括符号位〉全部取反。
- ^ :按位异或。当两位相同时返回0, 不同时返回1。
- <<:左移运算符。
- >> : 右移运算符。
- >>> : 无符号右移运算符。
一般来说,位运算符只能操作整数类型的变量或值。位运算符的运算法则如表3. 3 所示。表3.3
按位非只需要一个操作数,这个运算符将把操作数在计算机底层的二进制码按位(包括符号位)取反。如下代码测试了按位与和按位或运算的运行结果。
public class BitOperatorTest { public static void main(String[] args) { // System.out.println(2.5 & 3.0); System.out.println(5 & 9); // 将输出1 System.out.println(5 | 9); // 将输出13 System.out.println(~-5); // 将输出4 System.out.println(5 ^ 9); // 将输出12 System.out.println(5 << 2); // 输出20 System.out.println(-5 << 2); // 输出-20 System.out.println(-5 >> 2); // 输出-2 System.out.println(-5 >>> 2); // 输出1073741822 } }
执行5 ^ 9 的结果是12 。下面通过图3 .1 3 来介绍运算原理。
而5 ^ 9 的运算过程如图3 . 14 所示。
左移运算符是将操作数的二进制码整体左移指定位数,左移后右边空出来的位以0 填充。例如如下代码(程序清单同上) :
下面以-5 为例来介绍左移运算的运算过程,如图3.15 所示。
在图3.15 中, 上面的32 位数是-5 的补码,左移两位后得到一个二进制补码,这个二进制补码的最高位是L 表明是一个负数,换算成十进制数就是-20 。
Java 的右移运算符有两个: >>和>>>,对于>>运算符而言,把第一个操作数的二进制码右移指定位数后,左边空出来的位以原来的符号位填充,即如果第一个操作数原来是正数,则左边补0; 如果第一个操作数是负数,则左边补1。>>>是无符号右移运算符, 它把第一个操作数的二进制码右移指定位数后, 左边空出来的位总是以0填充。
进行移位运算时还要遵循如下规则。
- 对于低于int 类型(如byte 、short 和char) 的操作数总是先自动类型转换为int 类型后再移位。
- 对于int 类型的整数移位后>b , 当b>32 时,系统先用b 对32 求余(因为int 类型只有32 位),得到的结果才是真正移位的位数。例如, a>>33 和a>>1 的结果完全一样,而a>>32 的结果和a 相同。
- 对于long 类型的整数移位a>>b ,当b>64 时,总是先用b 对64 求余(因为long 类型是64 位) ,得到的结果才是真正移位的位数。
当进行移位运算时,只要被移位的二进制码没有发生有效位的数字丢失(对于正数而言,通常指被移出的位全部都是0),不难发现左移n 位就相当于来以2的n次方,右移n
位则是除以2的n次方。不仅如此,进行移位运算不会改变操作数本身,只走得到了一个新的运算结果,而原来的操作数本身是不会改变的。
3.7.4 扩展后的赋值运算符
赋值运算符可与算术运算符、位移运算符结合,扩展成功能更加强大的运算符。扩展后的赋值运算符如下。
- +=;对于x+=y ,即对应于x=x+y 。
- -=;对于x 一=y ,即对应于x = x - y。
- *=;对于x *= y ,即对应于x=x*y 。
- /=;对于x /=y ,即对应于x =x / y 。
- %=;对于x%=y ,即对应于x=x %y 。
- &=;对于x &=y ,即对应于x=x &y 。
- |= ; 对于x I=y ,即对应于x=x |y 。
- ^=;对于x ^=y ,即对应于x=x ^ y 。
- <<=;对于x <<= y ,即对应于x= x << Y 。
- >>= ;对于x >>= y ,即对应于x = x>> Y 。
- >>>= ; 对于x >>>= y ,即对应于x = x >>> y 。
只要能使用这种扩展后的赋值运算符,通常都推荐使用它们。因为这种运算符不仅具有更好的性能,而且程序会更加健壮。下面程序示范了+=运算符的用法。
public class EnhanceAssignTest { public static void main(String[] args) { // 定义一个byte类型的变量 byte a = 5; // 下面语句出错,因为5默认是int类型,a + 5就是int类型。 // 把int类型赋给byte类型的变量,所以出错 // a = a + 5; // 定义一个byte类型的变量 byte b = 5; // 下面语句不会出现错误 b += 5; } }
3.7.5 比较运算符
比较运算符用于判断两个变量或常量的大小,比较运算的结果是一个布尔值( true 或false ) 。Java支持的比较运算符如下。
- > ; 大于,只支持左右两边操作数是数值类型。如果前面变量的值大于后面变量的值,则返回true 。
- >= ; 大于等于, 只支持左右两边操作数是数值类型。如果前面变量的值大于等于后面变量的值,则返回true 。
- <; 小于,只支持左右两边操作数是数值类型。如果前面变量的值小于后面变量的值, 则返回true 。
- <=;小于等于,只支持左右两边操作数是数值类型。如果前面变量的值小于等于后面变量的值,则返回true o
- ==; 等于,如果进行比较的两个操作数都是数值类型,即使它们的数据类型不相同,只要它们的值相等,也都将返回true 。例如97 == 'a'返回true. 5.0 == 5 也返回true 。如果两个操作数都是引用类型,那么只有当两个引用变量的类型具有父子关系时才可以比较,而且这两个引用必须指向同一个对象才会返回true 。Java 也支持两个boolean 类型的值进行比较,例如. true = false将返回false 。
- !=:不等于,如果进行比较的两个操作数都是数值类型,无论它们的数据类型是否相同, 只要它们的值不相等,也都将返回true 。如果两个操作数都是引用类型,只有当两个引用变量的类型具有父子关系时才可以比较,只要两个引用指向的不是同一个对象就会返回true 。
下面程序示范了比较运算符的使用。
public class ComparableOperatorTest { public static void main(String[] args) { System.out.println("5是否大于 4.0:" + (5 > 4.0)); // 输出true System.out.println("5和5.0是否相等:" + (5 == 5.0)); // 输出true System.out.println("97和'a'是否相等:" + (97 == 'a')); // 输出true System.out.println("true和false是否相等:" + (true == false)); // 输出false // 创建2个ComparableOperatorTest对象,分别赋给t1和t2两个引用 ComparableOperatorTest t1 = new ComparableOperatorTest(); ComparableOperatorTest t2 = new ComparableOperatorTest(); // t1和t2是同一个类的两个实例的引用,所以可以比较, // 但t1和t2引用不同的对象,所以返回false System.out.println("t1是否等于t2:" + (t1 == t2)); // 直接将t1的值赋给t3,即让t3指向t1指向的对象 ComparableOperatorTest t3 = t1; // t1和t3指向同一个对象,所以返回true System.out.println("t1是否等于t3:" + (t1 == t3)); } }
3.7.6 逻辑运算符
逻辑运算符用于操作两个布尔型的变量或常量。逻辑运算符主要有如下6 个。
- &&: 与,前后两个操作数必须都是true 才返回true , 否则返回false 。
- &:不短路与,作用与&&相同,但不会短路。
- || : 或,只要两个操作数中有一个是true ,就可以返回true ,否则返回false 。
- |: 不短路或,作用与||相同,但不会短路。
- !: 非,只需要一个操作数,如果操作数为true ,则返回false ; 如果操作数为false ,则返回true 。
- ^ : 异或,当两个操作数不同时才返回true ,如果两个操作数相同则返回false 。
下面代码示范了或、与、非、异或4 个逻辑运算符的执行示意。
public class LogicOperatorTest { public static void main(String[] args) { // 直接对false求非运算,将返回true System.out.println(!false); // 5>3返回true,'6'转换为整数54,'6'>10返回true,求与后返回true System.out.println(5 > 3 && '6' > 10); // 4>=5返回false,'c'>'a'返回true。求或后返回true System.out.println(4 >= 5 || 'c' > 'a'); // 4>=5返回false,'c'>'a'返回true。两个不同的操作数求异或返回true System.out.println(4 >= 5 ^ 'c' > 'a'); // 定义变量a,b,并为两个变量赋值 int a = 5; int b = 10; // 对a > 4和b++ > 10求或运算 if (a > 4 | b++ > 10) { // 输出a的值是5,b的值是11。 System.out.println("a的值是:" + a + ",b的值是:" + b); } // 定义变量c,d,并为两个变量赋值 int c = 5; int d = 10; // c > 4 || d++ > 10求或运算 if (c > 4 || d++ > 10) { // 输出c的值是5,d的值是10。 System.out.println("c的值是:" + c + ",d的值是:" + d); } } }
执行上面程序,看到输出a 的值为5 , b 的值为11, 这表明b++ > 10 表达式得到了计算,但实际上没有计算的必要,因为a> 4 己经返回了true ,则整个表达式一定返回true 。
&与&&的区别与此类似: &,总会计算前后两个操作数,而&&先计算左边的操作数,如果左边的操作数为false ,则直接返回false ,根本不会计算右边的操作数。
3.7.7 三目运算符
三目运算符只有一个: ?:, 三日运算符的语法格式如下:
(expression) ?if - true - statement : if- false - statement;
public class ThreeTest { public static void main(String[] args) { String str = 5 > 3 ? "5大于3" : "5不大于3"; System.out.println(str); // 输出"5大于3" String str2 = null; if (5 > 3) { str2 = "5大于3"; } else { str2 = "5不大于3"; } int a = 11; int b = 12; // 三目运算符支持嵌套 System.out.println(a > b ? "a大于b" : (a < b ? "a小于b" : "a等于b")); } }
3.7.8 运算符的结合性和优先级
所有的数学运算都认为是从左向右运算的, Java 语言中大部分运算符也是从左向右结合的,只有单目运算符、赋值运算符和三目运算符例外,其中,单目运算符、赋值运算符和三日运算符是从右向左结合的,也就是从右向左运算。
乘法和加法是两个可结合的运算,也就是说,这两个运算符左右两边的操作数可以互换位置而不会影响结果。
运算符有不同的优先级,所谓优先级就是在表达式运算中的运算顺序。表3 .4 列出了包括分隔符在内的所有运算符的优先级顺序,上一行中的运算符总是优先于下一行的。