java核心技术8--笔记(3)_5章

第五章

1.类、超类和子类

java的继承格式:

class Manager extends Employee
{  
   added methods and fields
}

C++注释:java继承用extends代替了C++中的冒号(:)。在java中,所有的集成都是公有继承,没有C++中的私有继承和保护继承。

关于继承的类的不同叫法:

超类(superclass) / 基类(base class) / 父类(parent class)分别对应:

子类(subclass) / 派生类(derived calss) / 孩子类(child class)

java中在子类中调用超类方法的方法--使用super关键字,如:

class Manager extends Employee
{
   public Manager(String n, double s, int year, int month, int day)
   {
      super(n, s, year, month, day);
      bonus = 0;
   }
   public double getSalary() //覆盖超类中的getSalary()方法
   {
      double baseSalary = super.getSalary();
      return baseSalary + bonus;
   }
   
   private double bonus;
}

C++注释:java中的super调用超类相当于C++中的超类名加上::操作符,即Employee::getSalary()。

由于Manager类的方法(包括构造器)不能访问Employee类的私有域,所以必须使用Employee类的构造器对这部分私有域进行初始化。使用super调用构造器的语句必须是子类构造器的第一条语句。如果子类的构造器没有显示调用超类的构造器,则将自动调用超类默认(没有参数)的构造器。如果此时超类没有不带参数的构造器,则编译器将报错。

注释:前面讲到,关键字this有2个用途:一是引用隐式参数,而是调用该类其他的构造器。同样,super关键字也有2个用途:一是调用超类的方法,而是调用超类的构造器。在调用构造器的时候,两个关键字使用方式很相似。调用构造器的语句只能作为另一个构造器的第一条语句出现。

C++注释:在C++的构造函数中,使用初始化列表语法调用超类的构造函数,而不调用super。如在C++中应该这样:

Manager::Manager(String n, double s, int year, int month, int day) // C++
: Employee(n, s, year, month, day)
{  
   bonus = 0;
}

下面给出例子,功能为创建一个新经理和2个雇员,设置薪水和奖金,打印出每个人的薪水:

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 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());

结果如下:

Carl Cracker 85000.0
Harry Hacker 50000.0
Tommy Tester 40000.0

在这里,e.getSalary()能够确定应该调用哪一个getSalary方法,当e引用Employee对象时,调用Employee的getSalary,当e引用Manager对象时,调用Manager的getSalary方法。虚拟机知道e实际引用的对象类型并正确调用相应的方法。一个对象可以引用多种实际类型的现象叫多态。在运行时能自动选择调用哪个方法的现象叫动态绑定。

C++注释:在java中,不需要将方法声明为virtual,动态绑定是默认的处理方法。如果不希望方法具有虚拟特性,可标记为final。

(1)继承层次

C++注释:java不支持多继承。关于java中多继承功能的实现方式,参看下一张关于接口的讨论。

(2)多态

"is-a"规则是一个用来判断是否应该设计为继承关系的简单规则,它表明子类的每个对象也是超类的对象。"is-a"规则的另一种表示法是置换规则。它表明程序中出现超类对象的任何地方都可以用子类对象置换。如可以将一个子类的对象赋给超类变量:

Employee e;
e = new Employee(. . .);  // Employee object expected
e = new Manager(. . .); // OK, Manager can be used as well

在java中,对象变量是多态的。一个Employee变量既可以引用一个Employee对象,也可以引用一个Employee类的任何一个子类的对象。

然而,不能将一个超类的引用赋给子类变量。如下面的赋值时非法的:

Manager m = staff[i]; // ERROR

原因很清楚:不是所有雇员都是经理。如果赋值成功,则在后面调用Manager独有的方法m.setBonus(...)时就可能发生运行时错误。

警告:在java中,子类数组的引用可以转换成超类数组的引用,而不需要采用强制类型转换。如下面是合法的:

Manager[] managers = new Manager[10];
Employee[] staff = managers; // OK

然而,这样做有可能会引起一些问题。切记manager和staff引用的是同一个数组。现在看一下这条语句:

staff[0] = new Employee("Harry Hacker", ...); 

编译器竟然接纳了这个操作。但在这里,staff[0]和managers[0]引用的是同一个对象,当调用managers[0].setBonus(...)的时候,会导致调用一个不存在的实例域,进而搅乱相邻存储空间的内容。

为了确保不发生这类错误,所有数组都要牢记创建它们的元素类型,并负责仅将类型兼容的变量存储到数组中。如使用new managers[10]创建的是一个经理数组,如果试图存储一个Employee类型的引用就会引发ArrayStoreException异常。

(3)动态绑定

下面是调用对象方法过程x.f(“Hello”),且隐式参数x声明为C类的详细描述:

1)编译器查看对象的声明类型和方法名。编译器会一一列举所有C类中名为f的方法和其超类中访问属性为public且名为f的方法。至此,编译器已获得所有可能被调用的候选方法。

2)接下来,编译器将查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,就选这个方法,这个过程被称为重载解析;如果没有找到或通过类型转换后有多个方法匹配,就会报告一个错误。

注释:前面提到过,方法的名字和参数列表成为方法的签名,这里就是找出与要调用的方法相同签名的方法。

3)如果是private、static、final方法或者构造器,则编译器可以准确知道该调用哪个方法,这是静态绑定。于此对应的是,调用的方法依赖于隐式参数的实际类型,并在调用时实现动态绑定

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

每次调用方法都要进行搜索,开销很大,因此虚拟机预先为每个类创建了一个方法表,其中列出所有方法的签名和实际调用的方法。实际使用的时候,虚拟机仅查找这个表。需要注意,如果调用super.f(param),编译器将对隐式参数超类的方法表进行搜索。

警告:在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。特别是如果超类方法是public,子类方法一定要声明为public。如果遗漏,编译器将会把它解释为试图降低访问权限。

(4)阻止继承:final类和方法

 在定义类的时候使用final修饰符表示不能扩展;类中的方法声明为final类表示子类不能覆盖该方法(final类中的所有方法自动成为final方法,但不包括域。类中的域也可被声明为final,这样,构造对象之后就不允许改变它的值了)。

(5)强制类型转换

写法和C++一样,如:

Manager boss = (Manager) staff[0]; 

将一个值存入变量时,编译器将检查是否允许该操作。将一个子类的引用赋给一个超类变量,编译器是允许的,但将一个超类的引用赋给一个子类变量,必须经过类型转换才能呢个通过运行时的检查。

如果类型转换时“谎报”有关对象包含的内容,如:

Manager boss = (Manager) staff[1]; // ERROR

运行该程序时,java运行时系统将报告错误,并产生ClaseeCastException异常。如果没有捕获该异常,程序会终止。因此,应该养成这样的习惯:在进行类型转换前,先查看一下是否能转换成功。可以用instanceof运算符实现,如:

if (staff[1] instanceof Manager)
{  
   boss = (Manager) staff[1]; 
   . . .
}

如果这个类型转换不可能成功,编译器不会进行该转换。如,下面的类型转换会产生编译错误,因为Date不是Employee的子类:

Date c = (Date) staff[1];

综上所述:

只能在继承层次内进行类型转换;

在将超类转换成子类之前,应该使用instanceof进行检查。

(6)抽象类

抽象类可作为派生其他类的基类,而不作为实例类。如Employee类和Student类都是人,可将类Person作为基类,如下图:


使用abstract关键字的方法被称为抽象方法,抽象方法不需要实现,类似于C++中的纯虚函数。包含一个或多个抽象方法的类被称为抽象类,且必须被声明为abstract,如:

abstract class Person
{  . . .
   public abstract String getDescription();
}

除了抽象方法外,抽象类还可以包含具体数据和具体方法。建议尽量将通用的域和方法(不管是否抽象)放在超类(不管是否抽象)中。

抽象方法的具体实现在子类中。扩展抽象方法可以有两种选择,一是在子类中定义部分抽象方法或不定义抽象方法,这样必须将子类标记为抽象类;另一种是定义全部的抽象方法,这样,子类就不是抽象的了。

类中即使不含抽象方法,也可以将类声明为抽象类。

抽象类不能被实例化,如下面的表达式是错误的:

new Person("Vince Vu")

但是,可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象,如:

Person p = new Student("Vince Vu", "Economics");

C++注释:在C++中,纯虚函数是抽象方法。只要有一个纯虚函数,这个类就是抽象类,但没有用于表示抽象类的关键字。

(7)受保护访问

java中用于控制可见性的4个访问修饰符:

1)仅对本类可见--private

2)对所有类可见--public

3)对本包和所有子类可见--protected(C++中的protected是对子类和友元函数可见)

4)对本包可见--默认,即没有任何修饰符的情况


2.Object:所有类的超类

如果没有明确指出超类,Object就被认为是这个类的超类。本章介绍一些基本内容。在Object中有几个只在处理线程时才会被调用的方法,有关线程内容参看卷II。

可以用Object类型的变量引用任何类型的对象:

Object obj = new Employee("Harry Hacker", 35000);

当然,要对其中的内容进行具体的操作,还需要清楚对象的原始类型,并进行相应的类型转换:

Employee e = (Employee) obj;

在java中,只有基本类型不是对象,如数值、字符和布尔类型的值 都不是对象。所有数组类型,不管是对象数组还是基本类型的数组都扩展于Object类。

Employee[] staff = new Employee[10];
obj = staff; // OK
obj = new int[10]; // OK

C++注释:在C++中没有根类,不过,每个指针都可以转换成void*。

(1)Equals方法

用于检测一个对象是否等于另一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用。但在大多数类中,这种判断没有什么意义。不过,经常需要检测两个对象状态的相等性,如果对象状态相等,就认为这两个对象是相等的。

例如,如果两个雇员的姓名、薪水、雇佣日期都一样,就认为它们相等(实际上,比较ID更有意义)。如:

class Employee

   . . .
   public boolean equals(Object otherObject)
   {  
      // a quick test to see if the objects are identical
      if (this == otherObject) return true;
 
      // must return false if the explicit parameter is null
      if (otherObject == null) return false;
      // if the classes don't match, they can't be equal
      if (getClass() != otherObject.getClass()) 
         return false;
 
      // now we know otherObject is a non-null Employee
      Employee other = (Employee) otherObject;
      // test whether the fields have identical values
      return name.equals(other.name) 
         && salary == other.salary
         && hireDay.equals(other.hireDay);
   }
}

getClass方法将返回一个对象所属的类。

在子类中定义equals方法时,首先调用超类的equals方法,如果检测失败,对象就不可能相等:

class Manager extends Employee
{
   . . .
   public boolean equals(Object otherObject)
   {
      if (!super.equals(otherObject)) return false;
      // super.equals checked that this and otherObject belong to the same class
      Manager other = (Manager) otherObject; 
      return bonus == other.bonus;
   }

(2)相等测试与继承


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值