Java继承和多态
继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
继承的作用:通过继承可以快速创建新的类,实现代码的重用,提高程序的可维护性,节省大量创建新类的时间,提高开发效率和开发质量。
语法结构:
class 子类 extends 父类
注意:
- 子类不能选择性继承父类;
- Java不支持多重继承,但一个类可以实现多个接口,从而克服单继承的缺点;
- 构造方法不会被子类继承,但可以从子类中调用父类的构造方法。
继承的优点
继承过来的字段和方法,可以像任何其他字段和方法一样被直接使用;
在子类中可以声明一个与父类中同名的新字段或静态方法,从而“隐藏”父类中的字段或方法;
可以在子类中声明一个在父类中没有的新字段和方法;
可以在子类中编写一个父类当中具有相同名的新实例方法,这称为“方法重写”或“方法覆盖”;
可以在子类中编写一个调用父类构造方法的子类构造方法,既可以隐式地实现,也可以通过使用关键字super来实现
重写父类中的方法
访问修饰符大于等于父类并且有继承关系才可以重写
当一个子类中一个实例方法具有与其父类中的一个实例方法相同的签名(指名称、参数个数和类型)和返回值时,称子类中的方法“重写”了父类的方法。例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class A{ public void sayHello() { //输出英文欢迎 System.out.println( "Hello,Welcome to Java!!!" ); } public void sayBye() { System.out.println( "GoodBye,everyone" ); } } class B extends A { public void sayHello() { //输出中文欢迎 System.out.println( "大家好,欢迎学习Java!!!" ); } } public class myfirst { public static void main(String[] args) { B b= new B(); //创建子类B的一个实例对象,使用默认构造方法 b.sayHello(); //调用子类中重写的方法 b.sayBye(); //调用父类中的方法 } } |
运行结果:
大家好,欢迎学习Java!!!
GoodBye,everyone
注意:重写的方法具有与其所重写的方法相同的名称、参数数量、类型和返回值。
注意:当想在父类的方法中添加新的功能,需要使用super.方法名后再新增功能,super代表当前类的父类的对象
隐藏父类中的方法
如果一个子类定义了一个静态类方法,而这个类方法与其父类的一个类方法具有相同的签名(指名称、参数格式和类型)和返回值,则称在子类中的这个类方法“隐藏”了父类中的该类方法。
- 当调用被重写的方法时,调用的版本是子类的方法;
- 当调用被隐藏的方法时,调用的版本取决于是从父类中调用还是从子类中调用
如果是父类对象调用隐藏方法所得到的是父类的被隐藏方法,反之用子类对象调用则是子类的隐藏方法
继承的自动转型
1.向上转型(自动转型):把子类对象转成父类类型
前提:B extends A ===> 才能A a = new B( );
父类引用指向子类对象,可以把子类对象转成父类类型。
当我们把一个子类对象转型成父类类型之后,拿着这个对象去调用的每一个方法,它都是从父类中开始检测有没有这个方法,没有肯定是不能执行的,如果有,看子类有没有重写,如果子类重写优先执行子类重写的方法,因为这个对象是从子类转型过来的。
2.向下转型(强制转型):把父类对象转成子类类型
前提:强制转型的前提是该对象已经自动转型。
B b = (B)a;把父类对象转成子类类型。
在对象进行向下转型时,必须首先发生对象向上转型,否则运行时将会出现对象转换异常/强制类型转换异常(ClassCastException)。
当我们把一个父类对象转成子类类型之后,拿着这个对象去调用每一个方法,它都是从子类中开始找有没有定义过这个方法,如果子类没有,看从父类里面有没有继承过。
方法重写和隐藏后的修饰符
在子类中被重写的方法,其访问权限允许大于但不允许小于被其重写的方法,例如:父类中一个受保护的实例方法(protected)在子类中可以是公共的(public)的,但不可以是私有的(private)。如果一个方法在父类中是static方法,那么在子类也必须是static方法;如果一个方法在父类中是实例方法,那么在子类中也必须是实例方法
子类访问父类私有成员
子类继承其父类的所有public和protected成员,但不能继承其父类的private成员。若想在子类中访问到父类中不是public和protected的字段,可以在父类中提供用来访问其私有字段的public或protected方法,子类使用这些方法来访问相应的字段。eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class A{ //父类A private int value= 10 ; //声明一个私有变量value并赋值为10 public int getvalue() { //声明一个公有成员方法getvalue,返回value return value; } } class B extends A{ //A的子类B } public class myfirst { public static void main(String[] args) { B b= new B(); //创建子类B的一个实例对象 System.out.println( "子类通过父类提供的公共接口访问A中的私有字段value:" +b.getvalue()); } } |
使用super关键字
使用super调用父类中重写的方法、访问父类中被隐藏的字段
子类重写了父类中的某一个方法,隐藏父类中的字段,假如想在子类中访问到父类中被重写的方法和隐藏父类的字段,可以在子类中通过使用关键字super来调用父类中被重写的方法和访问父类中被隐藏的字段。eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package first; class A{ public String name= "张飞" ; //添加成员变量 public void say() { //添加成员方法say System.out.println( "我是父类A成员方法say" ); } } class B extends A{ public String name= "关羽" ; //与父类中同名的字段,隐藏父类 public void say(){ //重写方法say super .say(); //使用super关键字调用父类中的方法 System.out.println( "我是子类B成员方法say" ); System.out.println( "父类的name名字:" + super .name); //使用super关键字访问父类中的变量 } } public class myfirst { public static void main(String[] args) { B b= new B(); //创建子类的一个实例对象 b.say(); //调用子类中重写的方法 System.out.println( "子类的name名字:" +b.name); //调用子类中的name } } |
使用super调用父类的无参数构造方法/有参数构造方法
子类不继承其父类的构造方法。
- 当使用无参数的super()时,父类的无参数构造方法就会被调用;
- 当使用带有参数的super()方法时,父类的有参数构造方法就会被调用。
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 | class SuperClass { //创建父类SuperClass private int n; //声明一个私有变量n SuperClass(){ //父类无参数构造方法 System.out.println( "这是父类SuperClass无参数构造方法" ); } SuperClass( int n) { //父类有参数构造方法 System.out.println( "这是父类SuperClass有参数构造方法" ); this .n = n; } } class SubClass extends SuperClass{ // SubClass类继承SuperClass类 private int n; //声明一个私有变量n SubClass(){ // 自动调用父类的无参数构造器 System.out.println( "这是子类无参数构造方法" ); } public SubClass( int n){ //子类有参数构造方法 super ( 300 ); //调用父类中带有参数的构造器 System.out.println( "这是子类有参数构造方法" +n); this .n = n; } } public class myfirst { public static void main(String[] args) { SubClass sc1 = new SubClass(); //创建子类SubClass实例对象,调用其无参数构造方法 SubClass sc2 = new SubClass( 100 ); //创建子类SubClass实例对象,调用其有参数构造方法 } }<br><br> |
这是父类SuperClass无参数构造方法
这是子类无参数构造方法
这是父类SuperClass有参数构造方法
这是子类有参数构造方法100
注意
1.如果要初始化父类中的字段,可以在子类的构造方法中通过关键字super调用父类的构造方法;
2.对父类的构造方法的调用必须放在子类构造方法的第一行;
3.如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器;
4.如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表;
5.子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。
6.父类中如果定义了有参构造方法,那么就要在子类的构造方法中,一定要调用一次父类的构造方法,也可以在父类中重新定义一个无参构造方法,否则会报错!
7.创建子类对象(调用构造方法),先调用的是父类的构造方法,然后才调用子类的构造方法,其实先创建的是父类对象,父类中的无参构造方法会被子类默认调用
多态
多态的概述
1、多态的定义
同类型的对象,执行同一个行为,会表现出不同的行为特征。
2、多态的常见形式:
父类类型 对象名称 = new 子类构造器;
接口 对象名称 = new 实现类(子类)构造器;
3、多态中成员访问特点
方法调用:编译看左边,运行看右边(这样才能看到不同对象执行不同行为)。
变量调用:编译看左边,运行也看左边,因为变量没有多态的概念(多态侧重行为多态)。
4、多态的前提:
有继承/实现关系;有父类引用指向子类对象;有方法重写。
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 | package com.gch.d1_polymorphic; /** 父类 */ public class Animal { public String name = "动物名称" ; public void run(){ System.out.println( "动物可以跑~~~" ); } } package com.gch.d1_polymorphic; public class Dog extends Animal { public String name = "狗名称" ; @Override public void run() { System.out.println( "狗跑的贼溜~~~" ); } } package com.gch.d1_polymorphic; /** * 定义乌龟类 */ public class Tortoise extends Animal{ public String name = "乌龟名称" ; @Override public void run() { System.out.println( "乌龟跑的非常慢!!" ); } } package com.gch.d1_polymorphic; public class Test { public static void main(String[] args) { // 目标:先认识多态的形式 // 父类 对象名称 = new 子类构造器; // 1.狗这个类型本身也是动物类型 // 2.小范围类型的变量可以赋给大范围类型的变量 Animal a = new Dog(); a.run(); // 方法调用:编译看左边,运行看右边 System.out.println(a.name); // 变量调用:编译看左,运行也看左 System.out.println(); Animal a1 = new Tortoise(); a1.run(); // 方法调用:编译看左,运行看右 System.out.println(a1.name); // 变量调用:编译看左,运行也看左 } } |
多态的优势
在多态形式下,右边对象可以实现解耦合(右边对象想换就换),便于扩展和维护。
Aniaml a = new Dog() / new Tortoise();
a.run(); // 后续业务行为随对象而变,后续代码无需修改
定义方法的时候,使用父类类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。
多态的劣势
劣势下不能使用子类的独有功能,因为多态下方法的调用:编译看左边。
在多态劣势下不能调用子类的独有行为。
多态下引用数据类型的类型转换
1.多态形式下引用数据类型的类型转换
自动类型转换(向上转型):从子到父
强制类型转换(向下转型):从父到子,从父到子必须进行强制类型转换,否则报错:子类 对象名称 = (子类)父类类型的变量
2.强制类型转换的作用:强制类型转换可以转换成真正的子类类型,可以解决多态下的劣势,从而实现调用子类独有的功能。
3.注意:有继承/实现关系的2个类型就可以在编译阶段进行强制类型转换,编译无问题;但是,如果转型后的类型和对象真实对象的类型不是同一种类型,那么在运行代码时,就会出现ClassCastException(类型转换异常)。
Animal c = new Cat();
Dog d = (Dog)c; // 出现异常 ClassCastException
4.Java建议强制转换前使用instanceof判断当前对象的真实类型,再进行强制转换。
对象变量名 instanceof 真实类型
判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则返回true,反之false。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!