每日一言 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" ); } }
以上的方法是一个经典的继承重写,我们在重写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方法。运行结果如下:
尽管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(); } }
会报错:
这是应为s1被声明为 human
类型,而 human
类型没有 study()
方法,所以编译器会报错。
总结一下:
Java具有多态性特性,特别是动态绑定(Dynamic Binding)或运行时多态。
在Java中,方法调用的处理分为两个阶段:
编译时 :编译器检查引用变量的声明类型是否有该方法
运行时 :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 () { 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 () { 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)是一种静态多态性的体现,允许在同一个类中定义多个名称相同但参数列表不同的方法。
编译时确定
编译器根据方法调用的参数类型和数量确定要调用的具体方法
方法签名识别
编译器通过方法的签名来区分不同的重载方法
方法签名包括:方法名、参数类型、参数顺序、参数数量
返回值类型不是方法签名的一部分(因此不能仅通过不同的返回值类型来重载函数)
字节码实现
每个重载方法在字节码中拥有不同的描述符
JVM将不同参数的重载方法视为完全不同的方法
重写和重载的区别
区别点
重载方法
重写方法
参数列表
必须修改
一定不能修改
返回类型
可以修改
一定不能修改
异常
可以修改
可以减少或删除,一定不能抛出新的或者更广的异常
访问
可以修改
一定不能做更严格的限制(可以降低限制)