Java基础知识点4:继承

  继承是面向对象编程技术中非常重要的一个基本概念。它背后的基本思想就是:通过已有的类来创建一个新的类,这个新的类可以重用(或继承)已有的类方法;新的类也可以加入新的方法和属性。

  在这里我们通过一个实例来讲解继承的基本知识。假设我们在为一个公司设计一个管理系统,管理公司的人员,我们首先定义了一个雇员类Employee,它的定义如下:

1 public Employee {
 2     private String name;
 3     private double salary;
 4 
 5     public Employee(String name, double salary) {
 6         this.name = name;
 7         this.salary = salary;
 8     }
 9 
10     public String getName() {
11         return name;
12     }
13 
14     public void setName(String name) {
15         this.name = name;
16     }
17 
18     public double getSalary() {
19         return salary;
20     }
21 
22     public void setSalary(double salary) {
23         this.salary = salary;
24     }
25   
26 }

 

基本语法

基本定义

  定义一个继承自已有类的子类所涉及到的关键字是extends,基本语法如下:

  子类名  extends  超类名 {

    子类定义的新的属性和方法:

  }

    也就是说,定义一个继承自超类的子类,只需要声明二者不同的地方就可以了。

  比如现在我们想为公司的经理们设计一个类Manager,由于经理也是雇员,所以雇员的基本属性经理也都有。但是经理可能有一些其他雇员没有的属性和方法,比如经理会有分红bonus。所以采用继承的方式来定义Manager类是最合适不过的。

 1 public Manager extends Employee {
 2     
 3     private double bonus;
 4 
 5     public Manager(String name, double salary, double bonus) {
 6         super(name, salary);
 7     this.bonus = bonus;
 8     }
 9 
10     public double getBonus() {
11         return bonus;
12     }   
13 
14     public void setBonus(double bonus) {
15         this.bonus = bonus;    
16     }
17 18 }

 

  这样我们就成功的建立了一个继承自Employee的新类Manager,这个Manager不仅仅具有Employee类的属性和方法,而且还包含自己独有的属性和方法。

  关于子类这里有几点需要注意

  1. 子类能够直接调用超类的public,protected方法。之所以使用protected,这样可以保证这个方法是可以被该超类的子类所应用的。但是不能被其他的类所使用。

  2. 子类不能够访问超类中的private属性,方法。

  3. 子类只能够通过public的接口去访问超类中的private属性。

覆盖超类方法

  在实际情况中还会出现这样的需求,有些方法在超类中定义了,而且子类也需要使用这个方法。但是,子类对于这个方法的定义与超类的定义不一样。以上面的Manager类为例,Manager需要使用getSalary方法获取它的工资,在超类中的定义就是返回私有域salary的值,但是在Manager中,我们需要返回salary和bonus域的和。这里应该怎么去实现?

  这里就需要Manager类去覆写(override)这个getSalary方法:

public double getSalary() {
    return salary + bonus;  
}

public double getSalary() {
    double baseSalary = getSalary();
    return baseSalary + bonus;   
}

public double getSalary() {
    double baseSalary = super.getSalary();
    return baseSalary + bonus;  
}

  这里给出了三个版本,其中第一,二个都是错误的,只有第三个是对的。第一个错在不能直接访问超类中的private属性salary,必须调用超类的getSalary方法。第二个版本错在它调用的是子类的getSalary方法,不是超类的,所以这个方法会无限循环执行。只有第三个是正确的。

  所以这里要记住:

  在子类中可以定义一个声明完全和超类一样的方法,这个叫做方法的覆写,当子类再次调用这个方法时,那执行的就是子类中定义的版本。

  另外,在子类中如果想要调用超类的方法,一定要在方法名字前加上super。

构造方法 

  子类的构造方法的写法也是需要注意的。由于子类是无法直接访问超类中的private的数据域的,所以对于这些域,如果你想通过构造方法来为它们设定初值时,你就需要首先调用超类中的带参数构造方法,然后再为子类中特殊的域设置初值。

  比如我们写下Manager类的构造方法:

1   public Manager(String name, double salary, double bonus) {
2         super(name, salary);
3         this.bonus = bonus;
4   }

  其中第一句super就是调用超类中的带参数构造方法。这里要注意,这句super语句如果想这么使用,一定要把它放在第一句的位置。

  这里还有一点要注意:

  如果子类中没有显示的调用超类的构造方法,那么其实子类就会触发超类的无参数构造方法。如果超类中并不含有一个无参构造方法的话,那么子类还不显示的调用超类其他构造方法,那么java编译器就会报错。

  当前例子中Employee没有无参构造器,因为它已经有含参的了,所以如果在Manager中没有调用super方法,那么就会报错。所以这里的解决方案就是在Employee中再定义一个无参构造器。

多态性 

  子类的对象可以被超类变量所引用,比如我们声明一个Employee引用,它引用的是一个子类对象:

Employee boss = new Manager("Steve", 10000, 5000);
System.out.println(boss.getSalary());

  但是这里要注意:采用超类变量引用是无法调用子类中的独有方法的,比如上面的例子中boss变量不能调用setBonus方法。

  然后我们再分析上例中第二句:

  如果超类变量调用了被子类所覆写的超类方法,则此时要执行的是子类中定义的版本!

  所以此时打印出来的是15000。说明即便Manager对象boss被Employee类变量所引用,但是JDK可以识别这个对象到底属于哪一类。所以才会输出正确的结果。

  之所以能实现这一点就是归功于jdk的动态绑定技术。 

  动态绑定技术是在java程序,对象的某个方法被调用时发生的技术。当某个对象引用调用这个对象的某个方法时,将会发生以下几步:

  1. 根据声明的方法名,遍历所有可能被调用的函数。

  2. 再根据输入参数的类型,确定最可能匹配的函数。如果没有或者是有多个,java都会报错。

  3. 如果方法是private,static,final或构造器时,那么被调用的方法直接执行,这个叫做静态绑定。否则的话,java会按照对象到底是属于什么类型来调用正确的方法。

 

  那么如果想让Employee类型的变量boss,调用setBonus方法怎么办?

  方法就是强制类型转换,把Employee类型的变量boss强制转换为Manager类型,如下

  Manager newBoss = (Manager) boss;
  newBoss.setBonus(4000);

  强制类型转换之前一般会做一次检查,检查一下要被转换的变量是否真的是要强制转换的类型,如果不是的话,会产生异常。检查的方法就是用instanceof方法:

if(boss instanceof Manager)
    Manager newBoss = (Manager) boss;

final修饰符

  如果不希望其他程序继承自自己的类,那么可以把这个类用final修饰符修饰,就可以防止其他类去继承它。

  如果不希望子类覆写超类的方法,那么在超类中可以把这个方法用final修饰符修饰一下。

抽象类

  有的时候可能出现这种情况,好几个类都继承自同一个类,他们还有一个共同的方法,但是每一个类实现这个方法的方式都不一样。当这种问题发生时,你把这个方法的声明写在超类中还是子类中都是有不好的地方。所以这个时候就需要抽象类,抽象方法出场了。

   针对这种每个子类都有,但是却都实现方式不同的公有方法,我们可以把它在超类中声明成抽象方法即可。基本语法就是在这个方法的返回类型前面加上abstract修饰符。

   public abstract 返回类型 方法名(输入参数表)

   如果一个类中包含至少一个抽象方法的话,这个类也必须被声明为抽象类

   public abstract 类名 { ... }

   当然抽象类中不必所有方法都是抽象的,也可以有已经实现的。

   抽象类不能被实例化,一个类即便没有抽象方法也可以被声明为抽象的。当然抽象类的变量还是可以指向其非抽象的子类的。

 

posted @ 2016-02-27 14:09  fatsheep9146  阅读(1069)  评论(0编辑  收藏  举报