Java中的常量优化机制
问题抛出:静态常量可以再编译器确定字⾯量,但常量并不⼀定在编译期就确定了, 也可以在运⾏时确定.所以Java针对某些情况制定了常量优化机制。
常量优化机制:给⼀个变量赋值,如果等于号的右边是常量的表达式并且没有⼀个变量,那么就会在编译阶段计算该表达式的结果,然后判断该表达式的结果是否在左边类型所表⽰范围内,如果在,那么就赋值成功,如果不在,那么就赋值失败。但是注意如果⼀旦有变量参与表达式,那么就不会有编译期间的常量优化机制
结合问题,我们就可以⼤致猜出,如果常量能在编译期确定就会有优化,不能的话就不存在。
下⾯我们来详细讲解⼀下这个机制,Java中的常量池常量优化机制主要是两⽅⾯
⼀、就是对于byte/short/char三种类型的常量优化机制
先贴出⼀张Java⼋⼤数据类型⼤⼩范围表以供参考踢足球英语怎么写
1.以下⾯这个程序为例
byte b1 =1+2;
System.out.println(b4);
// 输出结果 7
运⾏结果解释:1和2都是常量,Java有常量优化机制,就是可以编译时可以明显确定常量结果,所以直接把1和2的结果赋值给b4了。(和直接赋值3是⼀个意思)
2.换⼀种情况看看,把右边常量改成变量
byte b1 =3;
byte b2 =4;
东方明珠英语
byte b3 = b1 + b2;
System.out.println(b3);// 程序报错
程序报错了,意思说类型不匹配:⽆法从int转换为byte
解释原因,从两个⽅⾯:
1. byte 与 byte (或者 short char ) 进⾏运算的时候会提升int 两个int 类型相加的结果也是int 类型
2. b1 和 b2 是两个变量,变量存储的是变化 ,在编译的时候⽆法判断⾥⾯的值,相加有可能会超出byte的取值
这就是为什么⼀旦有变量参与表达式,那么就不会有编译期间的常量优化机制
3.在这⾥我们试着把变量添加final改回常量,看看⼜有什么结果
final byte b1 =1;
final byte b2 =2;
byte b3 = b1 + b2;
System.out.println(b3);
发现程序可以正常运⾏,输出结果为3,所以可知常量优化机制⼀定是针对常量的。
4.接下来我们再看另外⼀个程序
byte b1 =127+2;
System.out.println(b4);
程序再次报错,同样也是类型不匹配:⽆法从int转换为byte,这⾥解释⼀下,byte取值范围为-128~12
7;很明显右边表达式的结果是否在左边类型所表⽰范围,这个就是导致此错误出现的原因。
5.某些场景下,取值范围⼤的数据类型(int)可以直接赋值给取值范围⼩的(byte、shor、char),⽽且只能特定int赋值给
byte/short/char,其他基本数据类型不⾏,如下图。
int num1 =10;
final int num2 =10;
byte var1 = num1 +20;// 存在变量,编译报错
byte var2 = num2 +20;// 编译通过
这个也是常量优化机制的⼀部分
所以我们这⾥总结⼀下byte/short/char三种类型的常量优化机制
1. 先判断值是否是常量, 然后再看值是否在该数据类型的取值范围内
2. 只有byte, short, char 可以使⽤常量优化机制,转换成int类型(这个你换成其他基本数据类型就不适应了)
来个程序测试⼀下,下⾯这个就是单纯把之前的byte改成了int型,发现并不像之前报错,反⽽正常运⾏,输出结果3,所以就说明了只有byte, short, char 可以使⽤常量优化机制
int a =1;
int b =2;
int c = a + b;
作一
System.out.println(c);
拓展⼀下(易错点):拉肚子吃苹果
byte var =10;
var = var +20;// 编译报错,运算中存在变量
var +=20;// 等效于: var = (short) (var + 20); 没有⾛常量优化机制,⽽是进⾏了类型转换
⼆、就是对于编译器对String类型优化(这个是重点难点)
String s1 ="abc";
String s2 ="a"+"b"+"c";
System.out.println(s1 == s2);
这个输出的结果是多少呢?有⼈就会认为 “a” + “b”+“c"会⽣成新的对象"abc”,但是这个对象和String s2 = "abc"不同,(a == b)是⽐较对象引⽤,因此不相等,结果为fal。
如果你是这样想的话,那恭喜你对java的String有⼀定了解,但是你不清楚Java的常量池常量优化机制。
这个代码正确输出结果为true
那么到底为什么呢,下⾯就来解释⼀下原因:
String s2 = “a” + “b”+“c”;编译器将这个"a" + “b”+“c"作为常量表达式,在编译时进⾏优化,直接取表达式结果"abc”,这⾥没有创建新的对象,⽽是从JVM字符串常量池中获取之前已经存在的"abc"对象。因此a,b具有对同⼀个string对象的引⽤,两个引⽤相等,结果true。
意思是说先通过优化,代码简化为
中国城市gdp排名
String s1 ="abc";
String s2 ="abc";
System.out.println(s1 == s2);
再基于jvm对String的处理机制的基础上,得出true的结论。
1.下⾯进⼀步探讨,什么样的String + 表达式会被编译器当成常量表达式?
String b ="a"+"b";
这个String + String被正式是ok的,那么string + 基本类型呢?
String a ="a1";
String b ="a"+1;
System.out.println((a == b));//result = true
素质拓展训练String a ="atrue";
String b ="a"+true;
System.out.println((a == b));//result = true
String a ="a3.4";
String b ="a"+3.4;
System.out.println((a == b));//result = true
可见编译器对String + 基本类型是当成常量表达式直接求值来优化的。
2.既然常量弄完了,我们换成变量来试试
String s1 ="ab";
String s2 ="abc";
个人利益最大化String s3 = s1 +"c";
System.out.println(s3 == s2);
输出的结果是fal
这⾥我们就可以看到常量优化只是针对常量,如果有变量的话就不能被优化
运⾏原理:String s3 = s1+“c”;这⼀句话,是在StringBuffer缓冲区中进⾏创建⼀个StringBuffer对象,将两者相加。但是对s3进⾏赋值时不能够直接将缓冲区的对象地址取来⽽是⽤toString⽅法变成另外的堆内存,然后赋值给s3,所以,s3和s2的地址值已经不同了,所以输出fal。
这⾥我们还可以拓展⼀下,把s1前⾯加final修饰符修改为常量看看
final String s1 ="ab";
String s2 ="abc";
String s3 = s1 +"c";
System.out.println(s2 == s3);
输出的结果居然变成了true,看来只要是进⼊常量池的常量,就有可能存在常量优化机制
3.再往⾥⾛⼀点,观察下⾯程序
private static String getS(){
return"b";
}
String s1 ="abc";
String s2 ="a"+getS();
System.out.println((s1 == s2));
结果⼜是出⼈意料,竟然是fal
运⾏原理:编译器发现s2值是要调⽤函数才能计算出来的,是要在运⾏时才能确定结果的,所以编译器就设置为运⾏时执⾏到String
s3=“a” + getS();时 要重新分配内存空间,导致s2和s1是指向两个不同的内存地址,所以==⽐较结果为fal;
看来String这个所谓的"对象",完全不可以看成⼀般的对象,Java对String的处理近乎于基本类型,最⼤限度的优化了⼏乎能优化的地⽅。我们来举个例⼦总结⼀下上⾯所有内容
public static void main(String[] arge){
//1
String str1 =new String("1234");
String str2 =new String("1234");
System.out.println("new String()==:"+(str1 == str2));
//2
String str3 ="1234";
String str4 ="1234";
System.out.println("常量字符串==:"+(str3 == str4));
//3
String str5 ="1234";
String str6 ="12"+"34";
System.out.println("常量表达式==:"+(str5 == str6));
//4
String str7 ="1234";
String str8 ="12";
String str9 = str8 +"34";
System.out.println("字符串和变量相加的表达式==:"+(str7 == str9));
//5
final String val ="34";
String str10 ="1234";
String str11 ="12"+ val;
System.out.println("字符串和常量相加的表达式==:"+(str10 == str11));
//6
超市的拼音String str12 ="1234";
String str13 ="12"+34;
System.out.println("字符串和数字相加的表达式==:"+(str12 == str13));
//7
String str14 ="12true";
String str15 ="12"+true;
System.out.println("字符串和Boolen相加表达式==:"+(str14 == str15));
//8
String str16 ="1234";
String str17 ="12"+getVal();
System.out.println("字符串和函数得来的常量相加表达式==:"+(str16 == str17));
}
private static String getVal()
{
return"34";
}
运⾏输出:
new String()==:fal
常量字符串==:true
常量表达式==:true
字符串和变量相加的表达式==:fal
字符串和常量相加的表达式==:true
字符串和数字相加的表达式==:true
字符串和Boolen相加表达式==:true
字符串和函数得来的常量相加表达式==:fal
代码分析:
Java中,String是引⽤类型;==是关系运算符,==⽐较两个引⽤类型时,判断的依据是:双⽅是否是指向了同⼀个内存地址。