继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称为派生类,继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
一、继承的引入
1、用例子进行简单的说明。
首先定义一个Dog类和Cat类
public class Dog {
public String name;
public String gender;
public int age;
public String color;
public void init(String name, String gender, int age, String color){
this.name = name;
this.gender = gender;
this.age = age;
this.color = color;
}
public void eat(){
System.out.println(name + "在吃狗粮");
}
public void sleep(){
System.out.println(name + "在睡觉");
}
public void bark(){
System.out.println(name + "在汪汪汪");
}
}
public class Cat {
public String name;
public String gender;
public int age;
public String temper;
public void init(String name,String gender,int age,String temper){
this.name = name;
this.gender = gender;
this.age = age;
this.temper = temper;
}
public void eat(){
System.out.println(name + "在吃猫粮");
}
public void sleep(){
System.out.println(name + "在睡觉");
}
public void mew(){
System.out.println(name + "在喵喵叫");
}
}
输入一些例子然后输出
public class TestPet {
public static void main(String[] args) {
Dog dog = new Dog();
dog.init("可乐","公",1,"黑白");//有个叫可乐的狗狗
dog.eat();
dog.sleep();
dog.bark();
System.out.println("=============================");
Cat cat = new Cat();
cat.init("辣条","公",1,"温顺");//有个叫辣条的喵咪
cat.eat();
cat.sleep();
cat.mew();
}
}
输出结果为:
由以上代码我们可以发现:Dog和Cat类大部分的实现都是相同的,在Dog和Cat类中出现了重复代码,每次有新的宠物出现,就需要添加对应的类,这些重复的字段和方法又会重复出现,导致做一些重复的事情,代码的可读性也不高
狗狗猫咪都有名字,性别,年龄,都需要吃、睡,这些属性和方法是所有动物都具有的——大家都有的方法和属性提取到一个类中单独实现。
2、以下代码用到继承使代码可读性提高
public class Animal { //基类、父类、超类
public String name;
public String gender;
public int age;
public void init(String name,String gender,int age){
this.name = name;
this.gender = gender;
this.age = age;
}
public void eat(){
System.out.println(name + "在吃饭");
}
public void sleep(){
System.out.println(name + "在睡觉");
}
}
public class Dog2 extends Animal{//派生类、子类
public String color;
public void bark(){
System.out.println(name + "在汪汪汪");
}
}
public class Cat2 extends Animal{//派生类、子类
//是对基类Animal进行扩增
public String temper;
public void mew(){
System.out.println(name + "在喵喵叫");
}
}
Dog2和Cat2都继承了Animal类,其中Animal称为父类/基类/超类,Dog2和Cat2被称为Animal的子类/派生类,继承之后,子类可以复用父类中的成员,子类在实现时只需要关心自己新增加的成员即可。
从继承概念中可以看出继承最大的作用是:实现代码复用,实现多态。
二、继承的语法
1、在Java中如果想表示类之间的继承关系,需要借助extends关键字。
注意:1)子类会将父类中的成员变量或者成员方法集成到子类中
2)子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同
2、父类成员访问
子类中访问父类的成员变量
注意:子类和基类可以存在相同名称的成员变量,并且类型和名字都可以完全相同,此时,在子类的方法中优先访问到的是子类的同名成员,从基类中继承下来的同名成员,不能在子类中被直接访问,称为同名隐藏(在方法中无法直接访问到基类相同名称的成员,直接访问的是子类自己新增的成员)。
① 在子类方法中永远都无法访问基类中相同名称的成员了吗?
方法一:可以在基类中提供对应的方法来操作
方法二:可以借助super关键字来操作
建议:一般情况下,在继承体系中尽量避免设计名字相同的成员变量
②在子类方法中或者通过子类对象访问成员时:
如果访问的成员变量子类中有,优先访问自己的成员变量
·如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
③在子类构造方法中并没有写任何关于基类的构造方法的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为子类对象中的成员是有两部分组成的,基类继承下来的以及子类新增加的部分,所以在构造子类对象的时候,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。
注意:
①若父类显示定义无参或者默认的构造方法,在子类构造方法第一行默认含有super()调用,即调用基类构造方法
②如果父类构造方法是带有参数的,此时需要用户为子类显示定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败
③在子类构造方法中,super()调用父类构造时,必须是子类构造中的第一条语句
④super()只能在子类构造方法中出现一次,并且不能和this同时出现
3、super关键字
由于设计不好,或者因为场景需要,子类和父类中会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,直接访问时无法做到的,这时Java提供了super关键字,它的主要作用是:在子类方法中访问父类的成员
以下代码加深理解
public class Base {
public int a;
public int b;
public Base(int a, int b) {
this.a = a;
this.b = b;
}
}
public class Derived extends Base{
public int c;
//子类构造方法
public Derived(int a, int b, int c) {
super(a, b);
this.c = c;
}
public static void main(String[] args) {
Derived derived = new Derived(1,2,3);
derived.a = 10;
derived.b = 20;
derived.c = 30;
}
}
在子类中,如果想要明确访问父类中成员时,可以借用super关键字
注意:1)super只能在非静态方法中使用
2)在子类方法中,访问父类的成员变量和方法
4、子类构造方法
子类对象构造时,先调用基类构造方法,然后执行子类的构造方法
public class Base {
public int a;
public int b;
public Base(int a, int b) {
this.a = a;
this.b = b;
}
}
public class Derived extends Base{
public int c;
//子类构造方法
public Derived(int a, int b, int c) {
super(a, b);
this.c = c;
}
public static void main(String[] args) {
Derived derived = new Derived(1,2,3);
derived.a = 10;
derived.b = 20;
derived.c = 30;
}
}
结论:
1)如果基类没有显式定义任何构造方法,则子类可以定义也可以不定义,子类如果需要构造方法就定义,不需要就不定义,此时编译器会给基类和子类都生成无参的构造方法
注意:此时创建对象的时候就不能带有参数
2)如果基类显式定义了构造方法,但是基类的构造方法时无参的,此时子类的构造方法可以定义,也可以不用定义,根据自己的需求
3)如果基类显式定义了带有参数的构造方法,此时子类必须显式定义自己的构造方法,否则编译报错
注意:当编译器在编译子类的构造方法的时候,会在子类构造方法中新增加一条语句,调用基类的构造方法,只能是调用基类的无参构造方法,编译器无法调用基类中带有参数的构造方法,因为带有参数的构造方法需要传参,编译器调用的时候是不知道传递什么参数的。
注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中
三、继承方式
1、单继承
public class A{
......
}
public class B extends A{
......
}
2、多层继承
public class A{......}
public class B extends A{......}
public class C extends B{......}
3、不同类继承同一个类(两个孩子一个爹)
public class A{......}
public class B extends A{......}
public class C extends A{......}
4、不支持多继承(不支持一个孩子两个爹)