`

java编译器对string常量表达式的处理和优化

    博客分类:
  • java
阅读更多
首先把问题摆出来,先看这个代码

String a = "ab";
String b = "a" + "b";
System.out.println((a == b));

打印结果会是什么?类似这样的问题,有人考过我,我也拿来考过别人(蛮好玩的,大家也可以拿来问人玩),一般答案会是以下几种:

1.true
    "a" + "b" 的结果就是"ab",这样a,b都是"ab"了,内容一样所以"相等",结果true
    一般java新人如是答。
2.false
    "a" + "a"会生成新的对象"aa",但是这个对象和String a = "ab";不同,(a == b)是比较对象引用,因此不相等,结果false
    对java的String有一定了解的通常这样回答。
3.true
    String a = "ab";创建了新的对象"ab"; 再执行String b = "a" + "b";结果b="ab",这里没有创建新的对象,而是从JVM字符串常量池中获取之前已经存在的"ab"对象。因此a,b具有对同一个string对象的引用,两个引用相等,结果true.
    能回答出这个答案的,基本已经是高手了,对java中的string机制比较了解。
    很遗憾,这个答案,是不够准确的。或者说,根本没有运行时计算b = "a" + "b";这个操作.实际上运行时只有String b = "ab";
    3的观点适合解释以下情况:
    String a = "ab";
    String b = "ab";
    System.out.println((a == b));
    如果String b = "a" + "b";是在运行期执行,则3的观点是无法解释的。运行期的两个string相加,会产生新的对象的。(本文后面对此有解释)

4.true
    下面是我的回答:编译优化+ 3的处理方式 = 最后的true
    String b = "a" + "b";编译器将这个"a" + "b"作为常量表达式,在编译时进行优化,直接取结果"ab",这样这个问题退化
    String a = "ab";
    String b = "ab";
    System.out.println((a == b));
    然后根据3的解释,得到结果true

    这里有一个疑问就是String不是基本类型,像
int secondsOfDay = 24 * 60 * 60;
    这样的表达式是常量表达式,编译器在编译时直接计算容易理解,而"a" + "b" 这样的表达式,string是对象不是基本类型,编译器会把它当成常量表达式来优化吗?
    下面简单证明我的推断,首先编译这个类:
public class Test {
    private String a = "aa";
}
       复制class文件备用,然后修改为
public class Test {
    private String a = "a" + "a";
}
    再次编译,用ue之类的文本编辑器打开,察看二进制内容,可以发现,两个class文件完全一致,连一个字节都不差.
    ok,真相大白了.根本不存在运行期的处理String b = "a" + "b";这样的代码的问题,编译时就直接优化掉了。


下面进一步探讨,什么样的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 + 基本类型是当成常量表达式直接求值来优化的。

再注意看这里的string都是"**"这样的,我们换成变量来试试:
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b));   //result = false
这个好理解,"a" + bb中的bb是变量,不能进行优化。这里很很好的解释了为什么3的观点不正确,如果String+String的操作是在运行时进行的,则会产生新的对象,而不是直接从jvm的string池中获取。

再修改一下,把bb作为常量变量:
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b));   //result = true
竟然又是true,编译器的优化好厉害啊,呵呵,考虑下面这种情况:
String a = "ab";
final String bb = getBB();
String b = "a" + bb;
System.out.println((a == b));    //result = false
private static String getBB() {
return "b";
}
看来java(包括编译器和jvm)对string的优化,真的是到了极点了,string这个所谓的"对象",完全不可以看成一般的对象,java对string的处理近乎于基本类型,最大限度的优化了几乎能优化的地方。

另外感叹一下,string的+号处理,算是java语言里面唯一的一个"运算符重载"(接触过c++的人对这个不会陌生)吧?
分享到:
评论
25 楼 skydream 2007-09-12  
这种题目只能考察出面试者对于java语言的某些细节的了解程度,当作小插曲聊聊天还可以,要据此对应试者的编程能力做出推断,是不可能的。


-----------------------

看来是我没有解释清楚了,其实我的意思也是如此,的确这个问题只是考察String这个一个非常小范围的了解程度,也仅此而已。

我只是觉得这个问题比问“String和StringBuffer的差别”这样的问题好些,因为后者即使答对了也完全不能说明什么(当然连这个都答不对那么就很成问题了),很难通过对方的回答来分辨新手和高手(同样只限于String这么一个非常小的范围),而我分析的这个问题则相比可以比较方便的考察出来对方对String的了解水准。

也仅此而已了,我觉得类似这种的细节问题,答出来说明这个知识点的掌握不错,答不出来则不应该就这一个问题作任何结论。
24 楼 ray_linn 2007-09-11  
skydream 写道

事实上没几个人会这么写:

变量X=常量A+常量B;

----------------------------------------

这里别说的这么绝对,某些情况下这种写法是存在的:

String words = "**  **"; 
因为长度很大,比如150个字符,超过了通常要求的单行80个字符的格式要求,而java中又没有类似c++的这种写法:
"aaaaaaaaa\
bbbbbbb"

因此可以考虑使用
String words = "**"
+ "**";
的写法,至少在sun的编译器下是达到理想的效果,即满足了格式要求又不带来额外的性能消耗。

当然如果有使用其他编译器的情况就只好想其他办法了。


请问,这种情况是不是在编程中是特例?而不是普适性的东西?如果仅仅是讨论某种特例,我当然不反对深入研究,可这东西作为考题,那就是不合适.
23 楼 hax 2007-09-11  
赞同ajoo。

你不要怪ajoo说的极端。矫枉必先过正。

这种题目只能考察出面试者对于java语言的某些细节的了解程度,当作小插曲聊聊天还可以,要据此对应试者的编程能力做出推断,是不可能的。

而且这也无关乎优化。字符串比较没有什么可以优化的,全部equals就完了。
22 楼 Illum 2007-09-11  
Stirng c="abab";
System.out.println(("abab" == c));
输出什么?Why?
21 楼 skydream 2007-09-11  

事实上没几个人会这么写:

变量X=常量A+常量B;

----------------------------------------

这里别说的这么绝对,某些情况下这种写法是存在的:

String words = "**  **"; 
因为长度很大,比如150个字符,超过了通常要求的单行80个字符的格式要求,而java中又没有类似c++的这种写法:
"aaaaaaaaa\
bbbbbbb"

因此可以考虑使用
String words = "**"
+ "**";
的写法,至少在sun的编译器下是达到理想的效果,即满足了格式要求又不带来额外的性能消耗。

当然如果有使用其他编译器的情况就只好想其他办法了。
20 楼 skydream 2007-09-11  
不跑题了,这个东西用来考人是否合适不要再争论了,反正每个人有每个人的想法,不要将自己的观点强加给人。

我写这个文章原本就是想将这个问题阐述清楚,因为见过类似的很多帖子和讨论。我不谦虚地说很多都没有将问题解释清晰,我个人认为我的理解和解释是比较合理和准确的,因此整理出来和大家分享,如果有误则希望能得到指正。

我个人的观点是这个话题对深入理解java/jvm对String的处理还有很有帮助的,和“i=i+++++i;”这样的话题不一样。
19 楼 eoeac 2007-09-11  
我觉着只要知道

String a1 = "a";
String a2 = new String("a2");

这两种写法完全是两个东西就可以。。关于编译器优化,有个印象就行不是重点。

而且Java中对String定义又确实比较特殊,不是把它当作纯对象去看。
18 楼 pikachu 2007-09-11  
defined behavior
unknow result.
这玩样就好像考cpp的
int i=1;
i=i+++++i;

一样无聊
17 楼 ray_linn 2007-09-11  
质疑:

这样应该不叫优化。因为事实上没几个人会这么写:

变量X=常量A+常量B;

99.9999%都是 变量x=常量A+变量Y.

这种优化是不是更近似画蛇添足 ?考官出这题,我是不是可以对他说“你吃饱了撑的?”

String的重点应该是值拷贝。
16 楼 duronshi 2007-09-11  
skydream 写道
首先把问题摆出来,先看这个代码

String a = "ab";
String b = "a" + "b";
System.out.println((a == b));

打印结果会是什么?类似这样的问题,有人考过我,我也拿来考过别人(蛮好玩的,大家也可以拿来问人玩),一般答案会是以下几种:

1.true
    "a" + "b" 的结果就是"ab",这样a,b都是"ab"了,内容一样所以"相等",结果true
    一般java新人如是答。
2.false
    "a" + "a"会生成新的对象"aa",但是这个对象和String a = "ab";不同,(a == b)是比较对象引用,因此不相等,结果false
    对java的String有一定了解的通常这样回答。
3.true
    String a = "ab";创建了新的对象"ab"; 再执行String b = "a" + "b";结果b="ab",这里没有创建新的对象,而是从JVM字符串常量池中获取之前已经存在的"ab"对象。因此a,b具有对同一个string对象的引用,两个引用相等,结果true.
    能回答出这个答案的,基本已经是高手了,对java中的string机制比较了解。
    很遗憾,这个答案,是不够准确的。或者说,根本没有运行时计算b = "a" + "b";这个操作.实际上运行时只有String b = "ab";
    3的观点适合解释以下情况:
    String a = "ab";
    String b = "ab";
    System.out.println((a == b));
    如果String b = "a" + "b";是在运行期执行,则3的观点是无法解释的。运行期的两个string相加,会产生新的对象的。(本文后面对此有解释)

4.true
    下面是我的回答:编译优化+ 3的处理方式 = 最后的true
    String b = "a" + "b";编译器将这个"a" + "b"作为常量表达式,在编译时进行优化,直接取结果"ab",这样这个问题退化
    String a = "ab";
    String b = "ab";
    System.out.println((a == b));
    然后根据3的解释,得到结果true

    这里有一个疑问就是String不是基本类型,像
int secondsOfDay = 24 * 60 * 60;
    这样的表达式是常量表达式,编译器在编译时直接计算容易理解,而"a" + "b" 这样的表达式,string是对象不是基本类型,编译器会把它当成常量表达式来优化吗?
    下面简单证明我的推断,首先编译这个类:
public class Test {
    private String a = "aa";
}
       复制class文件备用,然后修改为
public class Test {
    private String a = "a" + "a";
}
    再次编译,用ue之类的文本编辑器打开,察看二进制内容,可以发现,两个class文件完全一致,连一个字节都不差.
    ok,真相大白了.根本不存在运行期的处理String b = "a" + "b";这样的代码的问题,编译时就直接优化掉了。


下面进一步探讨,什么样的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 + 基本类型是当成常量表达式直接求值来优化的。

再注意看这里的string都是"**"这样的,我们换成变量来试试:
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b));   //result = false
这个好理解,"a" + bb中的bb是变量,不能进行优化。这里很很好的解释了为什么3的观点不正确,如果String+String的操作是在运行时进行的,则会产生新的对象,而不是直接从jvm的string池中获取。

再修改一下,把bb作为常量变量:
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b));   //result = true
竟然又是true,编译器的优化好厉害啊,呵呵,考虑下面这种情况:
String a = "ab";
final String bb = getBB();
String b = "a" + bb;
System.out.println((a == b));    //result = false
private static String getBB() {
return "b";
}
看来java(包括编译器和jvm)对string的优化,真的是到了极点了,string这个所谓的"对象",完全不可以看成一般的对象,java对string的处理近乎于基本类型,最大限度的优化了几乎能优化的地方。

另外感叹一下,string的+号处理,算是java语言里面唯一的一个"运算符重载"(接触过c++的人对这个不会陌生)吧?
15 楼 dwangel 2007-09-08  
基本上,比较好的编译器都会对静态表达式进行优化。

貌似c++的template有个什么template函数化的做法,
定义个template,通过编译器对template的展开,
来实现有限次递归性函数,来提高性能。
14 楼 Eastsun 2007-09-08  
ajoo 写道
我的答案:undefined behavior

这种题拿来考人,一个字儿:扯淡


ajoo此言差矣!
这些都是defined behavior
自己去看看<The Java™ Language Specification>就知道了.
里面对String的这些行为规定的清清楚楚,每个符合JAVA 规范的JDK/JRE都应该这样实现的,怎么能够说是undefined behavior呢?

而且这种问题是有意义的,某些情况下对程序的优化就需要了解这些知识.
13 楼 skydream 2007-09-07  
好久没有来看我的blog了。

看了一下上面的东西,突然想起来,那位说“这种题拿来考人,一个字儿:扯淡”的朋友,呵呵,可能不会想到。其实真有人考过我的,06年我曾去深圳微软面试,当时就有人问我类似的问题。
12 楼 skydream 2007-01-19  
ajoo 写道
我的答案:undefined behavior

这种题拿来考人,一个字儿:扯淡
这话说的过了,这个题目拿来考人,还是可以非常准确的考核一个人对java中string机制的理解。

答案是什么不重要,true.false或者undefined behavior不是关键,关键是回答的时候能否将3和4讲清楚。

基本这个问题问下来,对方对string的理解到了什么地方就一目了然,从回答1的新人,回答2的熟手,到回答3,4的高手(仅限string这个一个很小的领域),对于面试之类时间很短要求快速考核对方java语言水准的场合,类似的问题是非常有效的。远比问String,StringBuffer差别的问题有效的多,这个问题只适合考验入门级的新手。

ajoo你既然对这个问题这么反感,那请你拿出一个考string的问题来试试,看能否做到同样的效果?

11 楼 ajoo 2007-01-19  
我的答案:undefined behavior

这种题拿来考人,一个字儿:扯淡
10 楼 ll_2046 2007-01-18  
学习ing
9 楼 ahuaxuan 2007-01-18  
s1==s2.intern()应该是true
8 楼 LucasLee 2007-01-18  
String这个东西也挺搞的,看了这个帖子,才知道我昨天做了一道题目错了。:(
立即写了个小程序测试:
	public static void main(String[] args) {
		String s1 = "ab";
		String s2="a"+"b";
		System.out.println((s1==s2)+":"+(s1.equals(s2)));
		StringBuffer sb1=new StringBuffer("ab");
		StringBuffer sb2=new StringBuffer().append("a").append("b");
		System.out.println((sb1==sb2)+":"+(sb1.equals(sb2)));
	}

结果是
true:true
false:false

对于StringBuffer,只有引用相同,==和equals()才会成立。我看了equals()源码,直接就是返回==比较的结果。
对于String,正如楼主的编译器优化的说法,我再写了一个程序:
	private static String getString(){
		return "b";
	}
	public static void main(String[] args) {
		String s1 = "ab";
		String s2="a"+getString();
		System.out.println((s1==s2)+":"+(s1.equals(s2)));
	}

结果:
false:true

这个程序运行时产生的String对象是equals的,但==不成立。

7 楼 skydream 2007-01-18  
还有对于jvm对string的处理和优化,我的感觉(还不确定,请大家指正)是这样,jvm里面的string池,似乎只使用于两个情况:

1.在编译后的*.class文件里面定义的"abc"这样的"直接"常量
   对于new String(), String + String/基本类型,toString()方法之类生成的string不是从string池里面取,而是直接生成新的string类。
2.调用string的intern()方法
   这个算是"强制"加入池吧
6 楼 skydream 2007-01-18  
修改了一下文档的内容,将3的阐述改的清楚了一些。

实际这里存在两个问题
1.   3解释下面为true的问题
String a = "ab";
String b = "ab";
System.out.println((a == b));
   但是3的错误在于3中认为在运行期String b = "a"+"b";可以等同于String b = "ab";
2.   4通过编译器优化的解释解决了
String b = "a"+"b";等同于String b = "ab";的问题

相关推荐

    2Java SE(上).doc

    java编译器有一个优化措施,就是若计算表达式运算符两边都是字面量,那么编译器在生成class文件时就将结果计算完毕并保存到编译后的class文件中了。 3. String使用了final修饰,不能被继承 方法: 1)int length...

    JAVA复习资料

    16、据程序的构成和运行环境的不同,Java源程序分为两大类: Application 程序和 Applet 程序。 17、如果一个Java源程序文件中定义有4个类,则使用Sun公司的JDK编译器javac编译该源程序文件将产生 4 个文件名与类名...

    Java面试宝典(传说中的葵花宝典).doc

    由于 += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。 7、char型变量中能不能存贮一个中文汉字?为什么? char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字...

    java 面试题 总结

    java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 6、说出Servlet的生命周期,并说出Servlet和CGI的区别。 Servlet被服务器实例化后,容器运行其init方法,...

    Java 语言基础 —— 非常符合中国人习惯的Java基础教程手册

    观事务的软件化模拟,是变量(数据和数据结构)和相关方法(对数据操作和对象管理的程 序)的软件组合。 在面向对象的程序设计中,你可以用软件对象表示现实世界的对象,而这些软件对象和 现实世界对象是相对应的。...

    超级有影响力霸气的Java面试题大全文档

    java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 9、说出Servlet的生命周期,并说出Servlet和CGI的区别。  Servlet被服务器实例化后,容器运行其init方法...

    C++大学教程,一本适合初学者的入门教材(part1)

    第19章 string类与字符串流处理 19.1 简介 19.2 string的赋值与连接 19.3 比较string 19.4 子串 19.5 交换string 19.6 string的特性 19.7 寻找string中的字符 19.8 替换string中的字符 19.9 在string中插入...

    C++大学教程,一本适合初学者的入门教材(part2)

    第19章 string类与字符串流处理 19.1 简介 19.2 string的赋值与连接 19.3 比较string 19.4 子串 19.5 交换string 19.6 string的特性 19.7 寻找string中的字符 19.8 替换string中的字符 19.9 在string中插入...

    PHP5 完整官方 中文教程

    Strings — String 字符串处理函数 SVN — Subversion 函数 SWF — Shockwave Flash Functions swish — Swish Functions Sybase — Sybase Functions TCP Wrappers — TCP Wrappers Functions tidy — Tidy ...

    PHP5中文参考手册

    Strings — String 字符串处理函数 SVN — Subversion 函数 SWF — Shockwave Flash Functions swish — Swish Functions Sybase — Sybase Functions TCP Wrappers — TCP Wrappers Functions tidy — Tidy ...

    C#微软培训资料

    第五章 变量和常量 .44 5.1 变 量 .44 5.2 常 量 .46 5.3 小 结 .47 第六章 类 型 转 换 .48 6.1 隐式类型转换 .48 6.2 显式类型转换 .53 6.3 小 结 .56 第七章 表 达 式 .58 7.1 操 作 符 .58 ...

    【05-面向对象(下)】

     –使用函数式接口对Lambda表达式进行强制类型转换。 方法引用与构造器引用 种类 示例 说明 对应的Lambda表达式 引用类方法 类名::类方法 函数式接口中被实现方法的全部参数传给该类方法作为...

    PHP手册2007整合中文版

    其语法利用了 C,Java 和 Perl,非常容易学习。该语言的主要目标是让 web 开发人员可以很快写出动态生成的网页,但 PHP 的功能远不止如此。 目录 前言 作者和贡献者 I. 入门指引 1. 简介 2. 简明教程 II. 安装与...

    PHP官方手册中文版

    String 字符串处理函数 CLXII. Subversion 函数 CLXIII. Shockwave Flash Functions CLXIV. Swish Functions CLXV. Sybase Functions CLXVI. TCP Wrappers Functions CLXVII. Tidy Functions CLXVIII. ...

Global site tag (gtag.js) - Google Analytics