本文将继续对Java程序设计的对象和类进行深入详细介绍,主要涉及以下内容:
- 面向对象程序设计
- 如何创建标准Java类库中的类对象
- 如何编写自己的类
4、关于static
4.1 静态域
即将域定义为static,每个类中只有一个这样的域,而每一个对象对于所有的实例域却都有自己的一份拷贝。也可以说静态域是属于类的,不属于任何独立的对象。
4.2 静态常量
静态变量使用的比较少,但静态常量使用的比较多。
如Math类中定义了一个静态常量
可以采用Math.PI的形式获得这个常量。
如果关键字static被忽略,PI就变成了Math类的一个实例域,需要通过Math类的对象访问PI,并且每一个Math对象都有她自己的一份PI拷贝。
4.3 静态方法
静态方法是一种不能向对象实施操作的方法,如Math类中的pow方法,表达式为
Math.pow(x,a)
在运算时,并不适用任何Math对象,即没有隐式参数,既可以认为静态方法是没有this参数的方法。
由于静态方法不能操作对象,所以不能在静态方法中访问实例域,但是,静态方法可以访问自身类中的静态域。
一般在以下两种情况使用静态方法
(1)一个方法不需要访问对象状态,其所需参数都是通过显式参数提供的,如Math.pow
(2)一个方法只需要访问类的静态域
4.4 main方法
调用静态方法,并不需要使用对象,如不需要构造Math类对象就可以调用Math.pow。
因此,可见main方法也是一个静态方法,main方法并不对任何对象进行操作。
注意,每一个类可以有一个main方法,这是一个常用于对类进行单元测试的技巧。
4.5 小试牛刀
下面我们将上面介绍的知识应用到之前编写的Employee类中
/**
* This program demonstrates static methods.
* @version 1.01 2004-02-19
* @author Cay Horstmann
*/
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", 40000);
staff[1] = new Employee("Dick", 60000);
staff[2] = new Employee("Harry", 65000);
// 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 double getSalary()
{
return salary;
}
public int getId()
{
return id;
}
public void setId()
{
id = nextId; // set id to next available id
nextId++;
}
public static int getNextId()
{
return nextId; // returns static field
}
public static void main(String[] args) // unit test
{
Employee e = new Employee("Harry", 50000);
System.out.println(e.getName() + " " + e.getSalary());
}
}
可见上述程序包含了一个简单的Employee类版本,其中有一个静态域nextId和一个静态方法getNextId。Employee类也有一个静态的main方法用于单元测试。
5、方法参数
按···调用用来描述程序设计语言中方法参数的传递方式,即有关将参数传递给方法的方式。
各种程序设计语言的方法参数传递方式基本可以概括为
(1)按值调用表示方法接受的是调用者提供的值
(2)按引用调用表示方法接受的是调用者提供的变量地址
一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。
Java总是采用按值调用,即方法得到的是所有参数值的一个拷贝,且方法不能修改传递给它的任何参数变量的内容。
6、对象构造
Java提供了多种编写构造器的方式
6.1 重载
即类存在多个构造器,多个构造器方法有相同的明仔、不同的参数,便产生了重载。
此时,编译器必须挑选出具体执行哪个方法,它通过各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。
如
再次声明,Java允许重载任何方法,而非局限于构造器方法。因此,要完整描述一个方法,需要指出方法名以及参数类型,这叫做方法的签名,如String类有四个indexOf()的公有方法,签名依次为
注意,返回类型并不是方法签名的一部分。即不能有两个名字相同、参数类型也相同但返回不同类型值的方法。
6.2 默认域初始化
注意,域与局部变量不同的在于,必须明确地初始化方法中的局部变量,否则没有初始化类中的域将被初始化为默认值:数值为0,布尔值为false,对象引用为null。
6.3 无参数的构造器
有时类会包含一个无参数的构造函数,对象由无参数构造函数创建时,其状态会被设置为适当的默认值。
(1)如果在编写一个类时没有编写构造器,那么系统会提供一个无参数构造器,这个构造器将所有的实例域设置为默认值:数值为0,布尔值为false,对象引用为null。
(2)如果类中提供了至少一个构造器,但是没有提供无参数的构造器,则在构造对象时如果没有提供参数就会被视为不合法。
6.4 显示域初始化
由于类的构造器方法可以重载,所以可以采用多种形式设置类的实例域的初始状态。
可以在类定义中,在执行构造器之前,限制性对域的赋值操作。尤其当一个类的所有构造器都希望把相同的值富裕某个特定的实例域时,这种方式特别有用。当然初始值不一定时常量,也可以调用方法对域进行初始化,如
6.5 参数名
注意当构造器中的参数名与实例域名相同时,会对实例域进行屏蔽,因此,可以采用this作隐式参数的形式来访问实例域,如
6.6 调用另一个构造器
关键字this除了做隐式参数,还可以调用另一个构造器用。如果构造器的第一个语句形如this(···),这个构造器将调用同一个类的另一个构造器。如此一来对公共的构造器代码部分只需要编写一次。
6.7 初始化块
初始化数据域的方法有
(1)在构造器中设置值
(2)在声明中赋值
(3)初始化块:在一个类的声明中,可以包含多个代码块,只要构造类的对象,这些块就会被执行,如
上例中,无论使用哪个构造器构造对象,id域都在对象初始化块中被初始化。首先运行初始化块,然后才运行构造器的主体部分。
实际上,这种机制较少使用。
下面是调用构造器的具体处理步骤:
6.8 对象析构与finalize方法
C++中有显示的析构器方法,其中放置一些当对象不再使用时需要执行的清理代码,但由于Java有自动的垃圾回收器,不需要人工回收内存,因此,Java并不支持析构器。
但是某些对象使用了内存之外的其他资源时,当该资源不再需要时,Java支持为任何一个类添加finalize方法,此方法将在垃圾回收器清除对象之前调用。
如果某个资源需要在使用完毕后立刻被关闭,就需要由人工来管理。对象用完后,可以使用一个close方法来完成相应的清理操作。
6.9 小试牛刀
下面的程序综合展示了上面讨论的许多特性
import java.util.*;
/**
* This program demonstrates object construction.
* @version 1.01 2004-02-19
* @author Cay Horstmann
*/
public class ConstructorTest
{
public static void main(String[] args)
{
// fill the staff array with three Employee objects
Employee[] staff = new Employee[3];
staff[0] = new Employee("Harry", 40000);
staff[1] = new Employee(60000);
staff[2] = new Employee();
// print out information about all Employee objects
for (Employee e : staff)
System.out.println("name=" + e.getName() + ",id=" + e.getId() + ",salary="
+ e.getSalary());
}
}
class Employee
{
private static int nextId;
private int id;
private String name = ""; // instance field initialization
private double salary;
// static initialization block
static
{
Random generator = new Random();
// set nextId to a random number between 0 and 9999
nextId = generator.nextInt(10000);
}
// object initialization block
{
id = nextId;
nextId++;
}
// three overloaded constructors
public Employee(String n, double s)
{
name = n;
salary = s;
}
public Employee(double s)
{
// calls the Employee(String, double) constructor
this("Employee #" + nextId, s);
}
// the default constructor
public Employee()
{
// name initialized to ""--see above
// salary not explicitly set--initialized to 0
// id initialized in initialization block
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public int getId()
{
return id;
}
}