Java继承与组合
系列文章目录
你真的知道怎样用java敲出Hello World吗?—初识JAVA
你知道为什么会划分数据类型吗?—JAVA数据类型与变量
10 > 20 && 10 / 0 == 0等于串联小灯泡?—JAVA运算符
目录
1.继承
1.1Java中为什么有继承的概念
Java用继承将现实世界中多种多样的实体进行共性抽取,然后让实体继承共性,从而简化代码.比如,我们将猫和狗抽取出他们的共性—动物.
java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联,那在设计程序是就需要考虑。
我们用下面的代码描述狗和猫.
从上述代码可以看出,狗类和猫类存在一段重复的代码,这就是他们的共性,我们对这些重复的代码抽取共性,然后定义一个新的动物类.如下图:
对描述狗和猫的代码使用继承方式重新设计:
在Java中如果要表示类之间的继承关系,需要借助extends关键字
注意:子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
以上过程便是面向对象思想中提出的继承的概念,专门用来进行共性抽取,实现代码复用.
1.2 继承的概念
继承(inheritance)机制:面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类的特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
例如:狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。
上述图示中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类/超类,Dog和Cat可以称为Animal的子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态。
1.3 继承的语法
在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:
修饰符 class 子类 extends 父类 {
…
}
注意:
- 子类会将父类中的成员变量或者成员方法继承到子类中了
- 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
1.4 父类成员访问
子类将父类中的方法和变量继承下来了,子类中能直接访问父类中继承下来的成员.
1.4.1 子类中访问父类的成员变量
自我优先原则:
- 访问的成员变量子类中有,优先访问自己的成员变量。
- 访问的成员变量子类中无,访问父类继承下来的,如果父类也没有定义,编译报错。
- 访问的成员变量与父类中成员变量同(不考虑数据类型),优先访问自己的
子类和父类不存在同名成员变量
子类和父类存在同名成员变量.
同名时,非要访问父类成员变量,用super,可强制访问父类成员变量.
1.4.2 子类中访问父类的成员方法
成员方法名字不同:成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中也没有则报错。
public class Base {public void methodA(){System.out.println("Base中的methodA()");}}public class Derived extends Base{public void methodB(){System.out.println("Derived中的methodB()方法");}public void methodC(){methodB(); // 访问子类自己的methodB()methodA(); // 访问父类继承的methodA()// methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()}}
成员方法名字相同:子类对象访问父类与子类同名方法时,如果父类和子类同名方法但参数列表不同(重载),根据调用方法视传递的参数选择合适的方法访问,如果没有则报错
public class Base {public void methodA(){System.out.println("Base中的methodA()");}public void methodB(){System.out.println("Base中的methodB()");}}public class Derived extends Base{public void methodA(int a) {System.out.println("Derived中的method(int)方法");}public void methodB(){System.out.println("Derived中的methodB()方法");}public void methodC(){methodA(); // 没有传参,访问父类中的methodA()methodA(20); // 传递int参数,访问子类中的methodA(int)methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),父类的无法访问
由此可见,方法重载可以不在同一个类中.
//此处应有代码示范的图片(super构造方法)
1.5 super关键字
由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,直接访问是无法做到的,Java提供了super关键字,该关键字主要作用是:在子类方法中访问父类的成员.
有些书上说: super代表父类对象的引用这句话是错误的! !super其实就是一个非常简单的关键字,增加了代码的可读性!
super使用举例如下:
public class Father {int a;int b;public void methodA(){System.out.println("Father中的methodA()");}public void methodB(){System.out.println("Father中的methodB()");}}public class Father extends Son{int a; // 与父类中成员变量同名且类型相同char b; // 与父类中成员变量同名但类型不同// 与父类中methodA()构成重载public void methodA(int a) {System.out.println("Son中的method()方法");} // 与父类中methodB()构成重写(即原型一致,重写后序详细介绍)public void methodB(){System.out.println("Son中的methodB()方法");}public void methodC(){// 对于同名的成员变量,直接访问时,访问的都是子类的a = 100; // 等价于: this.a = 100;b = 101; // 等价于: this.b = 101;// 注意:this是当前对象的引用// 访问父类的成员变量时,需要借助super关键字// super是获取到子类对象中从父类继承下来的部分super.a = 200;super.b = 201;// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法methodA(); // 没有传参,访问父类中的methodA()methodA(20); // 传递int参数,访问子类中的methodA(int)// 如果在子类中要访问重写的父类方法,则需要借助super关键字methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),父类的无法访问到super.methodB(); // 访问父类的methodB()}}public class Father {public Father(){System.out.println("Father()");}
super有三种使用场景:
- super.成员变量
- super.方法名()
- super()调用父类构造方方法
使用super时注意:
- super只能在非静态方法中使用
- super只能在子类方法中访问父类的成员变量和方法
- super() 调用父类构造方法必须在子类中使用才能调用父类的方法和属性
- super() 帮助子类调用父类构造方法初始化成员变量
- super() 当父类有构造方法时,必须先帮父类完成构造方法, 子类才能完成构造方法,否则编译报错。
- this () 和 super () 不能在同一个方法中,因为这两条语句都必须放在代码的第一行
面试问题:this和super的区别是什么?
1.6 super和this的区别
相同点:
- super和this都可以在成员方法中用来访问:成员变量和调用其他的成员函数
- 都是Java中的关键字
- 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
- 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同点
- this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
- 在非静态成员方法中:this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
- 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有
1.7 静态代码块和实例代码块在有继承关系时的执行顺序
我们先用一段代码演示一下在没有继承关系时的执行顺序:
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;System.out.println("构造方法执行");} {System.out.println("实例代码块执行");} static {System.out.println("静态代码块执行");}}public class TestDemo {public static void main(String[] args) {Person person1 = new Person("bit",10);System.out.println("============================");Person person2 = new Person("gaobo",20);}}//执行结果://静态代码块执行//实例代码块执行//构造方法执行//============================//实例代码块执行//构造方法执行
我们可以得出以下结论:
- 静态代码块先执行,并且只执行一次,在类加载阶段执行
- 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
我们在来看在继承关系上的执行
class Person {public String name;public 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:静态代码块执行");}}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:静态代码块执行");}}public class TestDemo4 {public static void main(String[] args) {Student student1 = new Student("张三",19);System.out.println("===========================");Student student2 = new Student("gaobo",20);}public static void main1(String[] args) {Person person1 = new Person("bit",10);System.out.println("============================");Person person2 = new Person("gaobo",20);}}//执行结果:Person:静态代码块执行Student:静态代码块执行Person:实例代码块执行Person:构造方法执行Student:实例代码块执行Student:构造方法执行===========================Person:实例代码块执行Person:构造方法执行Student:实例代码块执行Student:构造方法执行
得出以下结论:
- 父类静态代码块优先于子类静态代码块执行,且是最早执行
- 父类实例代码块和父类构造方法紧接着执行
- 子类的实例代码块和子类构造方法紧接着再执行
- 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
1.8 protect
为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。在程序设计时,类要尽量做到 “封装”,即隐藏内部实现细节, 只暴露出必要的信息给类的调用者.因此我们在使用的时候应该尽可能的使用比较严格的访问权限. 例如如果一个方法能用private, 就尽量不要用 public. 还有一种 简单粗暴 的做法: 将所有的字段设为private, 将所有的方法设为public. 不过这种方式属于是对访问权限的滥用,不建议这样使用.
默认就是不加任何访问修饰限定符,也可以叫做default.
这里我们重点讲protected在不同位置中的使用.
同一个类:
同一个包中的类:
- 同一个包中的子类
- 同一个包中的非子类
不同包的子类:
注意:不同包中的子类要通过super访问
不同包且不是子类:
那我们什么时候用private什么时候用public呢?
在考虑用哪一个限定符时,我们应该考虑该类提供的字段方法到底给 “谁” 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用).
1.9 继承方式
在现实生活中事物的继承关系可能非常复杂且灵活,那么我们就需要对不同的继承关系分类.
我们举例写的类是现实事物的抽象. 而我们真正开发所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
如果想从语法上进行限制继承, 就可以使用 final 关键字
JAVA中的继承方式有以下几种:
注意:Java中不支持下面这种多继承方式,C++中支持.
1.10 final 关键字
上面我们提到可以用final关键字限制继承,下面我们来详细了解final.
- 修饰变量或字段,表示常量(即不能修改)
final int a = 10;a = 20; // 编译出错
定义的同时可以不初始化,但是只能初始化一次.
final int a;a = 100;System.out.println(a);
- 修饰类:表示此类不能被继承
final public class Animal {...}public class Bird extends Animal {...} // 编译出错Error:(3, 27) java: 无法从最终com.bit.Animal进行继
我们查看String字符串类可以看到,String字符串类是用final修饰的,不能被继承.
- 修饰方法,表示该方法不能被重写
2. 组合
2.1 组合的概念
Java是一个面向对象的语言,每一个学习过Java的人都知道,封装、继承、多态是面向对象的三个特征。但组合并不是一种特性,它只是一种实现手段.组合也是一种表达类之间关系的方式, 也是能够达到代码复用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
组合(Composition)体现的是整体与部分、拥有的关系,即has-a的关系.我们可以用xx是xx的一部分这样的语句描述组合中体现的关系.
继承(Inheritance)是一种联结类与类的层次模型,指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系,是一种is-a关系。
例如,我们用组合写一个汽车的代码表现这种关系,如下:
// 轮胎类class Tire{// ...} // 发动机类class Engine{// ...} // 车载系统类class VehicleSystem{// ...}class Car{private Tire tire; // 可以复用轮胎中的属性和方法private Engine engine; // 可以复用发动机中的属性和方法private VehicleSystem vs; // 可以复用车载系统中的属性和方法// ...} // 奔驰是汽车class Benz extend Car{// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来}
2.2 组合和继承的区别
下面我们从以下几个方面区别组合和继承:
- 表示关系上:
继承表示对象之间是is-a的关系(可以用xx是xx描述),比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系(可以用xx是xx的一部分描述),比如:零件是汽车的一部分
- 代码复用上:
继承:父类的内部细节对于子类是可见的,所以我们通常也可以说通过继承的代码复用是一种白盒式代码复用。(如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性;)
组合:通过对现有的对象进行拼装(组合)产生新的、更复杂的功能。因为在对象之间,各自的内部细节是不可见的,所以我们也说这种方式的代码复用是黑盒式代码复用。(因为组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法)
- 类的关系上:
继承:在写代码的时候就要指名具体继承哪个类,所以,在编译期就确定了关系。(从基类继承来的实现是无法在运行期动态改变的,因此降低了应用的灵活性。)
组合:在写代码的时候可以采用面向接口编程。所以,类的组合关系一般在运行期确定。
建议在同样可行的情况下,优先使用组合而不是继承。因为组合更安全,更简单,更灵活,更高效,长期大量的使用继承会给代码带来很高的维护成本。
来源地址:https://blog.csdn.net/m0_74387295/article/details/129720227
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341