类成员
类成员的定义
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访问权限
,允许任何类自由创建该类的对象。但在某些时候,允许其他类自由创建该类的对象没有任何意义,还可能造成系统性能下降
(因为频繁地创建对象、回收对象
带来的系统开销问题
)。
基于以上背景引入单例类的概念
如果一个类始终只能创建一个实例,则这个类被称为单例类。
实现一个单例类的步骤
- 根据良好封装的原则:一旦把
该类
的构造器隐藏起来
,就需要提供public方法
作为该类
的访问点
,用于创建该类的对象
,且该方法
必须使用static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)
。 该类
还必须缓存已经创建的对象
,否则该类
无法知道是否曾经创建过对象
,也就无法保证只创建一个对象
。为此该类
需要使用一个成员变量
来保存曾经创建的对象
,因为该成员变量
需要被上面的静态方法访问
,故该成员变量
必须使用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对象实际上是同一个对象。