4.3 用户自定义类
4.3.1 Empolyee类
在Java中,最简单的类定义形式为:class ClassName
{
field1
field2
...
constructor1
constructor2
...
method1
method2
...
}
下面是一个非常简单的Empolyee类.
class Empolyee
{
// instance fields
private String name;
private double salary;
private Date hireDay;
// constructor
public Empolyee(String n, double s, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
// a method
public String getName()
{
return name;
}
// more method
...
}
程序EmpolyeeTest.java显示了一个Employee类的实际使用.
源文件名为EmpolyeeTest.java,这是因为文件名必须与 public 类的名字相匹配. 在一个源文件中,只能有一个公有类,但可以有任意数目的非公有类.
EmpolyeeTest.java如下所示:
import java.util.*;
public class EmployeeTest
{
public static void main(String[] args)
{
// fill the staff array with three Employee objects
Employee[] staff = new Employee[3];
staff[0] = new Employee("li lei", 100, 1999, 1, 1);
staff[1] = new Employee("han meimei", 200, 1999, 1, 2);
staff[2] = new Employee("Tony Tester", 300, 1999, 1, 3);
// raise everyone's salary by 5%
for (Employee e : staff)
e.raiseSalary(5);
// print out information about all everyone objects
for (Employee e : staff)
System.out.println("name = " + e.getName() + ", salary = " + e.getSalary() + ", hireDay = " + e.getHireDay());
}
}
class Employee
{
private String name;
private double salary;
private Date hireDay;
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
// GregorianCalendar uses 0 for january
hireDay = calendar.getTime();
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public Date getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
}
4.3.2 多个源文件的使用
在程序EmpolyeeTest.java中,一个源文件包含了两个类.许多程序员习惯于将每一个类存在一个单独的源文件中.如果喜欢这样的组织,将可以有两种编译源程序的方法.一种是使用通配符调用Java编译器:
javac Employee*.java
于是,所有与通配符匹配的源文件都将被编译器编译成类文件.
第二种是键入下列命令:
javac EmpolyeeTest.java
虽然第二种方式并没有显式地编译Employee.java.但是,当Java编译器发现EmpolyeeTest.java使用了Employee类时会查找名为Employee.class的文件.如果没有找到这个文件,就会自动地搜索Employee.java,然后对它进行编译.更重要的是:如果Employee.java版本比已有的Employee.class版本新,Java编译器就会自动地重新编译这个文件.
注释:如果熟悉UNIX的make工具,可以认为Java编译器内置了make功能.
4.3.3 剖析Employee类
下面对Employee类进行剖析.首先从这个类的方法开始.通过查看源代码会发现,这个类包含一个构造器和4个方法:public Employee(String n, double s, int year, int month, int day)
public String getName()
public double getSalary()
public Date getHireDay()
public void raiseSalary(double byPercent)
这个类的所有方法都被标记为 public,关键字 public 意味着任何类的任何方法都可以调用这些方法.
接下来,需要注意在Employee类的实例中有三个实例域用来存放将要操作的数据:
private String name;
private double salary;
private Date hireDay;
关键字 private 确保只有Employee类自身的方法才能给访问这些实例域,而其他类的方法不能读写这些域.
注释:可以用 public 标记实例域,但这是一种极不提倡的做法. public 数据域允许程序中的任何方法对其进行读取和修改.这就完全破坏了封装.因此强烈建议将实例域标记为 private .
最后,有两个实例域本身就是对象:name域是String类对象,hireDay域是Date对象.这种情形十分常见:类通常包括类型属于某个类类型的实例域.
4.3.4 从构造器开始
下面先看看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();
}
可以看到构造器与类同名.在构造Employee类的对象时,构造器会运行,以便将实例域初始化为希望的状态.
例如,当使用下面这条代码创建Employee类实例时:
new Employee("james Bond", 100000, 1950, 1, 1)
将会初始化实例域设置相应的参数.
构造器与其他的方法有一个重要的不同.构造器总是伴随着 new 操作符的执行被调用,而不能对一个已经存在的对象调用构造器来达到重新设置实力域的目的.例如,
james.Employee("James Bond", 250000, 1950, 1, 1);
将会产生编译错误.
现在需要记住:
构造器与类同名
每个类可以有一个以上的构造器
构造器可以有0个,1个或多个参数
构造器没有返回值
构造器总是伴随着 new 操作符一起调用
注释:Java构造器的工作方式与C++一样.但是,要记住所有的Java对象都是在堆中构造的,构造器总是伴随着 new 操作符一起使用.C++程序员最易犯的错误就是忘记 new 操作符:
Employee number007("James Bond", 10000, 1950, 1, 1); // C++, not Java
这句话在C++中能够正常运行,但在Java中却不行.