Java入门——定义你自己的类(一)

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可能会在其他地方发生,没有简单的方法可以将其追溯到错误的构造函数参数。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容