static关键字
1、静态变量
在定义一个类时,只是在描述某类事物的特征和行为,并没有产生具体的数据。只有通过new关键字创建该类的实例对象后,系统才会为每个对象分配空间,存储各自的数据。有时候,我们希望某些特定的数据在内存中只有一份,而且能够被一个类的所有实例对象所共享。例如某个学校所有学生共享同一个学校名称,此时完全不必在每个学生对象所占用的内存空间中都定义一个变量来表示学校名称,而可以在对象以外的空间定义一个表示学校名称的变量让所有对象来共享。
在一个Java类中,可以使用static关键字来修饰成员变量,该变量被称作静态变量。静态变量被所有实例共享,可以使用“类名.变量名”的形式来访问。
案例1:
class Student {
static String schoolName; //定义静态变量schoolName
}
public class Example1 {
public static void main(String[] args) {
Student stu1=new Student(); //创建学生对象
Student stu2=new Student();
Student.schoolName="环太平洋联合大学"; //为静态变量赋值
System.out.println("我的学校是"+stu1.schoolName); //打印第一个学生对象的学校
System.out.println("我的学校是"+stu2.schoolName); //打印第二个学生对象的学校
}
}
案例1的Student类中定义了一个静态常量schoolName,用于表示学生所在的学校,它被所有的实例所共享。由于schoolName是静态常量,因此可以直接使用Student.schoolName的方式进行调用,也可以通过Student的实例对象进行调用,如stu2.schoolName。第8行代码将变量schoolName赋值为“坏太平洋联合大学”,通过运行结果可以知道stu1和stu2的schoolName均为“环太平洋联合大学”。
注意!!!
static关键字只能用于修饰成员变量,不能用于修饰局部变量,否则编译会报错,下面的代码是非法的。
public class Student {
public void study() {
static int num=10; //这行代码是非法的,编译会报错
}
}
2、静态方法
有时我们希望在不创建对象的情况下就可以调用某个方法,换句话说也就是使该方法不必和对象绑在一起。要实现这样的效果,只需要在类中定义的方法前加上static关键字即可,我们称这种方法伪静态方法。同静态变量一样,静态方法可以使用“类名.方法名”的方式来访问,也可以通过类的实例对象来访问。
案例2:
class Person {
public static void sayHello() { //定义静态方法
System.out.println("hello");
}
}
class Example2 {
public static void main(String[] args) {
Person.sayHello(); //调用静态方法
}
}
案例2的Person类中定义了静态方法sayHello(),在第8行代码处通过“Person.sayHello()”的形式调用了静态方法,由此可见静态方法不需要创建对象就可以调用。
在一个静态方法中只能访问用static修饰的成员,原因在于没有被static修饰的成员需要先创建对象才能访问,而静态方法在被调用时可以不创建任何对象。
3、静态代码块
在Java类中,使用一对大括号包围起来的若干行代码被称为一个代码块,用static关键字修饰的代码块称为静态代码块。当类被加载时,静态代码块会执行,由于类只加载一次,因此静态代码块只执行一次。在程序中,通常会使用静态代码块来对类的成员变量进行初始化。
案例3:
class Example3 {
//静态代码块
static {
System.out.println("测试类的静态代码块执行了");
}
public static void main(String[] args) {
//下面的代码创建了两个Person对象
Person p1=new Person();
Person p2=new Person();
}
}
class Person {
static String country;
//下面是一个静态代码块
static {
country="china";
System.out.println("Person类中的静态代码块执行了");
}
}
从运行结果可以看出,程序中的两段静态代码块都执行了。在命令行窗口输入“java Example3”后,虚拟机首先会加载类Example3,在加载类的同时就会执行该类的静态代码块,紧接着会调用main()方法。在该方法中创建了两个Person对象,但在两次实例化对象的过程中,静态代码块只执行一次,这就说明类在第一次使用时才会被加载,并且只会加载一次。
4、单例模式
在编写程序时经常会遇到一些典型的问题或需要完成某种特定需求,设计模式就是针对这些问题和需求,在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免得自己再去思考和摸索。
单例模式就是Java中的一种设计模式,它是指在设计一个类时,需要保证在整个程序运行期间针对该类只存在一个实例对象。就好像我们生存的世界只有一个月亮,假设现在要设计一个类表示月亮,该类只能有一个实例对象,否则就违背了事实。
案例4:
class Example4 {
//自己创建一个对象
private static Single INSTANCE=new Single();
private Single() {} //私有化构造方法
public static Single getInstance() { //提供返回该对象的静态方法
return INSTANCE;
}
}
案例4中的Single类就实现了单例模式,它具备如下的特点:
- 类的构造方法使用private修饰,声明为私有,这样就不能在类的外部使用new关键字来创建实例对象了。
- 在类的内部创建一个该类的实例对象,并使用静态变量INSTANCE引用该对象,由于变量应该禁止外界直接访问,因此使用private修饰,声明为私有成员。
- 为了让类的外部能够获得类的实例对象,需要定义一个静态方法getInstance(),用于返回该类实例INSTANCE。由于方法是静态的,外界可以通过“类名.方法名”的方式来访问。
案例5:
class Example5 {
public static void main(String[] args) {
Single s1=Single.getInstance();
Single s2=Single.getInstance();
System.out.println(s1==s2);
}
}
从运行结果可以看出,变量s1和s2值相等,这说明变量s1和s2引用同一个对象。也就是说两次调用getInstance()方法获得的是同一个对象,而getInstance()方法是获得Single类实例对象的唯一途径,因此Single类是一个单例的类。
注意!!!
单例模式也可以写成以下形式:
class Single {
private Single() {}
public static final Single INSTANCE = new Single();
}
在上述代码中,首先将类的构造方法私有,防止外界创建该类的实例。在类的内部创建了该类的实例对象,并使用静态变量INSTANCE来引用,变量INSTANCE的前面有三个修饰符,其中,public的作用是允许外部直接访问该变量,final的作用是禁止外部对该变量进行修改,static的作用是让外部可以使用“类名.变量名”的方式来访问变量。由于访问变量INSTANCE是获得Single类实例对象的唯一途径,因此该类实现了单例。
被关键字final修饰的变量为常量,其值不可改变。
内部类
1、成员内部类
在一个类中除了可以定义成员变量、成员方法,还可以定义类,这样的类被称作成员内部类。在成员内部类中可以访问外部类的所有成员。
案例6:
class Outer {
private int num=4; //定义类的成员变量
//下面的代码定义了一个成员方法,方法中访问内部类
public void test() {
Inner inner=new Inner();
inner.show();
}
//下面的代码定义了一个成员内部类
class Inner {
void show() {
//在成员内部类的方法中访问外部类的成员变量
System.out.println("num="+num);
}
}
}
public class Example6 {
public static void main(String[] args) {
Outer outer=new Outer(); //创建外部类对象
outer.test(); //调用test()方法
}
}
案例6中,Outer类是一个外部类,在该类中定义了一个内部类Inner和一个test()方法,其中,Inner类有一个show()方法,在show()方法中访问外部类的成员变量num,test()方法中创建了内部类Inner的实例对象,并通过该对象调用show()方法,将num值进行打印。从运行结果看,内部类可以在外部类中被调用,并能访问外部类的成员。
如果想通过外部类去访问内部类,则需要通过外部类对象去创建内部类对象,创建内部类对象的具体语法格式如下:
外部类名.内部类名 变量名=new 外部类名().new 内部类名();
接下来针对案例6中定义的Outer类写一个测试程序
案例7:
public class Example7 {
public static void main(String[] args) {
Outer.Inner inner=new Outer().new Inner(); //创建内部类
inner.show(); //调用内部类test()方法
}
}
运行结果同案例6一样。需要注意的是,如果内部类被声明为私有,外界将无法访问。
2、静态内部类
可以使用static关键字来修饰一个成员内部类,该内部类被称作静态内部类,它可以在不创建外部类对象的情况下被实例化。
外部类名.内部类名 变量名=new 外部类名.内部类名();
案例8:
class Outer {
private static int num=6;
//下面的代码定义了一个静态内部类
static class Inner {
void show() {
System.out.println("num="+num);
}
}
}
class Example8 {
public static void main(String[] args) {
Outer.Inner inner=new Outer.Inner(); //创建内部类对象
inner.show(); //调用内部类的方法
}
}
案例8中内部类Inner使用static关键字来修饰,是一个静态内部类。第12行代码创建了内部类对象,可以看出静态内部类的实例化方式与非静态的成员内部类的实例化方式是不一样的。
注意!!!
- 在静态内部类中只能访问外部类的静态成员,如将第2行代码定义的变量num前面的static去掉,程序编译会出错。
- 在静态内部类中可以定义静态的成员,而在非静态的内部类中不允许定义静态的成员。下面的代码是非法的。
class Outer {
class Inner {
static int num=10; //不能定义静态成员,编译报错
void show() {
System.out.println("num="+num);
}
}
}
3、方法内部类
方法内部类是指在成员方法中定义的类,它只能在当前方法中被使用。
案例9:
class outer {
private int num=4; //定义成员变量
public void test() {
//下面是在方法中定义的内部类
class Inner {
void show() {
System.out.println("num="+num); //访问外部类的成员变量
}
}
Inner inner=new inner(); //创建内部类的对象
inner.show(); //调用内部类的方法
}
}
public class Example9 {
public static void main(String[] args) {
Outer outer=new Outer(); //创建外部类对象
outer.test(); //调用test()方法
}
}
案例9中,在Outer类的test()方法中定义了一个内部类Inner。由于Inner是方法内部类,因此程序只能在方法中创建该类的实例对象并调用show()方法。方法内部类也可以访问外部类的成员变量num。