每日一言
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中定义的子类可以对父类中具有相同名称、参数列表和返回类型的方法进行重写。即在调用子函数的该函数时,调用子函数中重写的方法来执行,而非父函数中的方法。一个简单的示例如下:
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.
这样在运行以下代码时,结果如下:
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");
}
}

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

尽管s1是一个human对象,其调用的仍然是student的speak行为。
这是因为运行时,jvm虚拟机会根据对象的实际类型决定调用哪个版本的方法
接下来我们再看下面的代码:
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();
}
}
会报错:

这是应为s1被声明为 human类型,而 human类型没有 study() 方法,所以编译器会报错。
总结一下:
Java具有多态性特性,特别是动态绑定(Dynamic Binding)或运行时多态。
在Java中,方法调用的处理分为两个阶段:
- 编译时 :编译器检查引用变量的声明类型是否有该方法
- 运行时 :JVM根据对象的实际类型决定调用哪个版本的方法
虽然s1声明为 human类型,但它实际引用的是一个 student对象。当调用speak()方法时,JVM会查看s1指向的实际对象类型(student),并调用该类中的speak方法。
方法的重写规则
-
参数列表与被重写方法的参数列表必须完全相同
-
返回类型与被重写方法的返回类型可以不同,但是必须是父类返回值的派生类
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()调用,前提是父类有无参的构造函数。若父类没有无参的构造函数,且子类构造函数中没有显式调用父类的其他构造函数,编译会报错。(关于为什么可以有多个构造函数,会在下面重载部分详细讲解)
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 关键字来访问父类的成员变量。
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 关键字调用父类中被重写的方法。这在需要在子类的重写方法中保留父类方法的部分逻辑时非常有用。
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)
重载是在一个类里面,方法名字相同,而参数不同,返回类型可以相同也可以不同。需要注意的是不能仅以返回值类型的不同来重载一个方法,方法重载的关键点是有一个独一无二的参数列表。
重载规则:
- 被重载的方法必须改变参数列表(参数个数或类型不一样,传入顺序不同也算一种重载);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
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)是一种静态多态性的体现,允许在同一个类中定义多个名称相同但参数列表不同的方法。
- 编译时确定
- 编译器根据方法调用的参数类型和数量确定要调用的具体方法
- 方法签名识别
- 编译器通过方法的签名来区分不同的重载方法
- 方法签名包括:方法名、参数类型、参数顺序、参数数量
- 返回值类型不是方法签名的一部分(因此不能仅通过不同的返回值类型来重载函数)
- 字节码实现
- 每个重载方法在字节码中拥有不同的描述符
- JVM将不同参数的重载方法视为完全不同的方法
重写和重载的区别
| 区别点 | 重载方法 | 重写方法 |
|---|---|---|
| 参数列表 | 必须修改 | 一定不能修改 |
| 返回类型 | 可以修改 | 一定不能修改 |
| 异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
| 访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |