一、问题背景
博主在准备应聘的笔试、面试时,再次采用了多年以来的Java工具书《Java疯狂讲义》,并决定在每章详细复习后都要在博客中写下详细的阅读笔记。
二、阅读笔记与知识拓展——《Java疯狂讲义》第3章(数据类型和运算符)
Java是强类型(Strong Type)语言
。强类型语言定义:
- 所有的变量必须先声明后使用。
- 指定类型的变量只能接受类型与之匹配的值。
这意味着每个变量和每个表达式都有一个在编译时就确定的类型。类型限制了一个变量能被赋的值, 限制了一个表达式可以产生的值,限制了在这些值上可以进行的操作, 并确定了这些操作的含义。
强类型语言可在编译中发现源代码的错误,从而保证程序的健壮性。
Java有丰富的基本数据类型,基本数据类型包括数值类型、布尔类型。数值类型包括整形、字符型和浮点型,所有数值类型之间可以进行类型转换,此类型转换包括自动类型转换和强制类型转换。
Java还有丰富的运算符,如算术运算符、位运算符、比较运算符、逻辑运算符。
3.1注释
程序注释用于说明某段代码的作用。Java注释有:单行注释、多行注释、文档注释。
- 单行注释:以双斜杠
//
作为开头标识要被注释的内容。单行注释只能注释一行内容,用在注释信息内容少的地方。
- 多行注释:要被注释的内容包含在
/*
和*/
之间,能注释很多行的内容。为了可读性比较好,一般首行和尾行不写注释信息。
- 文档注释:要被注释的内容包含在
/**
和*/
之间,也能注释多行内容,一般用在类、方法和变量上面,用来描述其作用。注释后,鼠标放在类和变量上面会自动显示出我们注释的内容。
文档注释可以通过 Javadoc 命令把文档注释中的内容生成文档,并输出到 HTML 文件中,方便记录程序信息。还可以包含一个或多个 @ 标签,每个 @ 标签都在新的一行开始。关于 Javadoc 的具体标签和使用可阅读学习《Javadoc入门教程》一节。
多行注释可以嵌套单行注释,但是不能嵌套多行注释和文档注释。
文档注释能嵌套单行注释,不能嵌套多行注释和文档注释,一般首行和尾行也不写注释信息。
3.2标识符和关键字
3.2.1分隔符(Seperator/Delimiter)
Java的分隔符为分号;
、花括号{}
、方括号[]
、圆括号()
、空格、圆点
.
。它们都具有特殊的分隔作用,故被称为分隔符。
分隔符的主流英文单词是seperator
,但分隔符有时也用少见的delimiter
来表示。
separate /ˈseprət/ adj分开的;vi/vt分开;区分
separater /ˈsɛpəˌreɪtə/ n分离器;[计]分隔符
delimit /diːˈlɪmɪt/ vt给…定界限
delimiter /dɪ’lɪmɪtɚ/ n[计]分隔符
3.2.1.1分号(semicolon
)
Java 语言里对语句的分隔不是使用回车来完成的, Java 语言采用分号;
作为语句的分隔, 因此 每个 Java 语句必须使用分号作为结尾。 Java 程序允许一行书写多个语句,每个语句之间以分号隔开即可;一个语句也可以跨多行,只要在最后结束的地方使用分号结束即可。
例如,下面语句都是合法的 Java 语句。
int age = 25 ; String name = " 李刚 " ;
String hello = "你好 ! " +
"Java";
值得指出的是, Java 语句可以跨越多行书写, 但一个字符串、变量名不能跨越多行。例如, 下面的 Java 语句是错误的。
//字符串不能跨越多行
String a = "dddddd
xxxxxxx";
//变量名不能跨越多行
String na
me = "李刚";
虽然 Java 语法允许一行书写多个语旬,但从程序可读性角度来看,应该避免在一行书写多个语句。
分号的英文单词是semicolon
,其中semi-
前缀表示半
的意思,则semi-colon
直译表示半冒号,意译为分号。
colon /ˈkəʊlən/ n冒号;[身体部位]结肠
semicolon /sɛmɪˈkəʊlən/ n分号
3.2.1.2花括号/大括号(braces
/curly brackets
)
花括号的作用就是定义一个代码块, 一个代码块指的就是{
和}
所包含的一段代码, 代码块在逻辑上是一个整体。 对 Java 语言而言, 类定义部分必须放在一个代码块里,方法体部分也必须放在 一个代码块里。 除此之外,条件语句中的条件执行体和循环语句中的循环体通常也放在代码块里。花括号一般是成对出现的, 有一个{
则必然有一个}
,反之亦然。
花括号/大括号的英文是braces
/curly brackets
。而所有括号的左右形式则用open
和closed
区别,如左大括号{
的英文是open brace
,而右大括号}
的英文是`closed brace"。
brace /breɪs/ n.大括号;牙箍;[腿部]支架; vt支撑;绷紧(肩或膝盖)
embrace /ɪmˈbreɪs/ vi/vt/n拥抱;接纳(变化,思想);包括
bracket /ˈbrækɪt/ n(收入,年龄,价格)范围;支架;小括号/中括号/大括号其中的一个
3.2.1.3方括号/中括号(square brackets
)
方括号的主要作用是用于访问数组元素, 方括号通常紧跟数组变量名,而方括号里指定希望访问的数组元素的索引 。
例如,如下代码:
// 下面代码试图为名为 a 的数组的第四个元素赋值
a [3] = 3;
方括号/中括号的英文是square brackets
。
3.2.1.4圆括号/小括号(round brackets
/parentheses
)
圆括号是一个功能非常丰富的分隔符: 定义方法时必须使用圆括号来包含所有的形式参数声明, 调用方法时也必须使用圆括号来传入实参值;不仅如此,圆括号还可以将表达式中某个部分括成一个整体, 保证这个部分优先计算;除此之外,圆括号还可以作为强制类型转换的运算符。
圆括号/小括号的英文是round brackets
/parentheses
。
parentheses /pəˈrɛnθəsɪz/ n圆括号
3.2.1.5空格(blank
)
Java 语言使用空格分隔一条语句的不同部分。 Java 语言是一门格式自由的语言,所以空格几乎可以出现在 Java 程序的任何地方,也可以出现任意多个空格,但不要使用空格把一个变量名隔开成两个, 这将导致程序出错。
Java 语言中的空格(blank
)包含空格符(Space
)、制表符 (Tab
) 和回车 (Enter
) 等。
除此之外, Java 源程序还会使用空格来合理缩进 Java 代码,从而提供更好的可读性。
3.2.1.6圆点(dot
)
圆点(.)通常用作类/对象和它的成员(包括成员变量、方法和内部类)之间的分隔符,表明调用某个类或某个实例的指定成员。
3.2.2 Java9的标识符(identifier
)规则
标识符的英文是identifier
。
identity /aɪˈdɛntɪtɪ/ n身份
identical /aɪˈdentɪkl/ adj完全相同的
identify /aɪˈdentɪfaɪ/ vt识别;vi(+with)认同;vt(sth with sth)认为…之间密切相关
identification /aɪˌdentɪfɪˈkeɪʃn/ n识别;密切关联,同情,认同
identifier /aɪˈdentəfaɪər/ n标识符
标识符就是用于给程序中变量、类、方法命名的符号。 Java 语言的标识符必须以字母、下划线(_
)、 美元符($
)开头,后面可以跟任意数目的字母、 数字、下划线( )和美元符($
)。 此处的字母并不局限于 26 个英文字母,甚至可以包含中文字符、日文字符等。
由于 Java 9 支持 Unicode 8.0 字符集,因此 Java 的标识符可以使用 Unicode 8.0 所能表示的多种语言的字符。Java 语言是区分大小写的,因此 abc 和 Abc 是两个不同的标识符。
Java 9 规定:不允许使用单独的下划线(_
)作为标识符。 也就是说,下划线必须与其他字符组合在一起才能作为标识符。
使用标识符时,需要注意如下规则:
- 标识符可以由字母、数字、下划线(
_
)和美元符($)组成,其中数字不能打头。 - 标识符不能是 Java 关键字和保留字,但可以包含关键字和保留字。
- 标识符不能包含空格。
- 标识符只能包含美元符(
$
),不能包含@、#等其他特殊字符。
3.2.3Java关键字(keyword
)
Java语言中有一些具有特殊用途的单词被称为关键字(keyword
), 当定义标识符时,不要让标识符和关键字相同, 否则将引起错误。 例如,下面代码将无法通过编译。
// 试图定义一个名为 boolean的变量,但 boolean 是关键字,不能作为标识符
int boolean;
Java 的所有关键字都是小写的,但true
、 false
和 null
都不是 Java 关键字,它们是Java的字面常量(literal
)。(什么是Java的字面常量?)
Java 一共包含 50 个关键字,如下表所示。
abstract | assert | boolean | break | byte |
case | catch | char | class | const |
continue | default | do | double | else |
enum | extends | final | finally | float |
for | goto | if | implements | import |
instanceof | int | interface | long | native |
new | package | private | protected | public |
return | strictfp | short | static | super |
switch | synchronized | this | throw | throws |
transient | try | void | volatile | while |
上面的 50 个关键字中, enum 是从 Java 5 新增的关键字,用于定义一个枚举。 而 goto 和 const 这两个关键字也被称为保留字 (reserved word) ,保留字的意思是, Java 现在还未使用这两个关键字,但可能在未来的 Java 版本中使用这两个关键字;不仅如此,Java 还提供了三个特殊的字面常量(literal
): true
、 false
和 null
;Java 语言的标识符也不能使用这三个特殊的字面常量。
值得注意的是:Java中的3个特殊字面常量null
, false
, true
在使用时必须是全小写的,如果全大写和半小写半大写是错误的。
上述文段涉及的重要英文单词如下:
boolean /ˈbuːliən/ n布尔值
null /nɔl/或者/nʌl/ n空值
literal /ˈlɪtərəl/ adj字面意思上的;n[计]字面常量
enum /`iˌnəm/ n枚举(enumeration /ɪˌnjuːməˈreɪʃn/ 的缩写)
enumerate /ɪˈnjuːməˌreɪt/ vt列举(一系列事物)
3.3数据类型分类
Java是强类型(Strong Type)语言
。强类型语言定义:
- 所有的变量必须先声明后使用。
- 指定类型的变量只能接受类型与之匹配的值。
这意味着每个变量和每个表达式都有一个在编译时就确定的类型。类型限制了一个变量能被赋的值, 限制了一个表达式可以产生的值,限制了在这些值上可以进行的操作, 并确定了这些操作的含义。
强类型语言可在编译中发现源代码的错误,从而保证程序的健壮性。
声明变量的语法非常简单,只要指定变量的类型和变量名即可,如下所示:
type varName[ = 初始值 ] ;
上面语法中,声明变量时既可指定初始值,也可不指定初始值(上述语法的中括号[]
括起来的内容代表可选填项)。随着变量的作用范围的不同(变量有成员变量和局部变量之分,具体请参考本书 5.3 节内容),变量还可能使用其他修饰符。但不管是哪种变量,声明变量至少需要指定变量类型和变量名两个部分。声明变量时的变量类型可以是 Java 语言支持的所有类型。
Java 语言支持的类型分为两类:基本类型 (Primitive Type)
和引用类型 (Reference Type)
。
基本类型
包括boolean 类型
和数值类型
。数值类型有整数类型和浮点类型。整数类型包括 byte、short、int、 long、 char, 浮点类型包括 float 和 double。
char代表字符型,实际上字符型也是一种整数类型,相当于无符号整数类型。
引用类型
包括类、接口和数组类型
,还有一种特殊的null类型
。 所谓引用数据类型就是对一个对象的引用,对象包括实例和数组两种。实际上,引用类型变量就是一个指针,只是 Java 语言里不再使用指针这个说法。
空类型 (null type) 就是 null 值的类型, 这种类型没有名称。因为 null 类型没有名称,所以不可能声明一个 null 类型的变量或者转换到 null 类型。空引用(null)是 null 类型变量唯一的值。空引用(null)可以转换为任何引用类型,也就是说null可以赋值给任意的引用类型变量,如Object object = null;
。
在实际开发中 , 程序员可以忽略 null 类型,假定 null 只是引用类型的一个特殊的直接量(字面常量)。
空引用( null )只能被转换成引用类型 , 不能转换成基本类型,因此不要把一个 null 值赋给基本数据类型的变量。如
int number=null;
会导致编译器报错。
3.4基本数据类型
Java 的基本数据类型分为两大类: boolean 类型和数值类型。而数值类型又可以分为整数类型和浮
点类型,整数类型里的字符类型也可被单独对待。 因此常把 Java 里的基本数据类型分为 4 类 8 种,如下图所示。
Java 只包含上述 8 种基本数据类型,值得指出的是,String字符串不是基本数据类型, String字符串是一个类,也就是一个引用数据类型。
3.4.1整型(整数类型)
通常所说的整型,实际指的是如下 4 种类型:
- byte:一个byte类型整数在内存中占8位,表数范围是-128(-27)~127(27-1)
- short:一个short类型整数在内存中占16位,表数范围是-32768(-215)~32767(215-1)
- int:一个byte类型整数在内存中占32位,表数范围是-2147483648(-231)~2147483647(231-1)
- long:一个byte类型整数在内存中占64位,表数范围是(-263)~(263-1)
int 是最常用的整数类型,因此在通常情况下, 直接给出一个整数值默认就是 int 类型。除此之外, 有如下两种情形必须指出。
- 如果直接将一个较小的整数值(在 byte 或 short 类型的表数范围内)赋给一个 byte 或 short 变量,系统会自动把这个整数值当成 byte 或者 short 类型来处理。
- 如果使用一个巨大的整数值(超出了 int 类型的表数范围)时, Java 不会自动把这个整数值当成 long 类型来处理。如果希望系统把一个整数值当成 long 类型来处理,应在这个整数值后增加 l 或者 L 作为后缀。通常推荐使用 L,因为英文字母 l 很容易跟数字 1 搞混。
下面的代码片段验证了上面的结论:
// 下面代码是正确的,系统会自动把 56 当成 byte 类型处理
byte a = 56;
//下面代码是错误的,系统不会把 9999999999999 当成 1ong 类型处理 所以超出 int 的表数范围,从而引起错误
// 1ong bigVa1ue = 9999999999999 ;
//下面代码是正确的,在巨大的整数值后使用 L 后缀,强制使用J.ong 类型
1ong bigVa1ue2 = 9223372036854775807L;
可以把一个较小的整数值(在 int 类型的表数范围以内)直接赋给一个 long 类型的变量,这并不是因为 Java 会把这个较小的整数值当成 long 类型来处理, Java依然把这个整数值当成 int 类型来处理,只是因为 int 类型的值会自动类型转换到 long 类型。
Java 中整数值有 4 种表示方式: 十进制、 二进制、八进制和十六进制,其中二进制的整数以 0b 或 0B 开头;八进制的整数以 0 开头;十六进制的整数以 0x 或者 0X 开头,其中 10~15 进制分别以 a-f (此处的a-f不区分大小写)来表示。
二进制的整数以 0b 或 0B 开头
是因为字母B全称为Binary /ˈbaɪnəri/ adj/n二进制(的)
八进制的整数以 0 开头
是因为数字0长得像英文单词octal
的首字母o,而英文前缀oct-
是第八的意思,其中octal /'ɒkt(ə)l/ adj/n八进制(的)
十六进制的整数以 0x 或者 0X 开头
是因为字母X全称为hexadecimal /heksəˈdesɪml/ adj/n十六进制(的)
10~15进制分别以 a-f开头
是因为在十六进制里面,数字10~15分别用a-f表示
下面的代码片段分别使用二进制、八进制和十六进制的数。
//以 0b 开头的整数值是二进制的整数
int binaryValue=0b1011;
//以 0 开头的整数值是八进制的整数
int octa1Va1ue = 013 ;
//以 0x 或 0X 开头的整数值是十六进制的整数
int hexVa1ue1 = 0x13 ;
int hexVa1ue2 = 0XaF;
在某些时候,程序需要直接使用二进制整数, 二进制整数更"真实",更能表达整数在内存中的存在形式。不仅如此, 有些程序(尤其在开发一些游戏时)使用二进制整数会更便捷。
从 Java 7 开始新增了对二进制整数的支持, 二进制的整数以 0b 或者 0B 开头。程序片段如下:
// 定义两个 8 位的二进制整数
int binaryValue1 = 0b11010100 ;
byte binaryValue2 = 0B01101001;
// 定义一个 32 位的二迸制整数, 最高位是符号位
int binaryValue3 = 0B10000000000000000000000000000011;
System . out . print1n(binaryValue1) ; // 输出 212
System . out . println(binaryValue2) ; // 输出 105
System . out . println(binaryValue3); //输出 - 2147483645
从上面粗体字可以看出, 当定义 32 位的二进制整数时,最高位其实是符号位,当符号位是1时, 表明它是一个负数,负数在计算机里是以补码的形式存在的,因此还需要换算成原码。
所有数字在计算机底层都是以二进制形式存在的,原码是直接将一个数值换算成二进制数。 但计算机以补码的形式保存所有的整数。 补码的计算规则: 正数的反码和补码都与该正数的原码相同;负数的补码是其反码加1;反码是对原码按住取反,只是最高位(符号位)保持不变。
其中原码、反码和补码之间符号位改变的特殊情况分析可见博主的文章原码、反码和补码之间符号位改变的特殊情况分析。
将上面的二进制整数 binaryValue3 转换成十进制数的过程如下图所示。
正如前面所指出的,整数值默认就是int类型,因此使用二进制形式定义整数时,二进制整数默认占 32 位, 其中第 32 位是符号位:如果在二进制整数后添加 l 或 L 后缀,那么这个二进制整数默认占64 位,其中第 64 位是符号位。
例如如下程序:
//定义一个 8 位的二进制整数,该数值默认占 32 位,因此它是一个正数
//只是强制类型转换成 byte 时产生了溢出,最终导致 binVa14 变成了 -23
byte binVa14 = (byte)0b11101001;
//定义一个 32 位的二进制整数,最高位是 1//但由于数值后添加了 L 后缀, 因此该整数实际占 64 位,第 32 位的 1 不是符号位
//因此 binVa15 的值等于 2 的 31 次方+ 2 + 1
long binVa15 = 0B10000000000000000000000000000011L;
System.out.println(binVa14) ; //输出 -23
System.out.println(binVa15 ); //输出 2147483651
上面程序中粗体字代码与前面程序片段的粗体字代码基本相同,只是在定义二进制整数时添加了"L" 后缀,这就表明把它当成 long 类型处理,因此该整数实际占 64 位。 此时的第 32 位不再是符号位, 因此它依然是一个正数。
至于程序中的byte binVa14 = (byte)0b11101001;
代码,其中 0b11101001 依然是一个 32 位的正整数, 只是程序进行强制类型转换时发生了溢出,导致它变成了负数。关于强制类型转换的知识请参考本章 3.5 节。
3.4.2字符型
字符型通常用于表示单个的字符, 字符型值必须使用单引号'
括起来。 Java 语言使用 16 位的Unicode 字符集
作为编码方式,而 Unicode 被设计成支持世界上所有书面语言的字符,包括中文字符, 因此 Java 程序支持各种语言的字符。
字符型值有如下三种表示形式:
- 直接通过单个字符来指定字符型值,例如’A’、 '9’和’0’等。
- 通过转义字符(Escape character)表示特殊字符型值,例如’\n’、 '\t’等。(什么是转义字符?)
- 直接使用 Unicode 值来表示字符型值,格式是’\uXXXX’,其中字母u的全称是Unicode,而 XXXX 代表一个十六进制的整数。
Java 语言中常用的转义字符(Escape character)如下表所示:
转义字符 | 说明 | Unicode表达方式 |
---|---|---|
\b | 退格符 | \u0008 |
\n | 换行符 | \u000a |
\r | 回车符 | \u000d |
\t | 制表符 | \u0009 |
\" | 双引号\u0022 | |
\’ | 单引号 | \u0027 |
\\ | 反斜线 | \u005c |
字符型值也可以采用十六进制编码方式来表示,范围是’\u0000’~’\u00FF’, 一共可以表示 65536 个字符,其中前 256 个 (’\u0000’ ~ ‘\u00FF’) 字符和 ASCII 码中的字符完全重合。
由于计算机底层保存字符时,实际是保存该字符对应的编号,因此 char 类型的值也可直接作为整型值来使用, 它相当于一个 16 位的无符号整数, 表数范围是 0~65535。
char类型的变量值完全可以参与加减乘除等数学运算,也可以比较大小, 实际上都是用该字符对应的编码参与运算。
如果把 0-65535 范围内的一个 int 整数赋值给 char 类型变量,系统会自动把这个 int 整数当成 char 类型来处理。
下面程序简单示范了字符型变量的用法:
public class CharTest {
public static void main(String[] args){
//直接指定单个字符a作为字符值
char aCharacter='a';
System.out.println(aCharacter);
//直接指定转义字符\r作为字符值
char enterChar='\r';
System.out.println(enterChar);
//使用“香”的Unicode编码值作为字符值
char chineseChar='\u9999';
System.out.println(chineseChar);
//将char字符型'疯'赋值给int变量,以证明字符型相当于无符号整数,即可以把
char chineseChar2='疯';
int intChineseChar2=chineseChar2;
System.out.println(intChineseChar2);
//将int整型98赋值给char字符型变量,以证明字符型相当于无符号整数
//即可把0-65535范围内的一个int整数赋值给char字符型变量,编译器会自动把这个int整数转化成char字符型
char b=98;
System.out.println(b);
//若把0-65535范围外的数值,如-66或99999赋值给char字符型变量,编译器会报错
//char c=-66;
}
}
由上述代码可得:
- char字符型相当于无符号整数,其表数范围是 0~65535。即可把0-65535范围内的一个int整数赋给char字符型变量,编译器会自动把这个int整数转化成char字符型。
- 任意一个char字符可赋值给int整型变量。因为char字符型是2字节,而int整型是4字节,即int整型的表数范围完全覆盖char字符型的表数范围。
Java 没有提供表示字符串的基本数据类型,而是通过 String 类来表示字符串,由于字符串由多个字符组成,因此字符串要使用双引号括起来。 如下代码:
\\下面代码定义了一个 s 变量, 它是一个字符串实例的引用,它是一个引用类型的变量
String s = "沧海月明珠有泪,蓝因玉暖日生烟。 " ;
读者必须注意: char 类型字符使用单引号括起来,而字符串使用双引号括起来。
值得指出的是, Java 语言中的单引号、双引号和反斜线都有特殊的用途,如果一个字符串中包含了这些特殊字符,则应该使用转义字符的表示形式。 例如,在 Java 程序中表示一个绝对路径:"c:\codes"
,但这种写法得不到期望的结果, 因为Java会把单个反斜线\
和字母c
当成转义字符\c
。所以应该写成这种形式:"c:\\codes"
,只有同时写两个反斜线, Java 才会把第一个反斜线和后一个反斜线当成转义字符从而组成真正的反斜线\
。
在 Java 程序中表示一个绝对路径:"c:\codes"
的实测代码如下:
上述代码的最后两个System.out.println
输出结果如下:
3.4.3浮点型
Java 的浮点类型有两种:float 和 double。Java 的浮点类型有固定的表数范围和字段长度, 字段长度和表数范围与机器无关。 Java 的浮点数遵循 IEEE754标准,采用二进制数据的科学计数法来表示浮点数, 对于 float 型数值,第 1 位是符号位, 接下来 8 位表示指数, 再接下来的 23 位表示尾数:对于 double 类型数值,第 1 位也是符号位,接下来的 11 位表示指数, 再接下来的 52 位表示尾数。
因为 Java 浮点数使用二进制数据的科学计数法来表示浮点数,因此可能不能精确表示一个浮点数。 例如把5.2345556f值赋给一个 float 类型变量,接着输出这个变量时看到这个变量的值已经发生了改变。 使用 double 类型的浮点数比 float 类型的浮点数更精确,但如果浮点数的精度足够高(小数点后的数字很多时),依然可能发生这种情况。 如果开发者需要精确保存一个浮点数,则可以考虑使用 BigDecimal 类。
//不带'f'的"5.2345556"默认为double,故输出时精度无误
System.out.println(5.2345556);
//带'f'的"5.2345556"是float,小数过多故输出时精度有误,输出结果为5.2345557
System.out.println(5.2345556f);
//带'f'的"5.2345556"是float,小数过多故输出时精度有误,输出结果为5.2345557
float number=5.2345556f;
System.out.println(number);
double 类型代表双精度浮点数, float 类型代表单精度浮点数。一个 double 类型的数值占 8 字节、64 位, 一个 float 类型的数值占 4 字节、 32 位。
Java 语言的浮点数有两种表示形式:
- 十进制数形式:这种形式就是简单的浮点数,例如 5.12、 512.0、 .512。浮点数必须包含一个小数点,否则会被当成 int 类型处理。
- 科学计数法形式:例如 5.12e2 (即5.12x102, 5.12E2 (也是 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指Not A Number)。
必须指出的是,所有的正无穷大数值都是相等的,所有的负无穷大数值都是相等的; 而 NaN 不与任何数值相等, 甚至和 NaN 都不相等。
只有浮点数除以 0 才可以得到正无穷大或负无穷大,因为 Java 语言会自动把和浮点数运算的 0 ( 整数)当成 0.0 (浮点数)处理。如果一个整数值除以0,则会抛出一个异常:ArithmeticException: / by zero (除以 0 异常)。
下面程序示范了上面介绍的关于浮点数的各个知识点:
public class FloatTest {
public static void main(String[] args){
float floatNumber=3.2345556f;
//下面将看到floatNumber在控制台终端被输出的值变了,为3.2345555
System.out.println(floatNumber);
double c=Double.NEGATIVE_INFINITY;
float d= Float.NEGATIVE_INFINITY;
//看到 float 和 double 的负无穷大是相等的,下面输出结果为true
System.out.println(c==d);
// 0.0除以0.0的结果是非数,下面输出结果为NaN
System.out.println(0.0/0.0);
//两个非数之间是不相等的,下面输出结果为false
System.out.println(0.0/0.0==Float.NaN);
//所有正无穷大都是相等的,下面输出结果为true
System.out.println(1.0/0==2.0/0.0);
//整数除以0将抛出除以0的异常java.lang.ArithmeticException: / by zero
System.out.println(3/0);
}
}
3.4.4数值中使用下划线分隔
正如前面程序中看到的,当程序中用到的数值位数特别多时,程序员眼睛"看花"了都看不清到底有多少位数。 为了解决这种问题, Java 7 引入了一个新功能:程序员可以在数值中使用下划线,不管是整型数值,还是浮点型数值,都可以自由地使用下划线。通过使用下划线分隔, 可以更直观地分辨数值中到底包含多少位。如下面程序所示:
public class UnderScoreNumber {
public static void main(String[] args) {
int binaryValue=0B1000_0_001_010101_11;
double height=1_46_7893.1_41_5926;
//自由地使用的下划线不影响原来的数值,下面输出结果为33111
System.out.println(binaryValue);
//自由地使用的下划线不影响原来的数值,下面输出结果为1467893.1415926
System.out.println(height);
}
}
3.4.5布尔型
布尔型只有一个 boolean 类型,用于表示逻辑上的"真"或"假"。在 Java 语言中, boolean 类型的数值只能是 true 或 false,不能用0或者非0来代表。其他基本数据类型的值也不能转换成 boolean 类型。
Java 规范并没有强制指定 boolean 类型的变量所占用的内存空间 。 虽然 boolean 类型
的变量或值只要1位即可保存,但由于大部分计算机在分配内存时允许分配的最小内存单元是字节(8位), 因此 bit 大部分时候实际上占用 8 位。
如下面代码定义了两个 boolean 类型的变量,并指定初始值:
//定义旧的值为 true
boolean bl = true;
//定义 b2 的值为 false
boolean b2 = false ;
字符串"true"和"false"不能直接赋值给 boolean 类型的变量,但如果使用一个 boolean 类型的值和字符串进行连接运算,则 boolean 类型的值将会自动转换成字符串。 看下面代码(程序清单同上)。
//正确的boolean类型变量的赋值方法
boolean b=true;
//字符串"true"和"false"不能转换成 boolean 类型,下面的写法boolean c="true";是错误的
//boolean c="true";
//使用boolean类型的值和字符串进行连接运算,boolean类型的值会自动转换成字符串
String string = true + "";
//下面将输出true
System.out.println(string);
boolean 类型的值或变量主要用做旗帜标志FLAG来进行流程控制, Java 语言中使用 boolean 类型的变量或值控制的流程主要有:if条件控制语句、while 循环控制语句、do while 循环控制语句、for 循环控制语句。
除此之外, boolean 类型的变量和值还可在三目运算符?:
中使用。这些内容在后面将会有更详细的介绍。
3.5基本类型的类型转换
在 Java 程序中,不同的基本类型的值经常需要进行相互转换。 Java 语言所提供的 7 种数值类型之间可以相互转换,有两种类型转换方式:自动类型转换和强制类型转换。
3.5.1 自动类型转换
Java 所有的数值型变量可以相互转换,如果系统支持把某种基本类型的值直接赋给另一种基本类型的变量,则这种方式被称为自动类型转换
。 当把一个表数范围小的数值或变量直接赋给另一个表数范围大的变量时,系统将可以进行自动类型转换;否则就需要强制转换。
表数范围小的可以向表数范围大的进行自动类型转换,就如同有两瓶水,当把小瓶里的水倒入大瓶中时不会有任何问题。 Java 支持自动类型转换的类型如下图所示:
上图中所示的箭头左边的数值类型可以自动类型转换为箭头右边的数值类型。 下面程序示范了自动类型转换。
public class AutoConversion {
public static void main(String[] args) {
int integerNumber=6;
//int类型可以自动转换为 float 类型
float floatNumber=integerNumber;
//下面将输出 6.0
System.out.println(floatNumber);
//定义一个byte类型的整数变量
byte byteNumber=9;
//下面代码将出错,因byte类型不能自动类型转换为char类型
//char character = byteNumber;
//byte类型变量可以自动类型转换为 double 类型
double doubleNumber = byteNumber ;
//下面将输出 9.0
System.out.println (doubleNumber) ;
}
}
不仅如此,当把任何基本类型的值和字符串值进行连接运算时,基本类型的值将自动类型转换为字符串类型,虽然字符串类型不是基本类型,而是引用类型。 因此,如果希望把基本类型的值转换为对应的字符串时,可以把基本类型的值和一个空字符串进行连接。
+
不仅可作为加快向使用 ,还可作为字符串连接运算符使用。
看如下代码:
public class PrimitiveAndString {
public static void main(String[] args){
//下面代码是错误的,因为5是一个整数,可以赋值给char字符型变量,但不能赋值给一个String字符串对象
//String string1 = 5;
//一个基本类型的值和字符串进行连接运算时,基本类型的值自动转换为字符串
String string2=3.5f+"";
//下面输出3.5
System.out.println(string2);
//下面语句输出7Hello!,因为+运算符是从左往右的
System.out.println(3+4+"Hello!");
// 下面语句输出 Hello!34 ,因为 Hello!+3会把3当成字符串处理,而后再把4当成字符串处理
System.out.println("Hello! " + 3 + 4);
}
}
上面程序中有一个3+4+"Hello!"
表达式,这个表达式先执行3+4
运算,这是执行两个整数之间的加法得到7,然后进行7 +"Hello!"
运算,此时会把7当成字符串进行处理,从而得到 7Hello!
。 反之,对于 "Hello!"+ 3 + 4
表达式,先进行"Hello! "+ 3
运算,得到一个 Hello! 3
字符串 ,再 和 4 进行连接运算, 4 也被转换成字符串进行处理
3.5.2 强制类型转换(Narrow Conversion)
如果希望把下图中箭头右边的类型转换为箭头左边的类型,则必须进行强制类型转换,强制类型转换的语法格式是:(targetType) value
,强制类型转换的运算符是圆括号()
。当进行强制类型转换时,类似于把一个大瓶子里的水倒入一个小瓶子,如果大瓶子里的水不多还好,但如果大瓶子里的水很多,将会引起溢出 ,从而造成数据丢失。 这种转换也被称为"缩小转换 (Narrow Conversion)"。
conversion /kənˈvɜːrʒn/ n转换
下面程序示范了强制类型转换:
public class NarrowConversion {
public static void main(String[] args){
int integerValue=233;
//强制把一个int型值转换为byte类型的值并赋值给byte类型的变量
byte byteValue=(byte)integerValue;
//强制类型转换为byte类型后的233的输出结果为-23
System.out.println(byteValue);
double doubleValue=3.98;
//强制把一个double类型的值转换为int类型的值,输出结果为3
System.out.println((int)3.98);
}
}
在上面程序中,把一个浮点数3.98强制类型转换为整数3时, Java 将直接截断浮点数的小数部分。 除此之外,上面程序还把 233 强制类型转换为 byte 类型的整数,从而变成了-23, 这就是典型的溢出 。下图示范了这个转换过程。
从上图可以看出, 32 位int类型的 233 在内存中如图上所示,强制类型转换为 8 位的byte类型,则需要截断前面的 24 位,只保留右边 8 位,最左边的 1 是一个符号位,此处表明这是一个负数,负数在计算机里是以补码形式存在的,因此还需要换算成原码。将补码减 1 得到反码形式,再将反码取反就可以得到原码。最后的二进制原码为 10010111,这个 byte 类型的值为-(16+4+2+1),也就是-23。从上图很容易看出, 当试图强制把表数范围大的类型转换为表数范围小的类型时,必须格外小心, 因为非常容易引起信息丢失。
经常上网的读者可能会发现有些网页上会包含临时生成的验证字符串,那么这个随机字符串是如何生成的呢?可以先随机生成一个在指定范围内的int数字(如果希望生成小写字母,就在 97~122 之间), 然后将其强制转换成char类型,再将多次生成的字符连缀起来即可。
下面程序示范了如何生成一个 6 位的随机字符串,这个程序中用到了后面的循环控制,不理解循环的读者可以参考后面章节的介绍。
public class RandomString {
public static void main(String[] args) {
//定义一个空字符串
String string="";
//进行6次循环
for (int i = 0; i < 6; i++) {
//随机生成一个97~122之间的int类型整数,其中Math.random()随机返回大于等于0.0且小于1.0的伪随机double值
int integerValue=(int) ( Math.random()*26+97 );
//将integerValue强制转换为char类型后连接到string后面
string=string+(char)integerValue;
}
//输出随机生成的字符串string
System.out.println(string);
}
}
还有下面一行容易出错的代码:
//直接把 5.6 赋值给 f1oat 类型变量将出现错误, 因为5.6默认是 doub1e 类型
float a = 5.6 ;
上面代码中的 5.6 默认是一个 double 类型的浮点数,因此将 5.6 赋值给一个 float 类型变量将导致错误,必须使用强制类型转换才可以,即将上面代码改为如下形式:
f1oat a = (f1oat)5.6 ;
在通常情况下,字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可以实现把字符串转换成基本类型。例如,把字符串转换成int类型,则可通过如下代码实现:
String a = "45";
//使用包装类Integer的方法parseInt()将一个字符串转换成int类型
int iVa1ue =Integer.parseInt (a);
System.out.println(a);
Java 为 8 种基本类型都提供了对应的包装类: boolean 对应 Boolean、byte 对应 Byte、short 对应Short、 int 对应 Integer、 long 对应 Long、 char 对应 Character、 float 对应 Float、 double 对应 Double , 8 个包装类都提供了一个 parseXxx(String str)静态方法用于将字符串转换成基本类型。关于包装类的介绍,请参考本书第6章。
3.5.3表达式类型的自动提升
当一个算术表达式中包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升。 Java定义了如下的自动提升规则。
- 所有的 byte 类型、 short 类型和 char 类型变量之间的运算操作都将运算结果提升到 int 类型。所有byte、short、char类型变量与字面常量相加的表达式的类型将被提升到int类型。
- 整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型。操作数的等级排列如下图所示,位于箭头右边类型的等级高于位于箭头左边类型的等级。
下面程序示范了一个典型的错误:
public class AutoPromotion {
public static void main(String[] args) {
//定义一个short类型变量
short shortValue1=555;
//字面常量之间的运算结果仍然是字面常量,不会变成int类型,故下条语句正确
short shortValue2=666+5;
byte byteValue1=34;
//下面语句中byte、short、char类型变量与字面常量相加的表达式的类型将被提升到int类型,故报错
//short shortResult2=shortValue1+8;
//下面语句中即便两个short类型变量相加,其运算结果任然是int类型,故报错
//short shortResult2=shortValue1+shortValue2;
//下面语句中即便short类型变量与byte类型变量相加,其运算结果任然是int类型,故报错
//short shortResult3=shortValue1+byteValue1;
}
}
上面的shortValue+8
表达式的类型将被提升到 int 类型, 这样就把右边的 int 类型值赋给左边的 short 类型变量, 从而引起错误。
下面代码是表达式类型自动提升的正确示例代码:
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 类型整数进行除法运算, 即使无法除尽, 也将得到一个 int 类型结果:
//右边表达式中两个操作数都是int类型,故右边表达式的类型为int
//虽然 23/3 不能除尽,但依然得到一个int类型整数
int intResult = 23 / 3;
System.out.println(intResult); // 将输出 7
从上面程序中可以看出,当两个整数进行除法运算时, 如果不能整除, 得到的结果将是把小数部分截断取整后的整数。
如果表达式中包含了字符串,则又是另一番情形了。 因为当把加号(+)放在字符串和基本类型值之间时, 这个加号是一个字符串连接运算符, 而不是进行加法运算。 看如下代码:
//输出字符串Hello!a7
System.out.println("Hello!"+'a'+7);
//输出字符串104Hello!,因为char字符型的a的ASCII码值为97
System.out.println('a'+7+"Hello!");
对于第一个表达式"Hello!"+'a'+7
, 先进行"Hello!"+'a'
运算, 把'a'
转换成字符串 , 拼接成字符串Hello!a
, 接着进行"Hello!a" + 7
运算,这也是一个字符串连接运算, 得到结果是Hello!a7
。 对于第二个表达式, 先进行'a' + 7
加法运算,其中'a'
自动提升到 int 类型,变成 a 对应的ASCII值97,从97 + 7
将得到104
,然后进行104 + "Hello!"
运算 ,104
会自动转换成字符串,将变成两个字符串进行连接运算, 从而得到104Hello!
。
3.6直接量(字面常量literal)
直接量是指在程序中通过源代码直接给出的值, 例如在int a = 5;
这行代码中, 为变量a
所分配的初始值5
就是一个直接量(字面常量literal)。
3.6.1直接量的类型
并不是所有的数据类型都可以指定直接量, 能指定直接量的通常只有三种类型: 基本类型、字符串类型和 null 类型。 具体而言 , Java 支持如下 8 种类型的直接量:
- int 类型的直接量:在程序中直接给出的整型数值, 可分为二进制、十进制、八进制和十六进制 4 种,其中二进制需要以 0B 或 0b 开头, 八进制需要以 0 开头, 十六进制需要以 0x 或 0X 开头。
例如 123、 012 (对应十进制的 10)、 0x12 (对应十进制的 18 ) 等。 - long 类型的直接量:在整型数值后添加 l 或 L 后就变成了 long 类型的直接量。 例如 3L、0x12L(对应十进制的18L)。
- 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.6.2直接量的赋值
通常总是把一个直接量赋值给对应类型的变量,例如下面代码都是合法的。
int a = 5;
char c = 'a';
boolean b = true ;
float f = 5.12f;
double d = 4.12;
String author = "李刚";
String book = "疯狂 Android 讲义";
除此之外, Java还支持数值之间的自动类型转换,因此允许把一个数值直接量直接赋给另一种类型的变量,这种赋值必须是系统所支持的自动类型转换,例如把 int 类型的直接量赋给一个 long 类型的变量。 Java 所支持的数值之间的自动类型转换图如下图所示,箭头左边类型的直接量可以直接赋给箭头右边类型的变量;如果需要把下图中箭头右边类型的直接量赋给箭头左边类型的变量,则需要强制类型转换。
String 类型的直接量不能赋给其他类型的变量, null 类型的直接量可以直接赋给任何引用类型的变量,包括 String 类型。 boolean 类型的直接量只能赋给 boolean 类型的变量,不能赋给其他任何类型的变量。
关于字符串直接量有一点需要指出, 当程序第一次使用某个字符串直接量时, Java 会使用常量池(constant pool) 来缓存该字符串直接量,如果程序后面的部分需要用到该字符串直接量时, Java 会直接使用常量池 (constant pool) 中的字符串直接量。
由于 String 类是一个典型的不可变类(final类),因此 String 对象创建出来就不可能被改变,因此无须担心共享 String 对象会导致混乱。 关于不可变类的概念参考本书第 6 章。
常量池( constant pool )指的是在编译期被确定,并被保存在己编译的.class 文件中的一些数据。 它包括关于类、方法、 接口中的常量, 也包括字符串直接量。
看下面的程序:
String s0="hello";
String s1="hello";
String s2="he"+"llo";
//下面结果输出为true
System.out.println(s0==s1);
//下面结果输出为true
System.out.println(s0==s2);
Java会确保每个字符串常量只有一个,不会产生多个副本。 例子中的 s0 和 s1 中的"hello"都是字符
串常量,它们在编译期就被确定了,所以s0 == s1
返回 true;而"he"
和"llo"
也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它本身也是字符串常量, s2 同样在编译期就被解析为一个字符串常量,所以 s2 也是常量池中"hello"
的引用 。 因此,程序输出s0 == s1
返回 true,s1 == s2
也返回 true。
3.7运算符
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。 Java 语言使用运算符将一个或多个操作数连缀成执行性语句,用以实现特定功能。
Java 语言中的运算符可分为如下几种:算术运算符、赋值运算符、比较运算符、逻辑运算符、位运算符、类型相关运算符。
3.7.1 算数运算符
Java 支持所有的基本算术运算符,这些算术运算符用于执行基本的数学运算:加、减、乘、 除、求余、自加和自减等。 下面是 7 个基本的算术运算符。
3.7.1.1 +加法运算符
除下面代码中的加法运算之外,+还可以作为字符串的连接运算符:
double a=5.2;
double b=3.1;
double sum=a+b;
//sum的值为8.3
System.out.println(sum);
//+还可以作为字符串的连接运算符
String s2="he"+"llo";
3.7.1.2 -减法运算符
-除法不仅可以作为减法运算符,还可以作为求负的运算符:
double a=5.2;
double b=3.1;
double sub=a-b;
//sub的值为2.1,其中sub是subtract的缩写
System.out.println(sub);
//-除法不仅可以作为减法运算符,还可以作为求负的运算符
double x=-5.0;
//将x求负,其值变成5.0
x=-x;
3.7.1.3 *乘法运算符
double a=5.2;
double b=3.1;
double multiply=a*b;
//multiply的值为16.12
System.out.println(multiply);
3.7.1.4 /除法运算符
除法运算符有些特殊,如果除法运算符的两个操作数都是整数类型,则计算结果也是整数, 就是将自然除法的结果截断取整(不会四舍五入),例如19/4
的结果是4
,而不是5
。如果除法运算符的两个操作数都是整数类型,则除数不可以是0, 否则将引发除以零异常。
但如果除法运算符的两个操作数有一个是浮点数,或者两个都是浮点数,则计算结果也是浮点数,这个结果就是自然除法的结果。 而且此时允许除数是0, 或者 0.0,得到结果是正无穷大或负无穷大。
看下面的代码:
public class DivisionTest {
public static void main(String[] args){
double a=5.2;
double b=3.1;
double division=a/b;
//下面输出的division的值是1.6774193548387097
System.out.println(division);
//输出正无穷大:Infinity,Infinity为基本类型的包装类型Double和Float中的常量POSITIVE_INFINITY
System.out.println(5/0.0);
//输出负无穷大:-Infinity,-Infinity为基本类型的包装类型Double和Float中的常量NEGATIVE_INFINITY
System.out.println(-5/0.0);
//下面代码将出现异常
//java.1ang.ArithmeticException: / by zero
//System.out.println(-5/0);
}
}
3.7.1.5 %求余运算符
求余运算的结果不一定总是整数, 它的计算结果是使用第一个操作数除以第二个操作数, 得到一个整除的结果后剩下的值就是余数。由于求余运算也需要进行除法运算,因此如果求余运算的两个操作数都是整数类型,则求余运算的第二个操作数不能是 0,否则将引发除以零异常。 如果求余运算的两个操作数中有一个或者两个都是浮点数,则允许第二个操作数是 0 或 0.0,只是求余运算的结果是非数NaN
。 0 或 0.0 对零以外的任何数求余都将得到 0 或 0.0。
看如下程序:
public class ModTest {
public static void main(String[] args) {
double a=5.2;
double b=3.1;
double mod=a%b;
System.out.println(mod);//mod的值为2.1
System.out.println(5%0.0);//输出非数NaN
System.out.println(-5%0.0);//输出非数NaN
System.out.println(0%5.0);//输出0.0
System.out.println(0%0.0);//输出非数NaN
//下面代码将出现异常: java.1ang .ArithmeticException : / by zero
//System.out.println(-5%0);
}
}
3.7.1.6 ++自加运算符
++自加运算符运算符有两个要点:
- 自加是单目运算符,只能操作一个操作数;
- 自加运算符只能操作单个数值型 (整型、浮点型都行)的变量, 不能操作常量或表达式。
++自加运算符运算符既可以出现在操作数的左边,也可以出现在操作数的右边。 但出现在左边和右边的效果是不一样的。 如果把++放在左边,则先把操作数加1,然后才把操作数放入表达式中运算;如果把++放在右边,则先把操作数放入表达式中运算,然后才把操作数加 1。 看如下代码:
int a=5;
//让a先执行算术运算,然后自加
int b=a++ + 6;
//输出 a 的值为 6 , b 的值为 11
System.out.println(a+"\n"+b);
int aa=5;
//让aa先自加,然后执行算术运算
int bb=++aa + 6;
//输出 aa 的值为 6 , bb 的值为 12
System.out.println(aa+"\n"+bb);
3.7.1.7 --自减运算符
–自减运算符也是单目运算符,用法与++自加运算符基本相似,只是将操作数的值减1。
自加和自减只能用于操作变量,不能用于操作数值直接量、常量或表达式。例如, 5++、6–等写法都是错误的。
Java并没有提供其他更复杂的运算符,如果需要完成乘方、开方等运算,则可借助于java.lang.Math类的工具方法完成复杂的数学运算,见如下代码:
import com.sun.tools.javac.Main;
public class MathTest {
public static void main(String[] args) {
double a=3.2;
//求a的5次方,并将计算结果赋给b
double b=Math.pow(a,5);
//下面输出b的值为335.5443200000001
System.out.println(b);
//求a的平方根,并将结果赋给c,输出的c的值为1.7888543819998317
double c=Math.sqrt(a);
System.out.println(c);
//计算随机数,返回一个0~1之间的伪随机数
double d=Math.random();
System.out.println(d);
//求1.57的sin函数值:1.57被当成弧度数,输出的e的值为0.9999996829318346
double e= Math.sin(1.57);
System.out.println(e);
}
}
Math 类下包含了丰富的静态方法,用于完成各种复杂的数学运算。
3.7.2赋值运算符
赋值运算符用于为变量指定变量值,与C类似, Java 也使用=
作为赋值运算符。通常,使用赋值运算符将一个直接量值赋给变量。例如如下代码:
String str = "Java" ; //为变量 str 赋值 Java
double pi = 3.14; //为变量pi赋值 3 . 14
boolean visited = true ; //为变量 visited 赋值 true
除此之外,也可使用赋值运算符将一个变量的值赋给另一个变量:
String str = "Java" ; //为变量 str 赋值 Java
String str2=str; //将变量 str 的值赋给 str2
按前面关于交量的介绍,可以把交量当成一个可盛装数据的容器。 而赋值运算就是将被赋的值"装入"变量的过程。 赋值运算符是从右向左执行计算的,程序先计算得到
=
右边的值,然后将该值"装入"=
左边的变量,因此赋值运算符=
左边只能是变量。
值得指出的是,赋值表达式是有值的,赋值表达式的值就是右边被赋的值。 例如String st2 = str;
赋值表达式的值就是 str
。 因此,赋值运算符支持连续赋值,通过使用多个赋值运算符,可以一次为多个变量赋值。 如下代码是正确的:
String string="Java";
String string2;
//赋值表达式string2=string的输出结果为Java,因此可证明:赋值表达式是有值的,赋值表达式的值就是右边被赋的值
System.out.println((string2=string));
int a;
int b;
int c;
//通过为 a, b, c 赋值常数7, 三个变量的值都是 7
a=b=c=7;
//输出三个变量的值,其结果都为7
System.out.print1n(a + " \ n " + b + " \n " + c) ;
虽然 Java支持这种一次为多个变量赋值的写法,但这种写法导致程序的可读性降低, 因此不推荐这样写。
赋值运算符还可用于将表达式的值赋给变量。 如下代码是正确的:
doub1e d1 = 12.34;
doub1e d2 = d1 + 5;//将表达式的值赋给 d2
System.out.print1n(d2) ;//输出 d2 的值, 将输出 17.34
赋值运算符还可与其他运算符结合,扩展成功能更加强大的赋值运算符,参考 3.7.4 节。
3.7.3位运算符
Java 支持的位运算符有如下 7 个:
&
: 按位与。当两位同时为1时才返回1。|
: 按位或。 只要有一位为1即可返回1。~
: 按位非。 单目运算符,将操作数的每个位(包括符号位)全部取反。^
:按位异或。当两位相同时返回0,不同时返回1。<<
:左移运算符。>>
: 右移运算符。>>>
: 无符号右移运算符。
一般来说,位运算符只能操作整数类型的变量或值。 位运算符的运算法则如下表所示:
按位非
只需要一个操作数,这个运算符将把操作数在计算机底层的二进制码按位(包括符号位)取反。 如下代码测试了按位与
和按位或
运算的运行结果。
//因为5、9的二进制分别是0101和1001,故他两的按位与后的结果是0001(2)即1(10)
System.out.println(5 & 9);
//因为5、9的二进制分别是0101和1001,故他两的按位或后的结果是1101(2)即13(10)
System.out.println(5 | 9);
下面是按位异或和按位取反的执行代码:
System.out.println(~-5) ; //将输出4,原理见下图
System.out.println(5 ^ 9) ; //将输出12
程序执行~-5
的结果是4
,执行5 ^ 9
的结果是12
。 下面通过下图来介绍运算原理:
而5 ^ 9
的运算过程如下图所示:
左移运算符<<
是将操作数的二进制码整体左移指定位数,左移后右边空出来的位以0填充。 例如如下代码:
System.out.println(5 << 2) ; //输出 20
System.out.println( - 5 << 2) ; //输出 - 20
下面以-5 为例来介绍左移运算的运算过程,如下图所示:
在上图中, 上面的 32 位数是-5的补码,左移两位后得到一个新的二进制补码,这个二进制补码的最高位是1表明是一个负数,换算成十进制数就是-20。
Java的右移运算符有两个:>>
和>>>
,对于>>
运算符而言,把第一个操作数的二进制码右移指定位数后,左边空出来的位以原来的符号位填充,即如果第一个操作数原来是正数,则左边补0;如果第一个操作数是负数,则左边补1。>>>
是无符号右移运算符, 它把第一个操作数的二进制码右移指定位数后, 左边空出来的位总是以0填充。
看下面代码:
System.out.println( - 5 >> 2) ; //输出 -2
System.out.println( - 5 >>> 2) ; //输出 10 73741822
下面用示意图来说明>>和>>>运算符的运算过程:
从下图来看,-5右移2位后左边空出2位, 空出来的2位以符号位补充。从图中可以看出,右移运算后得到的结果的正负与第一个操作数的正负相同。 右移后的结果依然是一个负数,这是一个二进制补码,换算成十进制数就-2。
从下图来看, -5 无符号右移 2 位后左边空出 2 位, 空出来的 2 位以 0 补充。 从图中可以看出,无符号右移运算后的结果总是得到一个正数。 下图中下面的正数是 1073741822 (230~2)。
进行移位运算时还要遵循如下规则:
- 对于低于 int 类型(如 byte、 short 和 char) 的操作数总是先自动类型转换为 int 类型后再移位。
- 对于 int 类型的整数移位
a>>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|=y,即对应于 x=x|y 。
- ^=:对于x ^= y,即对应于 x=x ^ y。
- <<=:对于x<<=y,即对应于 x= x << y。
>>=
:对于 x >>= y,即对应于 x = x>> y。>>>=
:对于 x >>>= y,即对应于 x = x >>> y。
只要能使用这种扩展后的赋值运算符,通常都推荐使用它们。因为这种运算符不仅具有更好的性能,而且程序会更加健壮。 下面程序示范了+=运算符的用法。
byte a = 4;
//下面语句出错,因为5默认是int类型,a+5就是int类型,把int类型赋给byte类型的变量所以出错
//a = a + 5 ;
byte b = 4 ;
//下面语句不会出现错误,因为b+=125的原理是b=(byte)(b+125)
b+=125;
//最后b的输出结果是-127,因为byte类型范围是-128~127,而4+125=129溢出了,
//故byte会截取低8位的129的二进制补码10000001,而10000001最高位符号位是1,
//那么10000001是一个负数的补码,先减1得到其反码10000000,再取反得到其原码11111111,即-127
System.out.println(b);
byte c = 6;
//下面语句也不会出现错误,因为c++的原理是c=(byte)(c+1);
c++;
//最后结果输出为7
System.out.println(c);
运行上面程序, 不难发现:若int a=1
,则a=a+ 5
和a += 5
虽然运行结果相同, 但底层的运行机制还是存在一定差异的。 因此,如果可以使用这种扩展后的运算符,则推荐使用它们。
3.7.5比较运算符
比较运算符用于判断两个变量或常量的大小,比较运算的结果是一个布尔值(true或false)。Java支持的比较运算符如下:
>
:大于,只支持左右两边操作数是数值类型。如果前面变量的值大于后面变量的值,则返回 true。>=
:大于等于, 只支持左右两边操作数是数值类型。如果前面变量的值大于等于后面变量的值,则返回 true。<
:小于,只支持左右两边操作数是数值类型。如果前面变量的值小于后面变量的值, 则返回 true。<=
:小于等于,只支持左右两边操作数是数值类型。如果前面变量的值小于等于后面变量的值,则返回 true。==
:等于,如果进行比较的两个操作数都是数值类型,即使它们的数据类型不相同,只要它们的值相等,也都将返回 true。 例如 97 == 'a’返回 true, 5.0 == 5 也返回 true。 如果两个操作数都是引用类型,那么只有当两个引用变量的类型具有父子关系时才可以比较,而且这两个引用必须指向同一个对象才会返回 true。Java 也支持两个 boolean 类型的值进行比较,例如,true==false将返回 false。!=
:不等于,如果进行比较的两个操作数都是数值类型,无论它们的数据类型是否相同, 只要它们的值不相等,也都将返回 true。 如果两个操作数都是引用类型,只有当两个引用变量的类型具有父子关系时才可以比较,只要两个引用指向的不是同一个对象就会返回true。
基本类型的变量、值不能和引用类型的变量、值使用==进行比较, boolean 类型的变量、值不能与其他任意类型的变量、值使用进行比较;如果两个引用类型之间没有父子继承关系,那么它们的变量也不能使用==进行比较。
下面程序示范了比较运算符的使用:
public class ComparableOperatorTest {
public static void main(String[] args) {
//如果进行比较的两个操作数都是数值类型,即使它们的数据类型不相同,依然可进行比较
System.out.println(5>4.0);//返回true
System.out.println(5==5.0);//返回true
System.out.println(97=='a');//返回true
System.out.println(true==false);//返回false
//创建2个ComparableOperatorTest对象,分别赋给tl和t2两个引用
ComparableOperatorTest t1 =new ComparableOperatorTest();
ComparableOperatorTest t2 =new ComparableOperatorTest();
//tl 和 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)) ;
}
}
值得注意的是, Java 为所有的基本数据类型都提供了对应的包装类,关于包装类实例的比较有些特殊,具体介绍可以参考 6.1 节。
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 >= 51 || '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) ;
}
执行上面程序,看到输出 a 的值为 5, b 的值为 11, 这表明 b++ > 10 表达式中的b++得到了运行,但实际上没有运行的必要,因为 a> 4 己经返回了 true,则整个表达式一定返回 true。 再看如下代码,只是将上面示例的不短路逻辑或改成了短路逻辑或:
//定义变量 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) ;
}
上面代码执行的结果是: c 的值为 5,而 d 的值为 10。
对比两段代码, 后面的代码仅仅将不短路或改成短路或, 程序最后输出的d值不再是 11,这表明表达式d++ > 10
没有获得执行的机会。 因为对于短路逻辑或||
而言,如果第一个操作数返回 true,||
将不再对第二个操作数求值,直接返回 true。不会计算d++>10
这个逻辑表达式,因而d++
没有获得执行的机会。 因此,最后输出的 d 值为 10。而不短路或|
总是执行前后两个操作数(或表达式)。
&
与&&
的区别与此类似:&
总会计算前后两个操作数,而&&
先计算左边的操作数,如果左边的操作数为 false,则直接返回 false,根本不会计算右边的操作数。
3.7.7三目运算符
三目运算符只有一个:?:
, 三目运算符的语法格式如下:
(expression) ? if-true-statement : if-false-statement;
三目运算符的规则是: 先对逻辑表达式 expression 求值,如果逻辑表达式返回 true,则返回第二个操作数的值,如果逻辑表达式返回 false,则返回第三个操作数的值。 看如下代码:
String string=5>3? "5大于3" : "5不大于3";
System.out.println(string);
大部分时候, 三目运算符都是作为 if else 的精简写法。因此,如果将上面代码换成 if else 的写法, 则代码如下:
String str2 = null;
if (5 > 3)
str2 = " 5 大于 3 ";
else {
str2 = " 5 不大于 3 ";
}
System.out.println(str2);
这两种代码写法的效果是完全相同的。 三日运算符和 if else 写法的区别在于:if后的代码块可以有多个语句, 但三目运算符是不支持多个语句的。
三目运算符可以嵌套,嵌套后的三目运算符可以处理更复杂的情况,如下程序所示:
int a = 11;
int b = 12 ;
//三目运算符支持嵌套
System.out.println(a>b? "a大于b" : (a<b? "a小于b" : "a等于b") ) ;
上面程序中粗体字代码是一个由三目运算符构成的表达式,这个表达式本身又被嵌套在三目运算符中。 通过使用嵌套的三目运算符,即可让三目运算符处理更复杂的情况。
3.7.8运算符的结合性和优先级
所有的数学运算都认为是从左向右运算的, Java 语言中大部分运算符也是从左向右结合的,只有单目运算符、赋值运算符和三目运算符例外,其中,单目运算符、赋值运算符和三日运算符是从右向左结合的,也就是从右向左运算。
乘法和加法是两个可结合的运算,也就是说,这两个运算符左右两边的操作数可以互换位置而不会影响结果。
运算符有不同的优先级,所谓优先级就是在表达式运算中的运算顺序。下表列出了包括分隔符在内的所有运算符的优先级顺序,上一行中的运算符总是优先于下一行的。
根据上表中运算符的优先级,下面分析一下int a = 3;int b = a + 2 * a;
语句的执行过程。 程序先执行 2*a 得到 6,再执行 a+6 得到 9。 如果使用()
就可以改变程序的执行顺序,例如int b = (a + 2) * a
, 则先执行 a+2 得到结果 5,再执行 5*a 得到 15。 在上表中还提到了两个类型相关的运算符instanceof
和(type)
,这两个运算符与类、继承有关,此处不作介绍,在第5章将有更详细的介绍。
因为Java运算符存在这种优先级的关系,因此经常看到有些学生在做SCJP,或者某些公司的面试题,有如下Java 代码:int a = 5; int b = 4; int c = a++ - --b * ++a / b-- >>2 % a--;
, c 的值是多少?这样的语句实在太恐怖了,即使多年的老程序员看到这样的语句也会眩晕。
这样的代码只能在考试中出现,如果笔者带过的 team 里有 member 写这样的代码,恐怕他马上就得走人了,因为他完全不懂程序开发:源代码就是一份文档,源代码的可读性比代码运行效率更重要。
因此在这里要提醒读者:
- 不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它分成几步来完成。
- 不要过多地依赖运算符的优先级来控制表达式的执行)11页序,这样可读性太差,尽量使用o来控制表达式的执行顺序。
参考文献:
[1]Java注释:单行、多行和文档注释
[2]What are () {} and [] called?
[3]数据类型之二:字面量(literal)