Java的關鍵字final通常是指被它修飾的數據是不能被改變的,不想改變可能出于兩種理由:設計或效率。主要特性:
* final數據的使用
* final參數的使用
* final方法的使用
* final類不能被繼承
### final數據
**1.1 final修飾變量**
final 變量一經初始化,就不能改變其值
這里的值對于一個對象或者數組來說指的是這個對象或者數組的引用地址。因此,一個線程定義了一個final變量之后,其他任意線程都拿到這個變量。但有一點需要注意的是,當這個final變量為對象或者數組時,雖然我們不能講這個變量賦值為其他對象或者數組,但是我們可以改變對象的域或者數組中的元素;
> final域能確保初始化過程的安全性,從而可以不受限制地訪問不可變對象,并在共享這些對象時無須同步;
線程對這個對象變量的域或者數據的元素的改變不具有線程可見性;
所以final修飾的變量必須被初始化
```
public static final String LOAN = "loan";
LOAN = new String("loan") //invalid compilation error
```
final變量初始化時機:
* 定義時初始化
* 初始化塊中,但不可在靜態初始化塊中,靜態的final實例變量才可以在靜態初始化塊中
* 構造方法中,但靜態final實例變量不可以在其中
特性
* 修飾的變量是基本數據類型:告知編譯器這一塊數據是不變的,這樣可以在執行計算時,減少一些運行時的負擔;
* final變量必須在聲明時被初始化;
* final實例變量必須在所有的構造函數末尾初始化;
* 在JMM中要求final域的初始化動作必須在構造方法return之前完成;
```
package com.quancheng;
import java.util.Random;
public class FinalTest {
private static Random random = new Random(20);
private final int VALUE_A = 10;
private static final int VALUE_B = 20;
public static final int VALUE_C = random.nextInt(10);
public final int VALUE_D = random.nextInt(10);
public static void main(String[] args) {
FinalTest test = new FinalTest();
//test.VALUE_A = 5;//Error:不可以這樣做
//test.VALUE_B =21;//Error:不可以這樣做
FinalTest finalT = new FinalTest();
FinalTest finalT1 = new FinalTest();
System.out.println("VALUE_C:"+VALUE_C);
System.out.println("VALUE_C:"+finalT.VALUE_C);
System.out.println("VALUE_C:"+finalT1.VALUE_C);
System.out.println("---------");
System.out.println("VALUE_D:"+finalT.VALUE_D);
System.out.println("VALUE_D:"+finalT1.VALUE_D);
}
}
output==>
VALUE_C:3
VALUE_C:3
VALUE_C:3
---------
VALUE_D:1
VALUE_D:1
```
以上結果得出一點我們不能因為某數據時final的就認為在編譯時可以知道它的值。在運行時使用隨機生成的VALUE\_C和VALUE\_D說明了這一點。示例代碼展示了final數值定義為static和非static的區別。此區別只有當數值在運行時內被初始化時才會顯現,這是因為編譯器對編譯時數值一視同仁(并且他們可能因為優化而消失)。使用static final 的數值是不可以通過一個新的對象而改變的。這是因為在裝載時已經被初始化,而不是每次創建新對象時初始化。
**1.2 final用于引用**
當使用final用于對對象的引用而不是基本類型時,對于基本類型final使數值恒定不變,而對于對象引用,final使引用恒定不變。一旦引用被初始化指向一個對象,就無法再把它改為指向另一個對象。然而,對象自身的數據是可以修改的
```
package com.game.lll;
public class FinalTest {
private final int a = 10;
private final Value v1 = new Value(10);
public static void main(String[] args) {
FinalTest test = new FinalTest();
//test.a = 5;//不可以這樣做
test.v1.value++;
//test.v1 = new Value(12);//Error:不可以這樣做
System.out.println("對象內的數據:"+test.v1.value);
}
class Value{
int value;
public Value(int value) {
this.value = value;
}
}
}
```
**1.3 final用于數組**
final用于數組和引用時一樣的,數組只不過是另一種引用,對于這個變量的引用是不能被重新賦值,但是對象本身是可以修改的。代碼如下:
```
package com.game.lll;
public class FinalTest {
private final Value v1 = new Value(10);
private final int[] values = { 1, 2, 3, 4, 5, 6 };
public static void main(String[] args) {
FinalTest test = new FinalTest();
//test.a = 5;//不可以這樣做
test.v1.value++;
//test.v1 = new Value(12);//Error:不可以這樣做
test.values[0] = 100;//對象本身是可以修改的
for(int i = 0; i < test.values.length;i++){
System.out.println(test.values[i]++);//對于這個變量的引用是不能被重新賦值
}
}
class Value{
int value;
public Value(int value) {
this.value = value;
}
}
}
```
**1.4 空白final**
Java允許生成“空白final”,空白final是指被聲明為final但又給未定初值的域。代碼如下:
```
package com.game.lll;
public class BlankFinal {
private final int a;
public BlankFinal(int i)
{
this.a = i;
}
public static void main(String[] args) {
BlankFinal blankFinal = new BlankFinal(5);
System.out.println(blankFinal.a);
}
}
```
雖然未對a直接賦值,但是在構造函數中對a進行了初始化。所以無論什么情況,都要確保final在使用前被初始化
> 在JMM中要求final域的初始化動作必須在構造方法return之前完成
### final方法
使用final方法的原因是把方法鎖定,以防任何繼承類修改它的含義。final聲明的方法不能被重寫;
* 當調用final方法時,直接將方法主體插入到調用處,而不是進行方法調用,這樣能提高程序效率\(內聯機制\)
* 如果你認為一個方法的功能已經足夠完整了,子類中不需要改變的話,你可以聲明此方法為final
* final方法比非final方法快,因為在編譯時候已靜態綁定了,不需要在運行時再動態綁定
```
class PersonalLoan{
public final String getName(){
return "personal loan";
}
}
class CheapPersonalLoan extends PersonalLoan{
@Override
public final String getName(){
return "cheap personal loan"; //compilation error: overridden method is final
}
}
```
### final類
當你將某個類定義為final class時,那么子類就無法繼承該類
思考一個有趣的現象:
```
byte b1=1;
byte b2=3;
byte b3=b1+b2; //當程序執行到這一行的時候會出錯,因為b1、b2可以自動轉換成int類型的變量,運算時java虛擬機對它進行了轉換,結果導致把一個int賦值給byte
final byte b1=1;
final byte b2=3;
byte b3=b1+b2; //不會出錯,相信你看了上面的解釋就知道原因了。
```
### 其它特性
* final變量一經初始化,就不能改變其值;
* final變量應該全部應用大寫(編碼規則);
* final類的所有方法為隱式的final方法;
* 類不能同時被聲明為final和abstract;
### 知識點
* 為何System類中的out/err可以更改值
```
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
```
setOut/setIn/setErr是native方法,可以繞過java語言的存取控制機制;這種罕見的情況來自早期JAVA,并且不會再其它地方遇到;
```
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
```
- java演變
- JDK各個版本的新特性
- JDK1.5新特性
- JDK1.6新特性
- JDK1.7新特性
- JDK1.8新特性
- JAVA基礎
- 面向對象特性
- 多態
- 方法重載
- 方法重寫
- class
- 常量
- 訪問修飾符
- 類加載路徑
- java-equals
- 局部類
- java-hashCode
- Java類初始化順序
- java-clone方法
- JAVA對象實例化的方法
- 基礎部分
- JAVA基礎特性
- JAVA關鍵字
- javabean
- static
- 日期相關
- final
- interface
- 函數式接口
- JAVA異常
- 異常屏蔽
- try-with-resource資源泄露
- JAVA引用
- WeakReference
- SoftReference
- PhantomReference
- 位運算符
- try-with-resource語法糖
- JDK冷知識
- JAVA包裝類
- JAVA基本類型與包裝類
- java.lang.Boolean
- java.lang.Integer
- java.lang.Byte
- java.lang.Short
- java.lang.Long
- java.lang.Float
- java.lang.Double
- java.lang.Character
- 日期相關
- TemporalAdjusters
- String
- 字符串常量池
- String拼接
- String編譯期優化
- StringBuilder&StringBuffer
- intern
- 注解
- java標準注解
- 內置注解
- 元注解
- 自定義注解
- 注解處理器
- JVM注解
- Java8 Annotation新特性
- 反射-Reflective
- Reflection
- Class
- Constructor
- Method
- javabean-property
- MethodHandles
- 泛型
- 類型擦除
- bridge-method
- Accessor&Mutator方法
- enum
- JAVA數組
- finalize方法
- JAR文件
- JAVA高級編程
- CORBA
- JMX
- SPI
- Java SPI使用約定
- ServiceLoader
- 實際應用
- IO
- 工具類
- JDK常用工具類
- Objects
- System
- Optional
- Throwable
- Collections
- Array
- Arrays
- System
- Unsafe
- Number
- ClassLoader
- Runtime
- Object
- Comparator
- VarHandle
- 數據結構
- 棧-Stack
- 隊列(Queue)
- Deque
- PriorityQueue
- BlockingQueue
- SynchronousQueue
- ArrayBlockingQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
- ConcurrentLinkedQueue
- 列表
- 迭代器
- KV鍵值對數據類型
- HashMap
- TreeMap
- Hash沖突
- ConcurrentHashMap
- JDK1.7 ConcurrentHashMap結構
- jdk7&jdk8區別
- 集合
- Vector
- Stack
- HashSet
- TreeSet
- ArrayList
- LinkedList
- ArrayList && LinkedList相互轉換
- 線程安全的集合類
- 集合類遍歷性能
- 并發容器
- CopyOnWriteArrayList
- ConcurrentHashMap
- 同步容器
- BitMap
- BloomFilter
- SkipList
- 設計模式
- 設計模式六大原則
- 單例模式
- 代理模式
- 靜態代理
- 動態代理
- JDK動態代理
- cglib動態代理
- spring aop
- 策略模式
- SpringAOP策略模式的運用
- 生產者消費者模式
- 迭代器模式
- 函數式編程
- 方法引用
- 性能問題
- Lambda
- Lambda類型檢查
- Stream
- findFirst和findAny
- reduce
- 原始類型流特化
- 無限流
- 收集器
- 并行流
- AOP
- 靜態織入
- aspect
- aspect的定義
- AspectJ與SpringAOP
- 動態織入
- 靜態代理
- 動態代理
- JDK動態代理
- CGLib動態代理
- Spring AOP
- SpringAOP五種通知類型
- @Before
- @AfterReturning
- @AfterThrowing
- @After
- @Around
- Aspect優先級
- SpringAOP切點表達式
- within
- execution
- 嵌套調用
- 系統優化與重構
- 重疊構造器模式
- 工具類構造器優化
- 常見面試題
- new Object()到底占用幾個字節
- 訪問修飾符
- cloneable接口實現原理
- 異常分類以及處理機制
- wait和sleep的區別
- 數組在內存中如何分配
- 類加載為什么要使用雙親委派模式,有沒有什么場景是打破了這個模式
- 類的實例化順序
- 附錄
- JAVA術語
- FAQ
- 墨菲定律
- 康威定律
- 軟件設計原則
- 阿姆達爾定律
- 字節碼工具
- OSGI