按照惯例,贴上来源:整数运算 - 廖雪峰的官方网站
整数运算
Java的整数运算遵循四则运算规则,可以使用任意嵌套的小括号。四则运算规则和初等数学一致。例如:
public class Main {
public static void main(String[] args) {
int i = (100 + 200) * (99 - 88); // 3300
int n = 7 * (5 + (i - 9)); // 23072
System.out.println(i);
System.out.println(n);
}
}
还有一种简写的运算符,即+=
,-=
,*=
,/=
,它们的使用方法如下:
n += 100; //相当于 n = n + 100;
n -= 100; //相当于 n = n - 100;
整数的数值表示不但是精确的,而且整数运算永远是精确的,即使是除法也是精确的,因为两个整数相除只能得到结果的整数部分;求余运算使用%
:
int x = 12345 / 67; // 184
int y = 12345 % 67; // 12345÷67的余数是17
溢出
要特别注意,整数由于存在范围限制,如果计算结果超出了范围,就会产生溢出,而溢出不会出错,却会得到一个奇怪的结果;
我们把整数2147483640
和15
换成二进制做加法,由于最高位计算结果为1
,因此,加法结果变成了一个负数。要解决上面的问题,可以把int
换成long
类型。
移位运算
在计算机中,整数总是以二进制的形式表示。例如,int
类型的整数7使用4字节表示的二进制为:00000000 0000000 0000000 00000111
可以对整数进行移位运算。对整数7左移1位将得到整数14,左移两位将得到整数28:
int n = 7; // 00000000 00000000 00000000 00000111 = 7
int a = n << 1; // 00000000 00000000 00000000 00001110 = 14
int b = n << 2; // 00000000 00000000 00000000 00011100 = 28
int c = n << 28; // 01110000 00000000 00000000 00000000 = 1879048192
int d = n << 29; // 11100000 00000000 00000000 00000000 = -536870912
关于右移,类似的:
int n = 7; // 00000000 00000000 00000000 00000111 = 7
int a = n >> 1; // 00000000 00000000 00000000 00000011 = 3
int b = n >> 2; // 00000000 00000000 00000000 00000001 = 1
int c = n >> 3; // 00000000 00000000 00000000 00000000 = 0
如果对一个负数进行右移,最高位的1不动,结果仍然是一个负数:
int n = -536870912;
int a = n >> 1; // 11110000 00000000 00000000 00000000 = -268435456
int b = n >> 2; // 11111000 00000000 00000000 00000000 = -134217728
int c = n >> 28; // 11111111 11111111 11111111 11111110 = -2
int d = n >> 29; // 11111111 11111111 11111111 11111111 = -1
还有一种不带符号的右移运算,使用>>>
,它的特点是符号位跟着动,因此,对一个负数进行>>>
右移,它会变成正数,原因是最高位的1变成了0:
int n = -536870912;
int a = n >>> 1; // 01110000 00000000 00000000 00000000 = 1879048192
int b = n >>> 2; // 00111000 00000000 00000000 00000000 = 939524096
int c = n >>> 29; // 00000000 00000000 00000000 00000111 = 7
int d = n >>> 31; // 00000000 00000000 00000000 00000001 = 1
位运算
位运算是按位进行与、或、非和异或的运算。
- 与运算的规则是,必须两个数同时为1,结果才为1:
n = 0 & 0; // 0
n = 0 & 1; // 0
n = 1 & 0; // 0
n = 1 & 1; // 1
- 或运算的规则是,只要任意一个为1,结果就为1:
n = 0 | 0; // 0
n = 0 | 1; // 1
n = 1 | 0; // 1
n = 1 | 1; // 1
= 非运算的规则是,0和1互换:
n = ~0; // 1
n = ~1; // 0
- 异或运算的规则是,如果两个数不同,结果为1,否则为0:
n = 0 ^ 0; // 0
n = 0 ^ 1; // 1
n = 1 ^ 0; // 1
n = 1 ^ 1; // 0
浮点数运算
- 浮点数运算和整数运算相比,只能进行加减乘除这些数值计算,不能做位运算和移位运算。
- 在计算机中,浮点数虽然表示的范围大,但是,浮点数有个非常重要的特点,就是浮点数常常无法精确表示。举个栗子:
浮点数0.1在计算机中就无法精确表示,因为十进制的0.1换算成二进制是一个无限循环小数,很显然,无论使用float
还是double
,都只能存储一个0.1的近似值。但是,0.5这个浮点数又可以精确地表示。 - 由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数:
// 比较x和y是否相等,先计算其差的绝对值:
double r = Math.abs(x - y);
// 再判断绝对值是否足够小:
if (r < 0.00001) {
// 可以认为相等
} else {
// 不相等
}
布尔运算
对于布尔类型boolean
,永远只有true
和false
两个值。
布尔运算是一种关系运算,包括以下几类:
- 比较运算符:
>
,>=
,<
,<=
,==
,!=
- 与运算
&&
- 或运算
||
- 非运算
!
下面是一些示例:
boolean isGreater = 5 > 3; // true
int age = 12;
boolean isZero = age == 0; // false
boolean isNonZero = !isZero; // true
boolean isAdult = age >= 18; // false
boolean isTeenager = age >6 && age <18; // true
布尔运算的一个重要特点是短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。
因为false && x
的结果总是false
,无论x
是true
还是false
,因此,与运算在确定第一个值为false
后,不再继续计算,而是直接返回false
。
public class Main {
public static void main(String[] args) {
boolean b = 5 < 3;
boolean result = b && (5 / 0 > 0);
System.out.println(result);
}
}
如果没有短路运算,&&
后面的表达式会由于除数为0
而报错,但实际上该语句并未报错,原因在于与运算是短路运算符,提前计算出了结果false
。
类似的,对于||
运算,只要能确定第一个值为true
,后续计算也不再进行,而是直接返回true
:
boolean result = true || (5 / 0 > 0); // true
Java还提供一个三元运算符b ? x : y
,它根据第一个布尔表达式的结果,分别返回后续两个表达式之一的计算结果。示例:
// 三元运算
public class Main {
public static void main(String[] args) {
int n = -100;
int x = n >= 0 ? n : -n;
System.out.println(x);
}
}
上述语句的意思是,判断n >= 0
是否成立,如果为true
,则返回n
,否则返回-n
。这实际上是一个求绝对值的表达式。
字符类型
字符类型char
是基本数据类型,它是character的缩写。一个char保存一个Unicode字符,还可以直接用转义字符\u
+Unicode编码来表示一个字符:
char c1 = 'A';
char c2 = '中';
// 注意是十六进制:
char c3 = '\u0041'; // 'A',因为十六进制0041 = 十进制65
char c4 = '\u4e2d'; // '中',因为十六进制4e2d = 十进制20013
字符串类型
和char
类型不同,字符串类型String
是引用类型,用双引号"..."表示字符串。一个字符串可以存储0个到任意个字符:
String s = ""; // 空字符串,包含0个字符
String s1 = "A"; // 包含一个字符
String s2 = "ABC"; // 包含3个字符
String s3 = "中文 ABC"; // 包含6个字符,其中有一个空格
因为字符串使用双引号"..."表示开始和结束,那如果字符串本身恰好包含一个"字符的时候,需要借助转义字符\
,再者,因为\
是转义字符,所以,两个\\
表示一个\字符:
String s = "abc\"xyz"; // 包含7个字符: a, b, c, ", x, y, z
String s = "abc\\xyz"; // 包含7个字符: a, b, c, \, x, y, z
常见的转义字符包括:
-
\"
表示字符"
-
\'
表示字符'
-
\\
表示字符\
-
\n
表示换行符 -
\r
表示回车符 -
\t
表示Tab -
\u####
表示一个Unicode编码的字符
Java的编译器对字符串做了特殊照顾,可以使用+
连接任意字符串和其他数据类型,这样极大地方便了字符串的处理。如果用+
连接字符串和其他数据类型,会将其他数据类型先自动转型为字符串,再连接。例如:
// 字符串连接
public class Main {
public static void main(String[] args) {
int age = 25;
String s = "age is " + age;
System.out.println(s); // age is 25
}
}
如果我们要表示多行字符串,字符串可以用"""..."""
表示多行字符串(Text Blocks)了。举个例子:
public class Main {
public static void main(String[] args) {
String s = """
SELECT * FROM
users
WHERE id > 100
ORDER BY name DESC
""";
System.out.println(s);
}
}
Java的字符串除了是一个引用类型外,还有个重要特点,就是字符串不可变。
public class Main {
public static void main(String[] args) {
String s = "hello";
System.out.println(s); // 显示 hello
s = "world";
System.out.println(s); // 显示 world
}
}
执行String s = "hello";
时,JVM虚拟机先创建字符串"hello"
,然后,把字符串变量s
指向它;紧接着,执行s = "world"
;时,JVM虚拟机先创建字符串"world"
,然后,把字符串变量s
指向它;结果是:原来的字符串"hello"
还在,只是我们无法通过变量s
访问它而已。因此,字符串的不可变是指字符串内容不可变。
public class Main {
public static void main(String[] args) {
String s = "hello";
String t = s;
s = "world";
System.out.println(t);
}
}
引用类型的变量可以指向一个空值null
,它表示不存在,即该变量不指向任何对象。例如:
String s1 = null; // s1是null
String s2; // 没有赋初值值,s2也是null
String s3 = s1; // s3也是null
String s4 = ""; // s4指向空字符串,不是null
注意要区分空值null
和空字符串""
,空字符串是一个有效的字符串对象,它不等于null
。
数组类型
如果我们有一组类型相同的变量,例如,5位同学的成绩,可以这么写:
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int n1 = 68;
int n2 = 79;
int n3 = 91;
int n4 = 85;
int n5 = 62;
}
}
但其实没有必要定义5个int变量。可以使用数组来表示“一组”int类型。
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[5];
ns[0] = 68;
ns[1] = 79;
ns[2] = 91;
ns[3] = 85;
ns[4] = 62;
}
}
定义一个数组类型的变量,使用数组类型“类型[]”,例如,int[]
。和单个基本类型变量不同,数组变量初始化必须使用new int[5]
表示创建一个可容纳5个int
元素的数组。
Java的数组有几个特点:
- 数组所有元素初始化为默认值,整型都是
0
,浮点型是0.0
,布尔型是false
; - 数组一旦创建后,大小就不可改变。
- 要访问数组中的某一个元素,需要使用索引。数组索引从
0
开始,例如,5个元素的数组,索引范围是0~4
。 - 可以修改数组中的某一个元素,使用赋值语句,例如,
ns[1] = 79;
。 - 可以用数组变量
.length
获取数组大小:
也可以在定义数组时直接指定初始化的元素,这样就不必写出数组大小,而是由编译器自动推算数组大小。例如:
public class Main {
public static void main(String[] args) {
// 5位同学的成绩:
int[] ns = new int[] { 68, 79, 91, 85, 62 };
System.out.println(ns.length); // 编译器自动推算数组大小为5
}
}
字符串数组
如果数组元素不是基本类型,而是一个引用类型,那么,修改数组元素会有哪些不同?
字符串是引用类型,因此我们先定义一个字符串数组:
String[] names = {
"ABC", "XYZ", "zoo"
};
对于String[]
类型的数组变量names
,它实际上包含3个元素,但每个元素都指向某个字符串对象,对names[1]
进行赋值,例如names[1] = "cat";
,这里注意到原来names[1]
指向的字符串"XYZ"
并没有改变,仅仅是将names[1]
的引用从指向"XYZ"
改成了指向"cat"
,其结果是字符串"XYZ"
再也无法通过names[1]
访问到了。
public class Main {
public static void main(String[] args) {
String[] names = {"ABC", "XYZ", "zoo"};
String s = names[1];
names[1] = "cat";
System.out.println(s); // XYZ"
}
}