Java的final关键字与String的内部比较方法

最近抽了点时间温故,一些零零散散的问题还是整理了起来。我决定把一些曾经坑过自己的问题写成博客文章,给学弟学妹们一个警示吧。

今天的故事从一个例子开始:

@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

有没有一点出乎您的意料呢?


那么这到底是怎么了,s4和s5都是"happBKs"与"Offer"的凭借,为什么再==和equals下的结果不相同呢?

我们先说说==和equals方法的区别。这两种判断两个值是否相等的方式其实存在本质的不同。==在判定基本数据类型时,没有什么不同,就是判断连个基本数据类型的值是否相等;在判定对象数据类型时,会根据对象的类中覆盖的equals方法来判定对象是否相等(如果没有重新实现类的equals方法,运行时会判定两个对象的地址是否指向同一个对象)。

这时候,也许纯洁无邪的孩子们会说:“我明白了,如果是int类型,那么equals和==没什么区别,Integer类型则不是”。这时候,就会有人在旁边阴笑,心里想:“拆箱和装箱都不懂,呵呵。。。结果肯定都一样”

我给个例子:

@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对象,而原数组没有变化。这样做的目的有性能的、线程安全诸多方面的考虑,以后我会专门开一个文章讲这个。



转载于:https://my.oschina.net/happyBKs/blog/493904

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值