2017-11-05 15:08:47
- final关键字
Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。
使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。 --Java编程思想
一、final修饰符的作用:
- final类不能被继承,没有子类,final类中的方法默认是final的。
- final方法不能被子类的方法覆盖,但可以被继承。
- final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
- final不能用于修饰构造方法。
- 用来修饰方法参数,表示在变量的生存期中它的值不能被改变。
注意:类的private方法会隐式地被指定为final方法。
final变量存放在方法区的常量池中
~ final类:
final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。
~ final方法:
如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。使用final方法的原因有二:第一、把方法锁定,防止任何继承类修改它的意义和实现。第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。
~ final变量(常量):
用final修饰的成员变量表示常量,值一旦给定就无法改变!final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。另外,final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。
~ final参数:
当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。
二、深入理解final关键字
~ final变量与普通变量的区别
当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
String a = "hello2"; String t = "hello2"; final String b = "hello"; String d = "hello"; String c = b + 2; String e = d + 2; String f = "hello" + 2; System.out.println(e); System.out.println((a == c)); //true System.out.println((a == e)); //false System.out.println((a == t)); //true System.out.println((a == f)); //true
首先来解释一下为什么第二个为false,第三个为true。
其实这两个是很简单的,第二个为false是因为Java中的+其实在底层是调用了new StringBuilder()操作,这样的操作不是直接从常量池中取数据的地址,所以自然最后比较地址的时候就不一样了。
第三个为true的原因是,这两个的地址值均为在常量池中的地址值,因此是一样的。
那么为什么第一个也是true呢?
这里面就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。这种和C语言中的宏替换有点像。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量 b 替换为它的值。
其实这就和最后的f是同样的,这里是做了一个编译器的优化操作的结果。
只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,比如下面的这段代码就不会进行优化:
public class Test { public static void main(String[] args) { String a = "hello2"; final String b = getHello(); String c = b + 2; System.out.println((a == c)); } public static String getHello() { return "hello"; }}
这段代码的输出结果为false。这里要注意一点就是:不要以为某些数据是final就可以在编译期知道其值,通过变量b我们就知道了,在这里是使用getHello()方法对其进行初始化,他要在运行期才能知道其值。
~ 被final修饰的引用变量指向的对象内容可变吗
在上面提到被final修饰的引用变量一旦初始化赋值之后就不能再指向其他的对象,那么该引用变量指向的对象的内容可变吗?看下面这个例子:
public class Test { public static void main(String[] args) { final MyClass myClass = new MyClass(); System.out.println(++myClass.i); } } class MyClass { public int i = 0; }
这段代码可以顺利编译通过并且有输出结果,输出结果为1。这说明引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
~ final参数
在实际应用中,我们除了可以用final修饰成员变量、成员方法、类,还可以修饰参数、若某个参数被final修饰了,则代表了该参数是不可改变的。如果在方法中我们修改了该参数,则编译器会提示你:The final local variable i cannot be assigned. It must be blank and not using a compound assignment。看下面的例子:
public class TestFinal { public static void main(String[] args){ TestFinal testFinal = new TestFinal(); int i = 0; testFinal.changeValue(i); System.out.println(i); } public void changeValue(final int i){ //final参数不可改变 //i++; System.out.println(i); }}
上面这段代码changeValue方法中的参数i用final修饰之后,就不能在方法中更改变量i的值了。值得注意的一点,方法changeValue和main方法中的变量i根本就不是一个变量,因为java参数传递采用的是值传递,对于基本类型的变量,相当于直接将变量进行了拷贝。所以即使没有final修饰的情况下,在方法内部改变了变量i的值也不会影响方法外的i。
再看下面这段代码:
public class TestFinal { public static void main(String[] args){ TestFinal testFinal = new TestFinal(); StringBuffer buffer = new StringBuffer("hello"); testFinal.changeValue(buffer); System.out.println(buffer); } public void changeValue(final StringBuffer buffer){ //final修饰引用类型的参数,不能再让其指向其他对象,但是对其所指向的内容是可以更改的。 //buffer = new StringBuffer("hi"); buffer.append("world"); }}
运行这段代码就会发现输出结果为 helloworld。很显然,用final进行修饰虽不能再让buffer指向其他对象,但对于buffer指向的对象的内容是可以改变的。现在假设一种情况,如果把final去掉,结果又会怎样?看下面的代码:
public class TestFinal { public static void main(String[] args){ TestFinal testFinal = new TestFinal(); StringBuffer buffer = new StringBuffer("hello"); testFinal.changeValue(buffer); System.out.println(buffer); } public void changeValue(StringBuffer buffer){ //buffer重新指向另一个对象 buffer = new StringBuffer("hi"); buffer.append("world"); System.out.println(buffer); }}
运行结果:
hiworldhello
运行结果可以看出,将final去掉后,同时在changeValue中让buffer指向了其他对象,并不会影响到main方法中的buffer,原因在于java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。
换句话说,引用类型其实是一种弱化的指针,final限定的引用类型的变量其实是限定了这个指针的指向,但指针指向的内存中地址的值确是可以自行改变的。