4.Java 的运算系统
一:基本运算符
+,+=-,-=*,*=/,/=%,%=-(取反)
基本运算符非常简单,涉及到的无非是加,减,乘,除,取模运算。
值得一提的是取模运算是带符号运算,初学者很容易踩到这个坑。
示例:
// 判断一个整数是不是奇数
public static boolean isOdd(int num){
return num % 2 == 1;
}
初学者很有可能写出这样的代码,对于判断一个整数是否为基数,我们还要考虑输入为负数的情况
如果 num 为负数,譬如 -3 ,那么 -3 % 2 的结果为 -1 !
所以,判断一个整数是否为奇数的代码应该改为:
// 判断一个整数是不是奇数
public static boolean isOdd(int num){
return num % 2 != 0;
}
/ 和 % 运算符比较经典的应用是求一个数字的数位和
什么叫一个数字的数位和?譬如,有数字 921 ,该数字的数位和为:9 + 2 + 1 = 12
获取一个数字的数位和代码:
int getSum(int num){
int res = 0;
while(num != 0){
res += num % 10;
num = num / 10;
}
return num;
}
自增自减运算符
i++++ii----i
i++ 和 ++i 有什么区别呢?答案是:赋值的顺序不同。
i++ 是先赋值后加 1;++i 则是先加 1 再赋值
我们来看一个程序:
public class Main {
public static void main(String[] args) {
int a = 0;
int b = 0;
System.out.println(a++);
System.out.println(++b);
}
}
该程序输出的结果为:
0
1
原因就在于,System.out.println(a++); 会先将表达式的结果打印,然后再执行 a + 1 这个操作,所以打印输出的结果为 0;System.out.println(++b); 则会先执行 b + 1 ,然后打印结果,所以打印的结果值为 1 。
来看一道比较经典题目:
public class Main {
public static void main(String[] args) {
int i = 0;
i = i++ + ++i;
int j = 0;
j = ++j + j++ + j++ + j++;
System.out.println(i);
System.out.println(j);
}
}
请说出该程序输出的结果?
该程序输出的结果为:
2
7
没有做对的小伙伴不如仔细思考下,一定要明确的是,+ 运算符两侧连接的是两个表达式,我们只需要搞清楚表达式的值这个概念,本题就可以迎刃而解。
比较运算符
><==>=<=!=
比较运算符非常简单,使用比较运算符进行两个变量的比较操作,其结果返回的是一个布尔值。
初学者一定要明确的是 = 和 == ,前者是赋值操作,后者才是比较变量值是否相等。
二:逻辑运算符与短路特性及断路特性
&&||!
逻辑与:&&
逻辑或:||
非:!
逻辑运算符两侧连接的是两个布尔变量,返回的结果仍是布尔值。
当两个值均为真时,逻辑与的结果才返回 true,否则返回 false;当两个值有一个为真,逻辑或的结果就返回 true,只有两个值均为假,逻辑或才返回 false。
逻辑运算符的短路特性
我们先来看两个示例程序
程序一:
public class Main {
public static boolean trueThing() {
System.out.println("this is true!");
return true;
}
public static boolean falseThing() {
System.out.println("this is false!");
return false;
}
public static void main(String[] args) {
if (trueThing() || falseThing()) {
}
}
}
该程序输出的结果为:
this is true!
我们看到,在 if 判断中,只执行了 trueThing() 的代码,并没有执行 falseThing() 的代码,这是因为 || 运算具有 短路特性,当判断出第一条语句为真时,后面的部分就没有执行的意义了,因为结果必然返回 true!
程序二:
public class Main {
public static boolean trueThing() {
System.out.println("this is true!");
return true;
}
public static boolean falseThing() {
System.out.println("this is false!");
return false;
}
public static void main(String[] args) {
if (falseThing() && trueThing()) {
}
}
}
该程序输出的结果为:
this is false!
我们看到,在 if 判断中,又是只执行了前半段,没有执行后半段,其原因在于,&& 运算具有断路特性,当判断出第一条语句为假时,后面的部分就没有执行的意义了,因为结果必然返回 false !
三:三元运算符
三原运算符是软件编程中的一个固定格式,语法为:
条件表达式 ? 表达式1:表达式2;
如果条件表达式为 true 那么则调用表达式 1,否则就调用表达式 2
示例程序:
对 res 进行赋值
如果满足 a > b ,则 res 复制为 1
如果满足 a < b,则 res 赋值为 -1
如果a == b,则 res 赋值为 0
上述需求可以用三元运算符的方式简洁地实现:
int res = a > b ? 1 : (a < b ? -1 : 0);
四:位运算详解与实战
位运算符
~,按位取反&,按位与|,按位或^,异或<<,左移>>,带符号右移>>>,无符号右移(总是补 0)
用最有效率的方法计算2乘以8
最有效率的方法即位运算。
<<左移;左移一位相当于乘以2>>带符号右移;正数右移高位补0,负数右移高位补1右移一位相当于除以
24 >> 1结果为2-4 >> 1结果为-2>>>无符号右移;无论正数负数,高位均补0对于正数而言
>>和>>>无区别对于负数而言,举例:
-2 >>> 1结果为:2147483647(Integer.MAX_VALUE)
所以,本题要求使用最有效率的方法来计算2 * 8
我们可以:
int res = 8 << 1
即可。
& 和 && 的区别
首先
&和&&都可以作为逻辑与的运算符;区别是&&具有断路特性,如果&&符号左边的表达式为false,则不再计算第二个表达式&可以用作位运算符,当&两边的表达式不是boolean类型时,&表示按位与的操作1 & 1 = 11 & 0 = 00 & 0 = 0
如示例程序:
public class Test {
public static void main(String[] args) {
// 0x7A : 0111 1010
// 0x53 : 0101 0011
// 0x7A & 0x53 = 0101 0010 = 0x52
System.out.println(Integer.toHexString(0x7A & 0x53));
}
}
结果输出:
52
我们在上面也提到了,& 也可以作为逻辑与,和 && 不同的是,& 是不具有断路特性的。
如示例程序:
public class Main {
public static boolean trueThing() {
System.out.println("this is true!");
return true;
}
public static boolean falseThing() {
System.out.println("this is false!");
return false;
}
public static void main(String[] args) {
if (falseThing() & trueThing()) {
}
}
}
该程序输出的结果为:
this is false!
this is true!
如我们所想,即使第一个表达式为假,使用 & 运算符还是会执行第二个表达式。
| 和 || 也是一样的;| 也可以用作逻辑或运算,其也不具有 || 的短路特性。
使用最快的方式,交换整型数组中任意两个位置的数字
我们可以使用异或运算快速交换整型数组中任意两个位置的数字。
异或运算是一种基于二进制的位运算,对于运算符^两侧数的每一个二进制位,同值取0,异值取1,形象地说就是不进位加法。
异或运算满足以下的几种性质:
交换律
结合律
对于任何数
x,都满足x ^ x = 0;x ^ 0 = x
所以,通过异或运算可以快速交换数组中任意两个位置的数字,代码如下:
public class Main {
public static void swap(int[] nums,int i,int j){
if(i == j || nums[i] == nums[j]){
return;
}
nums[i] = nums[i] ^ nums[j];
nums[j] = nums[i] ^ nums[j];
nums[i] = nums[i] ^ nums[j];
}
}
一个数组中,只有一个数出现了奇数次,其他数则均出现偶数次,如何找到这个数
利用异或^的运算性质可以轻松求解
异或(XOR)运算性质:
两个
bit位数字相同,异或结果为0两个
bit位数字不同,异或结果为1
也就是说:
0 ^ 0 = 00 ^ 1 = 11 ^ 0 = 11 ^ 1 = 0
并且,异或运算满足交换律和结合律
a ^ b = b ^ a(a ^ b) ^ c = a ^ (b ^ c)
回到本问题:
已知数组中,只有一个数出现了奇数次,其余数均出现偶数次
思路:
将数组中所有数异或起来,偶数次的数字异或起来结果必为0
最后就是出现奇数次的那个数和0异或,结果为这个数字
参考代码:
public class XORDemo {
public int findOddTimesNum(int[] nums){
int xor = 0;
for(int n : nums){
xor ^= n;
}
return xor;
}
}
位运算中的几个小技巧
n & (n - 1)
n & (n - 1)是位运算中非常重要的一种技巧,它是将二进制数n最右边的1变成0的操作
示例:n = 10101000
n = 10101000
n - 1 = 10100111
n & (n - 1) = 10100000 // 将二进制数n最右边的那个1变成了0,其余不变
n & (~n + 1)
n & (n - 1) 可以找到一个二进制数 n 最右边的那个 1 代表的数
来看示例:
num = 10101000
我们知道num最右边的那个1代表数为00001000
~num = 01010111
~num + 1 = 01011000
num & (~num + 1) = 00001000
这也是一个涉及位运算的算法题中常用的小技巧,它可以找到一个二进制数 n 最右边的那个 1 代表的数
五:运算符优先级与字符串加法
关于运算符的优先级,我们只需要明确两点即可:
乘除高于加减
其他全部加括号,括号的优先级最高!
字符串加法
字符串拼接调用 toString 方法或者原生类型对应的表示
JDK 偷偷把字符串连接转换成 StringBuilder 调用
来看一个程序:

我们在 StringBuilder 类的构造器上打一个断点

使用 IDEA 的 debug 模式运行程序,我们会发现,程序最终会停留在断点处。
这是因为:
JDK 会偷偷把字符串连接转换成 StringBuilder 类的调用,进行字符串拼接,以节省内存空间。
为什么要这样做呢?
因为字符串本身是一个不可变类。
String类中使用final关键字字符数组保存字符串
private final char value[];
所以String对象是不可变的,这就导致了每次对String对象添加,更改或删除字符的操作都会产生一个新的String对象。不仅效率低下,而且浪费了空间。
所以,JDK 偷偷帮我们调用了 StringBuilder 类,在我们对 StringBuilder 类的对象进操作时,不会像 String 类那样频繁创建新的对象,而是都在同一块内存上进行字符串的增删改操作,减少了零碎的对象带来的内存压力,大大节省了空间。
爪哇笔记
关于 LearnKu