1.一个Employee class
Java中类定义的最简单形式是:
class ClassName
{
field1
field2
. . .
constructor1
constructor2
. . .
method1
method2
. . .
}
考虑以下非常简化的Employee类版本,企业在编写工资系统时可能会使用它:
class Employee
{
// instance fields
private String name;
private double salary;
private LocalDate hireDay;
// constructor
public Employee(String n, double s, int year, int month, int day) {
name = n;
salary = s;
hireDay = LocalDate.of(year, month, day);
}
// a method
public String getName()
{
return name;
}
// more methods
. . .
}
我们将在以下各节中详细介绍该类的实现。 不过,下面是一个展示Employee class在实际应用的程序,
1import java.time.*;
2 public class EmployeeTest
3 {
4 public static void main(String[] args)
5 {
6 // fill the staff array with three Employee objects
7 Employee[] staff = new Employee[3];
8
9 staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15);
10 staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
11 staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);
12
13 // raise everyone's salary by 5%
14 for (Employee e : staff)
15 e.raiseSalary(5);
16
17 // print out information about all Employee objects
18 for (Employee e : staff)
19 System.out.println("name=" + e.getName() + ",salary=" + e.getSalary() + ",hireDay="
20 + e.getHireDay());
21 }
22 }
23
24 class Employee
25 {
26 private String name;
27 private double salary;
28 private LocalDate hireDay;
29
30 public Employee(String n, double s, int year, int month, int day)
31 {
32 name = n;
33 salary = s;
34 hireDay = LocalDate.of(year, month, day);
35 }
36
37 public String getName()
38 {
39 return name;
40 }
41
42 public double getSalary()
43 {
44 return salary;
45 }
46
47 public LocalDate getHireDay()
48 {
49 return hireDay;
50 }
51
52 public void raiseSalary(double byPercent)
53 {
54 double raise = salary * byPercent / 100;
55 salary += raise;
56 }
57 }
在该程序中,我们构造了一个Employee数组并用三个Employee对象填充它:
Employee[] staff = new Employee[3];
staff[0] = new Employee("Carl Cracker", . . .);
staff[1] = new Employee("Harry Hacker", . . .);
staff[2] = new Employee("Tony Tester", . . .);
接下来,我们使用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类和带有公共访问说明符的EmployeeTest类。
我们刚刚描述的指令的main方法包含在EmployeeTest类中。源文件的名称为EmployeeTest.java,因为文件名必须与公共类的名称匹配。 在源文件中只能有一个公共类,但是可以有任意数量的非公共类。
接下来,当您编译此源代码时,编译器会在目录中创建两个类文件:EmployeeTest.class和Employee.class,然后启动 通过为字节码解释器提供包含程序主要方法的类的名称来创建程序:
java EmployeeTest
字节码解释器开始在EmployeeTest类的main方法中运行代码。 这段代码依次构造了三个新的Employee对象,并显示它们的状态。
2.使用多个源文件
以上程序在一个源文件中有两个类。 许多程序员喜欢将每个类放入其自己的源文件中。 例如,您可以将Employee类放入文件Employee.java中,并将EmployeeTest类放入EmployeeTest.java中。
如果您喜欢这种安排,则有两种选择来编译程序。您可以使用通配符来调用Java编译器:
javac Employee * .java
然后,所有与通配符匹配的源文件都将被编译为类文件。
或者,您可以简单地键入
EmployeeTest.java
即使从未显式编译Employee.java文件,第二种选择仍然有效,您可能会感到惊讶。 但是,当Java编译器看到EmployeeTest.java中正在使用Employee类时,它将寻找一个带文件名的Employee.class。 如果找不到该文件,它将自动搜索Employee.java并进行编译。 此外,如果找到的Employee.java版本的时间戳记比现有Employee.class文件的时间戳记新,则Java编译器将自动重新编译该文件。
3.剖析Employee类
在以下各节中,我们将剖析Employee类。 让我们从此类中的方法开始。 通过检查源代码可以看到,该类具有一个构造函数和四个方法:
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类本身的方法。 没有外部方法可以读取或写入这些字段.
注意:您可以在实例字段中使用public关键字,但这不是一个好主意。 具有public关键字的字段将允许程序的任何部分读取和修改实例字段,从而完全破坏封装。 任何类的任何方法都可以修改公共字段,并且根据我们的经验,某些代码在您最不期望的时候会利用该访问权限。 我们强烈建议您将所有实例字段都设为私有。
最后,请注意,两个实例字段本身就是对象:
name和hireDayfields是对String和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 Employee("James Bond", 100000, 1950, 1, 1)
您已按如下所示设置了实例字段:
name = "James Bond";
salary = 100000;
hireDay = LocalDate.of(1950, 1, 1); // January 1, 195
构造函数和其他方法之间存在重要区别。 只能与new运算符一起调用构造函数。 您无法将构造函数应用于现有对象以重置实例字段。 例如,
james.Employee("James Bond", 250000, 1950, 1, 1) // ERRO
现在,请记住以下几点:
•构造函数与类具有相同的名称。
•一个类可以具有多个构造函数。
•构造函数可以接受零个,一个或多个参数。
•构造函数没有返回值 。
•始终使用new运算符调用构造函数。
C++注释:构造函数在java中和C++一样工作。记住,所有java对象都是在堆上构建的,构造函数必须与新的组合。忘记这一点是C++程序员的常见错误Employee number007(“詹姆斯邦德”,100000,1950,1,1);//C++
注意:注意不要引入与实例字段同名的局部变量。 例如,以下构造函数将不会设置薪水:
public Employee(String n,double s,...)
{
String name = n; //ERROR
double salary = s; // ERROR
...
}
构造函数声明局部变量名称和工资。 这些变量只能在构造函数内部访问。 它们使实例字段具有相同的名称。不要使用与实例字段名称相同的变量名称。
5.var关键字
从Java 10开始,您可以使用var关键字声明局部变量,而不用指定其类型,只要可以从初始值推断出它们的类型即可。 例如,声明
Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
相当于
var harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
这样做很好,因为它避免了重复使用类型名称Employee。
从现在开始,在那些右侧类型很明显但不知道Java API的情况下,我们将使用var表示法。 一旦您对Java API有了更丰富的经验,您可能希望更频繁地使用var关键字。
请注意,var关键字只能与方法内部的局部变量一起使用。您必须始终声明参数和字段的类型。
6.null值的应用
如果将null值应用于方法,则会发生NullPointerException.
LocalDate birthday = null;
String s = birthday.toString(); // NullPointerException
这是一个严重的错误,类似于“索引超出范围”例外。 如果您的程序没有“捕获”异常,它将终止。 通常,程序不会捕获这类异常,而是首先要让程序员不要导致异常。定义类时,最好弄清楚哪些字段可以为null。
在我们的示例中,我们不希望name或hiringDay为null。 (我们不必担心薪金字段。它具有原始类型,并且永远不能为null。)
由于使用新的LocalDate对象初始化了hiredDay字段,因此可以保证其不为null。 但是如果使用n的null参数调用构造函数,则名称将为null。
有两种解决方案。 “宽松”的方法是将null参数转换为适当的non-null值:
if (n == null) name = "unknown"; else name = n;
从Java 9开始,Objects类具有用于此目的的便捷方法:
public Employee(String n, double s, int year, int month, int day)
{
name = Objects.requireNonNullElse(n, "unknown");
. . .
}
另一种方法是拒绝一个null参数:
public Employee(String n, double s, int year, int month, int day)
{
Objects.requireNonNull(n, "The name cannot be null");
name = n;
. . .
}
如果有人用空名称构造Employee对象,则会发生aNullPointerException。 乍一看,这似乎不是一种有用的补救方法,但是有两个优点:
1.异常报告中有问题的说明。
2.异常报告指出了问题的位置。 否则,NullPointerException可能会在其他地方发生,没有简单的方法可以将其追溯到错误的构造函数参数。