5.1 类、超类和子类

  

5.1 类、超类和子类

  

  子类比超类拥有的功能更加丰富。

  在本例中,Manager类比超类Employee封装了更多的数据,拥有更多的功能。

  在Manager类中,增加了一个用于存储奖金信息的域,以及一个用于设置这个域的方法:

class Manager extends Employee
{
    private double bonus;
    ...
    public void setBouns(double b)
    {
        bonus = b;
    }
}

  如果有一个Manager对象,就可以使用setBonus方法。 

Manager boss = ... ;
boss.setBonus(5000);

  由于setBonus方法不是在Employee类中的定义,所以属于Employee类的对象不能使用它。

  从超类中还继承了name、salary和hireDay这3个域,所以现在就有了4个域(由于增加了bonus域)。

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

  超类中的有些方法在子类Manager中不一定适用,比如说Manager中的getSalary方法应该返回薪水和奖金的总和,为此需要提供一个新的方法来覆盖(override)超类中的这个方法。

class Manager extends Employee
{
    ...
    //这个方法是错误的
    public double getSalary()
    {
        return salary + bonus;
    }
    ...
}

  由于Manager类的getSalary方法不能直接地访问超类的私有域。尽管每个Manager对象都拥有一个名为salary的域,但在Manager类的getSalary方法中并不能直接访问salary域。只有Employee类的方法才能够访问私有部分。如果Manager类的方法一定要访问私有域,就必须借助公共的接口,Employee类中的公有方法getSalary就是这个公共接口。

public double getSalary()
    {
        //通过子类的getSalary方法不能获得超类的salary域
        //所以以下这种方式也是错误的
        double baseSalary = getSalary();
        return baseSalary + bonus;
    }

  为了获取salary域,就必须要调用超类Employee中的getSalary方法,而不是当前类的这个方法。

  为了调用超类Employee中的getSalary方法,而不是当前类的getSalary,必须使用super关键字来解决这个问题:

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

  在子类中可以增加域、增加方法或覆盖超类的方法,然而绝对不能删除继承的任何域和方法。 

public Manager(String n, double s, int year, int month, int day)
    {
        super(n, s, year, month, day);
        bonus = 0;
    }

  这里的super语句super(n, s, year, month, day)是“调用超类Employee中含的n、s、year、month和day参数的构造器”的简写方式。

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

  综上,super的作用:

  1、调用超类的方法;

  2、调用超类的构造器。(调用构造器的语句只能作为另一个构造器的第一个语句出现)

  

  重新定义Manager对象的getSalary方法之后,奖金就会自动添加到经理的薪水中。

Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
boss.setBonus(50000);

  接下来定义一个包含3个雇员的数组,将经理和雇员的信息都放在数组中:

Employee[] staff = new Employee[3];
staff[0] = boss;
staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);

for(Employee e:staff)
{
    System.out,println(e.getName() + " " + e.getSalary());
}

  这里staff[1]和staff[2]仅输出基本薪水,这是因为它们对应的是Employee对象,而staff[0]对应的是Manager对象,它的getSalary方法将奖金和基本薪水加在了一起。

  在这里将e声明为Employee类型,但实际上它既可以引用Employee类型的对象,也可以引用Manager类型的对象。当e引用Employee对象的时候,e.getSalary()调用的是Manager类中getSalary方法,当e引用Manager对象的时候,e.getSalary()调用的是Manger类中的getSalary方法。

  虚拟机知道e实际引用的对象类型,因此能够正确地调用相应的方法。

  一个对象变量可以指示多种实际类型的现象被称为多态(polmorphism)。在运行时能够自动选择调用哪个方法的现象称为动态绑定(dynamic binding)。

Employee.java

package inheritance;

import java.util.Date;
import java.util.GregorianCalendar;

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 calender = new GregorianCalendar(year, month - 1, day);
		hireDay = calender.getTime();
	}

	public String getName()
	{
		return name;
	}

	public double getSalary()
	{
		return salary;
	}

	public Date getHireDay()
	{
		return hireDay;
	}

	public void raiseSalary(double byPercent)
	{
		double raise = salary * byPercent/10;
		salary += raise;
	}
}

Manager.java

package inheritance;

public class Manager extends Employee
{
	private double bonus;
	//其余的参数会从Employee继承来
	public Manager(String n, double s, int year, int month, int day)
	{
		super(n, s, year, month, day);
		bonus = 0;
	}

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

	public void setBonus(double b)
	{
		bonus = b;
	}
}

ManagerTest.java 

package inheritance;

public class MangerTest {
	public static void main(String args[])
	{
		Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
		boss.setBonus(5000);

		Employee[] staff = new Employee[3];

		staff[0] = boss;
		staff[1] = new Employee("Harry Cracker", 50000, 1989, 10, 1);
		staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);

		for(Employee e:staff)
		{
			System.out.println("name = " + e.getName() + ",salary = " + e.getSalary());
		}
	}

}

5.1.1 继承层次

  Java是不允许“实现多继承”,简称不允许“多继承”。但是Java支持“声明多继承”——Java的接口的多继承——一个类可以实现多个接口(“继承”了多个接口上的方法声明),而一个接口可以继承多个接口(同样是“继承”了多个接口上的方法声明)。

5.1.2 多态

  可以将一个子类的对象赋值给超类变量。

  对象变量是多态的,一个Employee变量既可以引用一个Employee对象,也可以引用一个Employee类的任何一个子类的对象(例如Manager类)。

Manager boss = new Manager(...);
Employee[] staff = new Employee[3];
staff[0] = boss;

  在这个例子中,staff[0]和boss引用的是同一个对象,但编译器将staff[0]看成Employee对象。所以以下调用方式是正确的:

  boss.setBonus(5000);

  但下面这种调用方式是错误的:

  staff[0].setBonus(5000);

  这是因为staff[0]声明的类型是Employee,而setBonus方法是Manager类的方法,不是Employee类的方法。

  可以将一个子类变量赋给一个超类变量,但不能将一个超类变量赋给一个子类变量。

5.1.3 动态绑定

  调用方法的过程可以分解为:

  (1)编译器查看对象的声明类型和方法名。假设调用x.f(param),且隐式参数x声明为C类的对象。有可能存在多个名字为f,但参数不一样的方法。例如可能存在方法f(int)和f(Stirng),编译器会一一列举所有C类中名为f的方法和其超类中访问属性为private且名为f的方法(超类的私有方法不可访问)。至此,编译器已获得所有可能被调用的候选方法。

  (2)编译器查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,就选择这个方法这个过程被称为重载解析(overloading resolution)。例如,对于调用x.f("Hello")来说,编译器将会挑选f(String),而不是f(int)。

  方法签名:方法的名字和参数列表称为方法的签名。

  (3)如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们讲这种方法称为静态绑定(static binding)。与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定

  (4)程序运行而且采用动态绑定调用方法的时候,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。假设x的实际类型是D,它是C类的子类,如果D类定义了一个方法f(String),就直接调用它,否则就在D类的超类中寻找f(String)方法,以此类推。

  每次调用方法的时候都要进行搜索,时间开销很大,所以虚拟机预先为每个类创建了一个方法表(method table),其中列出了所有方法的签名和实际调用的方法,当需要调用方法的时候,虚拟机查找这个表就可以了。

  对于之前的e.getSalary(),e声明为Employee类型,由于这个方法没有参数,所以不需要担心方法重载的问题。

方法重载(method overload)的具体规范

如果有两个方法的方法名相同,但参数不一致,那么可以说一个方法是另一个方法的重载。

一.方法名一定要相同。
二.方法的参数表必须不同,包括参数的类型或个数,以此区分不同的方法体。
1.如果参数个数不同,就不管它的参数类型了!
2.如果参数个数相同,那么参数的类型或者参数的顺序必须不同。
三.方法的返回类型、修饰符可以相同,也可不同。
四.main方法也可以被重载
方法重载的作用:可以一个相同的方法传入不同的参数以达到想要的结果

  所以,e.getSalary()执行的过程如下:

  (1)虚拟机提取e的实际类型的方法表,即可能是Employee、Manager的方法表,也可能是Employee类的其他子类的方法表;

  (2)虚拟机搜索定义getSalary签名的类,此时,虚拟机已经知道应该调用哪个方法;

  (3)虚拟机调用方法。

  动态绑定有一个非常重要的特征:无需对现存的代码进行修改,就可以对程序进行扩展。

  假设现在增加了一个新类Excutive,并且e有可能引用这个类的对象,我们不需要对包含调用e.getSalary()的代码进行重新编译。如果e恰好引用一个Excutive类的对象,就会自动调用Excutive.getSalary()方法。

5.1.4 阻止继承 final类和方法

  有时候,可能希望组织人们利用某个类定义子类,不允许扩张的类称为final类,如果在定义类的时候使用了final修饰符就表示这个类是final类。

  将方法或类声明为final的主要目的是:确保它们不会在子类中改变语义。例如,Calender类中的getTime和setTime方法都声明为final。这表明Calender类的设计者负责实现Date类与日历状态之间的转换,而不允许子类处理这些问题。

  

时间: 2024-12-26 17:18:15

5.1 类、超类和子类的相关文章

Core Java (十一) Java 继承,类,超类和子类

Core Java (十一) Java 继承,类,超类和子类 标签: javaJavaJAVA 2013-01-22 17:08 1274人阅读 评论(0) 收藏 举报  分类: java(58) 读书笔记(46)  版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 继承关系 两个类之间存在三种关系: 依赖,uses-a,如果一个类的方法操纵另一个对象,我们就说一个类依赖于另一个类. 聚合(关联),has-a,一个对象包含另外一个对象,聚合关系意味着类A的对象包含类B的对象

继承——类、超类、子类

http://user.qzone.qq.com/1282179846/blog/1470248763 引入一个简单的例子: //Employee类 import java.util.*; public class Employee { private String name; private double salary; private Date hireday; public Employee(String n,double s,int year,int month,int day) { n

Java超类引用子类对象的规律

首先,对于JVM实现引用的一个机制是:类实例的引用就是指向一个句柄(handle)的指针,这个句柄就是一堆指针:一个指针指向一块从java堆中为分配出来内存空间:另一个指针指向一张表(实际上这个表格也有两个指针(一个指针指向一个包含了对象的方法表,另外一个指向类对象,表明该对象所属的类型)) 下面举例进行分析:  Student st=new Student();//Student为子类 Person p=st;//Person 为超类 p.sayHello();//该方法在超类和子类中都存在

JS 超类和子类

此篇由别的大神的博客与<javascript高级程序设计>一书整理而来 原博客地址:https://hyj1254.iteye.com/blog/628555 看到javascript高级程序设计的面向对象章节看到超类与子类这个概念词,不懂上度娘查了才知道是怎么一回事. 说到超类与子类,就不得不提起原型模式,原型对象,原型链与原型链继承了 在原型模式章节中,对于原型模式这样描述:“我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含

C++ 基类指针,子类指针,多态

基类指针和子类指针之间相互赋值(1)将子类指针赋值给基类指针时,不需要进行强制类型转换,C++编译器将自动进行类型转换.因为子类对象也是一个基类对象. (2)将基类指针赋值给子类指针时,需要进行强制类型转换,C++编译器将不自动进行类型转换.因为基类对象不是一个子类对象.子类对象的自增部分是基类不具有的.(强制转换告诉编译器为对象增加子类所特有的部分) fish* fh1;  animal* an1 = new animal; fh1 = (fish*)an1; 原理: 当我们构造fish类的对

JAVA-获取实现了指定接口类的所有实现类或继承了指定类的所有子类

实际编程过程中,我们可能遇到这样的问题,就是获取实现了指定接口类的所有实现类. 本工具类就提供了这样的功能.下面是工具类的详细解析: /** * 查找指定路径下面实现指定接口的全部类 * @author longyin * @author 博客地址:http://blog.csdn.net/u010156024 * 如果大家有什么问题或疑问,欢迎留言或评论,谢谢!! */ public class ClassUtil { @SuppressWarnings({ "rawtypes",

使用iskindofclas来发现对象是否是某类或其子类的实例

发现对象是否是特定类或其子类的实例 要发现对象是否是某类或其子类的实例,请在对象上调用 isKindOfClass: 方法.当应用程序需要发现其响应的消息(实现的或继承的),它有时进行以上的检查. static int sum = 0; for (id item in myArray) { if ([item isKindOfClass:[NSNumber class]]) { int i = (int)[item intValue]; sum += i; } } isKindOfClass: 

小酌重构系列[11]&mdash;&mdash;提取基类、提取子类、合并子类

概述 继承是面向对象中的一个概念,在小酌重构系列[7]--使用委派代替继承这篇文章中,我"父子关系"描述了继承,这是一种比较片面的说法.后来我又在UML类图的6大关系,描述了继承是一种"is a kind of"关系,它更偏向于概念层次,这种解释更契合继承的本质.本篇要讲的3个重构策略提取基类.提取子类.合并子类都是和继承相关的,如果大家对继承的理解已经足够深刻了,这3个策略用起来应该会得心应手. 提取基类 定义:如果有超过一个类有相似的功能,应该提取出一个基类,并

OC3-父类指针指向子类对象

// // Cat.h // OC3-父类指针指向子类对象 // // Created by qianfeng on 15/6/17. // Copyright (c) 2015年 qianfeng. All rights reserved. // #import "Animal.h" @interface Cat : Animal { float _height; } @property (assign,nonatomic)float height; @end // // Cat.m