java 重写与重载

每日一言

Why is it that making our dreams come true, and being truly happy are often two separate things?.. I still haven’t figured that one out. – Nana Komatsu
from Nana

重写(Override)

java中定义的子类可以对父类中具有相同名称参数列表返回类型的方法进行重写。即在调用子函数的该函数时,调用子函数中重写的方法来执行,而非父函数中的方法。一个简单的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class human {
String name;
public void speak(String text) {
System.out.println(name+":"+text);
}
public human( String name) {
this.name = name;
}
}
class student extends human {
int grade;
@Override
public void speak(String text) {
System.out.println( "Student " +name+":"+text);
}
public void study() {
System.out.println("I am studying");
}
public student(String name) {
super(name);
}
}

子函数对于父函数的speak函数进行了重写,添加了说话人的身份 Student.

这样在运行以下代码时,结果如下:

1
2
3
4
5
6
7
8
9
10
public class society {

public static void main(String[] args) {
human h1 = new human("Kity");
student s1 = new student("John");
h1.speak("Hello");
s1.speak("Hello");
}

}

1740485441148

以上的方法是一个经典的继承重写,我们在重写speak函数的同时还添加了一个学生的行为“学习”,我们用 student类来声明 s1,那么我们再看如下代码:

1
2
3
4
5
6
7
8
9
10
public class society {

public static void main(String[] args) {
human h1 = new human("Kity");
human s1 = new student("John");
h1.speak("Hello");
s1.speak("Hello");
}

}

我们用human类来声明了一个student对象,并且调用他们的speak方法。运行结果如下:

1740485489192

尽管s1是一个human对象,其调用的仍然是student的speak行为。

这是因为运行时,jvm虚拟机会根据对象的实际类型决定调用哪个版本的方法

接下来我们再看下面的代码:

1
2
3
4
5
6
7
8
9
10
public class society {

public static void main(String[] args) {
human h1 = new human("Kity");
human s1 = new student("John");
h1.speak("Hello");
s1.speak("Hello");
s1.study();
}
}

会报错:

1740488096675

这是应为s1被声明为 human类型,而 human类型没有 study() 方法,所以编译器会报错。

总结一下:

Java具有多态性特性,特别是动态绑定(Dynamic Binding)或运行时多态。

在Java中,方法调用的处理分为两个阶段:

  1. 编译时 :编译器检查引用变量的声明类型是否有该方法
  2. 运行时 :JVM根据对象的实际类型决定调用哪个版本的方法

虽然s1声明为 human类型,但它实际引用的是一个 student对象。当调用speak()方法时,JVM会查看s1指向的实际对象类型(student),并调用该类中的speak方法。

方法的重写规则

  • 参数列表与被重写方法的参数列表必须完全相同

  • 返回类型与被重写方法的返回类型可以不同,但是必须是父类返回值的派生类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Animal {
    Animal getAnimal() {
    return new Animal();
    }
    }

    class Dog extends Animal {
    @Override
    Dog getAnimal() { // 返回类型是Animal的子类,这是允许的
    return new Dog();
    }
    }
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

  • 父类的成员方法只能被它的子类重写

  • 声明为final的方法不能被重写

  • 声明为static的方法不能被重写,但是能够被再次声明

  • 子类和父类在同一个包中,那么子类可以重写父类的所有方法,除了声明为private和final的方法

  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法

  • 构造方法不能被重写

Super关键字

调用父类的构造函数

在子类的构造函数中,可以使用 super() 调用父类的构造函数。这样做的好处是能够复用父类构造函数中的初始化逻辑,确保父类的成员变量得到正确的初始化。

注意事项:

  • super()方法必须是子类构造函数的第一条语句,否则会编译错误
  • 如果子类的构造函数中没有显式的调用super()方法,java编译器会自动在子类构造函数的第一行插入一个无参的super()调用,前提是父类有无参的构造函数。若父类没有无参的构造函数,且子类构造函数中没有显式调用父类的其他构造函数,编译会报错。(关于为什么可以有多个构造函数,会在下面重载部分详细讲解)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Parent {
private int value;

// 父类的有参构造函数
public Parent(int value) {
this.value = value;
System.out.println("Parent class constructor with value: " + value);
}
}

class Child extends Parent {
public Child(int value) {
// 调用父类的有参构造函数
super(value);
System.out.println("Child class constructor");
}
}

public class Main {
public static void main(String[] args) {
Child child = new Child(10);
}
}

方位父类的成员变量

当子类中定义了与父类同名的成员变量时,子类的成员变量会隐藏父类的成员变量。此时,可以使用 super 关键字来访问父类的成员变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Parent {
protected int value = 10;
}

class Child extends Parent {
private int value = 20;

public void printValues() {
// 访问子类的成员变量
System.out.println("Child's value: " + this.value);
// 访问父类的成员变量
System.out.println("Parent's value: " + super.value);
}
}

public class Main {
public static void main(String[] args) {
Child child = new Child();
child.printValues();
}
}

调用父类的成员方法

当子类重写了父类的方法时,可以使用 super 关键字调用父类中被重写的方法。这在需要在子类的重写方法中保留父类方法的部分逻辑时非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Parent {
public void printMessage() {
System.out.println("This is a message from the parent class.");
}
}

class Child extends Parent {
@Override
public void printMessage() {
// 调用父类的 printMessage 方法
super.printMessage();
System.out.println("This is a message from the child class.");
}
}

public class Main {
public static void main(String[] args) {
Child child = new Child();
child.printMessage();
}
}

综上所述,super 关键字在 Java 中主要用于处理子类和父类之间的关系,使得子类能够正确地访问和使用父类的成员,增强了代码的复用性和可维护性。

重载(overload)

重载是在一个类里面,方法名字相同,而参数不同,返回类型可以相同也可以不同。需要注意的是不能仅以返回值类型的不同来重载一个方法,方法重载的关键点是有一个独一无二的参数列表。

重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样,传入顺序不同也算一种重载);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。
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
public class Overloading {
public int test(){
System.out.println("test1");
return 1;
}

public void test(int a){
System.out.println("test2");
}

//以下两个参数类型顺序不同
public String test(int a,String s){
System.out.println("test3");
return "returntest3";
}

public String test(String s,int a){
System.out.println("test4");
return "returntest4";
}

public static void main(String[] args){
Overloading o = new Overloading();
System.out.println(o.test());
o.test(1);
System.out.println(o.test(1,"test3"));
System.out.println(o.test("test4",1));
}
}

通过不同的传参会调用不同的test函数。

重载的实现原理

Java重载(Overloading)是一种静态多态性的体现,允许在同一个类中定义多个名称相同但参数列表不同的方法。

  1. 编译时确定
    1. 编译器根据方法调用的参数类型和数量确定要调用的具体方法
  2. 方法签名识别
    1. 编译器通过方法的签名来区分不同的重载方法
    2. 方法签名包括:方法名、参数类型、参数顺序、参数数量
    3. 返回值类型不是方法签名的一部分(因此不能仅通过不同的返回值类型来重载函数)
  3. 字节码实现
    1. 每个重载方法在字节码中拥有不同的描述符
    2. JVM将不同参数的重载方法视为完全不同的方法

重写和重载的区别

区别点 重载方法 重写方法
参数列表 必须修改 一定不能修改
返回类型 可以修改 一定不能修改
异常 可以修改 可以减少或删除,一定不能抛出新的或者更广的异常
访问 可以修改 一定不能做更严格的限制(可以降低限制)

java 重写与重载
http://blog.ulna520.com/2025/02/25/java重写与重载_20250225_192817/
Veröffentlicht am
February 25, 2025
Urheberrechtshinweis