之前在JavaOO(7)中讲到过继承,不过因为篇幅关系讲的比较浅,有些需要注意的地方没有重点提到,所以今天我们就来开一篇专门来讲一下继承~
由于继承的基础点都在紫薇星上的Java——JavaOO(7)这篇文章中讲过,感兴趣的同学可以去看一下,这篇文章会开始补充细节和需要注意的地方。
1.引出继承
- 继承的概念
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类,继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
- 继承的特点
继承的主要特点在于:可以扩充已有类的功能。
- 引出继承问题
所谓良好的代码指的是结构性合理、适合于维护、可重用性很高的代码。但如果现在只是按照之前学到的概念来进行程序的编写,那么不可避免地需要面对重复的问题,比如我们有两个类:
//企鹅类
public class Penguin {
private String name;
private int id;
public Penguin(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好!我是" + id + "号" + name + ".");
}
}
//老鼠类
public class Mouse {
private String name;
private int id;
public Mouse(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好!我是" + id + "号" + name + ".");
}
}
大家可以看到,虽然实例化对象后我们知道这两个对象是不一样的,但在这两个类中出现了重复的代码结构,导致程序臃肿,增加维护成本,这种情况下重复的代码我们就可以使用继承来解决:
//公共父类
public class Animal {
private String name;
private int id;
public Animal(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好!我是" + id + "号" + name + ".");
}
}
我们用一个Animal类来将企鹅与老鼠的属性总结,然后在这两个类中代码就是:
//企鹅类
public class Penguin extends Animal {
public Penguin(String myName, int myid) {
super(myName, myid);
}
}
//老鼠类
public class Mouse extends Animal {
public Mouse(String myName, int myid) {
super(myName, myid);
}
}
这样就极大的减少了代码量,提高了代码效率。
2.继承实现
- 继承的特性
- 子类拥有父类非 private 的属性、方法。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
- Java 的继承是单继承,但可以多重继承。单继承就是一个子类只能继承一个父类,多重继承就是,例如 A 类继承 B 类,B 类继承 C 类,所以按照关系就是 C 类是 B 类的父类,B 类是 A 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
-
继承关键字
继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。
extends关键字
在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
public class Animal {
private String name;
private int id;
public Animal(String myName, String myid) {
//初始化属性值
}
public void eat() { //吃东西方法的具体实现 }
public void sleep() { //睡觉方法的具体实现 }
}
public class Penguin extends Animal{
}
在这个例子中,子类Penguin类没有任何的方法,仅仅简单继承了父类Animal类的方法,但是我们在实例化对象,调用父类方法时都没有问题。
implements关键字
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口。
public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
super 与 this 关键字
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
class Animal {
void eat() {
System.out.println("animal : eat");
}
}
class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 调用自己的方法
super.eat(); // super 调用父类方法
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Dog d = new Dog();
d.eatTest();
}
}
3.子类对象实例化例程
从正常的逻辑来讲,没有父类就没有子类,所以我们对于子类对象实例化前一定要实例化好父类对象。
现在我们来举一个例子,首先我们编写下列代码:
class Person{
public Person() {
System.out.println("【Person父类】一个新的Person父类对象实例化成功了!");
}
}
class Student extends Person{
public Student() {
System.out.println("【Student子类】一个新的Student子类对象实例化成功了!");
}
}
public class first {
public static void main( String args[] ){
new Student();
}
}
在上述代码中,我们创建了两个类:Person父类、Student子类,这两个类中只有一个构造方法,用于打印一条语句,现在我们仅仅简单的实例化一下子类对象,编译后运行结果如下:
【Person父类】一个新的Person父类对象实例化成功了!
【Student子类】一个新的Student子类对象实例化成功了!
可以看到,实例化子类对象前同时实例化了父类对象,说明我们即使没有调用父类实例化对象,系统也会自动的调用父类的构造方法,这就相当于在子类的构造方法中隐含了一个“super()”方法。
现在我们在子类的构造方法中添加一条super()语句:
class Student extends Person{
public Student() {
super();//写与不写此语句效果一样
System.out.println("【Student子类】一个新的Student子类对象实例化成功了!");
}
}
再次运行,结果如下:
【Person父类】一个新的Person父类对象实例化成功了!
【Student子类】一个新的Student子类对象实例化成功了!
super()表示的就是子类调用父类构造方法的语句,该语句子只允许放在子类构造方法的首行。在默认情况下的实例化处理时,子类只会调用父类中的无参构造方法,所以写与不写“super()”区别不大。但是如果父类中没有提供无参构造,这个时候就必须利用“super()”来调用有参构造。
我们现在在Person类中定义一个有参构造,并将Student类中方法注释掉:
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("【Person父类】一个新的Person父类对象实例化成功了!");
}
}
class Student extends Person{
// public Student() {
// super();
// System.out.println("【Student子类】一个新的Student子类对象实例化成功了!");
// }
}
public class first {
public static void main( String args[] ){
new Student();
}
}
上述代码运行结果如下:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Implicit super constructor Person() is undefined for default constructor. Must define an explicit constructor
at My_Demo/com.zijun.wang1.Student.<init>(first.java:27)
at My_Demo/com.zijun.wang1.first.main(first.java:37)
可以看到明显的报错,现在我们将Student类中的构造方法加进去,并使用super(),再添加一个属性school,完成了继承的使命——扩展功能,代码如下:
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("【Person父类】一个新的Person父类对象实例化成功了!");
}
}
class Student extends Person{
private String school;
public Student(String name, int age, String school) {
super(name, age);//明确调用父类构造
this.school = school;
System.out.println("【Student子类】一个新的Student子类对象实例化成功了!");
}
}
public class first {
public static void main( String args[] ){
new Student("紫郡",19,"哈佛大学");
}
}
上述代码编译运行结果如下:
【Person父类】一个新的Person父类对象实例化成功了!
【Student子类】一个新的Student子类对象实例化成功了!
可以看到成功实例化了父类对象与子类对象,所以在我们写类的时候,无参构造是非常重要的。
4.继承的相关限制
需要注意的是 Java 不支持多继承,但支持多重继承。
继承类型 | 描述 | 代码格式 |
单继承 | B类继承A类 | public class A{...} public class B extends A{...} |
多重继承 | C类继承B类,B类继承A类 | public class A{...} public class B extends A{...} public class C extends B{...} |
不同类继承同一个类 | C类继承A类,B类继承A类 | public class A{...} public class B extends A{...} public class C extends A{...} |
多继承(不支持) | C类继承A类,C类继承B类 | public class A{...} public class B{...} public class C extends A,B{...} |
继承的主要目的是扩展已有类的功能,但多重继承的目的是希望可以同时继承多个类中的方法,而面对多继承的要求应该将范围限定在同一类中。如果说使用了多重继承,在上述表格中,C类也可以继承B类的父类的方法,但应该有个限度,我们在编写代码的时候,理论上层次不应该超过三层。
在进行继承关系定义的时候,实际上子类可以继承父类中的所有操作结构。但是对于私有操作属于隐式继承,而所有非私有操作属于显式继承。继承一旦发生了,父类中所有的操作在子类中就都能操作了。
5.实际案例分析
下面我们来写一个实际案例,建立一个Person类和一个Student类,功能要求如下:
- Person类中包含4个私有型数据成员变量name,addr,sex,age,分别对应Person对象的姓名,地址,性别,年龄。一个4参构造方法,一个2参构造方法,一个无参构造方法。
- Student类继承Person类,并增加成员变量math,english,用来存放数学和英语的成绩。一个6参构造方法,一个2参构造方法,一个无参构造方法和重写输出方法显示6中属性。
正常来讲我们先不考虑之类的问题,我们先来实现Person类:
class Person{
private String name;
private int age;
private char sex;
private String addr;
public Person() {}
public Person(String name, String addr) {
this(name, 18, '男', addr);
}
public Person(String name, int age, char sex, String addr) {
this.name = name;
this.age = age;
this.sex = sex;
this.addr = addr;
}
public String getInfo() {
return " [姓名:" + this.name + ", 年龄:" + this.age + ", 性别:"
+ this.sex + ", 地址:" + this.addr + "]";
}
}
这里我们一共有三个构造方法,无参构造,2参构造,4参构造,其中4参是主要方法,2参构造引用了4参中的this属性。
接下来我们来编写Student类:
class Student extends Person{
private double math;
private double english;
public Student() {}
public Student(String name, String addr) {
super(name, addr);
}
public Student(String name, int age, char sex, String addr, double math, double english) {
super(name, age, sex, addr);
this.math = math;
this.english = english;
}
public String getInfo() {
return super.getInfo() + "\t|- 数学成绩:" + this.math + ", 英语成绩:" + this.english;
}
}
由于是继承Person类,所以编写上简单了很多。其中一共有三个构造方法,无参构造,2参构造,6参构造,其中6参是主要方法,使用了super()来继承父类的属性。
接下来我们实例化几个对象来看一下:
public class first {
public static void main( String args[] ){
Student stuA = new Student("紫薇一号", 23, '男', "上海", 98, 76);
Student stuB = new Student("紫薇二号", "北京");
System.out.println(stuA.getInfo());
System.out.println(stuB.getInfo());
}
}
我们随便实例化了两个对象,其中一个是6参,一个是2参,这里参数要与我们程序中设置的参数相同,运行结果如下:
[姓名:紫薇一号, 年龄:23, 性别:男, 地址:上海]
|- 数学成绩:98.0, 英语成绩:76.0
[姓名:紫薇二号, 年龄:18, 性别:男, 地址:北京]
|- 数学成绩:0.0, 英语成绩:0.0
继承问题我刚学Java的时候写过一次,只是当时还太年轻,简单几句写给我自己看;上次在JavaOO系列中也系统的写过,不过因为要留篇幅给其他知识点,一些重要的知识点比如super()没有详细说明,今天终于算稍微详细的说了一下,当然随着知识的长进我还会再写几次,每写一次都是让我对继承有更好的认识。今天就到这里,我们下次见👋