4.4 静态域与静态方法
在前面给出的示例程序中,main方法都被标记为 static 修饰符.下面讨论这个修饰符的含义.4.4.1 静态域
如果将域定义为 static,每个类中只有一个这样的域.而每一个对象对于所有的实例域却都有自己的一份拷贝.例如,假定需要给每一个Employee赋予唯一的标识符.这里给Employee类添加一个实例域id和一个静态域nextId:class Employee
{
private static int nextId = 1;
private int id;
...
}
现在,每一个Employee对象都有一个自己的id域,但这个类的所有实例都将共享一个nextId域.换句话说,
如果有1000个Employee类的对象,则有1000个实例域id,但是只有一个静态域nextId.
即使没有一个Employee对象,静态域nextId也存在.它属于类,而不属于任何独立的对象.
注释:在绝大多数的面向对象程序设计语言中,静态域被称为类域.
下面实现一个简单的方法:
public void setId()
{
id = nextId;
nextId++;
}
假定为harry设置Employee标识码:
harry.setId();
harry的id域被设置为静态域nextId当前的值,并且静态域nextId的值加1:
harry.id = Employee.nextId;
Employee.nextId++;
4.4.2 静态常量
静态变量使用的比较少,但静态常量却使用的比较多.例如,在Math类中定义了一个静态常量:public class Math
{
...
public static final double PI = 3.14159265358979;
...
}
在程序中,可以采用Math.PI的形式获得这个常量.
如果关键字 static 被省略,PI就变成了Math类的一个实例域.需要通过Math类的对象访问PI,并且每一个Math对象都有它自己的一份PI拷贝.
另一个多次使用的静态常量是System.out,它在System类中声明:
public class System
{
...
public static final PrintStream out = ...;
...
}
每个类对象都可以对公有域进行修改,所以,最好不要将域设计为 public,然而,公有常量(即 final 域)却没有问题.因为out被声明为 final,所以,不允许再将其他打印流赋给它.
Syste.out = new PrintStream(...); // ERROR, out is final
4.4.3 静态方法
静态方法是一种不能面向对象实施操作的方法.例如,Math类的pow方法就是一个静态方法.表达式Math.pow(x, a)
计算x的a次幂.运算时,不使用任何Math对象.换句话说,没有隐式的参数.
可以认为静态方法是没有 this 参数的方法(在一个非静态的方法中,this 参数表示这个方法的隐式参数).
因为静态方法不能操作对象,所以不能再静态方法中访问实例域.但是,静态方法可以访问自身类中的静态域.下面是使用这种静态方法的一个示例:
public static int getNextId()
{
return nextId; // return static field
}
可以通过类名调用这个方法:
int n = Employee.getNextId();
这个方法可以省略关键字 static 吗?答案是肯定的.但是需要通过Employee类对象的引用调用这个方法.
注释:可以使用对象调用静态方法.但是很容易造成混淆,其原因是静态方法的结算与对象毫无关系.最好使用类名而不是对象来调用静态方法.
在下面两种情况下使用静态方法:
一个方法 不需要访问对象状态,其所需参数都是通过显式参数提供(例如Math.pow)
一个方法 只需要访问类的静态域(例如Employee.getNextId)
注释:Java中的静态域与静态方法在功能上与C++相同.但是语法书写上却不同.在C++中使用::操作符访问自身作用域之外的静态域和静态访问,例如Math::PI .
术语"static"有一段不寻常的历史. 起初,C引入关键字 static 是为了表示退出一个块后依然存在的局部变量.在这种情况下,术语"static"是由意义的:变量一直存在,当再次进入该块时仍然存在.随后,static 在C中有了 第二种含义,表示不能被其他文件访问的全局变量和函数.为了避免引入一个新的关键字,关键字 static 被重用了.最后,C++ 第三次重用了这个关键字,与前面赋予的含义完全不一样,这里将其解释为:属于类且不属于类对象的变量和函数,这个含义与Java相同.
4.4.4 工厂方法
静态方法还有一种常见的用途,NumberFormat类使用工厂方法产生不同风格的格式对象.NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.getPercentInstance();
double x = 0.1;
System.out.println(currencyFormatter.format(x)); // prints $0.10
System.out.println(percentFormatter.format(x)); // prints 10%
为什么NumberFormat类不利用构造器完成这些操作呢?这主要有两个原因:无法命名构造器.构造器的名字必须与类名相同.但是,这里希望将得到的货币实例和百分比实例采用不同的名字.
当使用构造器时,无法改变所构造的对象类型.而Factory方法将返回一个DecimalFormat类对象,这是NumberFormat的子类.
4.4.5 main方法
需要注意,不需要使用对象调用静态方法.例如,不需要构造Math类对象就可以调用Math.pow.同理,main方法也是一个静态方法.
public class Application
{
public static void main(String[] args)
{
// construct objects here
...
}
}
main方法不对任何对象进行操作.事实上,在启动程序时还没有任何一个对象,静态的main方法将执行并创建程序所需要的对象.
提示:每一个类可以有一个main方法.这是一个常用于对类进行单元测试的技巧.例如,可以在Employee类中添加一个main方法:
class Employee
{
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
...
public static void main(String[] args) // unit test
{
Employee e = new Employee("Romeo", 50000, 2000, 1, 31);
e.raiseSalary(10);
System.our.println(e.getName() + " " + e.getSalary());
}
...
}
如果想要独立地测试Employee类,只需要执行java Employee
如果Employee类是一个大型应用程序的一部分,就可以使用下面这条语句运行程序java Application
并且Employee类的main方法永远不会执行.示例程序StaticTest中的Employee类也有一个静态的main方法用于单元测试.程序staticTest.java如下所示:
public class StaticTest
{
public static void main(String[] args)
{
// fill the staff array with three Employee objects
Employee[] staff = new Employee[3];
staff[0] = new Employee("Tom", 4000);
staff[1] = new Employee("Jerry", 5000);
staff[2] = new Employee("Harry", 6000);
// print out information about all Employee objects
for (Employee e : staff)
{
e.setId();
System.out.println("name = " + e.getName() + ", id = " + e.getId() + ", salary = " + e.getSalary());
}
int n = Employee.getNextId(); // calls static method
System.out.println("Next available id = " + n);
}
}
class Employee
{
private static int nextId = 1;
private String name;
private double salary;
private int id;
public Employee(String n, double s)
{
name = n;
salary = s;
id = 0;
}
public String getName()
{
return name;
}
public int getId()
{
return id;
}
public double getSalary()
{
return salary;
}
public void setId()
{
id = nextId; // set id to next available id
nextId++;
}
public static int getNextId()
{
return nextId;
}
public static void main(String[] args) // unix test
{
Employee e = new Employee("harry", 5000);
System.out.println(e.getName() + " " + e.getSalary());
}
}