Java继承:抽取相同共性,实现代码复用

编程入门 行业动态 更新时间:2024-10-09 13:24:19

Java继承:抽取相同<a href=https://www.elefans.com/category/jswz/34/1733517.html style=共性,实现代码复用"/>

Java继承:抽取相同共性,实现代码复用

  • 👑专栏内容:Java
  • ⛪个人主页:子夜的星的主页
  • 💕座右铭:前路未远,步履不停

目录

  • 一、继承的概念
  • 二、继承的语法
  • 三、父类成员访问
    • 1、子类中访问父类成员变量
      • Ⅰ、子类和父类不存在同名成员变量
      • Ⅱ、子类和父类成员变量同名
    • 2、子类中访问父类的成员方法
      • Ⅰ、成员方法名字不同
      • Ⅱ、成员方法名字相同
    • 3、`super`关键字
    • 4、子类构造方法
    • 5、`super`和`this`的区别
  • 四、继承方式
    • 1、 `final`关键字
    • 2、继承与组合


(。・∀・)ノ゙嗨,今天让我们来一起学习一下Java的继承吧。

Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,可能两个对象之间会存在某种关系。继承在编程中也是类似的概念,其中一个类可以继承另一个类的属性和方法,并且可以在此基础上添加或修改。这样可以使代码更加模块化、可维护,同时提高代码的复用性。

一、继承的概念

假设我们现在有两个类,一个是猫类,另外一个是狗类。

// Dog.java
public class Dog{string name;int age;float weight;public void eat(){System.out.println(name + "正在吃饭");}public void sleep(){System.out.println(name + "正在睡觉");}void Bark(){System.out.println(name + "汪汪汪~~~");}
}
// Cat.java
public class Cat{string name;int age;float weight;public void eat(){System.out.println(name + "正在吃饭");}public void sleep(){System.out.println(name + "正在睡觉");}void mew(){System.out.println(name + "喵喵喵~~~");}
}

通过观察上述代码会发现,猫和狗的类中存在大量重复,如下所示:


那能否将这些共性抽取呢?既然狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。

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

从上面可以看出,继承概念中可以看出继承最大的作用就是:共性的抽取,实现代码复用

二、继承的语法

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

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

让我们对上面的常见用继承重新设计一下吧。
首先,我们可以创建一个父类叫做动物类。

// nimal.java
public class Animal {String name; // 动物的名字int age; // 动物的年龄// 动物吃饭的方法public void eat() {System.out.println(name + "正在吃饭");}// 动物睡觉的方法public void sleep() {System.out.println(name + "正在睡觉");}
}

然后,让猫类和狗类分别继承动物类。

// Dog.java
public class Dog extends Animal { // Dog类继承自Animal类// 狗叫的方法void bark() {System.out.println(name + "汪汪汪~~~");}
}

// Cat.Java
public class Cat extends Animal { // Cat类继承自Animal类// 猫叫的方法void mew() {System.out.println(name + "喵喵喵~~~");}
}


这里,dog类和Cat类中并没有定义任何成员变量,nameage属性肯定是从父类Animal中继承下来的,其使用的eat()sleep()方法也是从Animal中继承下来的。

注意:

  • 子类会将父类中的成员变量或者成员方法继承到子类中了

  • 子类继承父类之后,必须要新添加自己特有的成员,否则就没有必要继承了

三、父类成员访问

在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?

1、子类中访问父类成员变量

Ⅰ、子类和父类不存在同名成员变量

public class Base {int a;int b;
}
/
public class Derived extends Base{int c;public void method(){a = 10; // 访问从父类中继承下来的ab = 20; // 访问从父类中继承下来的bc = 30; // 访问子类自己的c}
}

Ⅱ、子类和父类成员变量同名

public class Base {int a;int b;int c;
}
/
public class Derived extends Base{int a; // 与父类中成员a同名,且类型相同char b; // 与父类中成员b同名,但类型不同public void method(){a = 100; // 访问父类继承的a,还是子类自己新增的a?b = 101; // 访问父类继承的b,还是子类自己新增的b?c = 102; // 子类没有c,访问的肯定是从父类继承下来的c// d = 103; // 编译失败,因为父类和子类都没有定义成员变量b}
}

在子类方法中 或者 通过子类对象访问成员时

  • 如果访问的成员变量子类中有,优先访问自己的成员变量。

  • 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。

  • 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。

成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找

2、子类中访问父类的成员方法

Ⅰ、成员方法名字不同

public class Base {public void methodA(){System.out.println("Base中的methodA()");}
}
/
public class Derived extends Base{public void methodB(){System.out.println("Derived中的methodB()方法");}public void methodC(){methodB(); // 访问子类自己的methodB()methodA(); // 访问父类继承的methodA()// methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()}
}

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

Ⅱ、成员方法名字相同

public class Base {public void methodA(){System.out.println("Base中的methodA()");}public void methodB(){System.out.println("Base中的methodB()");}
}
/
public 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(),基类的无法访问到}
}

【说明】

  • 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。

  • 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错。

问题:如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?

在Java中,如果子类中存在与父类中相同名称的成员,那么在子类中默认访问的是子类自己的成员。要在子类中访问父类的同名成员,可以使用super关键字。super关键字用于引用当前子类实例的直接父类。

3、super关键字

由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,该如何操作?

直接访问是无法做到的,Java提供了super关键字。该关键字主要作用:在子类方法中访问父类的成员。

public class Base {int a; // 基类中的成员变量int b; // 基类中的成员变量public void methodA(){System.out.println("Base中的methodA()"); // 打印表明在基类中的语句}public void methodB(){System.out.println("Base中的methodB()"); // 打印表明在基类中的语句}
}/public class Derived extends Base {int a; // 使用相同的名称和类型遮蔽了基类中的成员变量'a'char b; // 使用相同的名称但不同类型遮蔽了基类中的成员变量'b'// 重载了基类中的methodA()public void methodA(int a) {System.out.println("Derived中的method()方法"); // 打印表明在派生类中的语句}// 重写了基类中的methodB()public void methodB(){System.out.println("Derived中的methodB()方法"); // 打印表明在派生类中的语句}public void methodC(){// 对于同名的成员变量,直接访问时,访问的是派生类中的变量this.a = 100; // 等价于:this.a = 100;this.b = 101; // 等价于:this.b = 101;// 注意:'this' 是当前对象的引用// 要访问基类中的成员变量,需要使用'super'关键字super.a = 200;super.b = 201;// 在基类和派生类中都存在重载的方法,可以通过参数列表区分访问基类还是派生类的方法methodA(); // 没有传参,访问基类中的methodA()methodA(20); // 传递int参数,访问派生类中的methodA(int)// 要访问重写的基类方法,需要使用'super'关键字methodB(); // 直接访问,则永远访问派生类中的methodB(),无法访问基类的super.methodB(); // 访问基类的methodB()}
}

在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。

注意事项

  • 只能在非静态方法中使用。

  • 在子类方法中,访问父类的成员变量和方法。

4、子类构造方法


子类对象构造时,需要先调用基类(父类)构造方法,然后执行子类的构造方法。

public class Base {public Base(){System.out.println("Base()"); // 打印在基类构造方法中的语句}
}public class Derived extends Base{public Derived(){// super(); 注意:子类构造方法中默认会调用基类的无参构造方法: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同时出现。

5、superthis的区别

superthis都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?

相同点

  • 都是Java中的关键字

  • 只能在类的非静态方法中使用,用来访问非静态成员方法和字段

  • 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

不同点

  • this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用

  • 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性

  • 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现

  • 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有

四、继承方式

在Java中只支持以下几种继承方式:


注意:Java中不支持多继承。

时刻牢记, 我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多,类之间的关系也会更加复杂。

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

1、 final关键字

final关键可以用来修饰变量、成员方法以及类。

final修饰符大多数应用于基本类型域,或不可变类的域。

将方法或者类的声明为final的主要目的是:确保它们不会在子类中改变语义。

  • 修饰变量或字段,表示常量(即不能修改)
final int a = 10;
a = 20; // 编译出错
  • 修饰类:表示此类不能被继承
final public class Animal {...
}
public class Bird extends Animal {...
}
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继

我们平时是用的String 字符串类, 就是用 final修饰的, 不能被继承

  • 修饰方法:表示该方法不能被重写

2、继承与组合

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

组合(或聚合)是另一种关系,表示一个类是由其他类的对象组成的。换句话说,一个类的对象包含了其他类的对象。这种关系通常被描述为“有-a"关系。例如,一辆汽车有一个引擎和四个轮子,因此汽车类可以包含一个引擎对象和四个轮子对象。

组合的主要优点是它提供了更好的封装,更强的代码灵活性,更易于修改和维护。由于类之间的关系不再像继承那样紧密,所以修改一个类的实现不太可能影响到使用该类的其他类。此外,组合也使得代码更加模块化,每个类都专注于一个单一的职责。

然而,组合也有一些缺点。它可能导致更复杂的代码,因为你需要管理和协调多个对象之间的交互。选择使用继承还是组合通常取决于具体的需求和上下文。

一般来说,如果两个类之间存在"is-a"(是一个)的关系,比如“苹果是一种水果”,那么继承可能是最好的选择;如果两个类之间存在"has-a"(有一个)的关系,比如“汽车有一个引擎”,那么组合可能是更好的选择。

更详细的可以看这个大佬的文章:深入理解Java中的组合和继承


继承就先说到这里了,我们下次再见!

更多推荐

Java继承:抽取相同共性,实现代码复用

本文发布于:2023-11-16 15:01:41,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1626061.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:共性   复用   代码   Java

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!