继承和多态(上)--继承
一丶继承概念以及写法
(1)基础概念
首先为什么会有继承?
在类的实现中,我们可以发现有时候会有很多重复的代码,比如,用类来形容两个植物:
名称:牡丹花
年龄:三个月
颜色:红色
食物来源:光合作用
功能:可以释放花香
名称:猪笼草
年龄:4个月
颜色:绿色
食物来源:昆虫
功能:捕捉昆虫
可以发现,他们属性的名称相同,都有“年龄”,“名称”,“颜色”,“功能”,如果我们将这些属性抽取出来重新生成一个植物类,这个类的属性有名称,年龄,颜色,食物来源。那么牡丹花类和猪笼草类生成的时候就可以省下大量的代码,这个时候创建牡丹花对象和猪笼草对象,再给对象赋值,无疑可以很大程度的提高效率。
这就是继承,继承解决的主要问题就是如上所述:共性抽取,以及代码复用
而上述的植物类就是父类,也叫基类,而猪笼草和牡丹花的类,叫做子类,也叫派生类。
接 下 来 给 出 继 承 的 概 念 : \color{red}{接下来给出继承的概念:} 接下来给出继承的概念:
继承指的是一个类型的对象获得另外一个类型的对象的属性和方法。即就是子类继承父类的特征和行为,使得子类拥有和父类拥有相同的实例域和方法。或者说是子类继承父类的方法,使得子类拥有和父类相同的行为。
(2)写法
继承的写法如下:
修饰符 class 子类 extends 父类 {
// ...
}
根据上述条件,我们对上述的继承来进行书写:
public class plant {//这是植物类,是父类/基类
String name;
int age;
String color;
public void eat(){
System.out.println(name + "正在进食");
}
}
=================================================
public class ZhuLongCao extends plant{//这是猪笼草类
public void action(){
System.out.println(name + "捕捉昆虫");
}
}
=================================================
public class MuDanHua extends plant{//这是牡丹花类
public void action(){
System.out.println(name + "释放花香");
}
}
可以发现,在上述代码中,猪笼草类和牡丹花类我使用了变量name,但是在这两个类中我并没有定义任何变量,所以这个变量是它们从父类–植物类继承下来。
给 出 结 论 : \color{red}{给出结论:} 给出结论:
1.子类继承父类中除了构造方法以外的所有属性和方法,比如:成员方法,成员变量
2.子类继承父类之后,一定要加上属于自己的内容,不然的话继承的意义就不复存在
二丶关于成员访问
(1)子类访问父类
1>同名问题
现在子类已经成功的继承了父类的变量和方法,那么子类访问父类的成员和方法是要遵循那些规则呢?如果子类的部分属性和父类相同,那又怎么办呢?
public class plant {
int a = 10;//父类独有变量a
int b = 20;//这是父类和子类同名变量b
public void methodA(){//这是父类和子类同名方法A
System.out.println("这是父类方法A");
}
public void methodB(){//父类独有方法B
System.out.println("这是父类方法B");
}
}
public class TestPlant extends plant{
byte b = 2;//这是父类和子类同名变量b
int c = 3;//子类独有变量c
public void methodA(){//这是父类和子类同名方法A
System.out.println("这是子类方法A");
}
public void methodC(){//子类独有方法C
System.out.println("这是子类方法C");
}
public void method(){
System.out.println(a);
System.out.println(b);
System.out.println(c);
}
public static void main(String[] args) {
TestPlant test1 = new TestPlant();
test1.method();//测试变量
test1.methodA();//测试同名方法
test1.methodB();
test1.methodC();//测试不同名方法
}
}
一步步来讨论, 首 先 对 于 m e t h o d : \color {blue} {首先对于method:} 首先对于method:
这设置了变量a (父类独有),b(子类父类同名),c(子类独有),然后methodA的打印就是为了探讨变量的访问顺序
再 接 着 对 于 m e t h o d B 和 m e t h o d C 以 及 m e t h o d A : \color{blue}{再接着对于methodB和methodC以及methodA:} 再接着对于methodB和methodC以及methodA:
方法我一共构建了三个方法methodA(子类父类同名),methodB(父类独有
),methodC(子类独有)
那么接下来,看运行结果:
可以看出,打印遵循以下规则:
1.如果访问的 成员变量/方法 子类中有,优先访问自己的成员变量。
2.如果访问的 成员变量/方法 子类中无,则访问父类继承下来的,如果父类也没有定义,则会编译报错。
3.如果访问的 成员变量/方法 与父类中成员变量同名,则优先访问自己的。
此
处
有
几
点
需
要
着
重
注
意
(
易
错
)
:
\color{red}{此处有几点需要着重注意(易错):}
此处有几点需要着重注意(易错):
1.
变
量
的
访
问
只
看
名
称
,
不
会
在
乎
变
量
类
型
\color{red}{1.变量的访问只看名称,不会在乎变量类型}
1.变量的访问只看名称,不会在乎变量类型
2.
方
法
是
否
相
同
注
意
除
了
名
称
外
,
还
有
参
数
列
表
。
(
方
法
重
载
)
\color{red}{2.方法是否相同注意除了名称外,还有参数列表。(方法重载)}
2.方法是否相同注意除了名称外,还有参数列表。(方法重载)
2>super的使用(重写在后面)
我们可以从上面看出,在子类中可以访问父类成员,但是有一个问题,我们在子类当中,如果想要更改变量的值轻易可以做到,但是怎么在子类中给父类的变量赋值呢?如果是不同名还好说,如果是同名我们要怎么办呢?
此处给出super关键字的定义以及使用说明:
- 只能在非静态方法中使用
- 在子类方法中,访问父类的成员变量和方法。
具体怎么使用呢?代码如下:
public class Fu {
int a = 1;
int b = 2;
public void print(){
System.out.println("我是父类方法");
}
}
public class Zi extends Fu{
int a = 10;
int b = 20;
public void print(){//父子同名方法
System.out.println("我是子类方法");
}
public void give(){
a = 1000;//子类赋值
super.a = 100;//父类赋值
System.out.println(a);//打印子类
System.out.println(super.a);//打印父类
}
public void direct(){.
print();//打印子类
super.print();//打印父类
}
public static void main(String[] args) {
Zi zi = new Zi();
zi.give();
zi.direct();
}
}
这里我要特别说明!!!!
虽 然 我 没 写 , 但 是 方 法 是 否 相 同 除 了 方 法 名 以 外 还 有 参 数 列 表 \color{red}{虽然我没写,但是方法是否相同除了方法名以外还有参数列表} 虽然我没写,但是方法是否相同除了方法名以外还有参数列表
接着,我们看一下运行的结果:
3>super 和 this
super和this都是JAVA中的关键字,都可以调用变量和方法,而且都可以作为方法的第一条语句,那么它的特殊之处在哪里呢?
在解释这两个关键字之前,我想先对对象模型有一个大概的解释:
c和d是子类自己新增加的成员,这是它自己构造方法应该做的事情,而继承下来的父类的信息,就需要使用super进行调用
那
么
在
此
时
给
出
关
于
t
h
i
s
和
s
u
p
e
r
的
相
同
点
:
\color{red}{那么在此时给出关于this和super的相同点:}
那么在此时给出关于this和super的相同点:
1.都是Java中的关键字
2.只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3 .在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
再 详 解 t h i s 和 s u p e r 的 不 同 点 : \color{red}{再详解this和super的不同点:} 再详解this和super的不同点:
1.this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是父类对象的引用
2.在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3. this是非静态成员方法的一个隐藏参数,super不是隐藏的参数
4. 成员方法中直接访问本类成员时,编译之后会将this还原,即本类非静态成员都是通过this来访问的;在子类中如果通过super访问父类成员,编译之后在字节码层面super实际是不存在的(通过字节码文件可以验证)
关于这些,我想使用一段继承代码在栈丶堆丶区中的来解释:
在这段代码中,可以很清晰的看到,this其实是这个方法整体的引用,它是隐藏的,但是在编译器编译之后会进行返回,但是super不会。
这
里
的
话
还
有
一
点
需
要
注
意
:
构
造
方
法
中
s
u
p
e
r
和
t
h
i
s
不
能
同
时
\color{red}{这里的话还有一点需要注意:构造方法中super和this不能同时}
这里的话还有一点需要注意:构造方法中super和this不能同时
出
现
,
原
因
很
简
单
,
因
为
一
个
变
量
不
能
被
初
始
化
两
次
\color{red}{出现,原因很简单,因为一个变量不能被初始化两次}
出现,原因很简单,因为一个变量不能被初始化两次
(2)初始化顺序
1>关于构造器
子类和父类的对象在创建之初肯定都要被构造器初始化,那么构造方法的初始化顺序是是什么呢?
继承关系也就是父子关系,那么先有儿子还是先有父亲呢?显而易见,看如下代码:
public class Fu {
Fu(){
System.out.println("我是父类构造器");
}
}
public class Zi extends Fu{
Zi(){
System.out.println("我是子类构造器");
}
public static void main(String[] args) {
Zi zi = new Zi();
System.out.println("=========================");
Fu fu = new Fu();
}
}
在此处,我们需要注意两个问题,我创建了两个对象,一个子类对象,一个父类对象,那么具体构造方法怎么调用,由代码运行结果来告诉我们:
这里先看父类对象,我们很好理解,创建父类对象,那么当然要被父类构造器初始化,所以就只输出了“我是父类构造器”。
那么子类是什么情况呢?我们打开DeBug模式来运行:
可以发现,这里是由子类构造器跳转到了父类构造器,那么什么情况下子类可以访问父类呢?
没错,子类构造器隐含了一个调用父类构造器的super()
这里特别注意一下: 创 建 那 个 对 象 就 调 用 那 个 对 象 的 构 造 方 法 \color{red}{创建那个对象就调用那个对象的构造方法} 创建那个对象就调用那个对象的构造方法
这里用一道题来进行练习:
这里的答案是什么呢?感兴趣的小伙伴可以做一下,答案在下方:
所以最后的答案是:YXYZ
2>初始化顺序
在代码中,各个部分的执行,总要排个序,什么意思?
比如:我创建一个对象,那么这个对象的初始化还有方法的执行次序总要排个序,不然还不得乱了套了,那么具体次序是什么呢?这个就需要让编译器告诉我们了。
如下:
public class Fu {
int age;
String name;
String gender;
public Fu(int age, String name, String gender) {
this.age = age;
this.name = name;
this.gender = gender;
System.out.println("执行了父类构造方法");
}
{
System.out.println("父类实例代码块执行");
}
static{
System.out.println("父类静态代码块执行");
}
}
public class Zi extends Fu{
int age;
String name;
String gender;
public Zi(int age, String name, String gender) {
super(age,name,gender);
this.age = age;
this.name = name;
this.gender = gender;
System.out.println("子类执行了构造方法");
}
{
System.out.println("子类实例代码块执行");
}
static{
System.out.println("子类静态代码块执行");
}
}
public class Test {
public static void main(String[] args) {
Zi zi = new Zi(18,"光头大魔王","男");
System.out.println("==================================");
Zi zi2 = new Zi(23,"索大","男");
}
}
执
行
具
体
结
果
如
下
:
\color{red}{执行具体结果如下:}
执行具体结果如下:
得 到 结 论 如 下 : \color{red}{得到结论如下:} 得到结论如下:
在继承关系中,初始化的顺序为:
父类静态代码块 -->子类静态代码块–>父类实例代码块=–>父类构造方法–>子类实例代码块–>子类构造方法
在初始化的时候,类加载阶段静态代码块就会执行,并且最先执行,而且只执行一次。
三丶继承拓展
(1)关于访问权限
在这里的访问权限,其实之前就有提过,但是因为之前没有子类的涉及,所以部分知识点无法展开,这一次的话具体来说一下。
首
先
给
图
:
\color{red}{首先给图:}
首先给图:
这里的话有一个点需要说明,就是如果当前类对变量进行了封装,那么一定要提供对外公开的接口来使得自己的数据可以被访问。
什么意思呢?
就是提供getter和setter方法。
(2)关于继承方式
1.JAVA一般继承不超过三层
2.就想父子关系一样,一个父亲可以有好几个儿子。一个父类可以有一个子类,一个父类也可以有好几个子类,但是不可以一个子类同时继承好几个父类。
(3)关于final
<1>
f
i
n
a
l
修
饰
类
\color{red}{final修饰类}
final修饰类
当final修饰类的时候,该类不能有派生类,也就是不能再作为父类被继承。
<2>
f
i
n
a
l
修
饰
方
法
\color{red}{final修饰方法}
final修饰方法
修饰方法的时候,该方法不能被重写。
<3>
修
饰
变
量
\color{red}{修饰变量}
修饰变量
修饰变量的时候,必须要在刚开始就给出初始值,然后该变量只能引用不能修改其值。
重点来了,这里有一点需要特别声明:
如果该变量是一个对象,那么该对象引用地址不可修改,但是对象属性可以修改。
(4)关于继承与组合
这里其实就是就是关于继承中一个问题的解决方式:
如果说,在继承中我更改了一个父类的属性或者方法,那么所有引用了该属性或者方法的子类都要更改,但是如果我把这些属性或者方法用一个对象进行引用,那么更改属性和方法就不会影响其他的引用,而是直接修改内部就可以。
PS:这一个解释在后续的抽象类和接口也适用,就是为什么接口可以多继承。
例如:
// 轮胎类
class Tire{
// ...
}
// 发动机类
class Engine{
// ...
}
// 车载系统类
class VehicleSystem{
// ...
}
class Car{
private Tire tire; // 可以复用轮胎中的属性和方法
private Engine engine; // 可以复用发动机中的属性和方法
private VehicleSystem vs; // 可以复用车载系统中的属性和方法
// ...
}
// 奔驰是汽车
class Benz extend Car{
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
如果我给这些对象属性里面添加东西,那么影响的是对象的内部,而不会对对象本身的引用产生影响。