Java 重写与重载

本文最后更新于:2024年3月18日 凌晨

Java 重写与重载

方法的重载

  • 方法重载就是同一类中存在多个方法名相同但参数不同的方法,参数的差异包括形式参数的个数,类型等,在例6-2中,类A定义了3个test()方法,它们的参数类型不同。
  • 方法调用的匹配处理原则是:首先按"精确匹配"原则去查找匹配方法,如果找不到,则按"自动类型转换匹配"原则去查找能匹配的方法。
  • 所谓"精确匹配"就是实参和形参类型完全一致,所谓"自动类型转换匹配"是指虽然实参和形参类型不同,但能将实参的数据按自动转换原则赋值给形参。

[例6-2]:方法调用的匹配测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class A {
void test(int x){
System.out.println("test(int):"+x);
}
void test(long x){
System.out.println("test(long):"+x);
}
void test(double x){
System.out.println("test(double):"+x);
}
public static void main(String[]args){
A a1= new A();
a1.test(5.0);
a1.test(5);
}
}
  • 根据方法调用的匹配原则,不难发现运行程序时将得到如下结果:
1
2
test(double):5.0
test(int):5
  • 如果将以上test(int x)方法注释掉,结果为:
1
2
test(double):5.0
test(long):5
  • 说明:因为实参5默认为int型数据,因此,有test(int x)方法存在时将按"精确匹配"原则处理,但如果无该方法存在,将按"转换匹配"原则优先考虑匹配test(long x)方法。

[例6-3]:从复数方法理解多态性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Complex {
private double x, y; //x,y分别代表复数的实部和虚部。

public Complex(double real, double imaginary) {// 构造方法。
x = real;
y = imaginary;
}

public String toString() {
return "(" + x + "," + y + "i" + ")";
}

/*方法1:将复数与另一复数a相加*/
public Complex add(Complex a) { // 实例方法。
return new Complex(x + a.x, y + a.y);
}

/*方法2:将复数与另一个由两实数a,b构成的复数相加*/
public Complex add(double a, double b) {// 实例方法。
return new Complex(x + a, y + b);
}

/*方法3:将两复数a和b相加*/
public static Complex add(Complex a, Complex b) {// 静态方法。
return new Complex(a.x + b.x, a.y + b.y);
}

public static void main(String args[]) {
Complex x, y, z;
x = new Complex(4,5);
y = new Complex(3.4,2.8);
z = add(x,y); // 调用方法3进行两复数相加。
System.out.println("result1="+z);
z=x.add(y); // 调用方法1进行两复数相加。
System.out.println("result2="+z);
z=x.add(6,8); // 调用方法2进行两复数相加。
System.out.println("result3="+z);
}
}

result1=(7.4,7.8i)
result2=(7.4,7.8i)
result3=(10.0,13.0i)
  • 以上有3个方法实现复数的相加运算,其中有两个为实例方法,一个时静态方法,它们的参数形式是不同的,调用方法时将根据参数形态决定匹配哪个方法,注意静态方法和实例方法的调用差异,实例方法一定要有一个对象作为前缀:静态方法则不依赖对象。
  • 以上3个方法进行复数相加时将产生一个新的复数作为方法的返回值,并不改变参与运算的两个复数对象的值,如果要改变调用方法的复数的值,则方法设计为如下形式:
1
2
3
4
public void add(Complex a){// 将另一复数的值加到当前复数上。
x = x + a.x;
y = y + a.y;
}

方法的覆盖与隐藏

  • 子类将继承父类的非私有方法,在子类也可以对父类定义的方法重新定义,这时将产生方法覆盖,也就是通过子类对象访问的方法是子类自己重新定义的方法,需要注意的是,子类在重新定义父类已有的方法时,应保持与父类完全相同的方法头部声明,即应与父类具有完全相同的方法名,参数列表,返回类型一般也要相同。
  • 例如,在以下类B中定义的方法,只有test(int x)存在对例6-2中类A的方法覆盖:
1
2
3
4
5
6
7
8
class B extends A{
void test(int x){
System.out.println("in B.test(int):"+x);
}
void test(String x,int y){
System.out.println("in B.test(Stirng,int):"+x+","+y);
}
}
  • 父类的实例方法被子类的同名实例方法覆盖,父类的静态方法被子类的同名静态方法隐藏,父类的实例变量和类变量可以被子类的实例变量和类变量隐藏
  • 通过父类引用可以暴露隐藏的变量和方法。
  • 方法覆盖不能改变方法的静态与非静态属性,子类中不能将父类的实例方法定义为静态方法,也不能将父类的静态方法定义为实例方法。
  • 不允许子类中方法的访问修饰符比父类有更多的限制,例如,不能将父类定义中用public修饰的方法在子类中重定义为private方法,但可以将父类的private方法重定义为public方法,通常应将子类中方法访问修饰与父类中的保持一致。

父类引用指向子类对象

  • 如果子类中定义了与父类同名的属性,在子类中将隐藏来自父类的同名属性变量,这里也是"最近优先原则",自己类中有就不会去找父类的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class SuperShow {
int y = 8; // 父类SuperShow的y属性。
int m = 2;

void show() { // 父类SuperShow的show()方法。
System.out.println("sup.show,y+" + y);
}
}

class ExtendShow extends SuperShow {
int y = 20;
int z = 1;

void show(){
System.out.println("ext.show,y+"+y);
}

public static void main(String args[]){
ExtendShow b = new ExtendShow();
SuperShow a = b; // 允许父类引用变量引用子类对象。
System.out.println("ext.y="+b.y); // 用子类引用变量访问y属性。
System.out.println("sup.y="+a.y); // 用父类引用变量访问y属性。
b.show(); // 用子类引用变量访问show()属性。
a.show(); // 用父类引用变量访问show()属性。
System.out.println("z="+b.z+",m="+b.m);
}
}

ext.y=20
sup.y=8
ext.show,y+20
ext.show,y+20
z=1,m=2

说明

  • 每个ExtendShow类型的对象将拥有4个属性,其中有两个y属性:一个是子类定义的,一个是父类定义的,在子类中,查找成员属性时将优先匹配本类定义的属性,在子类中没有找到的属性才会到父类中去查找,也就是在子类中将隐藏父类初始化过的同名属性。
  • 将子类对象赋值给父类的引用变量后,通过该引用变量去访问对象的成员时,访问的行为方法是子类对象的覆盖方法,而属性值却是父类的,原因在于对象执行方法时由实际对象的类型决定,而不是引用变量类型,访问属性时则由引用变量的类型决定,因为编译程序在分析程序时是基于类型来访问哪个属性变量。
  • 静态成员是依赖类的,静态成员的访问基于引用变量和的类型,而不是对象类型,尽管父类引用变量可指向子类对象,但通过父类引用变量访问的静态方法和静态方法均是父类的。
  • 细心体会隐藏和覆盖在用词上的差异,当子类与父类有相同成员时,通过子类引用访问的成员均是子类定义的,问题在于通过父类引用操作一个子类对象时就有差异了,隐藏的父类成员再现,而覆盖的成员被子类"替代",只有实例方法会是子类定义的,对象属性,静态属性,静态方法均是指父类定义的。

对象引用转换

对象引用赋值匹配

  • 允许将子类对象赋值给父类引用,这种赋值也经常发生在方法调用的参数传递时,如果一个方法的形式参数定义的是父类引用类型,那么调用这个方法时,可以使用子类对象作为实际参数,当然,任何方法调用将优先考虑参数精确匹配,然后才考虑转换匹配。

[例6-10]:方法的引用类型参数匹配处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RefTest {
void test(Object obj) {
System.out.println("test(Object):" + obj);
}

void test(String str) {
System.out.println("test(String):" + str);
}

public static void main(String[] args) {
RefTest a = new RefTest();
a.test("hello");
}
}

test(String):hello
  • 如果将以上的test(String str)方法定义注释掉,则运行结果为:
1
test(Object):hello
  • 注意:由于Object类是继承层次中最高层的类,所以任何对象均可匹配Object类型的形参,Java类库中有不少方法的参数未Object类型,方法定义考虑体现了通用性,在JDK1.5之后甚至基本类型的数据也可以赋值给Object类型或相应包装类型的引用变量,它是通过将基本类型自动包装成相应类型的对象来实现转换赋值的,例如,int类型数据包装成Integer类型的对象。

对象引用强制转换

  • 以下代码,尽管先前将一个字符串对象赋给Object类型的引用变量m,但将m直接赋给字符串类型的变量y不允许,因为编译程序只知道m的类型为Object,将父类对象赋给子类引用变量是不允许的。
  • 在将父类引用赋值给子类变量时要进行强制转换,这种强制转换在编译时总是认可的,但运行时的情况取决于对象的值,如果父类对象引用指向的就是该子类的一个对象,则转换是成功的,如果指向的是其他子类对象或父类自己的对象,则转换会抛出异常。
1
2
3
4
Object m = new String("123");	// 允许,父类变量引用子类对象。
String y = m; // 不允许。
String y = (String)m; // 强制转换,编译允许,且运行没问题。
Integer p = (Integer)m;// 强制转换,编译允许,但运行时出错。