您的位置:首页 > 编程语言 > Java开发

深入Final关键字

2016-07-19 11:25 369 查看
1.类的final变量和普通变量有什么区别?

public class FinalTest {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
System.out.println((a == c));
System.out.println((a == e));
System.out.println((a.equals(e)));
System.out.println("结果是:-true------false--true");
}
}


结果是:true false true

public class FinalTest {
public static void main(String[] args) {
String a = "hello2";
final String b = getHello();
String c = b + 2;
System.out.println((a == c));
System.out.println("结果是:--false--");
}

public static String getHello() {
return "hello";
}
}


结果是:false

原因:

第一个结果是true,第二个结果是false。这里面就是final变量和普通变量的区别,当final变量是基本数据类型以及String类型时,付过在编译期间能知道它的确切值,则编译会把它当做编译期常量使用。也就是说在用到该final变量的时候,相当于直接访问这个常量,不需要再运行时确定。这种和C语言中的宏替换有点儿像。因此变量b被final修饰,因此会被当做编译期常量,所以在使用到b的地方直接将变量b替换为它的值。而对于变量d的访问却需要在运行时通过连接来进行。注意在只有在编译期间能确切知道final变量的情况下才会进行这样的优化。

2.被final修饰的引用变量指向的对象内容可变吗?

package com.wisdom.effective.java;

public class FinalTest_2 {
public static void main(String[] args) {
final MyClass myClass = new MyClass();
System.out.println(++myClass.i);
}
}

class MyClass {
public int i = 0;
}


结果是:1

原因:

 这段代码可以顺利编译通过并且有输出结果,输出结果为1。这说明引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。

3.final和static

package com.wisdom.effective.java;

public class FinalTest_2 {
public static void main(String[] args) {
MyClass myClass1 = new MyClass();
MyClass myClass2 = new MyClass();
System.out.println(myClass1.i);
System.out.println(myClass1.j);
System.out.println(myClass2.i);
System.out.println(myClass2.j);
}
}

class MyClass {
public final double i = Math.random();
public static double j = Math.random();
}


结果是:0.05766174928636447

0.7614580638924601

0.9017659828502815

0.7614580638924601

运行这段代码就会发现,每次打印的两个j值都是一样的,而i的值却是不同的。

原因是:

很多时候会容易把static和final关键字混淆,static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变。

4.匿名内部类中使用的外部局部变量为什么只能是final变量?

public class Test {
public static void main(String[] args)  {

}

public void test(final int b) {
final int a = 10;
new Thread(){
public void run() {
System.out.println(a);
System.out.println(b);
};
}.start();
}
}


这段代码会被编译成两个class文件:Test.class和Test1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outter1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outterx.class(x为正整数)。

  


  根据上图可知,test方法中的匿名内部类的名字被起为 Test$1。

  上段代码中,如果把变量a和b前面的任一个final去掉,这段代码都编译不过。我们先考虑这样一个问题:

  当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了 复制 的手段来解决这个问题。

5.关于final参数的问题

关于网上流传的”当你在方法中不需要改变作为参数的对象变量时,明确使用final进行声明,会防止你无意的修改而影响到调用方法外的变量“这句话,我个人理解这样说是不恰当的。

  因为无论参数是基本数据类型的变量还是引用类型的变量,使用final声明都不会达到上面所说的效果。

  看这个例子就清楚了:

  


  上面这段代码好像让人觉得用final修饰之后,就不能在方法中更改变量i的值了。殊不知,方法changeValue和main方法中的变量i根本就不是一个变量,因为java参数传递采用的是值传递,对于基本类型的变量,相当于直接将变量进行了拷贝。所以即使没有final修饰的情况下,在方法内部改变了变量i的值也不会影响方法外的i。

  再看下面这段代码:

  

package com.wisdom.effective.java;

public class FinalTest_4 {
public static void main(String[] args) {
MyClass_3 myClass = new MyClass_3();
StringBuffer buffer = new StringBuffer("hello");
myClass.changeValue(buffer);
System.out.println(buffer.toString());
}
}

class MyClass_3 {
void changeValue(final StringBuffer buffer) {
buffer.append("world");
}
}


结果是:holleword

原因是:

运行这段代码就会发现输出结果为 helloworld。很显然,用final进行修饰并没有阻止在changeValue中改变buffer指向的对象的内容。有人说假如把final去掉了,万一在changeValue中让buffer指向了其他对象怎么办。有这种想法的朋友可以自己动手写代码试一下这样的结果是什么,如果把final去掉了,然后在changeValue中让buffer指向了其他对象,也不会影响到main方法中的buffer,原因在于java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。

  所以关于网上流传的final参数的说法,我个人不是很赞同。

把final去掉

package com.wisdom.effective.java;

public class FinalTest_4 {
public static void main(String[] args) {
MyClass_3 myClass = new MyClass_3();
StringBuffer buffer = new StringBuffer("hello");
myClass.changeValue(buffer);
System.out.println(buffer.toString());
}
}

class MyClass_3 {
void changeValue(StringBuffer buffer) {
buffer.append("world");
}
}


结果仍然是:holleword
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息