Java面向对象之继承,深度剖析继承的使用

各位看官早安午安晚安呀

如果您觉得这篇文章对您有帮助的话

欢迎您一键三连,小编尽全力做到更好
欢迎您分享给更多人哦

大家好我们今天来学习java面向对象的三大特性之一的继承

那大家肯定要问

  1. 什么是继承?
  2. 继承有什么用?
  3. 以及继承的语法格式是设么样的?

接下来跟着小编我带领大家一一解答!!

1:什么是继承以及作用?

继承 (inheritance) 机制 :是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能 这样产生新的类,称 派生类(也称子类) 。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
继承主要的作用就是:共性的抽取,实现代码复用
举个小例子:
猫和狗都是动物,他们都有动物的特点,譬如(名字,年龄,会跑等等),所以我们在写猫和狗这些类时就不必要再多写他们都有的特点了直接再写一个Animal的类,让猫和狗都可以用Animal里面的成员和方法(这就是继承),然后再在他们自己的类里面加上他们自己的属性。
Dog Cat 都可以继承 Animal 类,继承之后,其中: Animal类称为父类/基类或超类 Dog Cat 可以称为 Animal 的子类/ 派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。

2:继承的语法

Java 中如果要表示类之间的继承关系,需要借助 extends 关键字,具体如下:
修饰符 class 子类 extends 父类 {
// ...
}
class Dog extends Animal{
    public void wangWang(){
        System.out.println(name +"正在旺旺");
    }
}

但是这种不建议继承;

class Robot extends Animal (机器人这种继承动物就太牵强了,我们不建议)

具体大家可以看代码实现:

class Animal{
    String name;
    int age;
    public void eat() {
        System.out.println(name + "正在吃饭");
    }
}
class Cat extends Animal{
    
    public void miMi(){
        System.out.println(name +"正在喵喵");
    }
}

class Dog extends Animal{
    public void wangWang(){
        System.out.println(name +"正在旺旺");
    }
}

然后我们就可以通过子类调用父类的方法和变量了

  public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "旺仔";
        dog.eat();
        Cat cat = new Cat();
        cat.name = "咪咪";
        cat.eat();
    }

扫描二维码关注公众号,回复: 17438212 查看本文章
注意:
1. 子类会将父类中的成员变量或者成员方法继承到子类中了
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

3:如果父类和子类具有相同的成员属性呢?

3.1:父类和子类存在相同成员变量

下列的值应该是多少呢?

class Base {
    public int a = 1;
    public int b = 2;
     
}
 class Derived extends Base {
     public int a = 4;// 与父类中成员a同名,且类型相同

     public int c = 3;
    public void method(){
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
    }
}
如果子类和父类是同名的成员变量的时候:
  1. 如果访问的成员变量与父类中成员变量同名,则优先访问自己的
  1. 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
  2. 如果一定要访问父类的成员变量的时候,就要用到另外一个关键字super

3.2:父类和子类存在同名的成员方法

这个和同名的成员变量不太一样,因为这个会涉及到方法的重载:

 class Base {
    public int a = 1;
     public int b = 2;
     public void func(){
         System.out.println("父类的成员方法func");
     }
     public void func1(){
         System.out.println("父类的成员方法func1");
     }
     public void func2(int a){
         System.out.println("父类的成员方法func2");
         this.a = a;
     }
}
 class Derived extends Base {
     public int a = 4;// 与父类中成员a同名,且类型相同
     public int c = 3;
     public void func(){
         System.out.println("子类的成员方法func");
     }
     public void func2(){
         System.out.println("父类的成员方法func2");
     }
}
public class Test {

    public static void main(String[] args) {

        Derived derived = new Derived();
        derived.func();
        derived.func1();
        System.out.println(derived.a);
        derived.func2(100);
        System.out.println(derived.a);
    }
大家可以看看这段代码输出什么?

子类和父类都有func和func2,第一次derived.func调用的是子类的,但是第二次derived.func2调用的是却是父类的,因为这里构成了方法的重载

如果父类和子类同名方法的参数列表不同 ( 重载 ) ,根据调用 方法适传递的参数选择合适的方法访问,如果没有则报错
并且你话虽然改变了a的值,却改变的是父类的,打印出来的还是4
注意:
1.通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
2.通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同 ( 重载 ) ,根据调用 方法适传递的参数选择合适的方法访问,如果没有则报错;

4:super关键字

由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,该如何操作?直接访问是无法做到的,Java 提供了 super 关键字,该关键字主要作用:在子类方法中访问父 类的成员 。(其实就是一个关键字,不代表父类的引用)
(super在当前类里使用,那么这个类一定是子类)
我还是用上面的代码举例
 class Base {
    public int a = 1;
     public int b = 2;
    
     public void func1(){
         System.out.println("父类的成员方法func1");
     }
     public void func2(int a){
         System.out.println("父类的成员方法func2");
     }
}
 class Derived extends Base {
     public int a = 4;// 与父类中成员a同名,且类型相同
     public int c = 3;
    
     public void func1(){
         System.out.println("重写父类的成员方法func1");
     }
     public void func2(){
         System.out.println("父类的成员方法func2");
     }
     public void method(){
// 对于同名的成员变量,直接访问时,访问的都是子类的
         a = 100; // 等价于: this.a = 100;
         b = 101; // 等价于: this.b = 101;
// 注意:this是当前对象的引用
// 访问父类的成员变量时,需要借助super关键字
// super是获取到子类对象中从基类继承下来的部分
         super.a = 200;
         super.b = 201;
// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
         func2(); // 没有传参,访问父类中的func2
         func2(20); // 传递int参数,访问子类中的func2()
         
         
// 如果在子类中要访问重写的基类方法,则需要借助super关键字
         func1(); // 直接访问,则永远访问到的都是子类中的func1(),基类的无法访问到
         super.func1(); // 访问基类的func1()
     }
 }

最主要的就是这一点

// 如果在子类中要访问重写的基类方法,则需要借助super关键字
         func1(); // 直接访问,则永远访问到的都是子类中的func1(),基类的无法访问到
         super.func1(); // 访问基类的func1()
注意事项
1. 只能在非静态方法中使用(就像this一样)
2. 在子类方法中,访问父类的成员变量和方法
3.super只能访问从父类那里继承过来的成员属性
4.super()调用父类的构造方法(接下来讲解)

5:关于构造方法

俗话说: 父子父子,先有父再有子,即:子类对象构造时,必须要先调用父类的构造方法,然后再执行子类的构造方法。(但是我们上面的代码不是没有帮助构造还是运行起来了吗?其实是编译器帮我们写了并且隐藏了构造方法

注意子类构造方法中默认会调用基类的无参构造方法:super()

// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
// 并且只能出现一次

如果父类具有带参数的构造方法:
大家可以看到如果父类有了带参数的构造方法,子类却没有super(name)帮助父类完成构造,编译器就会报错
这个时候我们点击 Alt+Enter键(Alt+enter(双击enter)更快)就会可以让编译器帮助我们写出构造方法
解释
子类对象中成员是有两部分组成的,父类继承下来的以及子类新增加的部分 。父子父子 肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整
注意:
情况1: 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的 super() 调用,即调用基类构造方法
情况2:  如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。
3. 在子类构造方法中, super(...) 调用父类构造时,必须是子类构造函数中第一条语句。
4. super(...) 只能在子类构造方法中出现一次,并且不能和 this 同时出现(因为构造方法调用其他的构造方法this(参数)也需要在第一行

6:this和super的关系

6.1:相同点

1. 都是 Java 中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

6.2:不同点

1:this 是当前对象的引用,当前对象即调用实例方法的对象, super相当于是子类对象中从父类继承下来部分成 员的引用(这么说也不太准确) (其实super就只是一个关键字)
 
2. 在非静态成员方法中 this 用来访问本类的方法和属性, super 用来访问父类继承下来的方法和属性
3. 在构造方法中: this(...) 用于调用本类构造方法, super(...) 用于调用父类构造方法,两种调用不能同时在构造方法中出现
4. 构造方法中一定会存在 super(...) 的调用,用户没有写编译器也会增加(默认增加),但是 this(...) 用户不写则没有

7:回顾代码块(学完super()调用父类构造方法之后)

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person:构造方法执行");
    }
    {
        System.out.println("Person:实例代码块执行");
    }
    static {
        System.out.println("Person:静态代码块执行");
    }
}
class Student extends Person{
    public Student(String name,int age) {
        super(name,age);
        System.out.println("Student:构造方法执行");
    }
    {
        System.out.println("Student:实例代码块执行");
    }
    static {
        System.out.println("Student:静态代码块执行");
    }
}
public class Test2 {
    public static void main(String[] args) {
        Student student1 = new Student("张三", 19);
        System.out.println("===========================");
        Student student2 = new Student("李四", 20);
    }
}
静态代码块还是最先执行,然后帮父类构造方法(先实例代码块)帮助父类构造完然后轮到子类
并且静态代码块还是只能执行一次

8.protected访问限定符

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

不是说被在不同包里的子类就可以访问吗?但是为什么报警告呢?

警告显示a已经被protected修饰(我肯定知道啊)
换super.a就不会报警告了
接下来讲述一下被各个限定符修饰的成员属性:
// 为了掩饰父类中不同访问
// 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包中
// 不同包中的子类(protected还能起作用)
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成员变量虽然在子类中不能直接访问,但是也继承到子类中了
什么时候下用哪一种呢 ?
我们希望类要尽量做到 " 封装 ", 即隐藏内部实现细节 , 只暴露出 必要 的信息(接口)给类的调用者 .
因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限 . 例如如果一个方法能用 private, 就尽量不要用public.
另外 , 还有一种 简单粗暴 的做法 : 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用, 还是更希望大家能写代码的时候认真思考 , 该类提供的字段方法到底给 " " 使用 ( 是类内部自己用, 还是类的调用者使用 , 还是子类使用)

9.继承方式

java里面支持下列的继承方式:

但是 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了
.
如果想从语法上进行限制继承, 就可以使用 final 关键字

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. 修饰方法:表示该方法不能被重写 ( 后序介绍 )

11.继承和组合:

组合:其实就是一种代码的实现方式(没有关键字修饰或其他的)

继承表示对象之间是 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 {
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。
最后给大家提个问题:这个代码为什么会报错?
class Animal{
    String s;
    int a = 10;
}
class Dog extends Animal{

    a = 20;
}

Java中,如果你想在子类中修改从父类继承的字段的值,你需要在子类的构造器、方法或者初始化块中进行这个操作,而不能直接赋值

使用构造方法或者构造代码块都可以

    {
        a = 20;
    }

    public Dog() {
        a = 20;
    }

或者提供一个setter方法

class Animal {  
    String s;  
    int a = 10;  
  
    public void setA(int a) {  
        this.a = a;  
    }  
}  
  
class Dog extends Animal {  
    public Dog() {  
        this.setA(20); // 在Dog的构造器中调用setter方法  
    }  
}
  • 直接在类体中(如我们的原始尝试)为继承的字段赋值是不允许的,因为Java不支持这种语法。
  • 考虑到封装和代码的可维护性,通常推荐使用setter方法或者通过构造器来设置字段的值。
  • 或者通过super访问,一般访问父类里面的元素都是通过super访问

上述就是 Java面向对象之继承的全部内容了,能看到这里相信您一定对小编的文章有了一定的认可

有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正

您的支持就是我最大的动力​​​!!!!

猜你喜欢

转载自blog.csdn.net/2302_80639556/article/details/142741770