說明:
關于本博客使用的書籍,源代碼Gitee倉庫 和 其他的相關問題,請查看本專欄置頂文章:《Effective Java》第0條:寫在前面,用一年時間來深度解讀《Effective Java》這本書
正文:
原文P15:有時可能需要編寫只包含靜態方法和靜態域的類。
比如一些公共的常量類(如下代碼,電商平臺貨物的狀態),這些類我們不需要實例化他們的對象,直接調用靜態成員變量或者方法即可。所以可以通過私有化構造器,“強化不可實例化的能力”
// GoodsStatus類
public class GoodsStatus {private GoodsStatus() {}public static final String PENDING_PAYMENT = "待付款";public static final String TO_BE_SHIPPED = "待發貨";public static final String SHIPPED = "已發貨";public static final String RECEIVED = "已收貨";// 退貨public static String returnGoods(String goodStatus) {// 只有已收貨的情況下可以退貨return RECEIVED.equals(goodStatus) ? "退貨成功" : "退貨失敗";}
}
原文P15:我們可以利用這種類,以java.lang.Math或者java.util.Arrays的方式,把基本類型的值或者數組類型上的相關方法組織起來。我們也可以通過java.util.Collections的方式,把實現特定接口的對象上的靜態方法,包括工廠方法(詳見第1條)組織起來。
java.lang.Math類 和 java.util.Arrays類,就像上例一樣,只包含靜態方法和靜態域(請大家自行查看源碼),用來處理數學計算(三角函數,對數,開根號等) 和 處理數組(排序,對比,復制等)的一些操作。
java.util.Collections類,則是服務于所有實現了Collection接口的類,其中包括類似于EmptyList、EmptyMap、SingletonSet等等,一些工廠方法。還包括size、isEmpty、sort等等,一系列的工具類方法。
原文P15:從Java8開始,也可以把這些方法放進接口中,假定這是你自己編寫的接口可以進行修改
比如上面的例子,電商平臺貨物的狀態,也可以通過接口來寫,而且更加方便,我也更加推薦大家使用這種方式來寫公共的常量類,因為接口里的常量默認都是public static final的,不用再去寫了,如下代碼
// GoodsStatus2 接口
public interface GoodsStatus2 {// 可以省略 public static finalString PENDING_PAYMENT = "待付款";String TO_BE_SHIPPED = "待發貨";String SHIPPED = "已發貨";String RECEIVED = "已收貨";// 退貨,可以省略public,因為默認就是public的static String returnGoods(String goodStatus) {// 只有已收貨的情況下可以退貨return RECEIVED.equals(goodStatus) ? "退貨成功" : "退貨失敗";}
}
原文P15:還可以利用這種類把fina類上的方法組織起來,因為不能把它們放在子類中
當我們需要將多個final類(無法被繼承)的方法組織起來統一管理時,可以使用單例類作為 "工具整合器",集中封裝這些final類的功能,提供統一的訪問入口。這種方式適合整合多個獨立的工具類,簡化調用邏輯。
// MyDateUtils final類
final class MyDateUtils {// 私有構造器,禁止實例化private MyDateUtils() {}// 格式化日期public static String formatDate(LocalDate date) {return date.format(DateTimeFormatter.ISO_LOCAL_DATE);}// 解析日期字符串public static LocalDate parseDate(String dateStr) {return LocalDate.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE);}
}// MyStringUtils final類
final class MyStringUtils {// 私有構造器,禁止實例化private MyStringUtils() {}// 檢查字符串是否為空public static boolean isEmpty(String str) {return str == null || str.trim().isEmpty();}// 反轉字符串public static String reverse(String str) {if (isEmpty(str)) return str;return new StringBuilder(str).reverse().toString();}
}// 單例模式將上述兩個工具類組合
public class ToolkitSingleton {// 單例實例private static final ToolkitSingleton INSTANCE = new ToolkitSingleton();// 私有構造器private ToolkitSingleton() {}// 獲取單例public static ToolkitSingleton getInstance() {return INSTANCE;}// 整合DateUtils的方法public String formatDate(LocalDate date) {return MyDateUtils.formatDate(date); // 調用final類的靜態方法}public LocalDate parseDate(String dateStr) {return MyDateUtils.parseDate(dateStr);}// 整合StringUtils的方法public boolean isEmpty(String str) {return MyStringUtils.isEmpty(str);}public String reverseString(String str) {return MyStringUtils.reverse(str);}// 甚至可以組合多個final類的功能,形成新功能// 轉換并格式化時間public String reverseFormattedDate(String dateStr) {if (isEmpty(dateStr)) return "";LocalDate date = parseDate(dateStr);String formatted = formatDate(date);return reverseString(formatted);}
}// Main 類
// 獲取單例
ToolkitSingleton toolkit = ToolkitSingleton.getInstance();
// 使用整合后的日期功能
LocalDate date = LocalDate.of(2024, 8, 19);
System.out.println("格式化日期:" + toolkit.formatDate(date));
// 使用整合后的字符串功能
String str = "hello";
System.out.println("反轉字符串:" + toolkit.reverseString(str));
// 使用組合功能
System.out.println("反轉格式化日期:" + toolkit.reverseFormattedDate("2024-08-19"));
原文P15:這樣的工具類(utilityclass)不希望被實例化,因為實例化對它沒有任何意義。然而在缺少顯式構造器的情況下,編譯器會自動提供一個公有的、無參的缺省構造器(defaulconstructor)。
所以我們要私有化構造器,來防止這樣的工具類被實例化。
那么除私有化構造器之外,使用抽象類可不可以防止工具類被實例化呢?
原文P15:企圖通過將類做成抽象類來強制該類不可被實例化是行不通的。該類可以被子類化,并且該子類也可以被實例化。這樣做甚至會誤導用戶,以為這種類是專門為了繼承而設計的(詳見第19條)。
也就是說,首先抽象類不能防止工具類被實例化,因為繼承它的子類可以被實例化。其次,抽象類還會被誤解為是一個專門用來被繼承的類,實際上我們的目的并不是需要它被繼承。所以,不能用抽象類。
原文P16:由于顯式的構造器是私有的,所以不可以在該類的外部訪問它。AssertionError不是必需的,但是它可以避免不小心在類的內部調用構造器。它保證該類在任何情況下都不會被實例化。這種習慣用法有點違背直覺,好像構造器就是專門設計成不能被調用一樣。因此,明智的做法就是在代碼中增加一條注釋,如下面代碼所示。
// UtilityClass類
// Noninstantiable utility class(不可實例化的工具類)
public class UtilityClass {// Suppress default constructor for noninstantiability(抑制默認構造器)// 私有化構造器,防止被實例化private UtilityClass() {// 主要防止反射破壞單例模式throw new AssertionError();}// ......
}
throw new AssertionError(); 在這里的作用主要是防止反射破壞單例,看過第3條的同學應該有印象。
那么最后,這種用法有沒有弊端呢?
原文P16:這種習慣用法也有副作用,它使得一個類不能被子類化。所有的構造器都必須顯式或隱式地調用超類(superclass)構造器,在這種情形下,子類就沒有可訪問的超類構造器可調用了
也就是說,弊端就是被私有化構造器的類,不能被繼承了,相當于是違背了Java的三大思想中的一個,不過沒關系,我們之所以這么用,目的也不是為了讓子類繼承,更多的是需要一個工具來幫我們做事情,所以,該怎么用就怎么用吧!