3.3.2 浮点类型
- 浮点类型用于表示有小数部分的数值。在 Java 中有两种浮点类型,具体内容如表3-2所示。
double
表示这种类型的数值精度是float
类型的两倍(有人称之为双精度数值)。在很多情况下,float
类型的精度(6~7位有效数字)并不能满足需求。实际上,只有很少的情况适合用float
类型,例如,需要单精度数的库,或者需要存储大量数据时。float
类型的数值有一个后缀 F 或 f (例如,3.14F)。没有后缀F的浮点数值(如3.14)总是默认为double
类型。当然,也可以在浮点数值后面添加后缀 D 或 d(例如,3.14D)。
?注释: 可以使用十六进制表示浮点数值。例如,
0.125 = 2^-3
可以表示成0x1.0p-3
。在十六进制表示法中,使用 p 表示指数,而不是 e 。(e 是一个十六进制数位。)注意,尾数采用十六进制,指数采用十进制。指数的基数是 2 ,而不是 10 。
- 所有浮点数值计算都遵循
IEEE 754
规范。具体来说,下面是用于表示溢出合出错情况的三个特殊的浮点数值:- 正无穷大
- 负无穷大
- NaN(不是一个数字)
- 例如,一个正整数除以0的结果为正无穷大。计算 0/0 或者负数的平方根结果为 NaN。
?注释: 常量
Double.POSITIVE_INFINITY、Double.NEGATIVE_INFINITY
和Double.NaN
(以及相应的Float
类型的常量)分别表示这三个特殊值,但在实际应用中很少遇到。特别说明的是,不能如下检测一个特定值是否等于Double.NaN
:
if(x == Double.NaN) // is never true
所有“非数值”的值都认为是不相同的。不过,可以如下使用Double.isNaN
方法来判断:
if(Double.isNaN(x)) // check whether x is "not a number"
⚠ 警告: 浮点数值不适用于无法接受舍入误差的金融计算。例如,命令
System.out.println(2.0-1.1)
将打印出0.8999999999999999
,而不是人们期望的0.9
。这种舍入误差的的主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确地表示分数1/10
。这就好像十进制无法精确地表示分数1/3
一样。如果在数值计算中不允许有任何舍入误差,就应该使用BigDecimal
类。
3.3.3 char 类型
char
类型原本用于表示单个字符。不过,现在情况已经有所变化。如今,有些Unicode
字符可以用一个char
值描述,另外一些Unicode
字符则需需要两个char
值。char
类型的字面量值要用单引号括起来。例如:'A'
是编码值为65的字符常量。它与“A”
不同,“A”
是包含一个字符A的字符串。char
类型的值可以表示为十六进制值,其范围从\u0000
到\uFFFF
。例如,\u2122
表示商标符号(™),\u03C0
表示希腊字母 π。- 除了转义序列
\u
之外,还有一些用于表示特殊字符的转义序列,请参看表 3-3。所有这些转义序列都可以出现在加引号的字符字面量或字符串中。例如,'\u2122'
或"Hello\n"
。转义序列\u
还可以出现在加引号的字符常量或字符串之外(而其他所有转义序列不可以)。例如:
public static void main(String\u005B\u005D args)
- 就完全符合语法规则,
\u005B
和\u005D
分别是 [ 和 ] 的编码。
⚠ 警告:
Unicode
转义序列会在解析代码之前得到处理。例如,"\u0022+\u0022"
并不是一个由引号(U+0022)包围加号构成的字符串。实际上,\u0022
会在解析之前转换为","
,这会得到 “”+“” ,也就是一个空串。
更隐秘地,一定要当心注释中的\u
。注释// \u000A is a newline
会产生一个语法错误,因为读程序时\u000A
会替换为一个换行符。类似地,下面这个注释
// look inside c:\users
也会产生一个语法错误,因为\u
后面并没有跟着4个十六进制数。
3.3.4 Unicode 和 char 类型
- 要弄清楚
char
类型,就必须了解·Unicode
编码机制。Unicode
打破了传统字符编码机制的限制。在Unicode
出现之前,已经有许多种不同的标准:美国的ASCII
、西欧语言中的ISO 8859-1
、俄罗斯的KOI-8
、中国的GB 18030
和BIG-5
等。这样就产生了下面两个问题:一个是对于任意给定的代码值,在不同的编码方案下有可能对应不同的字母;二是采用大字符集的语言其编码长度有可能不同。例如,有些常用的字符采用单字符编码。而另一些字符则需要两个或多个字节。 - 设计
Unicode
编码的目的就是要解决这些问题。在20世纪80年代开始启动统一工作时,人们认为两个字节的代码宽度足以对世界上各种语言的所有字符进行编码,并有足够的空间留给未来扩展,当时所有人都这么想。在1991年发布了Unicode 1.0
,当时仅占用65536个代码值中不到一半的部分。在设计 Java 时决定采用 16 位的Unicode
字符集,这比使用 8 位字符集的其他程序设计语言有了很大的改进。 - 十分遗憾的是,经过一段时间后,不可避免地事情发生了。
Unicode
字符超过了 65536 个,其主要原因是增加了大量的汉语、日语和韩语中的表意文字。现在,16位的char
类型已经不能满足描述所有Unicode
字符的需要了。 - 下面利用一些专用术语来解释 Java 语言从 Java 5 开始如何解决这个问题。码点(code point)是指与一个编码表中的某个字符对应的代码值。在
Unicode
标准中,码点采用十六进制书写,并加上前缀 U+,例如U+0041
就是拉丁字母A
的码点。Unicode
的码点可以分成17个代码平面(code plane)。第一个代码平面称为基本多语言平面(basic multilingual plane),包括码点从U+0000
到U+FFFF
的“经典”Unicode
代码;其余16个平面的码点为从U+10000
到U+10FFFF
,包括辅助字符(supplementary character)。 UTF-16
编码采用不同长度的编码表示所有Unicode
码点。在基本多语言平面中,每个字符用16位表示,通常称为代码单元(code unit);而辅助字符编码为一对连续的代码单元。采用这种编码对表示的各个值落入基本多语言平面中未用的2048个值范围内,通常称为替代区域(surrogate area)(U+D800~U+DBFF
用于第一个代码单元,U+DC00~U+DFFF
用于第二个代码单元)。这样设计十分巧妙,我们可以从中迅速知道一个代码单元是一个字符的编码,还是一个辅助字符的第一或第二部分。例如,O是八元数集(http://math.ucr.edu/home/baez/octonions)的一个数学符号,码点为U+1D546
,编码为两个代码单元U+D835
和U+DD46
。(关于编码算法的具体描述见 https://tools.ietf.org/html/rfc2781。)- 在 Java 中,
char
类型描述了UTF-16
编码中的一个代码单元。 - 我们强烈建议不要再程序中使用
char
类型,除非确实需要处理UTF-16
代码单元。最好将字符串作为抽象数据类型处理。
3.3.5 Boolean 类型
boolean
(布尔)类型有两个值:false
和true
,用来判断逻辑条件。整型值和布尔值之间不能进行相互转换。
C++ 注释: 在 C++ 中,数值甚至指针可以代替
boolean
值。值 0 相当于布尔值false
,非 0 值相当于布尔值true
。在 Java 中则不是这样的。因此,Java 程序员不会遇到下述麻烦:
if ( x = 0) // oops... meant x == 0
在 C++ 中这个测试可以编译执行,其结果总是false
。而在 Java 中,这个测试将不能通过编译,其原因是整数表达式x=0
不能转换为布尔值。
3.4 变量与常量
- 与所有程序设计语言一样,Java 也使用变量来存储值。常量就是值不变的变量。
3.4.1 声明变量
- 在 Java 中,每个变量都有一个类型(
type
)。在声明变量时,先指定变量的类型,然后是变量名。这里列举一些声明变量的示例:
double salary;
int vacationDays;
long earthPopulation;
boolean done;
- 可以看到,每个声明都以分号结束。由于声明是一条完整的 Java 语句,而所有 Java 语句都以分号结束,所以这里的分号是必须的。
- 变量名必须是一个以字母开头并由字母或数字构成的序列。需要注意,与大多数程序设计语言相比,Java 中 ”字母“ 和 ”数字“ 的范围更大。包括
'A' ~ 'Z'
、'a'~'z'
、'_'
、'$'
或在某种语言中表示字母的任何Unicode
字符。例如,德国的用户可以在变量名中使用字母'ä'
;希腊人可以用π
。同样,数字包括'0'~'9'
和在某种语言中表示数字的任何Unicode
字符。但'+'
和'©'
这样的符号不能出现在变量名中,空格也不行。变量名中所有的字符都是有意义的,并且大小写敏感。变量名的长度基本无限制。
✔ 提示: 如果想要知道哪些
Unicode
字符属于 Java 中的 ”字母“,可以使用Character
类的isJavaIdentifierStart
和isJavaIdentifierPart
方法来检查。
✔ 提示: 尽管$
是一个合法的 Java 字符,但不要在你自己的代码中使用这个字符。它只用在 Java 编译器或其他工具生成的名字中。
- 另外,不能使用 Java 保留字作为变量名。
- 在 Java 9 中,单下划线
_
不能作为变量名。不过,将来的版本可能使用_
作为通配符。 - 可以在一行中声明多个变量:
int i,j;//both are integers
- 不过,不提倡使用这种风格。逐一声明每一个变量可以提高程序的可读性。
? 注释: 如前所述,变量名对大小写敏感,例如,
hireday
和hireDay
是两个不同的变量名。一般来讲,在对两个不同的变量进行命名时,最好不要只存在大小写上的差异。不过,有些时候,可能确实很难给变量取一个好的名字。于是,许多程序员将变量名命名为类型名,例如:
Box box;// "Box" is the type and "box" is the variable name
还有一些程序员更加喜欢在变量名前加上前缀 “a” 。
Box aBox;
3.4.2 变量初始化
- 声明一个变量之后,必须用赋值语句对变量进行显式初始化,千万不要使用未初始化的变量的值。例如,Java 编译器认为下面的语句序列是错误的:
int vacationDays;
System.out.println(vacationDays);// ERROR--variable not initialized
- 要想对一个已经声明过的变量进行赋值,就需要将变量名放在
=
左侧,再把一个适合取值的 Java 表达式放在=
的右侧。
int vacationDays;
vacationDays = 12;
- 也可以将变量的声明和初始化放在同一行中。例如:
int vacationDays = 12;
- 最后,在 Java 中可以将声明放在代码中的任何地方。例如,以下代码在 Java 中都是合法的:
double salary = 65000.0;
System.out.println(salary);
int vacationDays = 12;// OK to declare a variable here
- 在 Java 中,变量的声明尽可能地靠近第一次使用的地方,这是一种良好的程序编写风格。
? 注释: 从 Java 10 开始,对于局部变量,如果可以从变量的初始值推断出它的类型,就不再需要声明类型。只需要使用关键字
var
而无需指定类型:
var vacationDays = 12;// vacationDays is an int
var greeting = "Hello";// greeting is a String
C++ 注释: C 和 C++ 区分变量的声明与定义。如:
int i = 10;
是一个定义,而
extern int i;
是一个声明。在 Java 中,并不区分变量的声明和定义。
3.4.3 常量
- 在 Java 中,利用关键字
final
指示常量。例如:
public class Contants{
public static void main(String[] args){
final double CM_PER_INCH = 2.54;
double paperWidth = 8.5;
double paperHeight = 11;
System.out.println("Paper size in centimeters:"
+ paperWidth * CM_PER_INCH + "by" + paperHeight * CM_PER_INCH);
}
}
- 关键字
final
表示这个变量只能赋值一次。一旦被赋值之后,就不能够再更改了。习惯上,常量名使用全大写。 - 在 Java 中,经常希望某个常量可以在一个类的多个方法使用,通常将这些常量称为类常量(class constant)。可以使用关键字
static final
设置一个类常量。下面使用类常量的示例:
public class Constans2{
public static final double CM_PER_INCH = 2.54;
public static void main(String[] args){
double paperWidth = 8.5;
double paperHeight = 11;
System.out.println("Paper size in centimeters:"
+ paperWidth * CM_PER_INCH + "by" + paperHeight * CM_PER_INCH);
}
}
- 需要注意,类常量的定义位于
main
方法的外部。因此,在同一个类的其他方法中也可以使用这个常量。而且,如果一个常量被声明为public
,那么其他类的方法也可以使用这个常量。如这个示例中的Constants2.CM_PER_INCH
。
C++ 注释:
const
是 Java 保留的关键字,但目前并没有使用。在 Java 中,必须使用final
定义常量。
3.4.4 枚举类型
- 有时候,变量的取值只在一个有限的集合内。例如,销售的服装或披萨只有小、中、大和超大这四种尺寸。当然,可以将这些尺寸分别编码为 1、2、3、4 或 S、M、L、X。但这种设置很容易出错。很可能在变量保存的是一个错误的值(如 0 或 m)。
- 针对这种情况,可以自定义枚举类型。枚举类型包括有限个命名的值。例如,
enum Size{ SMALL , MEDIUM , LARGE , EXTRA_LARGE};
- 现在,可以声明这种类型的变量:
Size s = Size.MEDIUM;
Size
类型的变量只能存储这个类型声明中给定的某个枚举值,或者特殊值null
,null
表示这个变量没有设置任何值。
3.5 运算符
3.5.1 算术运算符
- 在 Java 中,使用算术运算符
+ 、- 、* 、 /
表示加、减、乘、除运算。当参与/
运算的两个操作数都是整数时,表示整数除法;否则,表示浮点除法。整数的求余操作(有时称为取模)用%
表示。例如,15/2
等于7
,15%2
等于1
,15/0.2
等于7.5
。 - 需要注意,整数被 0 除将会产生一个异常(
java.lang.ArithmeticException: / by zero
),而浮点数被 0 除将会得到无穷大或NaN
结果。
? 注释: 可移植性是 Java 语言的设计目标之一。无论在哪个虚拟机上运行,同一运算应该得到同样的结果。对于浮点数的算术运算,实现这样的可移植性是相当困难的。
double
类型使用64位存储一个数值,而有些处理器则使用80位浮点寄存器。这些寄存器增加了中间过程的计算精度。例如,以下运算:
double w = x * y / z;
很多 Intel 处理器计算x * y
,并且将结果存储在80位的寄存器中,再除以 z 并将结果截断为64位。这样可以得到一个更加精确的计算结果,并且还能够避免产生指数溢出。但是,这个结果可能与始终使用64位计算的结果不一样。因此,Java 虚拟机的最初规范规定所有的中间计算都必须进行截断。这种做法遭到了数字社区的反对。截断计算不仅可能导致溢出,而且由于截断操作需要消耗时间,所以在计算速度上实际上要比精确计算慢。为此,Java 程序设计语言承认了最优性能与理想的可再生性之间存在冲突,并给予了改进。在默认情况下,现在虚拟机设计者允许对中间计算结果采用扩展的精度。但是,对于使用strictfp
关键字标记的方法必须使用严格的浮点计算来生成可再生的结果。
例如,可以把main
方法标记为
public static strictfp void main(String[] args)
那么,main
方法中的所有指令都将使用严格的浮点计算。如果将一个类标记为strictfp
,这个类中的所有方法都要使用严格的浮点计算。
具体的计算细节取决于 Intel 处理器的行为。在默认情况下,中间结果允许使用扩展的指数,但不允许使用扩展的尾数(Intel 芯片支持截断尾数时并不损失性能)。因此,这两种方式的区别仅仅是采用默认方式不会产生溢出,而采用严格的计算有可能产生溢出。
如果没有仔细阅读注释,也没有什么关系。对大多数程序来说,浮点溢出不属于大问题。在本文中,将不使用strictfp
关键字。
3.5.2 数学函数与常量
- 在
Math
类中,包含了各种各样的数学函数。在编写不同类别的程序时,可能需要的函数也不同。 - 要想计算一个数值的平方根,可以使用
sqrt
方法:
double x = 4;
double y = Math.sqrt(x);
System.out,println(y);// prints 2.0
? 注释:
println
方法和sqrt
方法存在微小的差异。println
方法处理System.out
对象。但是,Math
类中的sqrt
方法并不处理任何对象,这样的方法被称为静态方法。
- 在 Java 中,没有幂运算,因此需要借助于
Math
类的pow
方法。以下语句:
double y = Math.pow(x,a);
- 将 y 的值设置为 x 的 a 次幂(x³)。
pow
方法有两个double
类型的参数,其返回结果也为double
类型。 floorMod
方法的目的是解决一个长期存在的有关整数余数的问题。考虑表达式n % 2
。所有人都知道,如果 n 是偶数,这个表达式为 0 ;如果 n 为奇数,表达式则为 1 。当然,除非 n 是负数。如果 n 为 负数,这个表达式则为 -1 .为什么呢?设计最早的计算机时,必须有人制定规则,明确整数除法和求余对负数操作数该如何处理。数学家们几百年来都知道这样一个最优(或称 欧几里得)规则:余数总是要 ≥ 0 。不过,最早制定规则的人并没有翻开数学书好好研究,而是提出了一些看似合理但实际上很不方便的规则。- 下面考虑这样一个问题:计算一个时钟时针的位置。这里要做一个时间调整,而且要归一化为一个
0~11
之间的数。这很简单:(position + adjustment) % 12
。不过,如果这个调整为负会怎么样?你可能会得到一个负数。所以要引入一个分支,或者使用((position + adjustment) % 12 + 12) % 12
。不管怎样都很麻烦。 floorMod
方法就让这个问题变得容易了:floorMod(position + adjustment , 12)
总会得到一个0~11
之间的数。(遗憾的是,对于负数,floorMod
会得到负数结果,不过这种情况在实际很少出现)。Math
类提供了一些常用的三角函数:Math.sin Math.cos Math.tan Math.atan Math.atan2
- 还有指数函数以及它的反函数——自然对数以及以10为底的对数:
Math.exp Math.log Math.log10
- 最后,Java 还提供了两个用于表示
π
和e
常量的最接近的近似值:Math.PI Math.E
提示: 不必在数学方法名和常量名前添加前缀 Math ,只要在源文件的顶部加上下面这行代码就可以了。
import static java.Math.lang.Math.*;
例如:
System.out.println("The square root of \u03C0 is " + sqrt(PI));
? 注释: 在
Math
类中,为了达到最佳的性能,所有的方法都使用计算机浮点单元中的例程。如果得到一个完全可预测的结果比运行速度更加重要的话,那么就应该使用StrictMath
类。它实现了可自由分发的数学库(Freely Distributable Math Library , FDLIBM) 的算法(www.netlib.org/fdlibm),确保在所有平台上得到相同的结果。
? 注释:Math
类提供了一些方法使整数有更好的运算安全性。如果一个计算溢出,数学运算符只是悄悄地返回错误的结果而不做任何提醒。例如,10亿乘以 3(1000000000 * 3)的计算结果将是-1294967296
,因为最大的int
值也只是刚刚超过 20 亿。不过,如果调用Math.multiplyExact(1000000000 , 3)
,就会产生一个异常。你可以捕获这个异常或者让程序停止,而不是允许它给出一个错误的结果然后悄无声息地继续运行。另外还有一些方法(addExact、subtractExact、incrementExact、decrementExact和 negateExact)
也可以正确地处理inr
和long
参数。
3.5.3 数值类型之间的转换
- 我们经常需要将一种数值类型转换为另一种数值类型。图 3-1 给出了数值之间的合法转换。
- 在图 3-1 中有6个实线箭头,表示无信息丢失的转换;另外有3个虚线箭头,表示可能有精度损失的转换。例如,123 456 789 是一个大整数,它所包含的位数比
float
类型所能够表示的位数多。当将这个整数转换为float
类型时,将会得到正确的大小,但是会损失一些精度。int n = 123456789; float f = n;// f is 1.23456792E8
- 当用一个二元运算符连接两个值时(例如
n+f
,n
是整数,```f````是浮点数),先要将两个操作数转换为同一种类型,然后再进行运算。- 如果两个操作数中有一个是
double
类型,另一个操作数就会转换为double
类型。 - 否则,如果其中一个操作数是
float
类型,另一个操作数将会转换为float
类型。 - 否则,如果其中一个操作数是
long
类型,另一个操作数将会转换为long
类型。 - 否则,两个操作数都将被转为
int
类型。
- 如果两个操作数中有一个是
3.5.4 强制类型转换
- 在上一小节中我们看到,在必要的时候,
int
类型的值将会自动地转换为double
类型。但另一方面,有时也需要将double
类型转换成int
类型。在 Java 中,允许进行这种数值之间的类型转换,当然,有可能丢失一些信息。这种可能损失信息的转换要通过强制类型转换(cast
)来完成。强制类型转换的语法格式是在圆括号中给出想要转换的目标类型,后面紧跟待转换的变量名。例如:double x = 9.997; int nx =(int) x;
- 这样,变量
nx
的值为 9 ,因为强制类型转换通过截断小数部分将浮点值转换为整型。 - 如果想对浮点数进行舍入运算,以便得到最接近的整数(在很多情况下,这种操作更有用),那就需要使用
Math.round
方法:double x = 9.997; int nx =(int) Math.round(x);
- 现在,变量
nx
的值为10。当调用round
的时候,仍然需要使用强制类型转换(int)
。其原因是round
方法返回的结果为long
类型,由于存在信息丢失的可能性,所以只有使用显式的强制类型转换才能够将long
类型转换成int
类型。
⚠ 警告: 如果试图将一个数值从一种类型强制转换为另一种类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值。例如,
(byte)300
的实际值为44.
C++注释: 不要在
boolean
类型与任何数值类型之间进行强制类型转换,这样可以防止发生一些常见的错误。只有极少数的情况才需要将布尔类型转换为数值类型,这样可以使用条件表达式b?1:0
。
3.5.5 结合赋值和运算符
- 可以在赋值中使用二元运算符,这是一种很方便的简写形式。例如,
x += 4
等价于
x = x + 4
(一般来说,要把运算符放在等号的左边,如*=
或%=
)。
? 注释: 如果运算符得到一个值,其类型与左侧操作数的类型不同,就会发生强制类型转换。例如,如果 x 是一个
int
值,则以下语句
x += 3.5;
是合法的,将把 x 设置为(int)(x + 3.5)
。
3.5.6 自增与自减运算符
- 当然,程序员都知道加 1 、减 1 是数值变量最常见的操作。在 Java 中,借鉴了 C 和 C++中的做法,也提供了自增、自减运算符:
n++
将变量 n 的当前值加 1 ,n--
则将 n 的值减 1 。例如,以下代码:int n = 12; n++;
- 将 n 的值改为 13 。由于这些运算符改变的是变量的值,所以它们不能应用于数值本身。例如,
4++
就不是一个合法的语句。 - 实际上,这些运算符有两种形式;上面介绍的是运算符放在操作数后面的“后缀” 形式。还有一种“前缀” 形式:
++n
。后缀和前缀形式都会使变量加 1 或减 1 。但用在表达式中时,二者就有区别了。前缀形式会先完成加 1;而后缀形式会使用变量原来的值。int m = 7; int n = 7; int a = 2 * ++m;// now a is 16,m is 18 int v = 2 * n++;//now b is 14,n is 18
- 建议不要在表达式中使用
++
,因为这样的代码很容易让人困惑,而且会带来烦人的BUG
。
3.5.7 关系和 boolean 运算符
- Java 包含丰富的关系运算符。要检测想相等性,可以使用两个等号
==
。例如,
3 == 7
的值为false
- 另外可以使用
!=
检测不相等。例如,
3 != 7
的值为true
。 - 最后,还有经常使用的
<(小于)
、>(大于)
、<=(小于等于)
和>=(大于等于)
运算符。 - Java 沿用了 C++ 的做法,使用
&&
表示逻辑“与” 运算符,使用||
表示逻辑“或” 运算符。从!=
运算符可以想到,感叹号!
就是逻辑非运算符。&&
和||
运算符是按照“短路” 方式来求值的:如果一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。如果用&&
运算符合并两个表达式,
expression1 && expression2
- 而且已经计算得到第一个表达式的真值为
false
,那么结果就不可能为true
。因此,第二个表达式就不必计算了。可以利用这一点来避免错误。例如,在下面表达式中:
x != 0 && 1 / x > x + y //no division by 0
- 如果 x 等于 0,那么第二部分就不会计算。因此,如果 x 为 0,也就不会计算
1 / x
,除以 0 的错误就不会出现。 - 类似地,如果第一个表达式为
true
,expression1 || expression2
的值就自动为true
,而无须计算第二个表达式。 - 最后一点,Java 支持三元操作符
?:
,这个操作符有时很有用。如果条件为true
,下面表达式
condition ? expression1 : expression2
就为第一个表达式的值,否则计算为第二个表达式的值。例如,
x < y ? x : y
会返回 x 和 y 中较小的一个。
- 如果 x 等于 0,那么第二部分就不会计算。因此,如果 x 为 0,也就不会计算
3.5.8 位运算符
- 处理整型类型时,可以直接对组成整数的各个位完成操纵。这意味着可以使用掩码技术得到整数中的各个位。位运算符包括:
&("and") |("or") ^("xor") ~("not")
- 这些运算符按位模式处理。例如,如果 n 是一个整数变量,而且用二进制表示的 n 从右边数第 4 位 为 1 ,则
int fourthBitFromRight = (n & 0b1000) / 0b1000;
会返回1,否则返回0。利用&并结合使用适当的 2 的幂,可以把其他位掩掉,而只留下其中的某一位。
? 注释: 应用在布尔值上时,
&
和|
运算符也会得到一个布尔值。这些运算符与&&
和||
运算符很类似,不过&
和|
运算符不采用 “短路” 方式来求值,也就是说,得到计算结果之前两个操作数都需要计算。
- 另外,还有
>>
和<<
运算符可以将位模式左移或右移。需要建立位模式来完成位掩码时,这两个运算符会很方便:
int fourthBitFromRight = (n & (1 << 3)) >> 3;
- 最后,
>>>
运算符会用 0 填充高位,这与>>
不同,它会用符号位填充高位。不存在<<<
运算符。
⚠ 警告: 移位运算符的右操作数要完成模 32 的运算(除非左操作数是
long
类型,在这种情况下需要对右操作数模 64)。例如,1 << 35
的值等同于1<<3
或 8。
C ++ 注释: 在 C/C++ 中,不能保证
>>
是完成算术移位(扩展符号位) 还是逻辑移位(填充0)。实现者可以选择其中更高效的任何一种做法。这意味着 C/C++ 中的>>
运算符对于负数生成的结果可能会依赖于具体的实现。 Java 则消除了这种不确定性。
3.5.9 括号与运算符级别
- 表 3-4 给出了运算符的优先级。如果不使用圆括号,就按照给出的运算符优先级次序进行计算。同一个级别的运算符从左到右的次序进行计算(但右结合运算符除外,如表中所示)。例如,由于
&&
的优先级比||
的优先级高,所以表达式
a && b || c
等价于(a && b) || c
又因为+=
是右结合运算符,所以表达式
a += b += c
等价于a += (b += c)
也就是将b+=c
的结果(加上 c 之后的 b)加到 a 上。
C ++ 注释: 与 C 或 C++ 不同,Java 不适用逗号运算符。不过,可以在
for
语句的第 1 和 第 3 部分中使用逗号分隔表达式列表。
3.6 字符串
- 从概念上讲,Java 字符串就是
Unicode
字符序列。例如,字符串"Java\u2122
"由 5 个Unicode
字符 J、a、v、a 和 ™组成。Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义类,很自然地叫做String
。每个用双引号括起来的字符串都是String
类的实例:String e = ""; String greeting ="Hello";
3.6.1 子串
String
类的substring
方法可以从一个较大的字符串提取出一个子串。例如:
创建一个由字符"Hel" 组成的字符串。String greeting = "Hello"; String s = greeting.substring(0,3);
? 注释: 类似于C 和 C++,Java 字符串中的代码单元和代码点从 0 开始计算。
substring
方法的第二个参数是不想复制的第一个位置。这里要复制位置为 0、1和 2 (从0 到 2 ,包括 0 和 2)的字符。在substring
中从 0 开始计数,直到 3 为止,但不包含 3 。substring
的工作方式有一个优点:容易计算子串的长度。字符串s.substirng(a,b)
的长度为b-a
。例如,子串 “Hel” 的长度为 3-0=3。
3.6.2 拼接
- 与大多数程序设计语言一样,Java 语言允许使用
+
号连接(拼接)两个字符串。String expletive = "Expletive"; String PG13 = "deleted"; String message = expletive + PG13;
- 上述代码将 “Expletivedeleted” 赋给变量
message
(注意,单词之间没有空格,+
号完全按照给定的次序将两个字符串拼接起来)。 - 当将一个字符串与一个非字符串的值进行拼接时,后者会转换成字符串。例如:
将int age = 13; String rating = "PG" + age;
rating
设置为 “PG13”。 - 这种特性通常在输出语句中。例如:
System.out.println("The answer is "+ answer);
- 这是一条合法的语句,并且会打印出所希望的结果(因为单词 is 后面加了一个空格,输出时也会加上这个空格)。
- 如果需要把多个字符串放在一起,用一个界定符分隔,可以使用静态
join
方法:
在 Java 11中,还提供了一个String all = String.join("/","S","M","L","XL"); //all is the string "S / M / L / XL"
repeat
方法:
String repeated = "Java".repeat(3);//repeated is "JavaJavaJava"