文章目录
一、继承的引入及概念
狗的行为有吃饭、睡觉、汪汪叫等,猫的行为有吃饭、睡觉、喵喵叫等;如果将狗和猫分别写成两个类,Dog类中有吃饭、睡觉、汪汪叫三个方法,Cat类中有吃饭、睡觉、喵喵叫三个方法;此时两个类中的大部分代码是重复的,所以引入了继承。新建一个 Animal 类,Dog 类和 Cat 类中都含有 eat() 方法和 sleep() 方法,所以在Animal 类中写入这两个方法,再新建Dog类和Cat类,分别在其中写入 Bark() 汪汪叫的方法和 mew() 喵喵叫的方法。Animal 称为父类/基类/超类,Dog 类和Cat 类都称为子类/派生类。
继承机制:是面向对象程序设计使代码可以复用的最重要的手段,允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生的类称为派生类(子类)。继承实现了面向对象程序设计的层次结构。继承主要解决的问题是共性的抽取,实现代码的复用,也通过继承实现多态。
二、继承的语法
修饰符 class 子类 extends 父类{
}
eg.
public class Animal {
String name;
int age;
public void eat(){
System.out.println(name + "在吃饭~");
}
public void sleep(){
System.out.println(name + "在睡觉~");
}
}
public class Dog extends Animal{
public void Bark(){
System.out.println(name + "汪汪~");
}
}
public class Cat extends Animal{
public void mew(){
System.out.println(name + "喵喵~");
}
}
public class TestExtend {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "二哈";
dog.age = 2;
System.out.println(dog.name);
System.out.println(dog.age);
dog.eat();
dog.sleep();
dog.Bark();
}
}
运行结果
二哈
2
二哈在吃饭~
二哈在睡觉~
二哈汪汪~
子类会将父类中的成员变量或者成员方法继承到子类中。
子类继承父类后,必须要添加自己特有的成员,体现出与父类的不同,否则就没有必要继承了。
父类中的构造方法没有被子类继承。
三、父类成员访问
1、子类中访问父类的成员变量
子类和父类不存在同名的成员变量
子类中没有与父类相同的成员变量,则访问的时候会访问从父类继承下来的成员变量
public class Base {
int a;
int b;
}
public class Derived extends Base {
int c;
public void method() {
a = 10; // 访问从父类继承下来的a
b = 20; // 访问从父类继承下来的b
c = 30; // 访问子类自己的c
}
}
子类和父类成员变量同名
若子类中含有与父类相同的成员变量,则优先访问子类自己的成员变量;若子类中没有则会访问从父类继承下来的成员变量;若父类中也没有,则编译报错。即成员变量的访问遵循就近原则
public class Derived extends Base {
int a;
char b;
public void method() {
a = 10; //访问子类自己的a
b = 20; //访问子类自己的b
//c = 30; //编译报错,子类和父类中都没有成员变量c
}
}
2、子类中访问父类的成员方法
成员方法名字不同
子类中没有与父类相同的成员方法名
public class Base {
public void methodA(){
System.out.println("父类中的methodA()");
}
}
public class Derived extends Base {
public void methodB(){
System.out.println("子类中的methodB()");
}
public void method(){
methodA(); // 访问从父类继承的methodA()
methodB(); // 访问子类自己的methodB()
// methodC(); // 编译失败,子类和父类中都没有该方法
}
}
成员方法名字相同
通过子类对象访问父类与子类不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,没有找到则报错。
若父类和子类方法名相同,参数不同,即构成重载,则根据调用的方法传递的参数选择适当的方法访问;若父类和子类同名方法的原型一致(重写),则只能访问到子类的,父类的无法访问到。
public class Base {
public void methodA(){
System.out.println("父类中的methodA()");
}
public void methodB(){
System.out.println("父类中的methodB()");
}
}
public class Derived extends Base {
public void methodA(int a){
System.out.println("子类中有参数的methodA(int)");
}
public void methodB(){
System.out.println("子类中的methodB()");
}
public void method(){
methodA(); // 访问从父类继承的methodA()
methodA(10); // 传参,访问子类自己的methodA(int)
methodB(); // 访问子类自己的methodB()
// methodC(); // 编译失败,子类和父类中都没有该方法
}
}
如果子类存在与父类相同的成员,但需要在子类中访问父类相同名称的成员,则需要使用 super 关键字
四、super关键字
作用:在子类方法中访问父类的成员
public class Base {
int a;
int b;
public void methodA(){
System.out.println("父类中的methodA()");
}
public void methodB(){
System.out.println("父类中的methodB()");
}
}
public class Derived extends Base {
int a;
char b;
public void methodA(int a){
System.out.println("子类中的methodA()");
}
public void methodB(){
System.out.println("子类中的methodB()");
}
public void method(){
//访问子类中的a,b
a = 10;
b = 20;
//访问父类中的a,b
super.a = 30;
super.b = 40;
methodA(); // 访问从父类继承的methodA()
methodA(10); // 传参,访问子类自己的methodA(int)
methodB(); // 访问子类自己的methodB()
super.methodB(); //访问父类中的methodB()
// methodC(); // 编译失败,子类和父类中都没有该方法
}
}
在子类方法中,访问父类的成员变量和方法。
super() 和 this()的区分
相同点:
1、都是Java关键字 、
2、只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3、在构造方法中调用时,必须是构造方法中第一条语句,且不能同时存在
不同点:
1、this是当前对象(调用实例方法的对象)的引用,super相当于子类对象从父类继承下来部分成员的引用
2、在非静态成员方法中,this 用来访问本类的方法和属性,super 用来访问父类继承下来的方法和属性
3、this 是非静态成员方法的一个隐藏参数,super 不是隐藏参数
4、成员方法中直接访问本类成员时,编译之后会将this 还原(非静态成员都是通过this来访问的);子类中如果通过super 访问父类成员,编译之后在字节码层面super 是不存在的,super只在代码层面体现出来,告诉编译器访问基类的成员
5、构造方法中一定存在super 的调用,即使用户没有写编译器也会自动添加,this 则是用户不写则没有。
五、子类构造方法
public class Base {
public Base(){
System.out.println("Base()");
}
}
public class Derived extends Base {
public Derived(){
// super();
// 子类构造方法中默认会调用基类的无参构造方法:super()
// 用户没有写时,编译器会自动添加,super()必须是子类构造方法中的第一条语句,且只能出现一次
System.out.println("Derived()");
}
}
public class Test {
public static void main(String[] args) {
Derived d = new Derived();
}
}
运行结果
Base()
Derived()
在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法;
创建哪个类的对象,就会调用那个类的构造方法;
构造子类对象时,先要将父类继承下来的成员初始化完整,然后再初始化子类自己新增的成员。
注意:1、若父类显式定义无参或者默认的构造方法,此时,子类的构造方法可以不用定义,如果需要再定义,在子类构造方法第一行默认有隐含的 super 调用,即调用基类构造方法。
2、如果父类构造方法是有参数的,此时编译器不会再给子类生成默认的构造方法,此时需要用户为子类显示定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
3、子类构造方法中,super() 调用父类构造时,必须是子类构造函数的第一条语句。
4、super() 和 this() 不能同时出现
六、初始化
【没有继承时】 代码执行顺序:
public class Test {
public String name;
public int age;
public Test(String name,int age){
this.name = name;
this.age = age;
System.out.println("执行构造方法");
}
{
System.out.println("执行实例代码块");
}
static{
System.out.println("执行静态代码块");
}
public static void main(String[] args) {
Test p1 = new Test("蓝胖子",18);
System.out.println("*******创建第二个对象*********");
Test P2 = new Test("胖虎",12);
}
}
执行静态代码块
执行实例代码块
执行构造方法
*******创建第二个对象*********
执行实例代码块
执行构造方法
静态代码块先执行,并且只执行一次,在类加载阶段执行;
当有对象创建时,才会执行实例代码块,实例代码块执行完成后,构造方法执行。
【有继承关系时】
public class Base {
public String name;
public int age;
public Base(String name,int age){
this.name = name;
this.age = age;
System.out.println("父类:构造方法");
}
{
System.out.println("父类:实例代码块");
}
static{
System.out.println("父类:静态代码块");
}
}
public class Derived extends Base {
public Derived(String name,int age){
super(name, age);
System.out.println("子类:构造方法");
}
{
System.out.println("子类:实例代码块");
}
static{
System.out.println("子类:静态代码块");
}
}
public class Test {
public static void main(String[] args) {
Derived d1 = new Derived("蓝胖子",18);
System.out.println("*******创建第二个子类对象*********");
Derived d2 = new Derived("胖虎",12);
}
public static void main1(String[] args) {
Base d1 = new Base("静香",11);
System.out.println("*******创建第二个父类对象*********");
Base d2 = new Base("大雄",12);
}
}
运行结果
父类:静态代码块
子类:静态代码块
父类:实例代码块
父类:构造方法
子类:实例代码块
子类:构造方法
*******创建第二个子类对象*********
父类:实例代码块
父类:构造方法
子类:实例代码块
子类:构造方法
若执行main1函数,则只执行父类中的方法,如下
父类:静态代码块
父类:实例代码块
父类:构造方法
*******创建第二个父类对象*********
父类:实例代码块
父类:构造方法
一般情况下,将所有字段设为private,将所有方法设为public,不过还是建议写代码的时候想一想,不滥用访问权限
七、继承方式
- 单继承:一个父类对应一个子类
- 多层继承:一个父类对应一个子类,子类再对应下个子类
- 不同类继承同一个类:多个子类继承一个父类
- 多继承(Java中不支持):一个子类对应多个父类
注意:Java中不支持多继承
一般不希望继承超过三层;如果继承层次太多,则需要考虑对代码的重构。
如果想要从语法层面进行限制继承,就可以使用 final 关键字。
八、final关键字
final 关键字用来修饰变量,成员方法以及类。
1、修饰变量或字段,表示常量(不能修改)
2、修饰类:表示此类不能被继承
3、修饰方法:表示该方法不能被重写
九、继承与组合
继承是 is-a 的关系
组合是 has-a 的关系
比如,汽车和轮胎类、发动机、方向盘、车载系统等关系是组合
宝马、大众、奥迪属于汽车,是继承关系
代码复用优先选择组合
原因(继承实现代码复用的缺点):
1、破坏类的封装性
2、子类和父类的耦合性太高(父类某个方法发生改变,子类一定会看到改变;基类某个方法多加了一个参数,子类调用该方法的函数都需要修改)
3、子类除了构造方法没有继承下来外,其他成员都继承到子类中了