问题:
为什么程序在main方法执行和在junit单元测试结果不一致 ?
测试步骤:
1、先抛代码,看结果(JDK8下测试,Junit4.x)2、纠正程序再度运行
3、疑惑
4、反思
一、先抛代码,看结果(JDK8下测试)
事情是这样子的,上个星期有个朋友截图发我一块代码,说他这个代码咋地咋地,最后说在main方法和junit测试结果不同。
我怀着不相信的态度执行了一次,初次的结果是一致的。所以我就告诉他结果是一致的
本来事情算是告了一段落,晚上睡觉前突然好像发现什么东西漏了。
早上起来再测试了一次… 哎,昨晚信誓旦旦的说没问题😂
果然,结果如他的一样,我失算了!
Main方法
public static void main(String[] args) {
String s1 = new String("1") + new String("1");
s1.intern();
String s2 = "11";
System.out.println(s1 == s2);
}
Junit方法
@Test
public void test(){
String s1 = new String("1") + new String("1");
s1.intern();
String s2 = "11";
System.out.println(s1 == s2);
}
第一种结果是 true,第二种结果是 false
首先,为什么我昨晚执行的结果会一致呢?
很不好意思,我昨晚漏写了intern(),所以 两者的结果都是 false …
假如没写 intern(),那么我们来分析一下反编译后的代码
首先看Main方法,先看s1 , 他new 了两个 “1” ,然后通过StringBuilder 的append方法对俩字符串进行添加操作,最后用 StringBuilder 的 toString 方法返回了一个 堆中的字符串对象 “11”。
其次看s2,他是直接在字符串常量池分配了一个对象 “11”
所以 堆中的"11" 并不等于 字符串常量池中的"11" , 返回 false
test方法里面也是跟main方法里面是一样的,所以返回 false。
二、纠正程序再度运行
那加上intern()呢?
public static void main(String[] args) {
String s1 = new String("1") + new String("1");
s1.intern();
String s2 = "11";
System.out.println(s1 == s2);
}
@Test
public void test2(){
//10,11,12这三个比较特殊
String s1 = new String("1") + new String("1");
s1.intern();
String s2 = "11";
System.out.println(s1 == s2);
}
首先,前者的执行结果是 true,后者的执行结果是 false。
先了解 intern() 是干嘛的
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
返回字符串对象的规范表示形式。
最初为空的字符串池由
类{@code-String}。
调用intern方法时,如果池已包含
等于此{@code string}对象的字符串,由
使用{@link#equals(Object)}方法,则池中的字符串为
返回。否则,此{@code String}对象将添加到
并返回对该{@code String}对象的引用。
因此,对于任何两个字符串{@code s}和{@code t},
{@code.s.intern()==t.intern()是{@code true}
当且仅当{@code s.equals(t)}是{@code true}。
所有文字字符串和字符串值常量表达式都是
实习。字符串文字在的第3.10.5节中定义
<引用>Java&trade;语言规范</cite>。
@返回一个与此字符串内容相同的字符串,但
保证来自唯一字符串池。
String.intern()是一个Native方法
在JDK1.6版本时:如果字符常量池中已经包含一个等于此String对象的字符串,则返回常量池中字符串的引用,否则,将新的字符串放入常量池,并返回新字符串的引用
在JDK1.7以后,如果字符常量池中已经包含一个等于此String对象的字符串,则返回常量池中字符串的引用,否则,将此字符串的引用放入常量池,并返回此引用
所以,main程序的s1,因为在堆中,常量池里面没有,所以它将指向字符串的引用放入常量池,s2创建时发现常量池已经有了引用(指向的值是堆中的"11"),所以s2指向常量池的引用,比较结果为 true。
那test程序的s1呢?怎么就故事到这就偏离了呢?
一开始我还以为难道 junit 里面测试使用的jdk版本是1.6?
怎么测试结果是1.6的结果,后来发现换成除10,11,12之外的字符串就是同1.8的结果一致
所以此猜想被推翻…
任我百般查阅资料,最终也只是找到了一个相关的说明!—— 10,11,12这三个比较特殊
搜索了挺久,也没找到其他相关的文章了,目前只能得知在单元测试中,10,11,12 这仨,在程序启动时就已经存在字符串常量池中,不然都没其他理由这样子…
三、疑惑Junit单元测试 和 main函数区别
网上搜罗了一下发现还真有挺多这些东西的…
四 、反思
其实对于这种东西没必要一直钻牛角尖,知道怎么用就行了,而官方的方法也是有返回值的,所以我们还是按照官方规范来编码就行了。即 String s2 = s1.intern();