这是Thinging in Java学习笔记的第三篇,主要是一些零散的笔记。
不足之处,欢迎斧正。
索引
Java 操作符优先级问题
int a = 1;
int b = 2;
System.out.println(a + b +"结束");
int + int = int
int +String = String
// 3结束
System.out.println("开始" + a + b +"结束");
String + int = String
String + int = String
String + String = String
// 开始12结束
System.out.println("开始" + a + b);
String + int = String
String + int = String
// 开始12
System.out.println(a + b + "中间" + a + b);
int + int = int
int + String = String
String + String = String
String + int = String
String + int = String
// 3中间12
System.out.println(a + b + "中间" + (a + b));
String + int = String
String + int = String
String + String = String
int + int = int
String + int = String
// 3中间3
由上面的代码可以看出:
1. System.out.print(println)中的 + 与正常的操作符操作是一样的。
2. + 号计算遵循计算优先级法则。
3. 一个字符串加上一个数字是把数字转换成字符串(toString)后再连接的,与数学计算无关。
别名现象
Tank t1 = new Tank();
Tank t2 = new Tank();
t1.level = 9;
t2.level = 47;
System.out.println("1: t1.level: " + t1.level +", t2.level: " + t2.level);
t1 = t2;
System.out.println("2: t1.level: " + t1.level +", t2.level: " + t2.level);
t1.level = 27;
System.out.println("3: t1.level: " + t1.level +", t2.level: " + t2.level);
/* Output:
*1: t1.level: 9, t2.level: 47
*2: t1.level: 47, t2.level: 47
*3: t1.level: 27, t2.level: 27
*/
在 1 的时候t1和t2还是相互独立的,但由于赋值操作的是一个对象的引用,所以修改t1的同时也修改了t2!
为了防止出现“别名现象”(Aliasing),可以这样写
t1.level = t2.level;
方法调用中的别名问题
class Letter {
char c;
}
public class PassObject {
static void f(Letter y) {
y.c = 'z';
}
public static void main(String[] args) {
Letter x = new Letter();
x.c = 'a';
System.out.println("1: x.c: " + x.c);
f(x);
System.out.println("2: x.c: " + x.c);
}
}
/* Output:
*1: x.c: a
*2: x.c: z
*/
在Java中方法调用传递的是一个引用。
操作符提前停止(短路)
&& 运算符
public static void main(String[] args) {
boolean b = test1()&&test2()&&test3();
System.out.println(b);
}
public static boolean test1(){
System.out.println("test1");
return true;
}
public static boolean test2(){
System.out.println("test2");
return false;
}
public static boolean test3(){
System.out.println("test3");
return true;
}
/* Output:
* test1
* test2
* false
*/
当&&运算符发现有错误(false)时,立即返回错误(false),而不进行后面的运算了。
|| 运算符
public static void main(String[] args) {
boolean b = test1()||test2()||test3();
System.out.println(b);
}
public static boolean test1(){
System.out.println("test1");
return false;
}
public static boolean test2(){
System.out.println("test2");
return true;
}
public static boolean test3(){
System.out.println("test3");
return true;
}
/* Output:
* test1
* test2
* true
*/
当&&运算符发现有正确(true)时,立即返回正确(true),而不进行后面的运算了。
利用这一特性,我们可以将易于判断(运算量小)的操作放在前面,以优化代码运行时间。
字符串连接耗时
public static void main(String[] args) {
test1();
test2();
}
public static void test1(){
long startTime=System.nanoTime();
String a = "String a",b = "String b",c = "String c";
String d = "";
for(int i=1;i<10000;i++){
d = d + a + b + c;
}
long endTime=System.nanoTime();
System.out.println("程序运行1时间: "+(endTime-startTime)+"ns");
}
public static void test2(){
long startTime=System.nanoTime();
String a = "String a",b = "String b",c = "String c";
StringBuilder stringBuilder = new StringBuilder();
for(int i=1;i<10000;i++){
stringBuilder.append(a);
stringBuilder.append(b);
stringBuilder.append(c);
}
long endTime=System.nanoTime();
System.out.println("程序运行2时间: "+(endTime-startTime)+"ns");
}
/* (JDK11)
* 程序运行1时间: 232712818ns
* 程序运行2时间: 1746122ns
*/
/* (JDK10)
* 程序运行1时间: 239926036ns
* 程序运行2时间: 1238935ns
*/
/* (JDK8)
* 程序运行1时间: 1314058092ns
* 程序运行2时间: 494996ns
*/
可以看出,直接用 + 号连接无论在哪个JDK版本都会比 StringBuilder 慢。
在JDK8以后(准确是JDK9),String的 + 连接进行了优化,但StringBuilder的速度却下降了(?)。
不过即使在这种情况下,StringBuilder也比直接+快200倍以上,如果是JDK8这个倍数可以达到2600倍!
goto语句 Java中的跳转
int x=10;
test1:
for(int i=0;i<3;i++) {
switch (x) {
case 10:
System.out.println("case 10");
break test1;
default:
System.out.println("default");
break;
}
}
System.out.println("End");
/* Output:
* case 10
* End
*/
int x=10;
test2:
for(int i=0;i<3;i++) {
switch (x) {
case 10:
System.out.println("case 10");
continue test2;
default:
System.out.println("default");
break;
}
}
System.out.println("End");
/* Output:
* case 10
* case 10
* case 10
* End
*/
Java中没有goto语句,但可以使用 continue label; 或 break label; 来进行跳转。
在Java中,标签起作用的唯一的地方刚好是在迭代语句之前。
continue lable; 同时中断内部迭代以及外部迭代,直接转到 label 处,随后继续进入迭代。它实际上是继续迭代过程,但却从外部迭代开始。
break label; 也会中断所有迭代,并回到 label 处,但并不重新进入迭代。也就是说,它实际是完全终止了两个迭代。
参考资料
《Thinking in Java》 Fourth Edition ——Bruce Eckel