static, enum, 内部类与单例模式

页面导航:首页 > 软件编程 > Java编程 > static, enum, 内部类与单例模式

static, enum, 内部类与单例模式

来源: 作者: 时间:2016-01-18 15:52 【

static, enum, 内部类与单例模式标签: Java与设计模式Java static与初始化块拾忆 static关键字的作用是把类的成员变成类相关,而不是实例相关 普通初始化块 当Java创建一个对象时,

 

static, enum, 内部类与单例模式

标签: Java与设计模式


Java static与初始化拾忆

static关键字的作用是把类的成员变成类相关,而不是实例相关.

普通初始化块

当Java创建一个对象时, 系统先为对象的所有实例变量分配内存(前提是该类已经被加载过了), 然后开始对这些实例变量进行初始化, 顺序是: 先执行初始化块或声明实例变量时指定的初始值(这两处执行的顺序与他们在源代码中排列顺序相同), 再执行构造器里指定的初始值.

静态初始化块

又名类初始化块(普通初始化块负责对象初始化, 类初始化块负责对类进行初始化). 静态初始化块是类相关的, 系统将在类初始化阶段静态初始化, 而不是在创建对象时才执行. 因此静态初始化块总是先于普通初始化块执行.

执行顺序

系统在类初始化以及对象初始化时, 不仅会执行本类的初始化块[static/non-static], 而且还会一直上溯到java.lang.Object类, 先执行Object类中的初始化块[static/non-static], 然后执行其父类的, 最后是自己.
顶层类(初始化块, 构造器) -> ... -> 父类(初始化块, 构造器)
-> 本类(初始化块, 构造器)

小结:

static{} 静态初始化块会在类加载过程中执行;
普通{} 则只是在对象初始化过程中执行, 但先于构造器;


Java Enum拾忆

枚举类与普通类的区别:
1. 枚举类继承了java.lang.Enum, 而不是Object, 因此枚举不能显示继承其他类; 其中Enum实现了Serializable和Comparable接口(implements Comparable, Serializable);
2. 非抽象的枚举类默认使用final修饰,因此枚举类不能派生子类;
3. 枚举类的所有实例必须在枚举类的第一行显示列出(枚举类不能通过new来创建对象); 并且这些实例默认/且只能是public static final的;
4. 枚举类的构造器默认/且只能是private;
5. 枚举类通常应该设计成不可变类, 因此建议成员变量都用private final修饰;
6. 枚举类不能使用abstract关键字将枚举类声明成抽象类(因为枚举类不允许有子类), 但如果枚举类里面有抽象方法, 或者枚举类实现了某个接口, 则定义每个枚举值时必须为抽象方法提供实现, 如:

/**
 * Created by jifang on 15/12/9.
 */
public interface GenderDesc {
    void info();
}
public enum Gender implements GenderDesc {
    MALE("男"), FEMALE("女");

    private final String name;

    Gender(String name) {
        this.name = name;
    }


    @Override
    public void info() {
        System.out.println("该枚举代表 " + name);
    }
}

或者:

public enum Gender implements GenderDesc {
    MALE("男") {
        @Override
        public void info() {
            System.out.println("该枚举代表 男");
        }
    }, FEMALE("女") {
        @Override
        public void info() {
            System.out.println("该枚举代表 女");
        }
    };

    private final String name;

    Gender(String name) {
        this.name = name;
    }
}

Java内部类拾忆

Java 外部类只有两种访问权限:public/default, 而内部类则有四种访问权限:private/default/protected/public. 而且内部类还可以使用static修饰; 内部类分为成员内部类与局部内部类, 相对来说成员内部类用途更广泛, 局部内部类用的较少(匿名内部类除外), 成员内部类又分为静态(static)内部类与非静态内部类, 这两种成员内部类同样要遵守static与非static的约束(如static内部类不能访问外部类的非静态成员等)

非静态内部类

非静态内部类在外部类内使用时, 与平时使用的普通类没有太大区别; Java不允许在非static内部类中定义static成员; 如果外部类成员变量, 内部类成员变量与内部类中的方法里面的局部变量有重名, 则可通过this, 外部类名.this加以区分. 非静态内部类的成员可以访问外部类的private成员, 但反之不成立, 内部类的成员不被外部类所感知. 如果外部类需要访问内部类中的private成员, 必须显示创建内部类实例, 而且内部类的private权限对外部类也是不起作用的:
public class OuterClass {

    @Test
    public void test() {
        visitInner();
    }

    public void visitInner() {
        InnerClass clazz = new InnerClass();
        clazz.value = 10;
        System.out.println(clazz.value);
    }

    private class InnerClass {
        private int value;
    }
}

静态内部类

使用static修饰内部类, 则该内部类隶属于该外部类本身, 而不属于外部类的某个对象. 由于static的作用, 静态内部类不能访问外部类的实例成员, 而反之不然;

匿名内部类

如果(方法)局部变量需要被匿名内部类访问, 那么该局部变量需要使用final修饰.

单例模式

保证一个类只有一个实例, 并提供一个访问他的全局访问点.

典型场景:

Windows中的任务管理器; 文件系统, 一个操作系统只能有一个文件系统; 连接池的设计与实现; Spring中, 一个Component就只有一个实例; Java-Web中, 一个Servlet类只有一个实例;

单例模式的优点:

由于单例模式只生成一个实例, 减少了系统性能开销(如: 当一个对象的产生需要比较多的资源时, 如读取配置, 产生其他依赖对象, 则可以通过在应用启动时直接产生一个单例对象, 然后永久驻留内存的方式来解决)

Singleton实现三个要点:

隐藏构造器 static Singleton实例 暴露实例获取方法
此处输入图片的描述

Singleton实现追求的三个目标

线程安全 调用效率高 延迟加载

常见的单例模式实现方式有五种: 饿汉式, 懒汉式, 双重锁定式, 静态内部类, enum枚举.

1. 饿汉式

/**
 * 饿汉式
 * 问题: 如果只是加载本类, 而没有调用getInstance方法, 会造成资源浪费
 * Created by jifang on 15/12/4.
 */
public class HungerSingleton {

    /**
     * 类初始化时理解初始化该实例
     * 类加载时, 天然的线程安全时刻
     */
    private static final HungerSingleton instance = new HungerSingleton();

    private HungerSingleton() {
    }

    /**
     * 方法没有同步(synchronized), 调用效率高
     */
    public static HungerSingleton getInstance() {
        return instance;
    }

    @Override
    public String toString() {
        return "HungerSingleton{}";
    }
}

2. 懒汉式

/**
 * 懒汉式
 * 问题: 每次调用getInstance都要同步(synchronized), 效率降低
 * Created by jifang on 15/12/4.
 */
public class LazySingleton {

    /**
     * 类加载时并没初始化, 延迟加载
     */
    private static LazySingleton instance;

    private LazySingleton() {
    }

    /**
     * 注意synchronized, 线程安全
     */
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

    @Override
    public String toString() {
        return "LazySingleton{}";
    }
}

3. 双重锁定式

由于同步只在第一次实例化Instance时才需要,也就是单例类实例创建的时候, 因此我们使用双重锁定式(double checked locking pattern)

注: 有的文章中提到由于JVM底层内部模型原因, 双重锁定偶尔会出问题, 不建议使用, 但是自从JDK1.5开始, 该问题已经被解决了, 因此可放心使用, 详见如何在Java中使用双重检查锁实现单例

/**
 * 双重锁定实现
 * 问题: 适用于JDK1.5之后的版本
 * Created by jifang on 15/12/4.
 */
public class DoubleCheckSingleton {

    /**
     * 需要使用volatile
     * 保证所有的写(write)都将先行发生于读(read)
     */
    private static volatile DoubleCheckSingleton instance;

    private DoubleCheckSingleton() {
    }

    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {                          //Single Checked
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {                  // Double Checked
                    instance = new DoubleCheckSingleton();
                }
            }
        }

        return instance;
    }

    @Override
    public String toString() {
        return "DoubleCheckSingleton{}";
    }
}

4. 静态内部类实现

/**
 * 静态内部类实现Singleton
 * Created by jifang on 15/12/4.
 */
public class StaticInnerSingleton {

    /**
     * 外部类没有static属性, 因此加载本类时不会立即初始化对象
     */
    private static class InnerClassInstance {
        private static final StaticInnerSingleton instance = new StaticInnerSingleton();
    }

    private StaticInnerSingleton() {
    }

    /**
     * 只有真正调用getInstance方法时, 才会加载静态内部类(延迟加载), 而且加载类是天然的线程安全的(线程安全), 没有synchronized(调用效率高)
     *
     * @return
     */
    public static StaticInnerSingleton getInstance() {
        return InnerClassInstance.instance;
    }

    @Override
    public String toString() {
        return "StaticInnerSingleton{}";
    }
}

5. 枚举实现

/**
 * 枚举实现单例
 * 基于JVM底层实现, Enum天然的单例以及线程安全
 * Created by jifang on 15/12/5.
 */
public enum EnumSingleton {

    /**
     * 构造方法默认为private
     */
    INSTANCE;

    /**
     * 可以添加其他操作
     * other operation
     */
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "EnumSingleton{" +
                "name='" + name + '\'' +
                '}';
    }
}

几种实现方式对比

方式 优点 缺点
饿汉式 线程安全, 调用效率高 不能延迟加载
懒汉式 线程安全, 可以延迟加载 调用效率不高
双重检测锁式 线程安全, 调用效率高, 可以延迟加载 -
静态内部类式 线程安全, 调用效率高, 可以延迟加载 -
枚举单例 线程安全, 调用效率高 不能延迟加载

单例破解与防御

反射破解单例
可以利用的反射机制破解上面的单例模式(Enum是破解不了的, 因为他是基于JVM底层实现的, 暂时还不知道如何破解), 下面仅介绍破解双重锁定单例, 其他类同, 不再赘述.
/**
 * 单例破解
 * Created by jifang on 15/12/4.
 */
public class TestCase {

    @Test
    public void testBreakDoubleCheck() {
        try {
            Class clazz = (Class) Class.forName("com.feiqing.singleton.DoubleCheckSingleton");
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);

            DoubleCheckSingleton instance1 = constructor.newInstance();
            DoubleCheckSingleton instance2 = constructor.newInstance();
            System.out.println("singleton? " + (instance1 == instance2));
            System.out.println(instance1.hashCode());
            System.out.println(instance2.hashCode());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}
序列化/反序列化破解单例
首先对LazySingleton进行改造, 使其实现Serializable接口, 以支持序列化/反序列化.
public class LazySingleton implements Serializable{

    private static final long serialVersionUID = 8511876423469188139L;
    /**
     * 类加载时并没初始化, 延迟加载
     */
    private static LazySingleton instance;

    private LazySingleton() {
        if (instance != null){
            throw new RuntimeException();
        }
    }

    /**
     * 注意synchronized, 线程安全
     */
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

    @Override
    public String toString() {
        return "LazySingleton{}";
    }
}

破解:

public class TestCase {

    private static final String SYSTEM_FILE = "/tmp/save.txt";

    @Test
    public void testBreakLazy() {
        LazySingleton instance1 = LazySingleton.getInstance();

        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(SYSTEM_FILE));
            oos.writeObject(instance1);

            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(SYSTEM_FILE));
            LazySingleton instance2 = (LazySingleton) ois.readObject();

            System.out.println("singleton? " + (instance1 == instance2));
            System.out.println(instance1.hashCode());
            System.out.println(instance2.hashCode());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}
防御: 在LazySingleton中添加下面一个方法:
    /**
     * 反序列化时, 如果定义了readResolve方法, 则直接返回此方法制定的对象.
     *
     * @return
     */
    private Object readResolve() {
        return instance;
    }

详细信息请参考深入理解Java对象序列化


单例性能测试

/**
 * 单例性能测试
 * Created by jifang on 15/12/4.
 */
public class TestCase {

    private static final String SYSTEM_FILE = "/tmp/save.txt";
    private static final int THREAD_COUNT = 10;
    private static final int CIRCLE_COUNT = 100000;

    @Test
    public void testSingletonPerformance() throws IOException, InterruptedException {
        final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        FileWriter writer = new FileWriter(new File(SYSTEM_FILE), true);

        long start = System.currentTimeMillis();
        for (int i = 0; i < THREAD_COUNT; ++i) {
            new Thread(
                    new Runnable() {
                        @Override
                        public void run() {
                            for (int i = 0; i < CIRCLE_COUNT; ++i) {
                                Object instance = HungerSingleton.getInstance();
                            }
                            latch.countDown();
                        }
                    }
            ).start();
        }
        latch.await();
        long end = System.currentTimeMillis();

        writer.append("HungerSingleton 共耗时: " + (end - start) + " 毫秒\n");
        writer.close();
    }
}

测试结果:

1 HungerSingleton 共耗时: 30 毫秒 2 LazySingleton 共耗时: 48 毫秒 3 DoubleCheckSingleton 共耗时: 25 毫秒 4 StaticInnerSingleton 共耗时: 16 毫秒 5 EnumSingleton 共耗时: 6 毫秒
Enum毫无疑问的成为了实现单例的王者, <Effective Java>中也推荐使用实现单例, 因此Enum成为Java中实现单例的最好方式, 但是Enum也有其自身的限制(见上), 因此在实际开发中, 还需要综合考虑.
Tags:

文章评论

最 近 更 新
热 点 排 行
Js与CSS工具
代码转换工具

<