Home Tags Posts tagged with "静态内部类"

静态内部类

0 47

Java 静态类、静态方法和静态变量

static 是Java中的一个关键字,我们不能声明普通外层类或者包为静态的。static用于下面四种情况。

1.静态变量:我们可以将类级别的变量声明为static。静态变量是属于类的,而不是属于类创建的对象或实例。因为静态变量被类的所有实例共用,所以非线程安全的。通常静态变量还和关键字final一起用,作为所有对象共用的资源或常量。如果静态变量不是私有的,那么可以通过ClassName.variableName来访问它.

    //静态变量的例子
    private static int count;
    public static String str;
    public static final String DB_USER = "myuser"

3.静态块就是类加载器加载对象时,要执行的一组语句。它用于初始化静态变量。通常用于类加载的时候创建静态资源。我们在静态块中不能访问非静态变量。我们可以在一个类中有多个静态块,尽管这么做没什么意义。静态块只会在类加载到内存中的时候执行一次。

static{
    //在类被加载的时候用于初始化资源
    System.out.println("StaticExample static block");
    //仅能访问静态变量和静态方法
    str="Test";
    setCount(2);
}

4.静态类:我们对嵌套类使用static关键字。static不能用于最外层的类。静态的嵌套类和其它外层的类别无二致,嵌套只是为了方便打包。


静态类与内部类的区别

内部类

1、内部类拥有普通类的所有特性,也拥有类成员变量的特性
2、内部类可以访问其外部类的成员变量,属性,方法,其它内部类

静态内部类

1、只有内部类才能声明为static,也可以说是静态内部类
2、只有静态内部类才能拥有静态成员,普通内部类只能定义普通成员
3、静态类跟静态方法一样,只能访问其外部类的静态成员
4、如果在外部类的静态方法中访问内部类,这时候只能访问静态内部类

/**
 * 外部类,不能声明为 static
 * 
 * @author zhanqi
 * 
 */
public class OuterClass {

    private int ab = 1;
    private static int sab = 2;

    /**
     * 普通内部类
     */
    public class NormalInnerClass {

        // private static int age = 22;
        private int age = 22; // 不能声明为static

        public NormalInnerClass() {
            // 可以访问外部类静态与非静态成员
            System.out.println(ab);
            System.out.println(sab);
        }
    }

    /**
     * 静态内部类
     */
    public static class StaticInnerClass {
        // 定义静态与非静态成员都是可以的
        private static int age = 22;
        private int age2 = 22;

        private void echo() {
            // System.out.println(ab);
            System.out.println(sab);// 只能访问外部类的静态成员
        }
    }
}

内部类实例化

1.访问内部类,必须使用:外部类.内部类,OutClass.InnerClass
2.普通内部类必须绑定在其外部类的实例上
3.静态内部类可以直接 new

/**
 * 外部类访问内部类
 */
public class OuterClass {

    /**
     * 普通方法
     */
    public void method() {
        StaticInnerClass sic = new StaticInnerClass();
        NormalInnerClass nic = new NormalInnerClass();
    }

    /**
     * Main
     */
    public static void main(String[] args) {
        // 在静态方法中,只能访问静态成员,静态内部类
        // NormalInnerClass nic = new NormalInnerClass();
        StaticInnerClass sic = new StaticInnerClass();
    }
}

/**
 * 其它类访问内部类
 */
class Test {
    public static void main(String[] args) {
        /**
         * 1:其它类访问内部类,必须使用:外部类.内部类,OutClass.InnerClass
         * 2:普通内部类必须绑定在其外部类的实例上
         * 3:静态内部类可以直接 new
         */
        OuterClass.StaticInnerClass sic = new OuterClass.StaticInnerClass();
        // OuterClass.NormalInnerClass n = new OuterClass.NormalInnerClass();

        OuterClass oc = new OuterClass();
        OuterClass.NormalInnerClass nic = oc.new NormalInnerClass();
    }
}

静态对象

静态对象我们经常使用,但是对静态对象和非静态对象的区别却并不一定那么了解。下表格就是静态对象和非静态对象主要区别。

1.拥有的对象,静态对象是类共同拥有的 ,而非静态对象是由类单独拥有;
2.内存空间,静态对象内存空间是固定,非静态类对象是附着相关类的分配;
3.分配顺序,先分配静态对象的空间,然后才会分配非静态对象也就是初始化。

0 68

单例模式是非常重要的设计模式,在框架中使用单例模式可以保证某些工具类不会被多次创建而造成资源浪费,特此记录单例模式的几种实现

 

什么是单例模式呢?

类中只有一个实例,且该类能自行创建这个实例

其三大特点为:

1.只有一个实例对象

2.单例对象必须由单例类自行创建

3.单例类对外提供一个访问该单例的全局访问点

实现一:懒汉单例

懒汉单例,即在真正需要的时候才去创建实例;在getInstance(对外提供的访问点)方法中,先判断实例是否为空再决定是否去创建实例,看上去很完美。

首先我们创建需要实现单例模式的类User,过程如下

看上去我们确实创建了一个单例对象 User ,但是,存在线程安全问题,在并发获取实例的时候,可能会存在构建了多个实例的情况,所以需要改进

改进方式:synchronized + Volatile 共同保证多线程安全

volatile保证 
1.多线程情况下对其他线程可见 
2.禁止指令重排
原理:
第一:使用volatile关键字会强制将修改的值立即写入主存;
第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。

synchronized为该方法加锁,与Volatile共同保证多线程安全

懒汉模式实现单例到这里就结束了吗?并没有

单例模式只需要创建一次实例,如果后面再次调用getInstance方法时,则直接返回之前创建的实例,可是现在我们的getInstance方法被加了锁,高并发情况下每次来调用此方法的时候,都需要进行锁竞争,效率很低,有没有什么办法可以保证我们的单例模式同时又提高效率呢?

那就是双重校验通过缩小synchronized的粒度,改善多线程下的单例对象的创建和获取的效率问题

至此,一个安全同时效率也不错的懒汉单例模式诞生了~~~

—————————嗝  吃了个午饭,我们接下来实现饿汉单例模式——————————————————————————————————————-

实现二:饿汉单例

饿汉单例是实现一个安全的单例模式最简单粗暴的写法,之所以称为饿汉模式,是因为像饿汉一样,肚子很饿,想立马吃到东西,不想等待生产时间,这种写法,在类加载的时候九八Singleton实例给创建出来了
当然其缺点也很明显,可能在还不需要此实例的时候就已经把实例创建出来了,没有起到懒加载的效果

创建需要实现单例模式的类Person
由于类加载的时候就已经创建了Person的实例intance,不管谁调用getIntance方法都得到是intance,因此实现了单例和线程安全
怎么样,是不是很简单…

那么接下来,我们来个骚操作,枚举实现单例模式

———————————————————————————————————————————————-

实现三:枚举实现单例模式

为什么我们可以用枚举来实现单例模式呢?
我们先来了解一下枚举,枚举是一种特殊的类,它继承自Enum类,可以重写方法,自定义属性等
同时它有一个最最最最重要的点,那就是枚举的构造方法只能由JVM虚拟机来调用!!!
也就是说,我们是无法通过构造方法来生产枚举类实例的,如果要生成,那么就在枚举类型内部自定义枚举实例

正是因为枚举有这些特性,我们可以通过枚举来实现单例模式

首先定义一个枚举类Worker,在其中只定义了一个ONLY实例

getInstance方法还是作为全局访问点返回ONLY同样的,测试方法中我们直接调用getInstance方法即可获得单例对象至此,通过枚举来实现单例也被我们收入囊中,今天真实收获满满的一天呢…

补充一下,静态内部类也可以实现枚举,此处不深究

———————————————————————————————————————————————

实现四:静态内部类

public class Singleton {

    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }

    private Singleton() {
        
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

通过静态内部类的方式实现单例模式是线程安全的,同时静态内部类不会在Singleton类加载时就加载,而是在调用getInstance()方法时才进行加载,达到了懒加载的效果。

似乎静态内部类看起来已经是最完美的方法了,其实不是,可能还存在反射攻击或者反序列化攻击。且看如下代码:

public static void main(String[] args) throws Exception {
    Singleton singleton = Singleton.getInstance();
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton newSingleton = constructor.newInstance();
    System.out.println(singleton == newSingleton);
}

运行结果:

通过结果看,这两个实例不是同一个,这就违背了单例模式的原则了。

除了反射攻击之外,还可能存在反序列化攻击的情况。如下:

引入依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>

这个依赖提供了序列化和反序列化工具类。

Singleton类实现java.io.Serializable接口。

如下:

public class Singleton implements Serializable {

    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }

    private Singleton() {

    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        byte[] serialize = SerializationUtils.serialize(instance);
        Singleton newInstance = SerializationUtils.deserialize(serialize);
        System.out.println(instance == newInstance);
    }

}

运行结果: