本篇筆記主要是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); } }