使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。
day013 用户自定义类(上)(Employee类、多个源文件的使用、剖析Employee类、从构造器开始、隐式参数与显示参数)
我们已经开始编写了一些简单的类。但是,那些类都只包含一个简单的main方法。现在开始学习如何设计复杂应用程序所需要的各种主力类(workhorseclass)。通常,这些类没有main方法,却有自己的实例域和实例方法。要想创建一个完整的程序,应该将若干类组合在一起,其中只有一个类有main方法。
1.Employee类
在Java中,最简单的类定义形式为:
class ClassName
{
fieldi
fieldi
....
constructor1
constructor2
....
method1
method2
....
}
下面看一个非常简单的Employee类。
class Employee
{
//instance fields
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String n,double s,int year,int month,int day)
{
name = n;
salary = s;
hireDay = LocalDate.of(year, month, day);
}
public String getName()
{
return name;
}
//more methods
...
}
这里将这个类的实现细节分成以下几个部分,并分別在稍后的给予介绍。
下面先看一个程序,这个程序显示了一个Employee类的实际使用。
import java.time.*;
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("Carl Cracker",75000,1987,12,15);
staff[1] = new Employee("Harry Hacker",50000,1989,10,1);
staff[2] = new Employee("Tony Tester",40000,1990,3,15);
//raise everyone's salary by 5%
for (Employee e : staff)
e.raiseSalary(5);
//print out information about all Employee 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 LocalDate hireDay;
public Employee(String n,double s,int year,int month,int day)
{
name = n;
salary = s;
hireDay = LocalDate.of(year, month, day);
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public LocalDate getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
}
在这个程序中,构造了一个Employee数组,并填人了三个雇员对象:
Employee[] staff = new Employee[3];
staff[0] = new Employee("Carl Cracker",75000,1987,12,15);
staff[1] = new Employee("Harry Hacker",50000,1989,10,1);
staff[2] = new Employee("Tony Tester",40000,1990,3,15);
接下来,利用Employee类的raiseSalary方法将每个雇员的薪水提高5%:
for (Employee e : staff)
e.raiseSalary(5);
最后,调用getName方法、getSalary方法和getHireDay方法将每个雇员的信息打印出来:
for (Employee e : staff)
System.out.println("name="+e.getName()+",salary="+e.getSalary()+",hireDay="+e.getHireDay());
注意,在这个示例程序中包含两个类:Employee类和带有public访问修饰符的EmployeeTest类。EmployeeTest类包含了main方法,其中使用了前面介绍的指令。
源文件名是EmployeeTest.java,这是因为文件名必须与public类的名字相匹配。在一个源文件中,只能有一个公有类,但可以有任意数目的非公有类。
接下来,当编译这段源代码的时候,编译器将在目录下创建两个类文件:EmployeeTest.class和Employee.class。
将程序中包含main方法的类名提供给字节码解释器,以便启动这个程序:
javaEmployeeTest
字节码解释器开始运行EmployeeTest类的main方法中的代码。在这段代码中,先后构造了三个新Employee对象,并显示它们的状态。
2.多个源文件的使用
在上面的程序中,一个源文件包含了两个类。许多程序员习惯于将每一个类存在一个单独的源文件中。例如,将Employee类存放在文件Employee.java中,将EmployeeTest类存放在文件EmployeeTest.java中。
如果喜欢这样组织文件,将可以有两种编译源程序的方法。一种是使用通配符调用Java编译器:
javac Employee*.java
于是,所有与通配符匹配的源文件都将被编译成类文件。或者键人下列命令:
javac EmployeeTest.java
使用第二种方式,并没有显式地编译Employee.java然而,当Java编译器发现EmployeeTest.java使用了Employee类时会查找名为Employee.class的文件。如果没有找到这个文件,就会自动地搜索Employee.java,然后,对它进行编译。更重要的是:如果Employee,java版本较已有的Employee.class文件版本新,Java编译器就会自动地重新编译这个文件。
3.剖析Employee类
下面对Employee类进行剖析。首先从这个类的方法开始。通过查看源代码会发现,这个类包含一个构造器和4个方法:
public Employee(String n,double s,int year,int month,int day)
public String getName()
public double getSalary()
public LocalDate getHireDay()
public void raiseSalary(double byPercent)
这个类的所有方法都被标记为public。关键字public意味着任何类的任何方法都可以调用这些方法。
接下来,需要注意在Employee类的实例中有三个实例域用来存放将要操作的数据:
private String name;
private double salary;
private LocalDate hireDay;
关键字private确保只有Employee类自身的方法能够访问这些实例域,而其他类的方法不能够读写这些域。
最后,请注意,有两个实例域本身就是对象:name域是String类对象,hireDay域是LocalDate类对象。这种情形十分常见:类通常包括类型属于某个类类型的实例域。
4.从构造器开始
下面先看看Employee类的构造器:
public Employee(String n,double s,int year,int month,int day)
{
name = n;
salary = s;
hireDay = LocalDate.of(year, month, day);
}
可以看到,构造器与类同名。在构造Employee类的对象时,构造器会运行,以便将实例域初始化为所希望的状态。
例如,当使用下面这条代码创建Employee类实例时:
new Emaployee("]ames Bond", 100000, 1950, 1, 1 )
将会把实例域设置为:
name = "James Bond";
salary = 100000;
hireDay = LocalDate.of(1950, 1, 1 ); // January 1, 1950
构造器与其他的方法有一个重要的不同。构造器总是伴随着new操作符的执行被调用,而不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的。例如:
james.Employee("James Bond", 250000, 1950, 1, 1 )//ERROR
将产生编译错误
需要记住以下几点:
- 构造器与类同名
- 每个类可以有一个以上的构造器
- 构造器可以有0个、1个或多个参数
- 构造器没有返回值
- 构造器总是伴随着new操作一起调用
注意:不要在构造器中定义与实例域重名的局部变量。例如,下面的构造器将无法设置salary。
public Employee(String n,double s,...)
{
String name = n;//Error
double salary = s;//Error
...
}
这个构造器声明了局部变量name和salary。这些变量只能在构造器内部访问。这些变量屏蔽了同名的实例域.这种错误很难被检查出来,因此,必须注意在所有的方法中不要命名与实例域同名的变量。
5.隐式参数与显示参数
方法用于操作对象以及存取它们的实例域。例如,方法:
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
将调用这个方法的对象的salary实例域设置为新值。看看下面这个调用:
number007.raiseSalary(5);
它的结果将number007.salary域的值增加5%。具体地说,这个调用将执行下列指令:
double raise = number007.salary * 5 / 100;
number007.salary += raise;
raiseSalary方法有两个参数。第一个参数称为隐式(implicit)参数,是出现在方法名前的Employee类对象。第二个参数位于方法名后面括号中的数值,这是一个显式(explicit)参数(有些人把隐式参数称为方法调用的目标或接收者。)
可以看到,显式参数是明显地列在方法声明中的,例如double by Percent。隐式参数没有出现在方法声明中。
在每一个方法中,关键字this表示隐式参数。如果需要的话,可以用下列方式编写raiseSalary方法:
public void raiseSalary(double byPercent)
{
double raise = this.salary * byPercent / 100;
this.salary += raise;
}
这样可以将实例域与局部变量明显地区分开来。