在java中声明类的成员变量和成员方法时,可以使用static关键字把成员声明为静态成员。静态变量也叫类变量,非静态变量叫实例变量;静态方法也叫类方法,非静态方法叫实例方法。
静态成员最主要的特点是它不属于任何一个类的对象,它不保存在任意一个对象的内存空间中,而是保存在类的公共区域中。所以任何一个对象都可以直接访问该类的静态成员,都能获得相同的数据值。修改时,也在类的公共区域修改。
Java中静态成员,包括静态方法、变量和常量,以及一些特殊的静态方法,如main方法和factory方法。
1.静态的方法和静态变量
通常情况下,方法必须通过它的类对象访问。但是如果希望该方法的使用完全独立于该类的任何对象,可以利用static关键字。通过该关键字可以创建这样一个方法,它能够被自己使用,而不必引用特定的实例。在方法的声明前面加上static即可。使用static关键字的方法即为静态方法。
如果一个方法被声明static,它就能够在它的类的任何对象创建之前被访问,而不必引用任何对象。但是在静态方法中,不能以任何方式引用this或super。
静态方法和静态变量示例如下。
//静态方法和静态变量示例
class StaticDemo{
//s为静态变量
public static String s="我是静态变量";
//printInfo为静态方法
public static void printInfo(){
System.out.println("我是静态方法");
}
}
public class Sample3_13{
public static void main(String args[])
{
//调用StaticDemo中的printInfo方法时,不需要创建新的StaticDemo对象,直接访问即可
StaticDemo.printInfo();
//调用StaticDemo中的s变量时,直接访问即可
System.out.println(StaticDemo.s);
}
}
2.静态变量和常量
在Java中没有一个直接的修饰符来实现常量,而是通过静态成员变量的方式来实现的,如以下代码说明了这个问题。
//声明3个常量X、Y、Z
public static final int X=10;
static public final int Y=20;
final static public int Z=40;
static表示属于类,不必创建对象就可以使用,因为常量应该不依赖于任何对象;final表示值不能改变;一般用作常量的静态成员变量访问权限都设置为public,因为常量应该允许所有类或对象访问。
另外需要注意的是,static可以与其他修饰符组合使用,且顺序可以任意调换。
对于非静态成员变量时,系统不会为其分配默认值,必须在构造器完成之前对其初始化。对于静态最终成员变量,系统也不会为其分配默认值,也要求开发人员必须对其进行初始化。但是静态变量属于类,是不能等到构造器运行再初始化,因为类加载完成之后其值必须可以使用。
在Java中,静态成员变量的初始化要求在静态语句块结束前必须完成。即Java中静态成员变量的初始化时机有两个,在声明的同时进行初始化或者在静态语句块中进行初始化。
举例初始化静态成员变量。
//初始化静态成员变量
public class Sample3_14{
//声明并初始化常量const1
public static final int const1 = 1111;
//声明常量const2
public static final int const2;
//静态语句块,也是静态成员的一种。在此静态语句块中初始化了常量const2。
//静态语句块在类加载时执行一次,可以将对类进行初始化的代码写在其中。
static{
//初始化常量const2
const2 = 2222;
}
public static void main(String[] args)
{
//打印两个常量的值
System.out.println("两个常量的值分别为:const1=" + const1 + ",const2=" + const2);
}
}
3.静态成员的访问
上小节已经介绍过静态成员是属于类的,因此对其进行访问应该不需要创建对象,可以使用“<类名>.<静态成员名>”
//静态成员访问示例
public class Sample3_15{
//声明静态成员变量并初始化
static int staticVar = 13;
public static void main(String[] args){
//调用静态成员变量
//直接用名称“staticVar”调用了静态成员变量staticVar,在静态成员所在的同一个类中是可以的
//在其他类中调用时要使用类名,或相应的对象引用,受访问限制级别的约束。
Sample3_15.staticVar = 16;
System.out.println("静态成员变量staticVar的值为:" + staticVar);
}
}
//静态方法访问非静态成员示例1
public class Sample3_16
{
//声明非静态成员变量并初始化
int staticVar = 13;
public static void main(String[] aegs)
{
System.out.println("成员变量staticVar的值为:" + staticVar + "。");
}
}
编译报“
无法从静态上下文中引用非静态变量staticVar”错,这是因为main()方法自身就是一个静态方法,而staticVar是非静态成员,,因为静态成员不依赖于该类的任何对象,所以当其所在的类加载成功以后,就可以被访问了,此时对象并不一定存在,非静态成员自然也不一定存在,静态成员的生命周期比非静态成员的长。
即使访问时存在非静态成员,静态方法也不知道访问的是哪一个对象的成员,因为静态方法属于类,非静态成员属于对象,所以静态方法将不知道关于其所属类对象的信息
Main()方法之所以被定义为静态的也因为如此,其只是程序开始执行的入口,不需要依赖任何对象。若要在静态方法中访问非静态成员只要使用指向特定对象的引用即可。
public static void main(String[] aegs)
{
Sample3_16 s = new Sample3_16();
System.out.println("成员变量staticVar的值为:" + s.staticVar + "。");
}
而静态方法访问静态成员时,自然是任何时候都没有问题,静态成员都属于类,只要类存在,静态成员都将存在。
同样的道理,在静态方法中是不能使用this预定义对象的引用的,即使其后边所操作的也是静态成员也不行,因为this代表指向自己对象的引用,而静态方法是属于类的,不属于对象,其成功加载后,对象不一定存在,即使存在,也不知道this指的是哪一个对象。
//静态方法访问非静态成员示例2
public class Sample3_16{
//声明静态成员变量
static int x = 1000;
public static void main(String[] args){
//在静态方法main()中使用this
int y = this.x;
}
}
编译系统将报“无法从静态上下文中引用非静态变量this”错误。
非静态方法访问静态成员
非静态方法访问静态成员的时候,规则比较简单。非静态成员的生命周期被静态成员生命周期包含,因此当非静态成员存在的时候,静态成员绝对存在。故非静态方法在任何时候都可以访问静态成员。
//在非静态方法访问静态成员
public class Sample3_18{
static int s = 10000;
// 在非静态方法中调用静态成员s
public void showStatic(){
System.out.println("非静态方法getStatic()成功调用静态成员s,其值为:" + Sample3_18.s);
}
public static void main(String args[])
{
Sample3_18 sa = new Sample3_18();
sa.showStatic();
}
}
4.main()方法
在Java中,main()方法是Java应用程序的入口方法,因为main()方法是由Java虚拟机调用的,所以必须为public,虚拟机调用main()方法的时候不需要产生任何对象,所以main方法声明为static,且不需要返回值,所以声明为void
public static void main(String[ ] args)
既然是类,Java允许类不加public关键字约束,当然类的定义只能限制为public或者无限制关键字(默认的)。
而且main()方法不准抛出异常,因此main方法中的异常要么处理,要么不处理,不能继续抛出。
当一个类中有main() 方法,执行命令“java类名”则会启动虚拟机执行该类中的main()方法。由于JVM在运行这个Java应用程序的时候,首先会调用main()方法,调用时不实例化这个类的对象,而是通过类名直接调用,因此需要限制其为public static。
5.Factory方法
Java的静态方法有一种的用途,就是使用Factory方法产生不同风格的对象,例如NumberFormat类使用Factory方法产生不同风格的格式对象。Factory Method是最常用的模式了,Factory方法在Java程序系统中可以说是随处可见。
Factory方法相当与创建实例对象的new,我们经常要根据类Classs生成实例对象,如A a = new A( ),Factory Method也是用来创建实例对象,所以以后new时可以考虑实用工厂模式,虽然这样做,可能多做一些工作,但会给系统带来更大的可扩展性和尽量少的修改量。
下面以类Sample为例,如果要创建Sample的实例对象。
Sample sample = new Sample( );
但实际情况是,通常用户都要在创建sample实例时做初始化的工作,例如赋值、查询数据库等。首先想到的是,可以使用Sample的构造函数,这样生成实例就写成
Sample sample = new Sample( 参数);
但是,如果创建sample实例时所做的初始化工作不是像赋值这样简单的事,可能是很长一段代码,如果写入构造函数中,那代码就很难看了。初始化工作如果是很长一段代码,说明要做的工作很多,将很多工作装入一个方法中,相当于将很多鸡蛋放在一个篮子里,是很危险的,这也是有悖于Java面向对象的原则的,面向对象的封装(Encapsulation)和分派(Delegation)告诉我们,尽量将长的代码“切割”成段,将每段再“封装”起来(减少段和段之间耦合性),这样,将会将风险分散,以后如果需要修改,只要更改每段。不会在发生牵一发动百的事情。
所以,首先需要将创建实例的工作和使用实例的工作分开,也就是说让创建实例需要的大量初始化工作从Sample的构造函数中分离出去。
这时就需要使用Factory方法来生成对象了,不能再用上面的“new Sample(参数)”了。还有,如果Sample有个继承如MySample,按照面向编程,则需要将Sample抽象成一个接口。现在Sample是接口,有两个子类MySample和HisSample,要实例化它们:
Sample mysample = new MySample( );
Sample hisample = new HisSample( );
上面所示的Sample类可能还会“生出很多儿子出来”(继承),那么要对这些儿子一个个实例化,而且可能还要对以前的代码进行修改,加入到后来生出的儿子的实例中。下面是一个简单的Factory例子。
<span style="font-size:14px;">//Fruit接口包含水果的种植生长和收获,实现该接口类的都必须覆盖这些方法
public interface Fruit{
//种植
void plant();
//生长
void grow();
//收获
void harvest();
}
//苹果类实现了Fruit接口
public class Apple implements Fruit{
private int treeAge;
//种植
public void plant(){
System.out.println("苹果已经种植");
}
//生长
public void grow(){
System.out.println("苹果正在生长中.....");
}
//收获
public void harvest(){
System.out.println("苹果已经收获");
}
//返回树龄
public int getTreeAge(){
return treeAge;
}
//设置树龄
public void setTreeAge(){
this.treeAge = treeAge;
}
}
//AppleGardener为生产苹果的工厂
public class AppleGardener{
public static Apple factory(){
Apple f = new Apple();
System.out.println(" 水果工厂(AppleGerdener)成功创建一个水果:苹果!");
return f;
}
}
public class Sample3_20{
private void test(){
//生产苹果
Apple a = AppleGardener.factory();
}
public static void main(String args[])
{
Sample3_20 test = new Sample3_20();
test.test();
}
}</span>
在上面例子中,苹果实现了水果接口。苹果的生产由专门的苹果Factory负责,这样,在不涉及苹果的具体子类的情况下,达到了封装效果,也减少了错误修改的机会。