EffectiveJava----私有构造器强化单例属性之最优解决方案枚举

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shentanweilan9/article/details/80734512
#私有构造函数强化singleton属性
  1. 公有的静态成员是一个final域,成员的声明很清楚的表达了这个类是一个singleton。

     public class Elvis {
         public static final Elvis INSTANCE = new Elvis();
         private Elvis() { }
         public void leaveTheBuilding() {
             System.out.println("Who a baby, I'm outta here!");
         }
         // This code would normally appear outside the class!
         public static void main(String[] args) {
             Elvis elvis = Elvis.INSTANCE;
             elvis.leaveTheBuilding();
         }
       }
    
  2. 提供一个公有的静态方法,而不是公有的静态final域。该方式提供了更大的灵活性,在不改变API的前提下,可以把该类改成singleton或者非singleton的。

    public class Elvis {
             private static final Elvis INSTANCE = new Elvis();
             private Elvis() { }
             public static Elvis getInstance() { return INSTANCE; }
             public void leaveTheBuilding() {
                 System.out.println("Who  a baby, I'm outta here!");
             }
             // This code would normally appear outside the class!
             public static void main(String[] args) {
                 Elvis elvis = Elvis.getInstance();
                 elvis.leaveTheBuilding();
             }
           }  
    
  3. 一般来说,第一种方法效率稍微高一些,然后,采用第一种方法实现singleton后,就没有改变的余地了,当你想把该类改成非singleton,显然是不行的了。所以,除非确实确定该类是一个singleton,那就用第一个方法吧。用第2种方法的时候,假如该类实现了serializable接口,那应该重写(override)readResolve()方法,否则再反序列化的时候是会产生一个新的实例,这与singleton相违背了

  4. 现代jvm几乎都能够将静态工厂方法进行调用内联化。

    • 方法调用 函数调用先转移到该函数的内存地址,程序内容读取完毕后转到函数执行前方法。这种操作要求保护现场并记忆执行此地址,执行完恢复现场。这就是通常说的出栈和入栈,这需要一定时间和内存的开销
    • 内联函数。怎么解决这个性能消耗问题呢,这个时候需要引入内联函数了程序编译时,编译器将程序中的调用表达式用目标函数体直接替换。这样就不会产生转去转回的问题,但是由于在编译时将函数体中的代码被替代到程序中,因此会增加目标程序代码量,进而增加空间开销,而在时间代销上不象函数调用时那么大,可见它是以目标代码的增加为代价来换取时间的节省。
    • 写C代码时,我们都学到将一些简短的逻辑定义在宏里。这样做的好处是,在编译器编译的时候会将用到该宏的地方直接用宏的代码替换。这样就不再需要象调用方法那样的压栈、出栈,传参了。性能上提升了。内联函数的处理方式与宏类似,但与宏又有所不同,内联函数拥有函数的本身特性(类型、作用域等等)。在C++里有个内联函数,使用inline关键字修饰。另外,写在Class定义内的函数也会被编译器视为内联函数
    • C++是否为内联函数由自己决定,Java由编译器决定。Java不支持直接声明为内联函数的,如果想让他内联,你只能够向编译器提出请求: 关键字final修饰 用来指明那个函数是希望被JVM内联的。public final void dealSomthing() {}
    • jvm内联运行时优化,a、短方法更利于jvm优化。(流程更明显,作用域更短,副作用也更明显),长方法直接就跪了。b、小方法频繁执行,jvm会执行内联。(它会把方法的调用替换成方法体本身)

      private int add4(int x1, int x2, int x3, int x4) {  
              return add2(x1, x2) + add2(x3, x4);  
          }  
      
          private int add2(int x1, int x2) {  
              return x1 + x2;  
          }
      // 运行一段时间后 jvm会把代码翻译成下面这样  
      private int add4(int x1, int x2, int x3, int x4) {  
              return x1 + x2 + x3 + x4;  
          }  
      
  5. 从Java1.5之后 实现Singleton还有第三种方法,只需要编写一个包含单个元素的枚举类型。他和公有域方法类似。但是它无偿提供了序列化机制(自由序列化),绝对的防止多次实例化(线程安全) 即使面对复杂的序列化和反射攻击的时候。

    enum SingletonDemo{
            INSTANCE;
            public void otherMethods(){
                System.out.println("Something");
            }
        }
    

    我们之前用的枚举 一般都是多个属性的常量 用于switch

    enum Color{
        RED,GREEN,BLUE;
    }
    public class Hello {
        public static void main(String[] args){
            Color color=Color.RED;
            int counter=10;
                switch (color){
                    case RED:
                        System.out.println("Red");
                        color=Color.BLUE;
                        break;
                    case BLUE:
                        System.out.println("Blue");
                        color=Color.GREEN;
                        break;
                    case GREEN:
                        System.out.println("Green");
                        color=Color.RED;
                        break;
                }
            }
        }
    }
    
    • enum是通过继承了Enum类实现的,enum结构不能够作为子类继承其他类,但是可以用来实现接口。此外,enum类也不能够被继承,在反编译中,我们会发现该类是final的
    • enum有且仅有private的构造器,防止外部的额外构造,这恰好和单例模式吻合,也为保证单例性做了一个铺垫。这里展开说下这个private构造器,如果我们不去手写构造器,则会有一个默认的空参构造器,我们也可以通过给枚举变量参量来实现类的初始化。
    • private修饰符对于构造器是可以省略的,但这不代表构造器的权限是默认权限。

        enum Color{
          RED(1),GREEN(2),BLUE(3);
          private int code;
          Color(int code){
              this.code=code;
          }
          public int getCode(){
              return code;
          }
        }
      
    • enum是如何工作的,就要对其进行反编译。使用枚举其实和使用静态类内部加载方法原理类似。枚举会被编译成如下形式:public final class T extends Enum{}枚举量的实现其实是public static final T 类型的未初始化变量,之后,会在静态代码中对枚举量进行初始化。所以,如果用枚举去实现一个单例,这样的加载时间其实有点类似于饿汉模式,并没有起到lazy-loading的作用
    • 对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。

猜你喜欢

转载自blog.csdn.net/shentanweilan9/article/details/80734512