目录
一.为什么需要继承
定义一只狗:
// Dog.javapublic class Dog {string name ;int age ;float weight ;public void eat (){System . out . println ( name + " 正在吃饭 " );}public void sleep (){System . out . println ( name + " 正在睡觉 " );}void Bark (){System . out . println ( name + " 汪汪汪 ~~~" );}}
定义一只猫:
// Cat.Javapublic class Cat {string name ;int age ;float weight ;public void eat (){System . out . println ( name + " 正在吃饭 " );}public void sleep () {System . out . println ( name + " 正在睡觉 " );}void mew (){System . out . println ( name + " 喵喵喵 ~~~" );}}
通过观察上述代码会发现,猫和狗的类中存在大量重复.那能否将这些共性抽取呢?面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。
二.继承的概念
继承
(inheritance)
机制
:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能
,这样产生新的类,称
派生类
。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用
。
![](https://img-blog.csdnimg.cn/d7bf5f44cfd346c48085435aa2fa28e1.png)
上述图示中,
Dog
和
Cat
都继承了
Animal
类,其中:
Animal
类称为父类
/
基类或超类,
Dog
和
Cat
可以称为
Animal
的子类/
派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。
三. 继承的语法
在
Java
中如果要表示类之间的继承关系,需要借助
extends
关键字,具体如下:
修饰符 class 子类 extends 父类 {// ...}
上述猫狗用继承思想设计:
// Animal.javapublic class Animal {String name ;int age ;public void eat (){System . out . println ( name + " 正在吃饭 " );}public void sleep (){System . out . println ( name + " 正在睡觉 " );}}// Dog.javapublic class Dog extends Animal {void bark (){System . out . println ( name + " 汪汪汪 ~~~" );}}// Cat.Javapublic class Cat extends Animal {void mew (){System . out . println ( name + " 喵喵喵 ~~~" );}}
注意:
1. 子类会将父类中的成员变量或者成员方法继承到子类中了,子类就不需要再定义父类中的成员变量或者成员方法了.
2.
子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
四.父类中的成员访问
4.1子类中访问父类的成员变量
1.
子类和父类不存在同名成员变量---直接用即可
public class Base {int a ;int b ;}public class Derived extends Base {int c ;public void method (){a = 10 ; // 访问从父类中继承下来的 ab = 20 ; // 访问从父类中继承下来的 bc = 30 ; // 访问子类自己的 c}}
2.
子类和父类成员变量同名
public class Base {int a = 10 ;int b = 20 ;}/public class Derived extends Base {int a = 1 ; // 与父类中成员 a 同名,且类型相同int c =2;public void method (){System . out . println(a);System . out . println(b);System . out . println(c);}}//输出结果:1 ---输出的是子类当中a的值202
得出结论:
在子类方法中 或者 通过子类对象访问成员时
:
1. 如果访问的成员变量子类中有,优先访问自己的成员变量。
2. 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
3. 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找
。
4.2子类中访问父类的成员方法
1.
成员方法名字不同
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()}}
2. 成员方法名字相同
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(), 父类的无法访问}}//输出结果:Base 中的 methodA()Derived 中的 method(int) 方法Derived 中的 methodB() 方法
得出结论:
1. 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
2. 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同
(
重载
)
,根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;
问题:如果子类中存在与父类中相同的成员变量和成员方法时,那如何在子类中访问父类相同名称的成员呢?
五. super关键字
该关键字主要作用:在子类方法中访问父
类的成员
。
1. super.成员变量 ---访问父类中的成员变量
上述4.1.2代码,想要访问父类的a,只需要在a前+super.
public class Base {int a = 10 ;int b = 20 ;}/public class Derived extends Base {int a = 1 ; // 与父类中成员 a 同名,且类型相同int c =2;public void method (){System . out . println( super. a);System . out . println(b);System . out . println(c);}}//输出结果:10 ---输出的是父类当中a的值202
2.super.成员方法 ---访问父类中的成员方法
上述4.2.2代码,若想访问父类中的methodB(),只需在前面+super.
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)super. methodB ();}}//输出结果:Base中的methodA()Derived中的method(int)方法Base中的methodB()方法
总结:在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。
其实,super的出现,是为了增强代码的可读性,让读者知道哪些是从父类继承过来的.
注:super关键字不能出现在static方法中.(static方法不依赖对象)
六. 子类构造方法
父子父子,先有父再有子,即:
子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。
public class Base {public Base (){System . out . println ( "Base()" );}}public class Derived extends Base {public Derived (){//super(); (编译器隐含的)System . out . println ( "Derived()" );}}public class Test {public static void main ( String [] args ) {Derived d = new Derived ();}}//结果打印:Base ()Derived ()
结果可见:
在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法
因为:
子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子
肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整
,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整
。
在子类构造方法创建时 , 默认会调用基类的无参构造方法:super(),
用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
并且只能出现一次.
上述代码:
class Animal {String name ;int age ;}class Dog extends Animal {void bark (){System . out . println ( name + " 汪汪汪 ~~~" );}}若在父类Animal中添加一个带有两个参数的构造方法,即:发现子类会报错
原因:在子类的默认构造方法中, 默认构造super(), 无参数, 但是如果在父类中构造了两个参数的构造方法, 在父类中将不再默认构造不带参数的构造方法, 则super()无效了. 此时如果想访问父类, 需要传带参数的super, 去访问父类中的构造方法.改正:构造子类的构造方法,在方法中添加 super(name,age),对父类成员进行初始化![]()
此时super出现了第三个使用方法:
super.() ---访问父类构造方法
注意:
1.
若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的
super()
调用,即调用基类构造方法
2.
如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
3.
在子类构造方法中,
super(...)
调用父类构造时,必须是子类构造函数中第一条语句。
4. super(...)
只能在子类构造方法中出现一次,并且不能和
this
同时出现
七. super和this
【
相同点
】
1.
都是
Java
中的关键字
2.
只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3.
在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
【
不同点
】
1. this
是当前对象的引用,当前对象即调用实例方法的对象,
super
相当于是子类对象中从父类继承下来部分成员的引用
![](https://img-blog.csdnimg.cn/a2ca472004814c9ba4715d6717f8d95e.png)
2.
在非静态成员方法中,
this
用来访问本类的方法和属性,
super
用来访问父类继承下来的方法和属性
3.
在构造方法中:
this(...)
用于调用本类构造方法,
super(...)
用于调用父类构造方法,两种调用不能同时在构造方法中出现
4.
构造方法中一定会存在
super(...)
的调用,用户没有写编译器也会增加,但是
this(...)
用户不写则没有
八. 继承关系上的执行顺序
在之前没讲继承关系时,我们知道静态代码块,实例代码块,构造方法同时存在时,执行顺序为:
静态代码块->实例代码块->构造方法
那么,当引入继承关系时,执行顺序又会变成怎样呢?
class Animal{
String name;
int age;
static{
System.out.println("父类静态代码块");
}
{
System.out.println("父类实例代码块");
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
System.out.println("父类构造方法");
}
}
// Dog.java
class Dog extends Animal{
static{
System.out.println("子类静态代码块");
}
{
System.out.println("子类实例代码块");
}
public Dog(String name, int age) {
super(name, age);
System.out.println("子类构造方法");
}
void bark(){
System.out.println(name + "汪汪汪~~~");
}
}
public class test {
public static void main(String[] args) {
Dog dog=new Dog("旺财",10);
}
}
当父类和子类中都有静态代码块,实例代码块,构造方法时,执行顺序为:
父类静态代码块->子类静态代码块->父类实例代码块->父类构造方法->子类实例代码块->子类构造方法
当第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行.
九. protected 关键字
// extend01 包中public class B {private int a ;protected int b ;public int c ;int d ;}// extend01 包中// 同一个包中的子类public class D extends B {public void method (){// super.a = 10; // 编译报错,父类private 成员在相同包子类中不可见super . b = 20 ; // 父类中 protected 成员在相同包子类中可以直接访问super . c = 30 ; // 父类中 public 成员在相同包子类中可以直接访问super . d = 40 ; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问}}// extend02 包中// 不同包中的子类public class C extends B {public void method (){// super.a = 10; // 编译报错,父类中private 成员在不同包子类中不可见super . b = 20 ; // 父类中 protected 修饰的成员在不同包子类中可以直接访问super . c = 30 ; // 父类中 public 修饰的成员在不同包子类中可以直接访问//super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问}}// extend02 包中// 不同包中的类public class TestC {public static void main ( String [] args ) {C c = new C ();c . method ();// System.out.println(c.a); // 编译报错, 父类中private 成员在不同包其他类中不可见// System.out.println(c.b); // 父类中protected 成员在不同包其他类中不能直接访问System . out . println ( c . c ); // 父类中 public 成员在不同包其他类中可以直接访问// System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问}}
什么时候下用哪一种呢
?
我们希望类要尽量做到
"
封装
",
即隐藏内部实现细节
,
只暴露出
必要
的信息给类的调用者
.
因此我们在使用的时候应该尽可能的使用
比较严格
的访问权限
.
例如如果一个方法能用
private,
就尽量不要用 public.
另外
,
还有一种
简单粗暴
的做法
:
将所有的字段设为
private,
将所有的方法设为
public.
不过这种方式属于是对访问权限的滥用,
还是更希望同学们能写代码的时候认真思考
,
该类提供的字段方法到底给
"
谁
"
使用
(
是类内部自己用,
还是类的调用者使用
,
还是子类使用
).
十. 继承方式
注意:
Java
中不支持多继承
。------>引入了接口
时刻牢记 , 我们写的类是现实事物的抽象 . 而我们真正在公司中所遇到的项目往往业务比较复杂 , 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示 , 所以我们真实项目中所写的类也会有很多 . 类之间的关系也会更加复杂.但是即使如此 , 我们并不希望类之间的继承层次太复杂 . 一般我们不希望出现超过三层的继承关系 . 如果继承层次太多, 就需要考虑对代码进行重构了 .如果想从语法上进行限制继承 , 就可以使用 final 关键字
十一. final 关键字
final
关键可以用来修饰变量、成员方法以及类。
1.
修饰变量或字段,表示常量
(
即不能修改
)
final int a = 10 ;a = 20 ; // 编译出错
2. 修饰类:表示此类不能被继承
final public class Animal {...}public class Bird extends Animal {...}// 编译出错Error :( 3 , 27 ) java : 无法从最终 com . bit . Animal 进行继承
3. 修饰方法:表示该方法不能被重写(后序介绍)
十二. 继承与组合
和继承类似
,
组合也是一种表达类之间关系的方式
,
也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如
extends
这样的关键字
),
仅仅是
将一个类的实例作为另外一个类的字段
。
继承表示对象之间是
is-a
的关系
,比如:狗是动物,猫是动物
组合表示对象之间是
has-a
的关系,比如:汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的。
// 轮胎类class Tire {// ...}// 发动机类class Engine {// ...}// 车载系统类class VehicleSystem {// ...}class Car {private Tire tire ; // 可以复用轮胎中的属性和方法private Engine engine ; // 可以复用发动机中的属性和方法private VehicleSystem vs ; // 可以复用车载系统中的属性和方法// ...}// 奔驰是汽车class Benz extend Car {// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来}
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。