对象的后续处理

对象的后续处理

目标

  • 了解方法重载和重写
  • 能够比较一个对象与另一个对象
  • 了解如何和何时使用类变量和方法

重载方法

是时候了解一下 Person 类了。Person 现在比较有用,但没有达到应有的实用程度。我们首先通过重载Person 的方法来增强它。

创建两个具有相同名称和不同参数列表(即不同的参数数量或类型)的方法时,您就拥有了一个重载 方法。在运行时,JRE 基于传递给它的参数来决定调用您的重载方法的哪个变体。

假设 Person 需要两个方法来打印其当前状态的审计结果。我将这两个方法都命名为 printAudit()。将清单 1 中的重载方法粘贴到 Eclipse 编辑器视图中的 Person 类中:

清单 1. printAudit():一个重载方法

public void printAudit(StringBuilder buffer) {
   buffer.append("Name=");
   buffer.append(getName());
   buffer.append(",");
   buffer.append("Age=");
   buffer.append(getAge());
   buffer.append(",");
   buffer.append("Height=");
   buffer.append(getHeight());
   buffer.append(",");
   buffer.append("Weight=");
   buffer.append(getWeight());
   buffer.append(",");
   buffer.append("EyeColor=");
   buffer.append(getEyeColor());
   buffer.append(",");
   buffer.append("Gender=");
   buffer.append(getGender());
}

public void printAudit(Logger l) {
   StringBuilder sb = new StringBuilder();
   printAudit(sb);
   l.info(sb.toString());
}

您拥有 printAudit() 的两个重载版本,一个版本甚至使用了另一个版本。通过提供两个版本,调用方能够选择如何打印类的审计结果。Java 运行时依据传递的参数而调用正确的方法。

在使用重载方法时,请记住两条重要规则:

不能仅通过更改一个方法的返回类型来重载它。 不能拥有两个具有相同名称和相同参数列表的方法。 如果违背这些规则,编译器就会抛出错误。

重写方法

如果一个子类提供其父类中定义的方法的自有实现,这被称为方法重写。要了解方法重写有何用处,需要在您的 Employee 类上执行一些工作。请观看下面的视频,了解如何设置 Employee 类和在该类中执行方法重写。

Employee:Person 的一个子类

回想一下 第 3 单元:面向对象的概念和原理,Employee 类可以是 Person 的子类(或孩子),它包含更多属性,比如纳税人识别号、员工编号、招入日期和工资。您现在将声明 Employee 类,稍后将添加这些属性。

要声明 Employee 类,可在 Eclipse 中右键单击 com.makotojava.intro 包。单击 New > Class...,在 New Java Class 对话框中,输入 Employee 作为类名,Person 作为它的超类。单击 Finish,可以在一个编辑窗口中看到 Employee 类代码。

您不是明确需要声明构造方法,但无论如何,让我们实现两个构造方法。让 Employee 类编辑窗口拥有焦点,转到 Source > Generate Constructors from Superclass...。在 Generate Constructors from Superclass 对话框中,选择两个构造方法并单击 OK。您现在拥有一个与清单 2 类似的 Employee 类。

清单 2. Employee 类

package com.makotojava.intro;

public class Employee extends Person {

  public Employee() {
    super();
    // TODO Auto-generated constructor stub
  }

  public Employee(String name, int age, int height, int weight,
  String eyeColor, String gender) {
    super(name, age, height, weight, eyeColor, gender);
    // TODO Auto-generated constructor stub
  }

}

Employee 是 Person 的子类 Employee 继承它的父类 Person 的属性和行为。添加 Employee 自己的一些属性,如清单 3 中第 7 到第 9 行所示。

清单 3. 包含 Person 的属性的 Employee 类

package com.makotojava.intro;

import java.math.BigDecimal;

public class Employee extends Person {

  private String taxpayerIdentificationNumber;
  private String employeeNumber;
  private BigDecimal salary;

  public Employee() {
    super();
  }
  public String getTaxpayerIdentificationNumber() {
    return taxpayerIdentificationNumber;
  }
  public void setTaxpayerIdentificationNumber(String taxpayerIdentificationNumber) {
    this.taxpayerIdentificationNumber = taxpayerIdentificationNumber;
  }

  // Other getter/setters...
}

不要忘了生成新属性的 getter 和 setter,就像在 第 5 单元:您的第一个 Java 类 中对 Person 所做的那样。

重写 printAudit() 方法

现在您将重写 printAudit() 方法(参见 清单 1),该方法用于格式化一个 Person 实例的当前状态。Employee 继承了 Person 的行为。如果实例化 Employee,设置它的属性,然后调用 printAudit() 的一个重载方法,调用将成功完成。但是,生成的审计结果没有完整表示一个 Employee。printAudit() 无法格式化特定于某个 Employee 的属性,因为 Person 不知道它们。

解决方案是重写 printAudit() 的一个接受 StringBuilder 作为参数的重载方法,添加代码来打印特定于 Employee 的属性。

在编辑器窗口中打开或在 Project Explorer 视图中选定 Employee,转到 Source > Override/Implement Methods...。在 Override/Implement Methods 对话框中,选择 printAudit() 的 StringBuilder 重载方法并单击 OK。Eclipse 为您生成了方法存根,然后您可填入剩余部分,类似这样:

@Override
public void printAudit(StringBuilder buffer) {
  // Call the superclass version of this method first to get its attribute values
  super.printAudit(buffer);

  // Now format this instance's values
  buffer.append("TaxpayerIdentificationNumber=");
  buffer.append(getTaxpayerIdentificationNumber());
  buffer.append(","); buffer.append("EmployeeNumber=");
  buffer.append(getEmployeeNumber());
  buffer.append(","); buffer.append("Salary=");
  buffer.append(getSalary().setScale(2).toPlainString());
}

请注意对 super.printAudit() 的调用。您在这里所做的是要求 (Person) 超类向 printAudit() 显示其行为,然后使用 Employee 类型的 printAudit() 行为来扩充它。

不需要先调用 super.printAudit(),只不过,先打印这些属性似乎是一个不错的主意。事实上,您完全不需要调用 super.printAudit()。如果不调用它,则必须在 Employee.printAudit() 方法中自行格式化来自 Person 的属性,否则它们不会包含在审计输出中。

比较对象

Java 语言提供了两种比较对象的方法:

  • == 运算符
  • equals() 方法

使用 == 比较对象

== 语法比较对象是否相等,只有在 a 和 b 拥有相同的值时,a == b 才返回 true。对于对象,需要两个对象引用同一个对象实例。对于原语,需要它们的值相等。

假设您为 Employee 生成一个 JUnit 测试(您已在第 5 单元:您的第一个 Java 类 中了解了如何做)。清单 4 中显示了 JUnit 测试。

清单 4. 使用 == 比较对象

public class EmployeeTest {
  @Test
  public void test() {
    int int1 = 1;
    int int2 = 1;
    Logger l = Logger.getLogger(EmployeeTest.class.getName());

    l.info("Q: int1 == int2?           A: " + (int1 == int2));
    Integer integer1 = Integer.valueOf(int1);
    Integer integer2 = Integer.valueOf(int2);
    l.info("Q: Integer1 == Integer2?   A: " + (integer1 == integer2));
    integer1 = new Integer(int1);
    integer2 = new Integer(int2);
    l.info("Q: Integer1 == Integer2?   A: " + (integer1 == integer2));
    Employee employee1 = new Employee();
    Employee employee2 = new Employee();
    l.info("Q: Employee1 == Employee2? A: " + (employee1 == employee2));
  }
}

在 Eclipse 中运行清单 4 的代码(在 Project Explorer 视图中选择 Employee,然后选择 Run As > JUnit Test),以生成以下输出:

Sep 18, 2015 5:09:56 PM com.makotojava.intro.EmployeeTest test
INFO: Q: int1 == int2?           A: true
Sep 18, 2015 5:09:56 PM com.makotojava.intro.EmployeeTest test
INFO: Q: Integer1 == Integer2?   A: true
Sep 18, 2015 5:09:56 PM com.makotojava.intro.EmployeeTest test
INFO: Q: Integer1 == Integer2?   A: false
Sep 18, 2015 5:09:56 PM com.makotojava.intro.EmployeeTest test
INFO: Q: Employee1 == Employee2? A: false

在清单 4 中的第一种情况下,原语的值相同,所以 == 运算符返回 true。在第二种情况下,Integer 对象引用同一个实例,所以 == 同样返回 true。在第三种情况下,尽管 Integer 对象包含相同的值,但 == 返回 false,因为 integer1 和 integer2 引用了不同的对象。可将 == 视为对 “相同的对象实例” 的测试。

使用 equals() 比较对象

equals() 是每种 Java 语言对象都可以自由使用的方法,因为它被定义为 java.lang.Object 的一个实例方法(每个 Java 对象都继承该对象)。

可以像这样调用 equals():

a.equals(b); 此语句调用对象 a 的 equals() 方法,向它传递对象 b 的引用。默认情况下,Java 程序使用 == 语法检查两个对象是否相同。但是因为 equals() 是一种方法,所以它可以被重写。将 清单 4 中的 JUnit 测试案例与清单 5 中的测试案例(我称之为 anotherTest())进行比较,后者使用了 equals() 来比较两个对象:

清单 5. 使用 equals() 比较对象

@Test
public void anotherTest() {
  Logger l = Logger.getLogger(Employee.class.getName());
  Integer integer1 = Integer.valueOf(1);
  Integer integer2 = Integer.valueOf(1);
  l.info("Q: integer1 == integer2 ? A: " + (integer1 == integer2));
  l.info("Q: integer1.equals(integer2) ? A: " + integer1.equals(integer2));
  integer1 = new Integer(integer1);
  integer2 = new Integer(integer2);
  l.info("Q: integer1 == integer2 ? A: " + (integer1 == integer2));
  l.info("Q: integer1.equals(integer2) ? A: " + integer1.equals(integer2));
  Employee employee1 = new Employee();
  Employee employee2 = new Employee();
  l.info("Q: employee1 == employee2 ? A: " + (employee1 == employee2));
  l.info("Q: employee1.equals(employee2) ? A : " + employee1.equals(employee2));
}

运行清单 5 的代码会生成以下输出:

Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: integer1 == integer2 ? A: true
Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: integer1.equals(integer2) ? A: true
Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: integer1 == integer2 ? A: false
Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: integer1.equals(integer2) ? A: true
Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: employee1 == employee2 ? A: false
Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: employee1.equals(employee2) ? A : false

一条关于比较整数的说明

在 清单 5 中,如果 == 返回 true,Integer 的 equals() 方法就会返回 true,不必对此感到奇怪。但请注意第二种情况中发生的事情,您创建了都包含值 1 的不同对象:== 返回 false,因为 integer1 和 integer2 引用了不同的对象;但 equals() 返回 true。

JDK 的编写者认为,对于 Integer,equals() 的含义与默认含义不同(回想一下,默认含义是比较对象引用,看看它们是否引用同一个对象)。对于 Integer,在底层(装箱)的 int 值相同时,equals() 返回 true。

对于 Employee,您没有重写 equals(),所以(使用 == 的)默认行为返回了您期望的结果,因为 employee1 和 employee2 引用了不同的对象。

然后,对于您编写的任何对象,您可定义适合您编写的应用程序的 equals() 的含义。

重写 equals()

可通过重写 Object.equals() 的默认行为,定义 equals() 对您的应用程序的对象的含义— 您可以在 Eclipse 中这么做。让 Employee 在 IDE 的源代码窗口中拥有焦点,选择 Source > Override/Implement Methods。您希望实现 Object.equals() 超类方法。所以应在方法列表中找到要重写或实现的 Object,选择 equals(Object) 方法,然后单击 OK。Eclipse 生成正确的代码并将它放在您的源文件中。

可以合理地理解为,如果两个 Employee 对象的状态相等,则这两个对象相等。也就是说,如果它们的值(姓名和年龄)相同,则它们相等。

自动生成 equals()

Eclipse 可根据您为某个类定义的实例变量(属性)来生成一个 equals() 方法。因为 Employee 是 Person 的子类,所以您首先为 Person 生成 equals()。在 Eclipse Project Explorer 视图中,右键单击 Person 并选择 Generate hashCode() and equals()。在打开的对话框中,单击 Select All 以包含 hashCode() 和 equals() 方法中的所有属性,然后单击 OK。Eclipse 生成一个类似于清单 6 的 equals() 方法。

清单 6. Eclipse 生成的一个 equals() 方法

@Override
public boolean equals(Object obj) {
  if (this == obj)
    return true;
  if (obj == null)
    return false;
  if (getClass() != obj.getClass())
    return false;
  Person other = (Person) obj;
  if (age != other.age)
    return false;
  if (eyeColor == null) {
    if (other.eyeColor != null)
      return false;
  } else if (!eyeColor.equals(other.eyeColor))
    return false;
  if (gender == null) {
    if (other.gender != null)
      return false;
  } else if (!gender.equals(other.gender))
    return false;
  if (height != other.height)
    return false;
  if (name == null) {
    if (other.name != null)
      return false;
  } else if (!name.equals(other.name))
    return false;
  if (weight != other.weight)
    return false;
  return true;
}

Eclipse 生成的 equals() 方法看起来很复杂,但它的作用很简单:如果传入的对象与清单 6 中的对象相同,那么 equals() 会返回 true。如果传入的对象为 null(表示缺少),那么它会返回 false。

接下来,该方法检查 Class 对象是否相同(表示传入的对象必须是一个 Person 对象)。如果相同,则检查传入的对象的每个属性值,查看它们是否与给定的 Person 实例的状态逐值匹配。如果属性值为 null,equals() 会检查尽可能多的次数,如果这些值匹配,则会认为这些对象相等。您可能不希望每个程序都具有此行为,但它适合大部分用途。

上一篇 下一篇