带你看懂什么是继承

带你看懂什么是继承

什么是继承

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的属性和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
举个例子:

class Animal {
    private int age;
    private String name;

    public void speak() {
        System.out.println("Animal在说话");
    }
}


class Dog extends Animal {
    
    private String color;

    public void eat() {
        System.out.println("Dog在吃饭");
    }

}

这是一个简单的关于继承的例子

  1. Dog类中的也有speak()方法,这是继承自Animal类
  2. Dog类中也有age,name属性,同样继承自Animal类
  3. Dog类中的color属性和eat()方法是新增加的

在通过扩展超类定义子类的时候,仅需要指出子类与超类的不同之处。因此在设计类的时候,应该将通用的方法放到超类中,而将具有特色用途的方法放在子类中,这种将通用的功能放到超类的做法,在面向对象程序设计中十分普遍。

继承的语法

关键字:extends

class Manager extends Employee {}

其中 Manager是子类,Employee是父类。

这是一个稍微复杂的例子:

public class Employee {
    private String name;
    private double salary;
    private Date hireDay;
 
    public Employee(String n, double s, int year, int month, int day)
    {
        name = n;
        salary = s;
        GregorianCalendar calendar = new GregorianCalendar(year, month, day);
        hireDay = calendar.getTime();
    }
 
    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }
    
    //more method
    ......
}
public class Manager extends Employee{
    ...
    private double bonus;
 
    public void setBonus(double bonus,String n, double s, int year, int month, int day) {
        super(String n, double s, int year, int month, int day);
        this.bonus = bonus;
    }

    @Override
    public double getSalary() {
        return super.getSalary() + bonus;
    }
}

}

Manager类中的getSalary方法显然和Employee类不同,因为他还包含了bonus属性,因此可以重写(Override)此方法,让它满足Manager类的要求,此时就会覆盖父类的同名方法。

@Override
public double getSalary() {
    return salary + bonus;//don't work
}

虽然Manager类继承Employee类的salary字段,但是却无法在Manager类中直接访问其父类的私有字段。

因此应该使用关键字super,调用父类的getSalary()方法

@Override
public double getSalary() {
    return super.getSalary() + bonus;
}

super 关键字有两个用途:

一、 调用超类的方法,
二、 是调用超类的构造器。

super 不是一个对象的引用,不能将 super 赋给另一个对象变量,它只是一个指示编译器调用超类方法的特有关键字。

由于 Manager 类的构造器不能访问 Employee 类的私有域,所以必须利用 Employee 类的构造器对这部分私有域进行初始化,我们可以通过 super 实现对超类构造器的调用。使用 super 调用构造器的语句必须是子类构造器的第一条语句。

public void setBonus(double bonus,String n, double s, int year, int month, int day) {
    
    super(String n, double s, int year, int month, int day);
    
    this.bonus = bonus;
}

继承的初始化过程

先举个例子:

class Animal {

    private static String A = Animal.fun1("Animal's static A is init");

    public Animal() {
        super();
        System.out.println("--Animal--");
    }

    public static String fun1(String s) {
        System.out.println(s);
    }
}

class Dog extends Animal{
    
    private static String D = Animal.fun1("Dog's static D is init");
    
    public Dog() {    
        super(); 
        System.out.println("--Dog--");
    }
}

class Hasky extends Dog{
    
    private static String H = Animal.fun1("Hasky's static H is init");
   
    public Hasky() {   
        super();    
        System.out.println("--Hasky--");
    }
}

public class Test {
    public static void main(String[] args) {
        Hasky h = new Hasky();
    }
}

运行结果:

Animal's static A is init
Dog's static D is init
Hasky's static H is init
--Animal--
--Dog--
--Hasky--

当创建子类对象时,会生成一个构造函数链Hasky–>Dog–>Animal–>Object,最后再执行Hasky的构造函数。这种向上链接的方式是有意义的。因为父类的字段在大多数情况下都是private的,子类没有权限对这些private字段初始化,因此只能让父类自己完成初始化,以确保父类的状态是完整的。

从运行结果可以看出:

  1. 根基类的 static 会首先执行,然后是下一个导出类,以此类推。
    原因:
 public static void main(String[] args) {
        Hasky h = new Hasky();
 }

首先main()方法执行,加载Test类,然后创建Hasky对象,此时加载Hasky类。

class Hasky extends Dog{}

但是此时jvm又看到extends关键字,Hasky继承自Dog,所以开始加载Dog类。

class Dog extends Animal{}

同理开始加载Animal类。
而在类加载后,静态字段,方法都会被加载,因此private static String A,private static String D,private static String H被依次加载。
2. 然后完成构造函数链。

注意
如果将Animal,Dog,Hasky的实例字段不声明为static

class Animal {
    private  String A = Animal.fun1("Animal's  A is init");
}

class Dog {
    private  String D = Animal.fun1("Dog's  D is init");
}

class Hasky {
    private  String H = Animal.fun1("Hasky's  H is init");    
}

运行结果:

Animal's  A is init
--Animal--
Dog's  D is init
--Dog--
Hasky's  H is init
--Hasky--

类初始化的过程

  1. 父类静态初始化
  2. 子类静态初始化
  3. 父类对象初始化
  4. 子类对象初始化

继承的特性

  1. 向上转型(“is a”)
 Aniaml animal = new Dog();//Dog is a Animal
 Dog d = new Aniaml();//don't work,Animal is not a Dog
  1. 子类可以比父类有更多的字段和方法,但最少要包括父类的全部字段和方法。

  2. 继承只有单继承,要实现多继承就只能借助接口(实现)

继承的优缺点

在面向对象语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点:

  1. 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
  2. 提高代码的重用性;
  3. 子类可以形似父类,但又异于父类;
  4. 提高代码的可扩展性,实现父类的方法就可以“为所欲为”了。
  5. 提高产品或项目的开放性。

自然界的所有事物都是优点和缺点并存的,继承的缺点如下:

  1. 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
  2. 降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
  3. 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大段的代码需要重构。

继承的深入理解

在创建子类对象时,实际上在其内部维护了一个父类对象。但是并没有new一个父类对象,而只是调用了父类的构造方法,确保了父类的状态。

继承的设计技巧

  1. 将公共操作和字段放在父类中

是将name字段和hiredate字段放在Employee中,而没有将它重复放在Manager中

  1. 不要使用受保护的字段

第一,子类集合无限制,任何一个人都可以从一个类继承,然后编写代码直接访问protect字段,从而破坏了封装性。
第二,Java中同一个包中的所有类都可以直接访问protect字段,而不管它们是否为这个类的子类。

  1. 使用继承实现“is a”关系

要保证子类 “is a” 父类的关系,这样此时比较好的设计。

  1. 除非所有继承的方法都有意义,否则不要使用继承

  2. 在覆盖方法时,不要改变预期的行为

你可能感兴趣的:(JAVA,java)