https://www.cnblogs.com/liun1994/p/6691094.html
https://www.cnblogs.com/xiaoxi/p/6392154.html
https://blog.csdn.net/hanghangde/article/details/50686565?locationNum=7
https://www.cnblogs.com/ktao/p/8586966.html
1. final关键字的含义
final表面意思就是不可更改的,恒量的意思;类似于C语言中的const关键字,指的是无法改变的量,这与静态标量static是有区别的,静态变量指的是只有一份存储空间,值是可以改变的。使用final一定原因是出于软件设计的角度,因为别人看到final这个关键字就知道是什么意思,达到心领神会的效果,但也正是由于这种”语义”的存在,在程序设计中要谨慎使用,以免误用。
在Java中final修饰的就是常量,而且变量名要大写;
Math类:
public static final double E = 2.7182818284590452354;
public static final double PI = 3.14159265358979323846;
......java源码中好多变量都用final修饰
2. final的作用
final根据修饰位置的不同作用也不相同,针对三种情况:
1)修饰变量,被final修饰的变量必须要初始化,赋初值后不能再重新赋值。
注意:局部变量不在我们讨论的范畴,因为局部变量本身就有作用范围,不使用private、public等词修饰。
2)修饰方法,被final修饰的方法代表不能重写。
3)修饰类,被final修饰的类,不能够被继承。
注意:final修饰的类,类中的所有成员方法都被隐式地指定为final方法。
2.1 final修饰变量
被final修饰的变量必须显示的初始化,初始化可以以三种方式:1)定义时初始化,2)在构造器中设置值,3)在非静态块中为final实例变量设置值。
final修饰变量指的是:这个变量被初始化后便不可改变,这里不可改变的意思:
对基本类型来说是其值不可变,而对于对象变量来说其引用不可变,即不能再指向其他的对象。
如果final修饰的变量是对象类型,那么不可更改指的是该变量不可以再指向别的对象,但是对象的值时可以更改的,比如:
final Operate operate = new Operate() ;// operate有一个普通变量i初始化为10
operate.i = 11;
operate.i = 12;
System.out.println(operate.i); //输出12
上述是自定义类,即便是数组,List等集合类型,所保存的值也是可以更改的。
当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。
2.2 修饰方法
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。即父类的final方法是不能被子类所覆盖的,也就是说子类是不能够存在和父类一模一样的方法的。
final修饰的方法表示此方法已经是“最后的、最终的”含义,亦即此方法不能被重写(可以重载多个final修饰的方法)。此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中final修饰的方法同时访问控制权限为private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,而是在子类中重新定义了新的方法。(注:类的private方法会隐式地被指定为final方法。)
3. final和static的区别
static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变,看一下网上的一个例子:
public class Test {
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();
}
//运行结果,两次打印,j的值都是一样的,j是static类型的属于类,因此两次值相同。i不是static的因此属于对象,但是i的值是不可变的。
4. 其他final相关的知识
1)使用final关键字,如果编译器能够在编译阶段确定某变量的值,那么编译器就会把该变量当做编译期常量来使用。如果需要在运行时确定,那么编译器就不会优化相关代码。
public class Test {
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));
}
}
//final类型,在编译阶段能够确定值。
//非final类型在编译阶段确定不了
输出:
true
false
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";
}
}
//即便是final类型,编译阶段也确定不了值。
输出
false
2)注意不要将final与finally、finalize()等搞混。
3)将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
4)接口中的变量都是public static final 的。
为什么接口中的常量必须使用public static final修饰 public:
使接口的实现类可以使用这个常量static:static修饰就表示它属于类的,随的类的加载而存在的,*如果是非static的话,就表示属于对象的*,只有建立对象时才有它,而接口是不能建立对象的,所以接口的常量必须定义为static
final:final修饰就是保证接口定义的常量不能被实现类去修改,如果没有final的话,由子类随意去修改的话,接口建立这个常量就没有意义了。
5、类的final变量和普通变量有什么区别?
当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
那么final变量和普通变量到底有何区别呢?下面请看一个例子:
public class Test {
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));
}
}
输出结果:true、false
大家可以先想一下这道题的输出结果。为什么第一个比较结果为true,而第二个比较结果为fasle。这里面就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。这种和C语言中的宏替换有点像。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的值。而对于变量d的访问却需要在运行时通过链接来进行。想必其中的区别大家应该明白了,不过要注意,只有在编译期间能确切知道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()方法对其进行初始化,他要在运行期才能知道其值。
6、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);
}
}
运行结果:
hiworld
hello
从运行结果可以看出,将final去掉后,同时在changeValue中让buffer指向了其他对象,并不会影响到main方法中的buffer。
java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。
7.finally
finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下。(×)(这句话其实存在一定的问题)
很多人都认为finally语句块一定会执行,但真的是这样么?答案是否定的,例如下面这个例子:
为什么在以上两种情况下都没有执行finally语句呢,说明什么问题?
只有与finally对应的try语句块得到执行的情况下,finally语句块才会执行。以上两种情况在执行try语句块之前已经返回或抛出异常,所以try对应的finally语句并没有执行。
但是,在某些情况下,即使try语句执行了,finally语句也不一定执行。例如以下情况:
finally 语句块还是没有执行,为什么呢?因为我们在 try 语句块中执行了 System.exit (0) 语句,终止了 Java 虚拟机的运行。那有人说了,在一般的 Java 应用中基本上是不会调用这个 System.exit(0) 方法的。OK !没有问题,我们不调用 System.exit(0) 这个方法,那么 finally 语句块就一定会执行吗?
再一次让大家失望了,答案还是否定的。当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。还有更极端的情况,就是在线程运行 try 语句块或者 catch 语句块时,突然死机或者断电,finally 语句块肯定不会执行了。可能有人认为死机、断电这些理由有些强词夺理,没有关系,我们只是为了说明这个问题。
易错点:
在try-catch-finally语句中执行return语句。我们看如下代码:
答案:4,4,4 。 为什么呢?
首先finally语句在改代码中一定会执行,从运行结果来看,每次return的结果都是4(即finally语句),仿佛其他return语句被屏蔽掉了。
事实也确实如此,因为finally用法特殊,所以会撤销之前的return语句,继续执行最后的finally块中的代码。
8. finalize
finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。
特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。
使用finalize还需要注意一个事,调用super.finalize();
一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。
finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。