最近抽了点时间温故,一些零零散散的问题仍是整理了起来。我决定把一些曾经坑过本身的问题写成博客文章,给学弟学妹们一个警示吧。java
今天的故事从一个例子开始:shell
@Test public void testFinal() { String s1="happyBKsOffer"; String s2="happyBKs"; final String s3="happyBKs";//s3.replace("h", "H"); String s4=s2+"Offer"; String s5=s3+"Offer"; if (s1 == s4) { System.out.println("s1==s4"); } else { System.out.println("s1!=s4"); } if (s1 == s5) { System.out.println("s1==s5"); } else { System.out.println("s1!=s5"); } if (s1.equals(s4)) { System.out.println("s1 equals s4"); } else { System.out.println("s1 not equals s4"); } if (s1.equals(s5)) { System.out.println("s1 equals s5"); } else { System.out.println("s1 not equals s5"); } }
若是你看到这个代码以为莫名其妙而且伴随着一点心虚,以为不都是同样的嘛,那么恭喜你,看完了今天的例子,你就不用再往坑里跳了。若是你一眼就看出了其中的坑,那么也恭喜你,能够在我博客下方评论处BB了,“这也算个问题,太基础了”。(本文出自:http://my.oschina.net/happyBKs/blog/493904,请自行代表出处)数组
实际的运行结果以下:安全
s1!=s4 s1==s5 s1 equals s4 s1 equals s5
有没有一点出乎您的意料呢?app
那么这究竟是怎么了,s4和s5都是"happBKs"与"Offer"的凭借,为何再==和equals下的结果不相同呢?性能
咱们先说说==和equals方法的区别。这两种判断两个值是否相等的方式其实存在本质的不一样。==在断定基本数据类型时,没有什么不一样,就是判断连个基本数据类型的值是否相等;在断定对象数据类型时,会根据对象的类中覆盖的equals方法来断定对象是否相等(若是没有从新实现类的equals方法,运行时会断定两个对象的地址是否指向同一个对象)。优化
这时候,也许纯洁无邪的孩子们会说:“我明白了,若是是int类型,那么equals和==没什么区别,Integer类型则不是”。这时候,就会有人在旁边阴笑,内心想:“拆箱和装箱都不懂,呵呵。。。结果确定都同样”spa
我给个例子:.net
@Test public void testInt() { int a=1,b=1; if (a == b) { System.out.println("a==b"); } else { System.out.println("a!=b"); } // if (a.equals(b)) { // System.out.println("a equals b"); // } else { // System.out.println("a not equals b"); // } Integer c=new Integer(1),d=new Integer(1); if (c == d) { System.out.println("c==d"); } else { System.out.println("c!=d"); } if (c.equals(d)) { System.out.println("c equals d"); } else { System.out.println("c not equals d"); } Integer e=1,f=1; if (e == f) { System.out.println("e==f"); } else { System.out.println("e!=f"); } if (e.equals(f)) { System.out.println("e equals f"); } else { System.out.println("e not equals f"); } }
那么结果会怎么样呢?线程
a==b c!=d c equals d 1 e==f e equals f
是的,笑别人单纯的人眼睛没有瞎,真的结果就如同小朋友们料想的那么单纯。好了,别捶胸顿足故做恍然大悟了,单纯的结果后面有着不单纯的缘由。
首先int的==比较没有什么可多说的。为何Integer类型的c和d,e和f在equals时会有两种不一样的记过呢?这是由于java编译器的优化机制造就的:c和d各自初始化了一个对象类型,因此在==比较时,他们指的不是一个对象,因此==比较是不一样的,而Integer的equals方法已经重写,比较的是二者实际的整型数值,因此是相等的。e和f的确用到了装箱的拆箱,可是java编译器在编译时作了优化,将两个常量1装箱后优化为一个对象,这样e和f指向同一个Integer对象了,因此在==时是相同的。
回到咱们刚才的实例中。s2和s3虽然都是"happyBKs",可是s2是final类型,s3是通常的类型。在java编译器编译时,一样会对常量数值作一些优化,编译器会将s4=s2+"Offer"进行优化,由于s2是常量、"Offer"也是常量,因此,s4会在编译后直接被转换成s4="happyBKsOffer",而s1的初始化赋值也是常量"happyBKsOffer",因此编译器会再次将它们优化为一个String对象,s1和s4指向同一个String对象,因此二者在==是相同的。而s5=s3+"Offer",s3不是final的,因此必须在运行时计算,这样s5只有在运行时候才能生成一个对象,确定与s1的那个不是同一个String对象,因此s1与s5在==比较时确定是不一样的。
明白了吧。也许你仍是很揪心,咱们这里只给出一个建议吧:那就是,尽可能养成用equals的好习惯!
好最后,咱们队final 自己再作个总结:final能够修饰类、方法和变量。
修饰类
当用final修饰一个类时,代表这个类不能被继承。也就是说,若是一个类你永远不会让他被继承,就能够用final进行修饰。final类中的成员变量能够根据须要设为final,可是要注意final类中的全部成员方法都会被隐式地指定为final方法。
在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在之后不会用来继承或者出于安全的考虑,尽可能不要将类设计为final类。
修饰方法
使用final方法的缘由有两个。第一个缘由是把方法锁定,以防任何继承类修改它的含义;第二个缘由是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。可是若是方法过于庞大,可能看不到内嵌调用带来的任何性能提高。在最近的Java版本中,不须要使用final方法进行这些优化了。
所以,若是只有在想明确禁止 该方法在子类中被覆盖的状况下才将方法设置为final的。
注:类的private方法会隐式地被指定为final方法。
修饰变量
修饰变量是final用得最多的地方,也是本文接下来要重点阐述的内容。首先了解一下final变量的基本语法:
对于一个final变量,若是是基本数据类型的变量,则其数值一旦在初始化以后便不能更改;若是是引用类型的变量,则在对其初始化以后便不能再让其指向另外一个对象。
String类型咱们也作一个补充总结:
String不是基本类型,而是对象类型,而且,其内部哟一个final的char数组,所以String的方法中不提供修改String中内容字符的方法。即使是subString、replace等看似修改了String的方法,其实只是返回了一个新的String对象,而原数组没有变化。这样作的目的有性能的、线程安全诸多方面的考虑,之后我会专门开一个文章讲这个。