Effective Java学习笔记 4 通过私有构造器增强不可实例化的能力

时间:2023-02-25 17:11:40

有些类不希望被实例化,比如一些工具类(只包含static方法和域),实例化没有任何意义。为了防止这样的类被实例化,不写构造函数是没有用的,因为默认的无参构造器(编译器自动生成)可以用。
同时,把类写成抽象类不可行。原因:

    1. 继承之后可以实例化;
2. 更糟糕的是,还有可能让用户觉得这是为了继承设计的。

解决方案:
加上私有构造器,并在方法体内部抛出异常(不是必须但是有效);最好再加上注释,表明这个类不应该被实例化。
于是我就特意查看Arrays的源码发现:

// Suppresses default constructor, ensuring non-instantiability.
private Arrays() {}

还有Math:

     /**
* Don't let anyone instantiate this class.
*/

private Math() {}

当然,这样的副作用就是无法继承,因为要生成子类父类必须先构造,而这样的类的构造器无法调用。



你以为这样就安全了吗?
太天真了…
我从Thinking in Java中看到Bruce Eckel在类型信息这一章最后一节强行反射调用了内部类方法,匿名内部类方法,private方法。而想要调用private方法的关键就是:

setAccessible(true);

所以最后实现的关键代码就是(异常已经在main中直接抛出):

    //获取构造函数,这里只有一个无参构造器
Constructor con=Math.class.getDeclaredConstructor();
//没有这句话会抛出IllegalAccessException
con.setAccessible(true);
//构造和强转
Math math=(Math) con.newInstance();
//验证
System.out.println(math.PI);

输出结果是:3.141592653589793

当然,在Math类有构造器里抛出异常的话…像这样:

/**
* Don't let anyone instantiate this class.
*/

private Math() {throw new AssertionError()}

依然没有用,相应的代码只需要把构造部分try catch不做任何处理就行。

//获取构造函数,这里只有一个无参构造器
Constructor con=Math.class.getDeclaredConstructor();
//没有这句话会抛出IllegalAccessException
con.setAccessible(true);
//构造和强转
try{
Math math=(Math) con.newInstance();
System.out.println(math.PI);
}
catch(Exception e){
//do nothing
}

……
当然,如果你都花这么大力气去构造了,还有什么理由要阻止你呢?毕竟在悬崖边建立护栏只是为了防止意外,你非要跳下去也许是为了飞起来吧。
:-P