文章目录
- 前言
- 一、为什么需要继承
- 二、继承的概念
- 三、继承的语法
- 四、父类成员访问
- 4.1子类中访问父类的成员变量
- 1.子类和父类不存在同名成员变量
- 2.子类和父类成员变量同名
- 4.2子类中访问父类的成员方法
- 1.成员方法名字不同
- 2.成员方法,名字相同
- 五、super和this关键字
- 六、子类构造方法
- 七、代码块的执行顺序
- 八、继承方式
- 九、final关键字
- 十、继承和组合
前言
在前面的学习中我们已经讲解了面向对象的三大特性中的封装,接下来我们再来讲解三大特性中的继承
一、为什么需要继承
class Dog {String name;int age;String color;public void eat() {System.out.println("正在吃");}public void bark() {System.out.println("正在汪汪叫");}
}
class Cat {String name;int age;public void eat() {System.out.println("正在吃");}public void sleep() {System.out.println("正在睡觉");}
}
通过观察以上代码,我们发现在狗和猫这两个类中我们定义了一些重复的代码,那么我们就可以使用继承来进行共性的抽取,实现代码的复用
二、继承的概念
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用
从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态(后序讲)
三、继承的语法
在Java中如果要表示类之间的继承关系,需要借助extends关键字
修饰符 class 子类 extends 父类 {
// ...
}
子类也可以叫做派生类,父类也可以叫做基类和超类
用继承重新设计狗和猫这两个类
class Animal {String name;int age;public void eat() {System.out.println(this.name+"正在吃");}
}
class Dog extends Animal {String color;public void bark() {System.out.println(this.name+"正在汪汪叫");}
}
class Cat extends Animal {public void sleep() {System.out.println(this.name+"正在睡觉");}
}
public class Test {public static void main(String[] args) {Dog dog = new Dog();//dog类中并没有定义name和age,name和age属性肯定是从父类Animal中继承下来的System.out.println("名字:"+dog.name+" 年龄:"+dog.age+" 颜色:"+dog.color);//eat方法也是从父类Animal中继承下来的dog.eat();dog.bark();Cat cat = new Cat();//cat类中也没有定义任何成员变量,name和age属性肯定是从父类Animal中继承下来的System.out.println("名字:"+cat.name+" 年龄:"+cat.age);//这里的eat方法也是从父类Animal中继承下来的cat.eat();cat.sleep();}
}
注意:
1.子类会将父类中的成员变量或者成员方法继承到子类中了
2.子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
四、父类成员访问
4.1子类中访问父类的成员变量
1.子类和父类不存在同名成员变量
class Base {int a;int b;
}
class Derived extends Base{int c;public void fend(){a = 5; // 访问从父类中继承下来的ab = 10; // 访问从父类中继承下来的bc = 15; // 访问子类自己的c}
}
2.子类和父类成员变量同名
class Base {int a;int b;int c;
}
class Derived extends Base{int a; // 与父类中成员a同名,且类型相同char b; // 与父类中成员b同名,但类型不同public void method(){a = 100; // 访问子类新增的ab = 101; // 访问子类新增的bc = 102; // 子类没有c,访问的肯定是从父类继承下来的c// d = 103; // 编译失败,因为父类和子类都没有定义成员变量b}
}
在子类方法中 或者 通过子类对象访问成员时:
1.如果访问的成员变量子类中有,优先访问自己的成员变量
2.如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错
3.如果访问的成员变量与父类中成员变量同名,则优先访问自己的
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找
4.2子类中访问父类的成员方法
1.成员方法名字不同
class Base {public void methodA(){System.out.println("Base中的methodA()");}
}
class Derived extends Base {public void methodB() {System.out.println("Derived中的methodB()方法");}public void methodC(){methodB(); // 访问子类自己的methodB()methodA(); // 访问父类继承的methodA()// methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()}
}
总结:成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中也没有则报错
2.成员方法,名字相同
class Base {public void methodA(){System.out.println("Base中的methodA()");}public void methodB(){System.out.println("Base中的methodB()");}
}
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(),基类的无法访问到}
}
说明:
1.通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
2.通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;
五、super和this关键字
super关键字,该关键字主要作用:在子类方法中访问父类的成员
class Base {public int a = 1;public int b = 2;
}
class Derived extends Base {public int a = 10;public int c = 20;public void print() {System.out.println(super.a);System.out.println(this.a);System.out.println(this.b);System.out.println(super.b);System.out.println(this.c);}
}
public class Test {public static void main(String[] args) {Derived derived = new Derived();derived.print();}
}
优先访问子类的,子类没有再访问父类
注意:
1.只能在非静态方法中使用
2.在子类方法中,访问父类的成员变量和方法
相同点:
1.都是Java中的关键字
2.只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3.在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同点:
1.this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
2.在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3.在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
4.构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有
六、子类构造方法
父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法
class Base {public Base() {System.out.println("Base::test()");}
}
class Derived extends Base {public Derived() {System.out.println("Derived::test()");}
}
public class Test {public static void main(String[] args) {Derived derived = new Derived();}
}
在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整
注意:
1.若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法
2.如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
3.在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句。
4.super(…)只能在子类构造方法中出现一次,并且不能和this同时出现
七、代码块的执行顺序
在没有继承关系时的执行顺序
class Person {public Person() {System.out.println("构造方法");}static {System.out.println("静态方法");}{System.out.println("实例方法");}
}
public class Test {public static void main(String[] args) {Person person1 = new Person();System.out.println("********");Person person2 = new Person();}
}
1.静态代码块先执行,并且只执行一次,在类加载阶段执行
2.当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
有继承关系时的执行顺序
class Person {public Person() {System.out.println("Person:构造方法");}static {System.out.println("Person:静态方法");}{System.out.println("Person:实例方法");}
}
class Student extends Person {public Student() {System.out.println("Student:构造方法");}static {System.out.println("Student:静态方法");}{System.out.println("Student:实例方法");}
}
public class Test {public static void main(String[] args) {Student student1 = new Student();System.out.println("********");Student student2 = new Student();}
}
结论如下:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
八、继承方式
在Java中支持以下几种继承方式:
单继承
class A {......
}
class B extends A {......
}
多层继承
class A {......
}
class B extends A {......
}
class C extends B {......
}
不同类继承同一个类
class A {......
}
class B extends A {......
}
class C extends A {......
}
注意:Java中不支持多继承
时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.
但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
如果想从语法上进行限制继承, 就可以使用 final 关键字
九、final关键字
final关键可以用来修饰变量、成员方法以及类
1.修饰变量或字段,表示常量(即不能修改)
2.修饰类:表示此类不能被继承
String字符串类,就是用final修饰的,不能被继承
3.修饰方法:表示该方法不能被重写(后序介绍)
十、继承和组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。简单说组合其实是一种思想
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车
class Tire{
// ...
} /
/ 发动机类
class Engine{
// ...
} /
/ 车载系统类
class VehicleSystem{
// ...
}
class Car{private Tire tire; // 可以复用轮胎中的属性和方法private Engine engine; // 可以复用发动机中的属性和方法private VehicleSystem vs; // 可以复用车载系统中的属性和方法// ...
}