重载与重写
在Java编程语言中,有两个概念非常相近,很容易混淆。它们就是重写与重载。
这里先在类继承的话题上,继续来说说重写,也叫覆盖。
没有继承,就不可能有重写。这是子类对父类的一种特殊操作。这里先提一下,重载主要发生在同一个类中的操作,也可以发生在父类与子类之间。
当一个子类继承了一个父类时,它也同时继承了父类的属性和方法。我们可以直接使用父类的属性和方法,或者,如果父类的方法不能满足子类的需求,则可以在子类中对父类的方法进行“改造”,这个“改造”的过程在Java中称为“覆盖(override)”。
比如,假设我们在Person 类中定义了一个方法用于显示姓名,如下:
public String showName(){
return name;
}
那么,在子类Teacher中,就会继承这个父类的showName()方法,此时,即使Teacher类中没有定义showName()方法,也可以在它的实例(instance)中使用这个方法。
比如,我们实例化了一个Teacher的实例,这是就可以直接调用从Teacher的父类Person中继承的这个showName()方法。如下:
Teacher teacher = new Teacher();
teacher.showName();
但是,我们在Teacher类中,我们或许需要在姓名后面加上“老师”,比如,“张三”就显示为“张三老师”,那么,这个时候就需要在子类“Teacher”中对从父类中继承的showName()方法进行“改造”,也就是在子类Teacher中覆盖父类Person的方法showName():
public class Teacher extends Person{
… …
public String showName(){
return name+”老师”;
}
}
在子类Teacher中,覆盖了父类的showName方法,在覆盖的过程中,需要提供和父类中的被覆盖方法相同的方法名称、输入参数(此处为空)以及返回类型。
另外,在子类对父类的方法进行覆盖的过程中,不能使用比父类中的被覆盖方法更严格的访问权限。
比如,父类Person中的方法showName()的修饰符是public,那么,在子类Teacher中的覆盖方法showName()就不能用protected、默认(Default)或者private等来限制。
从前面的讨论可以看出,类的继承主要可以从两方面来看:
对父类的扩充。如,在子类中加入新的属性、新的方法。
对父类的改造。比如,对方法的覆盖。
首先定义一个父类:Person,它有三个属性,分别由各自的存取方法来存取。
public class Person {
private String name;
private int age;
private String sex;
public String showName() {
return name;
}
public void setName(String theName) {
name = theName;
}
public int getAge() {
return age;
}
public void setAge(int theAge) {
age = theAge;
}
public String getSex() {
return sex;
}
public void setSex(String theSex) {
sex = theSex;
}
}
在这边,我们将它的属性的访问控制定义为Default,是因为在子类中我们需要访问这些属性。
接着,我们定义一个类“Teacher”,它继承“Person”类。
public class Teacher extends Person {
// 扩充父类属性
private String department;
// 扩充父类方法
public void setDepartment(String theDept) {
department = theDept;
}
public String getDepartment() {
return department;
}
// 方法覆盖
public String showName() {
return name + "老师";
}
// 测试
public static void main(String[] args) {
Teacher t = new Teacher();
t.setName("Alex");
System.out.println(t.showName());
}
}
在这个子类中,继承了父类,新增了一个“部门”的属性:department,并新增了相应的存取方法。改写(覆盖)了父类中的showName()方法,在姓名后面加上“老师”后返回。
因为在同一个包中的子类中用到了父类的属性name,所以,父类Person中的name属性不能定义为private的。最后定义了一个main()方法用于测试。
执行这个程序,在控制台上将会输出如下信息:
Alex老师
在程序中加入一个main()方法用于测试一个类是不错的主意,无论这个类是否是一个Java应用程序(Java Application)。
你可以通过类的main()方法测试这个类的各个方法是否与你的设计相一致。
1 重载
在Java程序中,如果同一个类(或者父类子类之间)中如果有两个相同的方法(方法名相同、返回值相同、参数列表相同)是不行的,因为这样编译器无法将方法调用和特定的方法联系起来。但是,在一个类中,如果有多个方法具有相同的名称,而有不同的参数,这种情况是允许的,我们就称这种行为为方法的重载(overload)。
我们经常使用println来向控制台输出各种类型的数据,这些println方法就是实现了方法的重载。
在进行方法的重载时,方法的参数列表必须不同(参数个数或者参数数据类型,或者两者皆不同)。而方法的返回值可以相同,也可以不同。
我们来看一个例子。还是以上面的“Person”类,我们需要取得“Person”对象的“name(姓名)”属性,可能会有两种情况:一种是直接得到对象的“name”属性,还有一种情况是在获取的对象属性“name”的方法上输入一个参数,将这个参数当作头衔和“name”结合起来,比如:输入的头衔是“先生”,则通过方法返回的是:“XXX先生”。
我们可以为这两个需求定义两个方法,但是,为了显示这两个方法的相似点,我们更倾向于使用方法重载来完成:
public class Person{
… …
public String getName() {
return name;
}
public String getName(String title) {
return name+title;
}
… …
}
这样,如果只需要得到“Person”对象的“name”属性,调用不带参数的getName()方法就可以了;如果需要得到“Person”对象的“name”属性并且需要指明它的头衔,则可以调用带一个参数的getName()方法。
在进行方法的重载时,有四条基本原则需要遵守:
方法名相同;
参数列表必须不同;
返回值可以不同;
可以相互调用。
注意:方法的返回值不是方法的签名(signature)的一部分,所以,进行方法的重载的时候,不能将返回值类型的不同当成两个方法的区别。也就是说,在同一个类中,不能有这样的两个方法,它们的方法名相同、参数相同,只是方法的返回值类型不同。
构造器重载
在前面我们说过,构造器在某种程度上可以看成是一个特殊的方法:它没有返回值,它的方法名称必需和类的名称一致。因此,构造器也常常被称为“构造方法”。
作为Java类的组成成分之一,构造器也可以进行重载,下例中的类Person就定义了4个重载的构造器以满足不同的需要。
public class Person {
private String name;
private int age;
private String sex;
public Person() {
System.out.println("构造器Person()被调用");
name = "";
}
public Person(String theName) {
System.out.println("构造器Person(String theName)被调用");
name = theName;
}
public Person(String theName, int theAge) {
System.out.println("构造器Person(String theName,int theAge)被调用");
name = theName;
age = theAge;
}
public Person(String theName, int theAge, String theSex) {
System.out.println("构造器Person(String theName,"
+ "int theAge,String theSex)被调用");
name = theName;
age = theAge;
sex = theSex;
}
// 其他代码
// …. …
}
在这个类中,定义了四个构造器,这四个构造器中,各自的参数个数都不一样。在创建对象的时候,编译器会根据参数类型和参数个数来确定到底调用哪一个构造器。
我们来看下面这个创建“Person”对象的例子:
public class NewPerson {
public static void main(String[] args) {
Person person = new Person("Tom", 18, "male");
}
}
编译并运行上面这个程序,它将会向控制台打印出如下信息:
构造器Person(String theName,int theAge,String theSex)被调用
这说明,它的带三个参数的构造器被调用来创建Person对象了。
1)方法重载(Overload)。表示两个或多个方法名字相同,但方法参数不同。方法参数不同有两层含义:1)参数个数不同。2)参数类型不同。 注意:方法的返回值对重载没有任何影响。
普通的方法在重载的情况下,可以互相调用。
2)构造方法重载:只需看参数即可。如果想在一个构造方法中调用另外一个构造方法,那么可以使用this()的方式调用,this()括号中的参数表示目标构造方法的参数。this()必须要作为构造方法的第一条语句,换句话说,this()之前不能有任何可执行的代码。
13. 方法重写(Override):又叫做覆写,子类与父类的方法返回类型一样、方法名称一样,参数一样,这样我们说
子类与父类的方法构成了重写关系。
14. 方法重写与方法重载之间的关系:重载发生在同一个类内部的两个或多个方法。重写发生在父类与子类之间。重载是一种平行关系,几个方法参数不同。重写是一种层次关系。
15. 当两个方法形成重写关系时,可以在子类方法中通过super.run()形式调用父类的run()方法,其中super.run()
不必放在第一行语句,因此此时父类对象已经构造完毕,先调用父类的run()方法还是先调用子类的run()方法是根据
程序的逻辑决定的。
Java中虚方法调用
我们前面已经知道了,在多态的情况下,声明为父类类型的引用变量只能访问父类中定义过的方法,但如果此变量实际引用的是子类的对象,而子类中又进行了方法覆盖时,实际调用的是子类中覆盖后的方法,这种机制称为虚方法调用。
在使用多态的情况下,有可能出现编译时类型和运行时的类型不一致的问题,如上面的例子中:
public class CalClass{
.. …
public int calPersonBirthYear(Person p) {
// 根据参数p的年龄来计算出生年代
}
}
在编译的时候,方法calPersonBirthYear 的参数类型是Person的,而在运行的时候,可能就是Student 或Teacher或Person或其他的Person子类类型了。