anonymous的Java学习笔记(17)——Java中的类成员及单例类的实现

@[toc]

类成员

类成员的定义

Java类里只能包含成员变量方法构造器初始化块内部类(包括接口、枚举)5种成员,目前已经介绍了前面4种,其中static可以修饰成员变量方法初始化块内部类(包括接口、枚举),static修饰的成员就是类成员。

  • static关键字修饰的成员就是类成员,前面已经介绍的类成员类变量类方法静态初始化块3个成分
  • static关键字不能修饰构造器
  • static修饰的类成员属于整个类,不属于单个实例
  • static修饰的初始化块叫做静态初始化块也是一种类成员。

如何访问类变量

  • 类变量既可通过来访问,也可通过类的对象来访问。通过对象访问类变量只是一种假象,通过对象访问的依然是该类的类变量,可以这样理解:通过对象来访问类变量时,系统会在底层转换为通过该类来访问类变量。因此,从程序运行表面来看,即可看到同一类的所有实例的类变量共享同一块内存区。
  • 很多对象都不允许通过对象来访问类变量,对象只能访问实例变量;类变量必须用类来访问。
  • 一个null对象访问实例成员(包括实例变量和实例方法),将会引发java.lang.NullPointerException空指针异常,因为null表明该实例根本不存在,既然实例不存在,那么它的实例变量和实例方法自然也不存在.

代码示例

package com.abc.part5;

/**
 * @author mi
 */
public class NullAccessStatic {
    public static void say() {
        System.out.println("static修饰的类方法~");
    }

    static String name;
    int age = 21;

    public String eat() {
        return "我正在吃饭";
    }

    public static void main(String[] args) {
//        NullAccessStatic nullAccessStatic = new NullAccessStatic();
        NullAccessStatic nullAccessStatic = null;
//        say();
        //以下代码虽然会正常输出。但是在IDEA中会报红,提示:不应该通过类实例访问静态成员,所以类成员必须使用类名来访问。
        nullAccessStatic.say();
        nullAccessStatic.name = "哈哈";
        System.out.println(nullAccessStatic.name);
        System.out.println("************************************************");
        NullAccessStatic.say();
        System.out.println(NullAccessStatic.name);
        System.out.println("************************************************");
        //一个null对象访问实例成员(包括实例变量和实例方法),将会引发`java.lang.NullPointerException`空指针异常,因为null表明该实例根本不存在,既然实例不存在,那么它的实例变量和实例方法自然也不存在.
        //System.out.println(nullAccessStatic.age);
        //nullAccessStatic.eat();
        /**
         * 以上代码会输出
         * static修饰的类方法~
         * 哈哈
         * ************************************************
         * static修饰的类方法~
         * 哈哈
         * ************************************************
         */
    }
}

单例类

背景

大部分时候都把类的构造器定义成public访问权限,允许任何类自由创建该类的对象。但在某些时候,允许其他类自由创建该类的对象没有任何意义,还可能造成系统性能下降(因为频繁地创建对象、回收对象带来的系统开销问题)。

基于以上背景引入单例类的概念

如果一个类始终只能创建一个实例,则这个类被称为单例类。

实现一个单例类的步骤

  1. 根据良好封装的原则:一旦把该类构造器隐藏起来,就需要提供public方法作为该类访问点,用于创建该类的对象,且该方法必须使用static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)
  2. 该类还必须缓存已经创建的对象,否则该类无法知道是否曾经创建过对象,也就无法保证只创建一个对象。为此该类需要使用一个成员变量保存曾经创建的对象,因为该成员变量需要被上面的静态方法访问,故该成员变量必须使用static修饰。

单例类代码示例

package com.abc.part5;

/**
 * @author mi
 */
public class Singleton {
    /**
     * 使用一个类变量来缓存曾经创建过的实例
     */
    private static Singleton instance;

    /**
     * 对构造器使用private修饰,隐藏该构造器
     */
    private Singleton() {

    }

    /**
     * 提供一个静态方法,用于返回Singleton实例
     * 该方法可以加入自定义控制,保证只产生一个Singleton对象
     *
     * @return Singleton对象
     */
    public static Singleton getInstance() {
        /**
         * 如果instance为null,则表明还不曾创建过Singleton对象
         * 如果instance不为null,则表明已经创建了Singleton对象,将不会重新创建新的实例
         */
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

}

单例测试类

package com.abc.part5;

public class SingletonTest {
    public static void main(String[] args) {
        /**
         * 创建Singleton类的对象不能通过构造器
         * 只能通过getInstance方法来创建类实例
         */
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println("s1:" + s1);
        System.out.println("s2:" + s2);
        System.out.println(s1.equals(s2));
        System.out.println(s1 == s2);
        /**
         * 输出
         * s1:com.abc.part5.Singleton@60e53b93
         * s2:com.abc.part5.Singleton@60e53b93
         * true
         * true
         */
    }
}

正是通过上面getInstance()方法提供的自定义控制(这也是封装的优势:不允许自由访问类的成员变量和实现细节,而是通过方法来控制合适暴露) ,保证Singleton类只能产生一个实例。

所以,在SingletonTest类的main()方法中,看到两次产生的Singleton对象实际上是同一个对象