自定义 Java 类(学习 Java 编程语言 026)

1. 定义类

下面是最简单的类的定义形式:

class ClassName {
    field1
    field2
    ...
    constructor1
    constructor2
    ...
    method1
    method2
    ...
}

下面是一个简单的 Employee(员工)类:

import java.time.LocalDate;
import java.util.Objects;

public class Employee {
    // 姓名
    private String name;
    // 薪资
    private double salary;
    // 认知日期
    private LocalDate hireDay;

    public Employee(String name, double salary, int year, int month, int day) {
        Objects.requireNonNull(name, "The name cannot be null");
        this.name = name;
        this.salary = salary;
        hireDay = LocalDate.of(year, month, day);
    }


    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public LocalDate getHireDay() {
        return hireDay;
    }

    /**
     * 增加工资
     * @param byPercent 百分比
     */
    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }

    public static void main(String[] args) {
        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);

        for (Employee e : staff) {
            e.raiseSalary(5);
        }

        for (Employee e : staff) {
            System.out.println("name=" + e.getName() +
                    ",salary=" + e.getSalary() +
                    ",hireDay=" + e.getHireDay());
        }
    }
}

这个程序中,构造了一个 Employee 数组,并填入了 3 个 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());
}

2. 剖析 Employee 类

Employee 类包含一个构造器和 4 个方法:

public Employee(String name, double salary, int year, int month, int day)
public String getName()
public double getSalary()
public LocalDate getHireDay()
public void raiseSalary(double byPercent)

这个类的所有方法都被标记为 public。关键字 public 意味着任何类的任何方法都可以调用这些方法。

在 Employee 类中的实例中有 3 个实例字段用来存放将要操作的数据:

private String name;
private double salary;
private LocalDate hireDay;

关键字 private 确保只有 Employee 类自身的方法能够访问这些实例字段,而其他方法不能够读写这些字段。

3. 构造器

Java 对象都是在堆中构造的。
先看看 Employee 类的构造器:

public Employee(String name, double salary, int year, int month, int day) {
    this.name = name;
    this.salary = salary;
    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); // 1950-01-01

构造器与其他方法有一个重要的不同:构造器总是结合 new 运算符来调用。不能对一个已经存在的对象调用构造器来达到重新设置的字段目的。

有关构造方法,需要记住:

  • 构造器与类同名
  • 每个类可以有一个以上的构造器。
  • 构造器可以有 0 个、1 个或多个参数。
  • 构造器没有返回值。
  • 构造器总是伴随着 new 操作符一起调用。

注意,不要在构造器中定义与实例字段同名的局部变量。例如:

public Employee(String n, double s, int y, int m, int d) {
    String name = n; // 错误
    double salary = s; // 错误
    ...
}

这个构造器声明了局部变量 name 和 salary。这些变量只能在构造器内部方法。这些变量会遮蔽同名的实例字段。这些程序员偶尔会不假思索地写出这类代码,因为它们的手指会不自觉地增加数据类型。这种错误很难检查出来,因此,必须注意在所有方法中都不要使用与实例字段同名的变量。

4. 用 var 声明局部变量

在 Java 10 中,如果可以从变量的初始值推导出它们的类型,那么可以用 var 关键字声明局部变量,而无需指定类型。例如:
Employee harry = new Employee("Harry Hacker", 500000, 1989, 10, 1);
可以改写为:
var harry = new Employee("Harry Hacker", 500000, 1989, 10, 1);
这样可以避免重复写类型为 Employee。

如果无须了解任何 Java API 就能从等号右边明显看出类型,在这种情况下可以使用 var 表示法。不要对数值类型使用 var,如 int、long 或 double,可以不用当心 0、0L 和 0.0 之间的区别。对 Java API 有了更多使用经验后,你可能会希望更多地使用 var 关键字。

注意: var 关键字只能用于方法中的局部变量。参数和字段的类型必须声明。

5. 使用 null 引用

一个对象变量可以包含一个对象的引用,或者包含一个特殊的 null,null 表示没有引用任何对象。

如果对 null 值应用一个方法,会产生一个 NullPointException 异常。

LocalDate birthday = null;
String s = birthday.toString(); // NullPointException

这是一个很严重的错误,类似于“索引越界”异常。如果你的程序没有“捕获”异常,程序就会终止。正常情况下,程序并不捕获这类异常,而是依赖于程序员从一开始就不要带来异常。

定义一个类时,最好清楚地知道哪些字段可能为 null。

针对可能为 null 的字段有两种解决方式:

  1. “宽容性”的方法,就是把 null 参数转换为一个适当的非 null 值:
    public Employee(String name, double salary, int year, int month, int day) {
        if (name == null) {
            this.name = "unknown";
        } else {
            this.name = name;
        }
    }
    
    在 Java 9 中,Objects 类对此提供了一个便利方法:
    public Employee(String name, double salary, int year, int month, int day) {
        this.name = Objects.requireNonNullElse(name, "unknown");
    }
    
  2. “严格型”方法则是干脆拒绝 null 参数:
    public Employee(String name, double salary, int year, int month, int day) {
        Objects.requireNonNull(name, "The name cannot be null");
        name = name;
        ...
    }
    
    如果 name 为 null 时,会抛出 NullPointerException 异常。使用这种方法有两个好处:
    1. 异常报告会提供这个问题的描述。
    2. 异常报告会准确地指出问题所在的问题所在位置,否则 NullPointException 异常可能在其他地放出现,而很难追踪到真正导致问题的这个构造方法。

如果要接受一个对象引用作为构造参数,就要认真思考是不是真的希望接受可有可无的值。如果不是,“严格型”方法更为合适。

6. 隐式参数与显式参数

方法用于操作对象以及存取它们的实例字段。

public void raiseSalary(double byPercent) {
    double raise = salary * byPercent / 100;
    salary += raise;
}

raiseSalary 方将会将调用这个方法的对象的 salary 实例字段设置为一个新值。
number007.raiseSalary(5);
它的结果是将 number007.salary 字段的值增加 5%。这个调用将执行下列指令:

double raise = number007.salary * 5 / 100;
number007.salary += raise;

raiseSalary 方法有两个参数。

  • 第一个参数称为隐式(implicit)参数,是出现在方法名前的 Employee 对象。
  • 第二个参数位 方法名后面括号中的数值,这是一个显式(explicit)参数。(有些人把隐式参数称为方法调用的目标或接受者。)

显式参数是明显地列在方法声明中的,隐式参数没有出现在方法声明中。在每一个方法中,关键字 this 表示隐式参数。

public void raiseSalary(double byPercent) {
    double raise = this.salary * byPercent / 100;
    this.salary += raise;
}

有些程序员更偏爱这样的风格,因为这样可以将实例域与局部变量明显地区分开来。

在 Java 中,所有的方法都必须在类的内部定义,但并不表示它们是内联方法。是否将某个方法设置为内联方法是 Java 虚拟机的任务。即时编译器会监事调用那些简洁、经常被调用、没有被重载以及可优化的方法。

你可能感兴趣的:(自定义 Java 类(学习 Java 编程语言 026))