跳转到内容

重载与重写

75% developed
来自维基教科书,开放的书籍,开放的世界

导航 类和对象 主题: v  d  e )

方法重载

[编辑 | 编辑源代码]

在一个类中,可以有几个同名方法。但是它们必须有不同的签名。方法的签名由其名称、参数类型及其参数的顺序组成。方法的签名包含其返回类型、可见性或它可能抛出的异常。在同一个类中定义两个或多个共享相同名称但具有不同参数的方法的做法称为方法重载

在一个类中具有相同名称的方法被称为重载方法。重载方法对 JVM 没有特殊的好处,但对程序员来说,让几个方法执行相同的事情但使用不同的参数非常有用。例如,我们可能有操作runAroundThe表示为两个同名但输入参数类型不同的方法

Example 代码部分 4.22:方法重载。
public void runAroundThe(Building block) {
  ...
}

public void runAroundThe(Park park) {
  ...
}

一种类型可以是另一种类型的子类

Computer code 代码清单 4.11:ClassName.java
public class ClassName {

  public static void sayClassName(Object aObject) {
    System.out.println("Object");
  }

  public static void sayClassName(String aString) {
    System.out.println("String");
  }

  public static void main(String[] args) {
    String aString = new String();
    sayClassName(aString);

    Object aObject = new String();
    sayClassName(aObject);
  }
}
Computer code 代码清单 4.11 的控制台
String
Object

虽然两种方法都适合调用带有String参数的方法,但将调用参数类型最接近的方法。更准确地说,它将调用参数类型是另一个方法的参数类型的子类的方法。因此,aObject将输出Object。注意!参数类型由对象的声明类型定义,不是实例化类型!

以下两个方法定义是有效的

Example 代码部分 4.23:带类型顺序的方法重载。
public void logIt(String param, Error err) {
  ...
}

public void logIt(Error err, String param) {
  ...
}

因为类型顺序不同。如果两个输入参数都是 String 类型,那就会有问题,因为编译器将无法区分两者

Warning 代码部分 4.24:错误的方法重载。
public void logIt(String param, String err) {
  ...
}

public void logIt(String err, String param) {
  ...
}

编译器也会对以下方法定义给出错误

Warning 代码部分 4.25:另一种错误的方法重载。
public void logIt(String param) {
  ...
}

public String logIt(String param) {
  String retValue;
  ...
  return retValue;
}

请注意,返回类型不是唯一签名的部分。为什么不呢?原因是可以在不将方法的返回值赋值给变量的情况下调用方法。此功能来自 C 和 C++。因此,对于调用

Warning 代码部分 4.26:不明确的方法调用。
logIt(msg);

编译器将不知道调用哪个方法。对于抛出的异常也是如此。

测试你的知识

问题 4.6Question6类的哪些方法会导致编译错误?

Computer code Question6.java
public class Question6 {
 
  public void example1() {
  }

  public int example1() {
  }

  public void example2(int x) {
  }

  public void example2(int y) {
  }

  private void example3() {
  }

  public void example3() {
  }

  public String example4(int x) {
    return null;
  }

  public String example4() {
    return null;
  }
}
答案
Computer code Question6.java
public class Question6 {
 
  public void example1() {
  }

  public int example1() {
  }

  public void example2(int x) {
  }

  public void example2(int y) {
  }

  private void example3() {
  }

  public void example3() {
  }

  public String example4(int x) {
    return null;
  }

  public String example4() {
    return null;
  }
}

example1example2example3 方法会导致编译错误。example1 方法不能共存,因为它们具有相同的签名(记住,返回类型不是签名的一部分)。example2 方法不能共存,因为参数的名称不是签名的一部分。example3 方法不能共存,因为方法的可见性不是签名的一部分。example4 方法可以共存,因为它们具有不同的方法签名。

可变参数

[编辑 | 编辑源代码]

您不必重载,也可以使用动态数量的参数。在最后一个参数之后,您可以传递相同类型的可选无限参数。这些参数通过添加最后一个参数并在其类型后添加...来定义。动态参数将作为数组接收

Example 代码部分 4.27:可变参数。
  public void registerPersonInAgenda(String firstName, String lastName, Date... meeting) {
    String[] person = {firstName, lastName};
    lastPosition = lastPosition + 1;
    contactArray[lastPosition] = person;

    if (meeting.length > 0) {
      Date[] temporaryMeetings = new Date[registeredMeetings.length + meeting.length];
      for (i = 0; i < registeredMeetings.length; i++) {
        temporaryMeetings[i] = registeredMeetings[i];
      }
      for (i = 0; i < meeting.length; i++) {
        temporaryMeetings[registeredMeetings.length + i] = meeting[i];
      }
      registeredMeetings = temporaryMeetings;
    }
  }

上面的方法可以用动态数量的参数调用,例如

Example 代码部分 4.27:构造函数调用。
registerPersonInAgenda("John", "Doe");
registerPersonInAgenda("Mark", "Lee", new Date(), new Date());

此功能在 Java 1.5 之前不可用。

构造函数重载

[编辑 | 编辑源代码]

构造函数可以重载。您可以定义多个具有不同参数的构造函数。例如

Computer code 代码清单 4.12:构造函数。
public class MyClass {

  private String memberField;
 
  /**
   * MyClass Constructor, there is no input parameter
   */
  public MyClass() {
     ...
  }
 
  /**
   * MyClass Constructor, there is one input parameter
   */
   public MyClass(String param1) {
     memberField = param1;
     ...
  }
}

代码清单 4.12中,我们定义了两个构造函数,一个没有输入参数,另一个有一个输入参数。您可能会问将调用哪个构造函数。这取决于使用new关键字创建对象的方式。见下文

Example 代码部分 4.29:构造函数调用。
// The constructor with no input parameter will be called
MyClass obj1 = new MyClass();

// The constructor with one input param. will be called
MyClass obj2 = new MyClass("Init Value");

代码部分 4.29中,我们从同一个类创建了两个对象,或者也可以说obj1obj2都具有相同的类型。这两者的区别在于,在第一个中,memberField字段未初始化,在第二个中,它被初始化为"Init Value"。构造函数也可以从另一个构造函数调用,见下文

Computer code 代码清单 4.13:构造函数池。
public class MyClass {

  private String memberField;
 
  /**
   * MyClass Constructor, there is no input parameter
   */
  public MyClass() {
    MyClass("Default Value");
  }
 
  /**
   * MyClass Constructor, there is one input parameter
   */
  public MyClass(String param1) {
    memberField = param1;
    ...
  }
}

代码清单 4.13中,没有输入参数的构造函数调用了具有默认初始值的另一个构造函数。此调用必须是构造函数的第一条指令,否则会发生编译错误。该代码为用户提供了选择,可以选择使用默认值创建对象,或者使用指定的值创建对象。第一个构造函数也可以使用this关键字编写

Example 代码部分 4.30:另一种构造函数池。
  public MyClass() {
    this("Default Value");
  }

这样的调用减少了代码重复。

方法重写

[编辑 | 编辑源代码]

为了便于记住在方法重写中可以做什么,请记住,在类对象中可以做的所有操作,在子类对象中也可以做,只是行为可能会改变。子类应该是协变的。

虽然方法签名在类内部必须是唯一的,但可以在不同的类中定义相同的签名。如果我们定义了一个在超类中存在的方法,那么我们就重写了超类方法。这被称为方法重写。这与方法重载不同。方法重载是指名称相同但签名不同的方法。方法重写是指在继承类之间名称和签名相同的方法。

返回值类型会导致我们上面看到的问题。当我们重写超类方法时,返回值类型也必须相同。如果不相同,编译器会报错。

注意!如果一个类声明了两个具有相同名称的公共方法,而一个子类重写了其中一个方法,则子类仍然会继承另一个方法。在这方面,Java编程语言与C++不同。

方法重写与动态链接运行时绑定有关。为了使方法重写起作用,要调用的方法调用不能在编译时确定。它将在运行时决定,并在表中查找。

Example 代码部分 4.31:运行时绑定。
MyClass obj;

if (new java.util.Calendar().get(java.util.Calendar.AM_PM) == java.util.Calendar.AM) {
  // Executed during a morning
  obj = new SubOfMyClass();
} else {
  // Executed during an afternoon
  obj = new MyClass();
}
 
obj.myMethod();

代码部分 4.31中,如果在早上执行,第 3 行的表达式为真,如果在下午执行,则为假。因此,obj的实例将是MyClassSubOfMyClass,具体取决于执行时间。因此,在编译时无法确定方法地址。因为obj引用可以指向一个对象及其所有子对象,并且只有在运行时才会知道,所以会维护一个包含所有可能方法地址的表以供调用。不要混淆

Example 代码部分 4.32:声明类型和实例化类型。
obj.myMethod(myParameter);

使用被调用对象的实例化类型(obj)和参数对象的声明类型(myParameter)来搜索此方法的实现。

还有另一个规则是,当你进行重写时,重写超类方法的新方法的可见性不能降低。但是,可见性可以提高。因此,如果超类方法的可见性是public,则重写方法不能是packageprivate。重写方法必须抛出与超类相同的异常,或其子异常。

super引用父类(即 super.someMethod())。它可以在子类中使用来访问子类已重写的继承方法或子类已隐藏的继承字段。

Note 一个常见的错误是认为如果我们可以重写方法,我们也可以重写成员变量。事实并非如此,因为这样做毫无意义。你不能重新定义超类中私有的变量,因为这样的变量是不可见的。


Clipboard

待办事项
添加一些类似于变量中的练习。


华夏公益教科书