【JAVA】面向对象三大特性之继承_如何理解面向对象继承
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、继承的概念和使用细则
- 二、继承和组合
- 总结
前言
提示:这里可以添加本文要记录的大概内容:
继承作为面向对象编程的核心支柱,通过对类之间共性的高效抽取实现代码复用,为构建层次化、可扩展的软件系统奠定坚实基础。
提示:以下是本篇文章正文内容,下面案例可供参考
一、继承的概念和使用细则
1.1 继承的基本使用和含义
继承(inheritance)机制:是⾯向对象程序设计使代码可以复⽤的最重要的⼿段,它允许程序员在保持原有类特性的基础上进⾏扩展,增加新功能,这样产⽣新的类,称派⽣类。
继承的目的就是对不同类之间进行共性的抽取,从而来实现代码的复用!!!!!!

被继承的类称为父类、基类和超类,继承别人的类称为子类和派生类,和生活中的情形很接近所以很好理解。
在代码中,继承最大的特性就是子类可以继承父类当中的成员变量和成员方法!!
接下来对上述图示中的类之间的关系,利用继承用代码进行实现。
package com.dzj;public class Animal { public String name; public int age; public float weight; public void eat(){ System.out.println(name+\"---吃东西---\"); } public void sleep(){ System.out.println(name+\"开始睡觉~~~~~\"); }}
package com.dzj;public class Cat extends Animal{ public void mew(){ System.out.println(name+\"喵喵喵\"); }}
package com.dzj;public class Dog extends Animal{ public void bark(){ System.out.println(this.name+\"狗叫\"); }}
package com.dzj;public class TestExtend { public static void main(String[] args) { Cat cat=new Cat(); cat.name=\"hzp\"; cat.eat(); cat.sleep(); cat.mew(); Dog dog=new Dog(); dog.name=\"xxx\"; dog.eat(); dog.sleep(); dog.bark(); }}

1.2 关于子类访问父类成员的问题
关于这个问题,只要遵循一个就近原则即可:如果子类有优先访问子类的,子类没有的再去父类找!!!!
子类和父类不存在同名成员变量
package com.demo1;public class Base { int a=10; int b=20;}
package com.demo1;public class Derived extends Base{ int c=30; public void method(){ System.out.println(a); System.out.println(b); System.out.println(c); }}
package com.demo1;public class Test { public static void main(String[] args) { Derived derived=new Derived(); derived.method(); }}

结果表明,如果子类和父类不存在同名的成员变量,子类可以直接访问父类的成员变量。
如果子类和父类存在同名成员变量:
package com.demo1;public class Base { int a=10; int b=20;}
package com.demo1;public class Derived extends Base{int a=100;int b=200; int c=30; public void method(){ System.out.println(a); System.out.println(b); System.out.println(c); }}
package com.demo1;public class Test { public static void main(String[] args) { Derived derived=new Derived(); derived.method(); }}

结果表明,如果父子类出现同名的成员变量,优先使用子类的成员变量,即遵循就近原则。
对于子类访问父类的成员方法其实原理比较类似,接下来直接上代码解释:
package com.demo1;public class Base { int a=10; int b=20; public void methodA(){ System.out.println(\"Base的methodA--------\"); } public void methodB(int x){ System.out.println(\"Base的methodA--------\"); System.out.println(x); }}
package com.demo1;public class Derived extends Base{ int a = 100; int b = 200; int c = 30; public void method(){ System.out.println(a); System.out.println(b); System.out.println(c); } public void methodA(){ System.out.println(\"Derived的methodA--------\"); } public void methodB(){ System.out.println(\"Derived的mmethodB--------\"); } public void methodC(){ System.out.println(\"Derived的methodC--------\"); }}
package com.demo1;public class Test { public static void main(String[] args) { Derived derived=new Derived(); derived.methodA(); derived.methodB(); derived.methodB(100); derived.methodC(); }}

从结果可以看出整个调用过程还是遵循就近原则的,唯一需要注意的是成员方法本身是支持重载的,如果存在方法的重载根据所给参数的不同来确定最后调用的是哪一个成员方法。
1.3 super关键的引出
问题:如果⼦类中存在与⽗类中相同的成员时,那如何在⼦类中访问⽗类相同名称的成员呢?,也就是说如果存在同名的情况,我一定要访问父类的成员变量和成员方法怎么做???
答案就是利用super关键字,接下来将用代码进行演示super关键的作用,最后在做一个小的总结。
package com.demo1;public class Base { int a=10; int b=20; public void methodA(){ System.out.println(\"Base的methodA--------\"); } public void methodB(int x){ System.out.println(\"Base的methodA--------\"); System.out.println(x); }}
package com.demo1;public class Derived extends Base{ int a = 100; int b = 200; int c = 30; public void method(){ System.out.println(a); System.out.println(b); System.out.println(c); System.out.println(super.a); System.out.println(super.b); super.methodA(); } public void methodA(){ System.out.println(\"Derived的methodA--------\"); } public void methodB(){ System.out.println(\"Derived的mmethodB--------\"); } public void methodC(){ System.out.println(\"Derived的methodC--------\"); }}
package com.demo1;public class Test { public static void main(String[] args) { Derived derived=new Derived(); /*derived.methodA(); derived.methodB(); derived.methodB(100); derived.methodC();*/ derived.method(); }}

从结果可以看出,在子类成员方法中可以使用super关键字访问父类的成员变量和成员方法
注意事项
**1. 只能在非静态⽅法中使用(和this关键字一样)
- 在⼦类⽅法中,访问⽗类的成员变量和⽅法。**
1.4 super调用父类当中指定的构造方法
package com.demo2;public class Base { public Base(int x){ System.out.println(\"Base构造方法\"); System.out.println(x+\"----\"+x); }}
package com.demo2;public class Derived extends Base{ public Derived() { System.out.println(\"Derived构造方法\"); }}
package com.demo2;import com.demo1.Derived;public class Test { public static void main(String[] args) { Derived derived=new Derived(); }}

上述结果表明Base构造方法缺少参数,出现错误的原因就是,在构建子类对象的同时,要先完成父类的构造函数初始化在完成子类构造函数的初始化!
public class Derived extends Base{ public Derived() { super(100); System.out.println(\"Derived构造方法\"); }}
稍作修改就可以编译成功!

【注意事项】
• 通过super(参数)的形式可以调⽤⽗类指定的构造⽅法
• super()的形式只能出现在⼦类的构造⽅法当中且必须在第⼀⾏
再思考一个问题:先看如下代码
package com.demo2;public class Base { public Base(){ System.out.println(\"Base构造方法\"); /*System.out.println(x+\"----\"+x);*/ }}
package com.demo2;public class Derived extends Base{ public Derived() { /*super(100);*/ System.out.println(\"Derived构造方法\"); }}
package com.demo2;import com.demo2.Derived;public class Test { public static void main(String[] args) { Derived derived=new Derived(); }}

代码可以正常运行!!!为什么这里没用super也能跑,因为再子类构造方法的第一行系统默认会加上一个super()调用父类无参构造方法。仅限⽗类的构造⽅法是不带参数的构造⽅法且只有这⼀个的情况下。
另外补充一点,super()和this()是不能一起使用的!!!,因为他们都要求放在构造函数第一行上
1.5 关于super和this
【相同点】
1. 都是Java中的关键字
2. 只能在类的⾮静态⽅法中使⽤,⽤来访问⾮静态成员⽅法和字段
3. 在构造⽅法中调⽤时,必须是构造⽅法中的第⼀条语句,并且不能同时存在
【不同点】
1. this是当前对象的引⽤,当前对象即调⽤实例⽅法的对象,super相当于是⼦类对象中从⽗类继承下
来部分成员的引⽤

从图中可以看出,super和this指向的是不同的空间。
2. 在⾮静态成员⽅法中,this⽤来访问本类的⽅法和属性,super⽤来访问⽗类继承下来的⽅法和属性
4. 在构造⽅法中:this(…)⽤于调⽤本类构造⽅法,super(…)⽤于调⽤⽗类构造⽅法,两种调⽤不能同时在构造⽅法中出现
5. 构造⽅法中⼀定会存在super(…)的调⽤,⽤⼾没有写编译器也会增加,但是this(…)⽤⼾不写则没有
1.6 继承关系上的代码块执行顺序
package com.demo3;public class Person { String name; int age; public Person(String name,int age){ this.name=name; this.age=age; System.out.println(\"Person:构造方法执行\"); } { System.out.println(\"Person:实例代码块执行\"); } static{ System.out.println(\"Person:静态代码块执行\"); }}
package com.demo3;public class Student extends Person{public Student(String name,int age){ super(name,age); System.out.println(\"Student:构造方法执行\");} { System.out.println(\"Student:实例代码块执行\"); } static{ System.out.println(\"Student:静态代码块执行\"); }}
package com.demo3;public class Test { public static void main(String[] args) { Student student1=new Student(\"dzj\",18); System.out.println(\"--------------------\"); Student student2=new Student(\"dzj\",18); }}

通过分析执⾏结果,得出以下结论:
1. ⽗类静态代码块优先于⼦类静态代码块执⾏,且是最早执⾏。
2. ⽗类实例代码块和⽗类构造⽅法紧接着执⾏。
3. ⼦类的实例代码块和⼦类构造⽅法紧接着再执⾏。
4. 第⼆次实例化⼦类对象时,⽗类和⼦类的静态代码块都将不会再执⾏
1.7 访问修饰限定符-protected关键字

如果被protected 关键字修饰该类当中成员变量,成员⽅法等表⽰要么只能在同⼀个包中的类中进⾏访问,要么在不同包中只能通过在继承关系上的⼦类对象来访问
package com.demo2;public class Base { /* public Base(){ System.out.println(\"Base构造方法\"); *//*System.out.println(x+\"----\"+x);*//* }*/ protected int x=100;}
package com.demo3;import com.demo2.Base;public class Derived extends Base { public static void main(String[] args) { Derived derived=new Derived(); System.out.println(derived.x); }}

打印结果表明:不同包下的子类可以访问父类中被protected修饰的成员变量.
特殊情况:
package com.demo3;import com.demo2.Base;class C extends Base{ public static void main(String[] args) { C c=new C(); System.out.println(c.x); }}public class Derived extends Base { public static void main(String[] args) { Derived derived=new Derived(); System.out.println(derived.x); C c=new C(); System.out.println(c.x); }}

虽然Derived 类继承⾃Base 类,但它不能访问其他 Base 类⼦类(这⾥是C类)实例的protected 成员。可能感觉比较抽象,一个简单的理解就是要访问protected修饰的变量,那么只能在本类中用自己的对象去访问,不能用别人的,所以个人认为这个protected的访问有比较大的局限性。
总结:
如果在情况允许下,能用private就用private,这样能最大程度的保持封装性,因此我们在使⽤的时候应该尽可能的使⽤⽐较严格的访问权限。
1.8 java中的继承方式

java中不允许多继承的存在,⼀般我们不希望出现超过三层的继承关系
1.9 final关键字
public class Test_final { public static void main(String[] args) { final int a=10; a=20; }}
final修饰变量即为常量,不可以发生改变
public final class Animal {}public class Bird extends Animal{}
final修饰类即为不可继承
final修饰方法即为该方法不可以被重写,这一点再后续的博客中再讲解。
二、继承和组合
继承表⽰对象之间是is-a的关系,⽐如:狗是动物,猫是动物
组合表⽰对象之间是has-a的关系,⽐如:汽⻋
用代码来解释更清楚:
package com.demo4;public class Car { private Tire tire; private Engine engine; public Tire getTire() { return tire; } public void setTire(Tire tire) { this.tire = tire; } public Engine getEngine() { return engine; } public void setEngine(Engine engine) { this.engine = engine; }}
package com.demo4;public class Engine { int size;}
package com.demo4;public class Tire { String color;}
package com.demo4;public class Benz extends Car{ public static void main(String[] args) { Benz benz =new Benz(); Tire tire=new Tire(); Engine engine=new Engine(); engine.size=100; tire.color=\"黑色\"; benz.setTire(tire); benz.setEngine(engine); System.out.println(benz.getEngine().size); System.out.println(benz.getTire().color); }}

组合和继承都可以实现代码复⽤,应该使⽤继承还是组合,需要根据应⽤场景来选择,⼀般建议:能⽤组合尽量⽤组合,继承层次太深的代码不利于维护。
总结
从基础使用到访问控制,从super关键字到代码块执行顺序,深入理解继承机制能显著提升代码复用性和可维护性,但需警惕过度继承带来的设计复杂性,合理运用final和组合优化架构。


