重写(Overriding)
重写是子类对父类的允许访问的方法的实现过程进行重新编写!返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。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
28class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
public void bark(){
System.out.println("狗可以吠叫");
}
}
public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
b.bark();
}
}
该程序将抛出一个编译错误,因为b的引用类型Animal
没有bark
方法。
这是由于在编译阶段,只是检查参数的引用类型。
然而在运行时,Java虚拟机(JVM)指定对象的类型并且运行该对象的方法。
如果将Animal b = new Dog(); // Dog 对象
改为Dog b = new Dog(); // Dog 对象
就不会报错。
方法的重写规则:
- 参数列表必须完全与被重写方法的相同
- 返回类型必须完全与被重写方法的返回类型相同
- 访问权限不能比父类中被重写的方法的访问权限更高。例如:如果父类的一个方法被声明为
public
,那么在子类中重写该方法就不能声明为protected
。 - 父类的成员方法只能被它的子类重写
- 声明为final的方法不能被重写
- 声明为static的方法不能被重写,但是能够被再次声明
- 构造方法不能被重写
- 重写方法不能抛出新的异常或者比被重写方法声明的检查异常更广的检查异常。但是可以抛出更少,更有限或者不抛出异常。
重载(Overloading)
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型呢?可以相同也可以不同。参数顺序也可以不相同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
方法的重载规则:
- 被重载的方法必须改变参数列表
- 被重载的方法可以改变返回类型
- 被重载的方法可以改变访问修饰符
- 被重载的方法可以声明新的或更广的检查异常
1 | public class Overloading { |
两者之间的区别
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须改变 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(但可以降低限制) |
多态性的不同表现
在Java中,方法的重写和重载是多态性的不同表现。
- 重写是父类与子类之间多态性的一种表现
- 重载是一个类中多态性的一种表现
面试中的相关问题
1.存在这么一种情况:两个方法仅返回类型不同,其它都相同,如下:1
2
3
4
5
6
7
8
9// 父类中的方法f
public void f(int i){
}
...
// 子类中的方法f
public int f(int i){
return i;
}
根据上面重写和重载的规则,上面的写法既不是重写也不是重载,那它是什么?java设计者可能是考虑到这种情况,所以上面的这种写法在编译时会报错,The return type is incompatible with 父类.f()
,所以要么是重写要么是重载,不会存在第三种情况
2.创建子类对象时,在父类构造函数中调用被子类重写或重载的方法会出现什么情况?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
44
45
46
47
48
49
50
51
52
53class Base{
int i;
public Base(){
System.out.println("Base");
add(1);
}
void add(int v){
System.out.println("Base Add");
i+=1;
System.out.println(i);
}
}
class SubBase1 extends Base{
public SubBase1(){
System.out.println("SubBase1");
}
void add(int v){
System.out.println("SubBase1 Add");
i+=v*2;
System.out.println(i);
}
}
class SubBase2 extends Base{
public SubBase2(){
System.out.println("SubBase2");
}
void add(char c){
System.out.println("SubBase2 Add");
System.out.println(i+c);
}
}
public class Solution{
public static void main(String[] args) {
Base base1 = new SubBase1();
Base base2 = new SubBase2();
}
}
/*output
Base
SubBase1 Add
2
SubBase1
Base
Base Add
1
SubBase2
*/
从运行结果上来看,在创建子类对象时,在父类构造函数中调用被子类重写的方法时会调用子类的方法,而在父类构造函数中调用被子类重载的方法时会调用父类的方法。
分析:当子类被加载到内存方法区后,会继续加载父类到内存中。 如果,子类重写了父类的方法,子类的方法引用会指向子类的方法,否则子类的方法引用会指向父类的方法引用。 如果子类重载了父类方法,则子类重载方法引用还指向子类方法。 如果子类方法没有重写也没有重载父类方法,则方法引用会指向父类方法。