说明:
关于本博客使用的书籍,源代码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的三大思想中的一个,不过没关系,我们之所以这么用,目的也不是为了让子类继承,更多的是需要一个工具来帮我们做事情,所以,该怎么用就怎么用吧!