【Java】继承

1.1 什么是继承

在我们日常写代码的时候,我们发现我们会遇到两个类里有相同的成员或者方法;那如此以往,会造成写代码效率的下降,并且代码冗余过多。由此,我们引出了我们今天的主角——继承。

比如说:

class Dog {
    public String name;
    public int age;
    public void eat() {
        System.out.println(this.name+"在吃饭!");
    }
    public void bark() {
        System.out.println(this.name+"在叫!");
    }
}

class Bird {
    public String wing;
    public String name;
    public int age;
    public void eat() {
        System.out.println(this.name+"在吃饭!");
    }
    public void bark() {
        System.out.println(this.name+"在叫!");
    }
    public void fly() {
        System.out.println(name+"在飞!");
    }
}

我们会发现,狗和鸟这两个不同的类,但是他们里面有好多重复的内容。那么我们是不是能否将这些共性抽取呢?面相对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。

1.2 继承概念

继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。

继承主要解决的问题是:共性的抽取,实现代码复用。

Dog和Bird都继承了Animal类,其中:Animal类称为父类/基类或超类,Dog和Birdt可以称为Animal的子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。

1.3 继承的语法

在Java中如果要表示类之间的继承关系,需要借助extends关键字。

修饰符 class 子类 extends 父类 {
            // ... 
}

我们对开局的代码重新书写--->

class Animal {
    public String name;
    public int age;

    public void eat() {
        System.out.println(this.name+"在吃饭!");
    }

    public void bark() {
        System.out.println(this.name+"在叫!");
    }
}
class Dog extends Animal{
    public void swin(){
        System.out.println(this.name+"正在狗刨!");
    }
}

class Bird extends Animal{
    public String wing;

    public void fly() {
        System.out.println(name+"在飞!");
    }
}

 我们写个main函数来验证是否可行;

public class TextDemo {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "大黄";
        dog.eat();
        dog.bark();
        System.out.println("-------------");
        Bird bird = new Bird();
        bird.name = "鹦鹉";
        bird.eat();
        bird.fly();
    }
}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_17,color_FFFFFF,t_70,g_se,x_16

成功。

注意:
1. 子类会将父类中的成员变量或者成员方法继承到子类中了
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

1.4 父类成员访问

1.4.1 子类中访问父类的成员变量

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
 }
}

 2. 子类和父类成员变量同名

class Base {
    public int a;
    public int b;
}

public class Derived extends Base {
    public int a;
    public boolean b;
    public int c;
    public int d;

    public void test() {
        //首先,在访问时优先访问子类的成员,在访问父类的成员。
        a = 10;
        //b = 20;//编程报错;因为优先访问子类的成员,错误!
        b = true;//访问的是子类的成员,boolean类型,正确!
        //但是如果想访问父类的成员;怎么办?
        //有一个关键字super
        super.b = 20;//利用super关键字,我们可以直接去访问父类成员。
        c = 30;
        d = 40;
        //e = 1;//报错;因为父类子类都没有变量e。
        System.out.println(a+" "+b+" "+c+" "+d);
    }

    public static void main(String[] args) {
        Derived derived = new Derived();
        derived.test();
    }

}

总结:

在子类方法中 或者 通过子类对象访问成员时:
---如果访问的成员变量子类中有,优先访问自己的成员变量。
---如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
---如果访问的成员变量与父类中成员变量同名,则优先访问自己的,即:子类将父类同名成员隐藏了。
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。


所以,在这里,我们又引出了一个重要的关键字  ——  super

【super】

super.date  访问父类的成员变量;

super.func() 访问父类的成员方法;

super(); 调用父类的构造方法。

注意:

public static void testD() {
        super.a = 10;//报错。Derived.super' cannot be referenced from a static context
    }

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_17,color_FFFFFF,t_70,g_se,x_16 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_20,color_FFFFFF,t_70,g_se,x_16

 

Derived.super' cannot be referenced from a static context 

【注意事项】
1. 只能在非静态方法中使用
2. 在子类方法中,访问父类的成员变量和方法。


1.4.2 子类中访问父类的成员方法

class Base {
    public int a;
    public int b;

    public void testA() {
        System.out.println("Base::testA()!");
    }
}

public class Derived extends Base {
    public int a;
    public boolean b;
    public int c;
    public int d;

    public void testB() {
        System.out.println("Drived::testB()!");
    }

    public void testC() {
        testB();//访问自己的test
        testA();//访问Base的test
    }
    public static void main(String[] args) {
        Derived derived = new Derived();
        derived.testC();
    }
}

总结:

成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中也没有则报错。

2. 成员方法名字相同

class Base {
    public int a;
    public int b;

    public void testA() {
        System.out.println("Base::testA()!");
    }

    public void testB() {
        System.out.println("Base::testB()!");
    }
}

public class Derived extends Base {
    public int a;
    public boolean b;
    public int c;
    public int d;

    public void testA(int a) {
        System.out.println("Derived::testA()!");
    }

    public void testB() { 
        System.out.println("Drived::testB()!");
    }

    public void testC() {
        testA(10);//传参了,所以访问的是子类的方法。
        testB();  //父类子类都是没有参数的方法,所以优先访问子类的方法。
        testA();  //没传参,所以访问的是父类的方法。
    }
    public static void main(String[] args) {
        Derived derived = new Derived();
        derived.testC();
    }

}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_16,color_FFFFFF,t_70,g_se,x_16

【说明】
1. 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
2. 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用
3. 方法适传递的参数选择合适的方法访问,如果没有则报错;如果父类和子类同名方法的原型一致(重写-后面讲),则只能访问到子类的,父类的无法通过派生类对象直接访问到。

1.5 子类构造方法

引文:父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。

class Animal {
    public String name;
    public int age;
     
    //当有没带参数的构造方法存在时,程序不会报错。
    /*public Animal() {
        System.out.println("Animal的不带参数的构造方法!");
    }*/
    //当没带参数的构造方法被提供的构造方法代替后,编译器不会再默认生成不带参数的方法。
    
    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Animal的带参数的构造方法!");
    }

}
class Dog extends Animal{
    public Dog() {
        super("11",2);
    }
}

class Bird extends Animal{
    public String wing;

    public Bird(String name, int age) {
        super(name, age);
    }
}

当子类继承了父类之后,要帮助父类进行构造

如何帮助父类进行构造呢? 显示的调用父类的构造方法,帮助父类进行 构造 ,此时 构造 的意思就是,初始化继承过来的父类的成员。

在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为:在构造子类对象时,先要将从父类继承下来的成员初始化完整,然后再初始化子类自己新增加的成员。

注意:
1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法;
2. 如果父类构造方法是带有参数的,此时编译器不会再给子类生成默认的构造方法,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败;
3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句
4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现

1.6 super和this

super和this都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句。
那他们之间有什么区别呢?
【相同点】
1. 都是Java中的关键字;
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段;
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在。
【不同点】
1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成的引用;

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_20,color_FFFFFF,t_70,g_se,x_16

2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性;
3. this是非静态成员方法的一个隐藏参数,super不是隐藏的参数;
4. 成员方法中直接访问本类成员时,编译之后会将this还原,即本类非静态成员都是通过this来访问的;在子类中如果通过super访问父类成员,编译之后在字节码层面super实际是不存在的(通过字节码文件可以验证)员;
5. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现;
6. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有。

1.7 再谈初始化

我们可以通过自己书写代码,运行自己进行感受。

class Animal {
    public String name;
    public int age;

    /*public Animal() {
        System.out.println("Animal的不带参数的构造方法!");
    }*/

    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Animal的带两个参数的构造方法!");
    }
}
class Dog extends Animal{
    public Dog() {
        super("11",2);
        System.out.println("Dog不带参数的构造方法!");
    }

    public void swin(){
        System.out.println(this.name+"正在狗刨!"+"年龄:"+age);
    }
}

class Bird extends Animal{
    public String wing;

    public Bird(String name, int age) {
        super(name, age);
        System.out.println("Bird的带两个参数的构造方法!");
    }
}


public class TextDemo {

    public static void main(String[] args) {
        Dog dog = new Dog();
        System.out.println("=========");
        Bird bird = new Bird("七七",23);
    }

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_20,color_FFFFFF,t_70,g_se,x_16

 

class Animal {
    public String name;
    public int age;

    static {
        System.out.println("Animal的静态代码块");
    }
    {
        System.out.println("Aniaml的实例代码块");
    }

    /*public Animal() {
        System.out.println("Animal的不带参数的构造方法!");
    }*/

    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Animal的带两个参数的构造方法!");
    }
}
class Dog extends Animal{
    public Dog() {
        super("11",2);
        System.out.println("Dog不带参数的构造方法!");
    }

    public void swin(){
        System.out.println(this.name+"正在狗刨!"+"年龄:"+age);
    }

    static {
        System.out.println("Dog的静态代码块");
    }
    {
        System.out.println("Dog的实例代码块");
    }
}

public class TextDemo {

    public static void main(String[] args) {
        Dog dog = new Dog();
    }
}

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_19,color_FFFFFF,t_70,g_se,x_16

 

class Animal {
    public String name;
    public int age;

    static {
        System.out.println("Animal的静态代码块");
    }
    {
        System.out.println("Aniaml的实例代码块");
    }

    /*public Animal() {
        System.out.println("Animal的不带参数的构造方法!");
    }*/

    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Animal的带两个参数的构造方法!");
    }
}
class Dog extends Animal{
    public Dog() {
        super("11",2);
        System.out.println("Dog不带参数的构造方法!");
    }

    public void swin(){
        System.out.println(this.name+"正在狗刨!"+"年龄:"+age);
    }

    static {
        System.out.println("Dog的静态代码块");
    }
    {
        System.out.println("Dog的实例代码块");
    }
}

public class TextDemo {

    public static void main(String[] args) {
        Dog dog = new Dog();
        System.out.println("========");
        Dog dog1 = new Dog();
    }
}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_20,color_FFFFFF,t_70,g_se,x_16

1. 静态代码块先执行,并且只执行一次,在类加载阶段执行
2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行

通过分析执行结果,得出以下结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行 

1.8 protected 关键字 

在类和对象章节中,为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_20,color_FFFFFF,t_70,g_se,x_16

证明:

//同一个包的同一个类
package Demo1;

public class Test1 {
    public int a = 10;
    protected int b =20;

    public static void main(String[] args) {
        Test1 test1 =new Test1();
        System.out.println("同一个包的同一个类"+test1.b);
    }
}

//同一个包的不同类
package Demo1;

public class Test2 {

    public static void main(String[] args) {
        Test1 test1 =new Test1();
        System.out.println("同一个包的不同类"+test1.b);
    }
}

//不同包中子类 和 不同一个包的非子类
package Demo2;

import Demo1.Test1;

public class Test3 extends Test1{

    public void func() {
        System.out.println("不同包中子类"+super.b);
    }
    public static void main(String[] args) {
        Test3 test3 =new Test3();
        test3.func();

        //System.out.println("不同一个包的不同类"+test1.b);//报错
    }
}

总结:

什么时候用哪个访问修饰限定符,没有明确规定。一般粗暴点:字段都是私有的,方法都是公开的。但是,一定要根据情况而定。

1.9 继承方式

 Java中只支持以下几种继承方式:watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_20,color_FFFFFF,t_70,g_se,x_16

 注意:Java中不支持多继承
=>时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂.
=>但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
=>如果想从语法上进行限制继承, 就可以使用 final 关键字

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_11,color_FFFFFF,t_70,g_se,x_16

 

1.10 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进行继

 我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承.
3. 修饰方法:表示该方法不能被重写(后序介绍)

 1.11 继承与组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。

继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车

组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。

end

 

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值