Java筆記:final修飾符


本篇筆記主要是final修飾符修飾類、成員變量、方法的用法,不可變類,實例緩存的不可變類

final關鍵字可以用來修飾類、變量、方法。final變量不能重新賦值,子類不能覆蓋父類的final方法,final類不能有子類。

1.final修飾的成員變量
(1)final修飾的成員變量一旦賦值后,不能被重新賦值。
(2)final修飾的實例Field,要么在定義該Field的時候指定初始值,要么在普通初始化塊或構造器中指定初始值。但是如果在普通初始化塊中為某個實例Field指定了初始值,則不能再在構造器中指定初始值。
(3)final修飾的類Field,要么在定義該Field的時候指定初始值,要么在靜態代碼塊中定義初始值。
(4)如果在構造器或初始化塊中對final成員變量進行初始化,則不要在初始化之前就訪問該成員的值。

package cn.lsl;

public class FinalTest {
    final int a = 5;    //直接賦值
    
    final String str;        //普通代碼塊中賦值
    {
        str = "zhangsan";
    }
    
    final int b;            //構造器中賦值
    public FinalTest(){
        b = 7;
    }
    
    final static int c = 8;        //直接賦值
    
    final static int d;            //靜態代碼塊中賦值
    static{
        d = 9;
    }
    
    
    
    //如果在構造器或初始化塊中對final成員變量進行初始化,則不要在初始化之前就訪問該成員的值。
    final int age;
    {
        //System.out.println(age);
        age = 22;
        System.out.println(22);
    }
}

2.final修飾的局部變量
(1)系統不會對局部變量進行初始化,布局變量必須要顯示的初始化。所以使用final修飾的局部變量,既可以在定義的時候指定默認值,也可以不指定默認值。
(2)final修飾形參的時候,不能為該形參賦值。

3.final修飾基本數據類型變量和修飾引用類型變量的區別
使用final修飾基本類型的變量,一旦對該變量賦值之后,就不能重新賦值了。但是對於引用類型變量,他保存的只是引用,final只能保證引用類型變量所引用的地址不改變,但不保證這個對象不改變,這個對象完全可以發生改變。

eg:

final Person p = new Person();
p.setAge(23);        //改變了Person對象的age Field
//p=null            //編譯出錯

final修飾的引用類型變量不能被重新賦值,但是可以改變引用變量所引用對象的內容。

4.final的“宏變量”
(1)final修飾符的一個重要用途就是“宏變量”。當定義final變量時就該為該變量指定了初始值,而且該初始值可以在編譯時就確定下來,那么這個final變量本質上就是一個“宏變量”,編譯器會把程序中
所有用到該變量的地方直接替換成該變量的值。

package cn.lsl;

public class FinalTest {
    public static void main(String[] args){
        final String name = "小明" + 22.0;
        final String name1 = "小明" + String.valueOf(22.0);
        System.out.println(name == "小明22.0");
        System.out.println(name1 == "小明22.0");
    }
}

final String name1 = "小明" + String.valueOf(22.0);中調用了String類的方法,因此編譯器無法再編譯的時候確定name1的值,所以name1不會被當成“宏變量”。

package cn.lsl;

public class FinalTest {
    public static void main(String[] args){
        String s1 = "小明";
        String s2 = "小" + "明";
        System.out.println(s1 == s2);    //true
        
        String str1 = "小";
        String str2 = "明";
        String s3 = str1 + str2;
        System.out.println(s1 == s3);        //false
        
        //宏替換
        final String str3 = "小";
        final String str4 = "明";
        String s4 = str3 + str4;
        System.out.println(s1 == s4);        //true
    }
}

分析:1.java會使用常量池量管理曾經使用過的字符串直接量。String a = "hello";  那么字符串池中會緩存一個字符串"hello",當執行String b = "hello";會讓b直接指向字符串池中的"hello"字符串。所以a==b返回true。
2.String s3 = str1 + str2;編譯時無法確定s3的值
3.String s4 = str3 + str4;因為執行了宏替換,所以在編譯的時候就已經確定了s4的值

5.用final修飾的方法不能被重寫。用final修飾的類不能有子類。

6.不可變類
不可變類是指創建該類的實例后,該實例的Field是不可改變的。
如果創建自定義的不可變類,應該遵循如下規則
(1)使用private和final修飾符來修飾該類的Field。
(2)提供帶參數的構造器,用於傳入參數來初始化類里的Field。
(3)僅為該類的Field提供getter方法,不要為該類的Field提供setter方法。
(4)如果有必要,重寫Object類的hashCode和equals方法。

package cn.lsl;

public class Address {
    private final String detail;
    private final String postCode;
    
    public Address() {
        this.detail = "";
        this.postCode = "";
    }
    public Address(String detail, String postCode) {
        this.detail = detail;
        this.postCode = postCode;
    }
    public String getDetail() {
        return detail;
    }
    public String getPostCode() {
        return postCode;
    }
    
    public boolean equals(Object obj){
        if(this == obj){
            return true;
        }
        if(obj !=null && obj.getClass() == Address.class){
            Address ad = (Address)obj;
            if(this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode())){
                return true;
            }
        }
        return false;
    }
    
    public int hashCode(){
        return detail.hashCode() + postCode.hashCode() * 31;
    }
    
}

因為final修飾引用類型變量時,表示這個引用變量不可重新被賦值,但引用類型變量所指向的對象依然可被改變。所以在創建不可變類的時候,如果包含的Field類型是可變的,那么這個不可變類就創建失敗了。

如下:

package cn.lsl;

class Name{
    private String firstName;
    private String lastName;
    
    public Name() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Name(String firstName, String lastName) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

public class Person {
    private final Name name;
    public Person(Name name){
        this.name = name;
    }
    public Name getName(){
        return name;
    }
    public static void main(String[] args) {
        Name n = new Name("明","小");
        Person p = new Person(n);
        System.out.println(p.getName().getFirstName());
        n.setFirstName("君");
        System.out.println(p.getName().getFirstName());
    }
}

通過n.setFirstName("君");改變了firstName。
為了保證對象的不可變性
可以修改為如下代碼

package cn.lsl;

class Name{
    private String firstName;
    private String lastName;
    
    public Name() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Name(String firstName, String lastName) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

public class Person {
    private final Name name;
    public Person(Name name){
        //this.name = name;
        this.name = new Name(name.getFirstName(), name.getLastName());
    }
    public Name getName(){
        //return name;
        return new Name(name.getFirstName(), name.getLastName());
    }
    public static void main(String[] args) {
        Name n = new Name("明","小");
        Person p = new Person(n);
        System.out.println(p.getName().getFirstName());
        n.setFirstName("君");
        System.out.println(p.getName().getFirstName());
    }
}

7.實例緩存的不可變類
如果程序需要經常使用想用的不可變類實例,則應該考慮緩存這種不可變類的實例,因為重復創建相同的對象沒有太大的意義,而且加大系統的開銷。
可以使用一個數組來作為緩存池,實現不可變類。

package cn.lsl;

class CacheImmutale {
    private static int MAX_SIZE = 10;
    //使用數組來緩存實例
    private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE];
    private static int pos = 0;
    private final String name;
    private CacheImmutale(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    
    public static CacheImmutale valueOf(String name){
        for(int i=0; i < MAX_SIZE; i++){
            if(cache[i] != null && cache[i].getName().equals(name)){
                return cache[i];
            }
        }
        if(pos == MAX_SIZE){
            cache[0] = new CacheImmutale(name);
            pos = 1;
        }else{
            cache[pos++] = new CacheImmutale(name);
        }
        return cache[pos-1];
    }
    
    public boolean equals(Object obj){
        if(this == obj){
            return true;
        }
        if(obj != null && obj.getClass() == CacheImmutale.class){
            CacheImmutale ci = (CacheImmutale)obj;
            return name.equals(ci.getName());
        }
        return false;
    }
    public int hashCode(){
        return name.hashCode();
    }
}


public class CacheImmutaleTest{
    public static void main(String[] args) {
        CacheImmutale c1 = CacheImmutale.valueOf("hello");
        CacheImmutale c2 = CacheImmutale.valueOf("hello");
        System.out.println(c1 == c2);
    }
}

分析:以上程序緩存池采用“先進先出”規則來決定哪個對象被移除緩存池。
程序中還使用了private修飾來隱藏該類的構造器,通過提供該類的valueOf方法來獲取實例。

Java提供的java.lang.Integer類也是采用類似的策略來處理的,但是只能Integer緩存-128~127之間的Integer對象。

package cn.lsl;

public class IntegerTest {
    public static void main(String[] args) {
        Integer a = new Integer(23);
        Integer b = Integer.valueOf(23);
        Integer c = Integer.valueOf(23);
        System.out.println(a == b);
        System.out.println(b == c);
        
        //Integer只能緩存-128~127之間的Integer對象
        Integer d = Integer.valueOf(230);
        Integer e = Integer.valueOf(230);
        System.out.println(d == e);
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM