类与继承

类的继承
Java引人注目的一项特性是代码的重复使用, 在象C那样的程序化语言里,代码的重复使用早已可行,但效果不是特别显著。与Java的其他地方一样,这个方案解决的也是与类有关的问题。我们通过创建新类来重复使用代码,但却用不着重新创建,可以直接使用别人已建好并调试好的现成类。
但这样做必须保证不会干扰原有的代码。
在这一章里,我们将介绍两个达到这一目标的方法。第一个最简单:在新类里简单地创建原有类的对象。我们把这种方法叫作“合成”,因为新类由现有类的对象合并而成。我们只是简单地重复利用代码的功能,而不是采用它的形式。
class WaterSource {
  private String s;
  WaterSource() {
        s = new String("Constructed");
  }
  public String toString() { return s; }
}

public class SprinklerSystem {
  WaterSource source;
 
  void print() {  
    System.out.println("source = " + source.toString());
  }
  public static void main(String[] args) {
    SprinklerSystem x = new SprinklerSystem();
    x.print();
  }
}

上面的示例是第一种方式”合成”,下面将详细介绍继承.

继承是面向对象编程的核心内容之一, 它允许创建层次化的分类,使用继承,可以创建一个通用类,它定义了一组关联项的常见特性,该类可以被其他具体的类继承,每个类都添加其特有的内容,用Java的术语来讲,被继承的类称为超类,继承的类称为子类,因此,一个子类是一个超类的特定版本,它继承了超类定义的所有实例变量和方法,同时添加了它自己的独一无二的元素.

要继承一个类,只需要使用extends关键词即可.请看示例package01
可以看到子类B包括其父类A的所有成员,这就是为什么subOb可以访问i,j,并调用showij()的原因,同样,在sum()中可以直接引用i,j,仿佛它们是B的一部分.
虽然A是B的一个超类,但同时它也是一个完全独立的,单独的类,也可以单独使用.

Java只支持单根继承,即一个子类只有一个父类,不能有多个父类.

成员访问和继承
尽管一个子类包括其父类的所有成员,但它不能访问声明为private的父类成员.请看示例package02
该程序不能编译,因为对B的sum()方法内的j的引用不合法的,因为j是private,所以仅能被自己类的其他成员访问,而子类不能访问它.

注意:尽管是私有的,但是子类还是继承下来了,只不过子类不能访问它.

请看Box示例的继承版本,package03
BoxWeight继承了Box的所有性质并增添了weight属性,对BoxWeight来说,不需要重新创建在Box中所有特性,简单地扩展Box可以满足其自身要求即可了,从而达到最大限度的复用代码.

继承的主要优点是,一旦创建一个父类,并且在此父类中定义了一组对象需要的常用属性,那么就可以使用它来创建任意数目的特定子类,每个子类都可以准确扩展自己及自己的分类.

继承的本质:一旦创建了定义对象常见特性的超类,那么就可以继承该超类以形成专用的类,每个子类只需要添加其自己的唯一属性,这就是的继承的本质.

父类引用可以引用子类对象
可以让父类的引用绑定子类的对象,继承的这个特点在各种场合都是很有用的.但是有些限制.
请看示例package04
其中bw是一个BoxWeight对象的引用,plainbox是一个Box对象的引用,因为BoxWeight是Box的子类,所以允许plainbox引用与BoxWeight对象进行绑定.

决定了可以访问什么成员的是引用变量的类型,而不是它引用的对象类型,也就是将父类引用与子类对象进行绑定时,只可以访问由父类所定义的那部分对象,这就是plainbox引用BoxWeight对象时,它不能访问weight的原因.因为父类不知道子类添加了什么.这也是前面程序段中最后一行被注释掉的原因,一个Box引用不能访问weight域,因为没有定义它.

super关键字
在前面的示例中,从Box中派生的类在实现时并没有像应有的那么高效或健壮,例如,BoxWeight的构造函数显示地初始化Box()的width,height,depth属性,这不仅复制了在其父类中创建的代码,而且还必须授予一个子类访问这些成员的权力. 但如果想创建一个父类,实现细节由它自己控制(即父类的属性是私有的),而子类就没有办法直接访问或初始化这些属性了.由于封装是OOP(面向对象编程思想)的一个主要特性,所以 Java提供了该问题的解决方案,子类可以使用super关键词来调用父类的方法.
super有两种常见的形式: 第一种子类调用父类的构造方法,第二种子类访问父类所隐藏的成员

使用super调用父类的构造方法
请看示例package05
其中BoxWeight类的构造方法中,用super关键字调用了父类带有参数的方法.这样,BoxWeight只需要初始化weight,所以Box可以根据需要随时让这些值成为private.因为构造方法可以被重载,所以可使用由这个父类定义的任何形式调用super(),所执行的构造方法将是与参数匹配的那一个.

创建一个类层次时,组成层次的类的构造函数按什么顺序被调用呢?
例如父类A,子类B,那么A的构造函数是在B的构造函数之前调用还是之后调用呢?答案是在一个类层次中,构造函数按派生顺序,即从超类到子类的顺序被调用,还有,因为super()必须是子类构造函数的第一条执行语句,所以不管是否使用了super(),这个顺序是不变的,如果没有使用super(),那么将执行每个超类的默认或无参数的构造函数.请看示例package06
构造函数按派生顺序执行是很有意义的,因为超类不知道任何子类的信息,任何它要执行的初始化可能和子类执行的初始化是分离的,而且是子类执行初始化的先决条件,因此,它必须先执行.

super的第二种形式,调用父类中隐藏的属性或方法.
请看示例package07
尽管在B中的实例变量i隐藏了A中的i,但super允许访问在超类中定义的i,当然super也可以调用被一个子类隐藏的方法.

创建多级层次结构
请看示例package08,这个程序中,Box的子类BoxWeight被作为Simpment类的父类,Simpment继承了BoxWeight和Box的所有特性并添加了一个称为cost的域,这个域包含运送类似包裹这种东西的价格.
因为继承关系,shipment能够利用前面定义的Box和BoxWeight类,仅为自己和特殊应用程序添加所需要的额外信息,这体现了继承的部分价值: 重用代码.
该示例展示了另一个重要的知识点:super()总是引用最近超类中的构造函数

方法重写
在类层次中,当一个子类中的方法与超类中的方法具有相同的名称和类型声明时,称子类的方法重写了超类中的方法.从子类中调用重写方法时,它总是引用该子类定义的方法,而超类定义的方法将被隐藏.请看示例package09
当类型B的对象调用show()时,使用的是在B内定义的show()版本,也就是说,B内的show()重写了A中声明的show().
若要访问被重写方法的超类版本,可以通过super来做到这一点,例如,在B版本中,show()的父类版本在子类版本中被调用.这将显示所有的实例变量

方法重写仅在两个方法的名称和类型声明相同时才发生,如果不同,那么两个方法就只是重载了.请看示例package10

动态方法调度
方法重写构成了Java最强大的一个概念基础,动态方法调度,动态方法调度是一种机制,借助于这种机制,对一个已重写方法的调用将在运行时,而不是在编译时解析,动态方法调度非常重要,因为这关系到Java如何实现运行时多态性的问题.

前面讲过,父类引用可以绑定子类对象,Java使用这个事实来解决在运行时对重写方法的调用.
运行原理:
当一个父类引用调用一个重写方法时,Java根据在调用时被绑定对象的类型来决定执行哪个版本的方法,因此这个决定是在运行时做出的,当父类引用绑定的子类对象类型不同,将调用不同版本的重写方法.换句话说,是被绑定的对象最终决定调用哪个版本的方法.请看示例package11
结果:所执行的callme()方法由调用时被引用对象的类型决定.

多态性也定义了派生类必须实现自己的方法,允许子类在加强一致接口的同时,灵活地定义他们自己的方法.因此,通过同时使用继承和重写方法,父类能够定义供其所有子类使用的方法的通用形式.运行时多态性是面向对象设计方法实现代码重用和健壮性的最强大机制之一,代码库在维持抽象接口同时不重新编译的情况下可以调用新类的实例.

示例package12, 方法参数的动态调用.

java中final的用法
修饰属性 属性值不能更改
修饰方法 方法不能被重写
修饰类   类不能被继承
请看示例package13

Object类
Java定义了一个非常特殊的类,即Object,其他所有的类都是Object的子类,Object是所有其他类的父类,一个Object类型的引用可以绑定任何类的对象,因为数组像类一样的实现,所以Object类型引用也可以引用任何数组.
请看Object类中定义的方法
Object 类
class Object {
Class getClass();  //获取对象所属的类;
boolean equal(Object object); //判断两个对象是否完全相同注意equal判断相同情况是两个对象同时指向一个内存.
Object clone();//学过C++的人都应该知道他的用途是复制对象的所有属性给另一个对象.
void finalize();//其意善后应该是在调用析构函数之前调用.
String toString();//此对象的文字描述是,hash的16进制表示.
int hashCode();//很复杂的解释.在JAVA中由 Collection派生出来的两个类List和set.他两的区别不是一个是大哥一个是小弟.而是List是有序元素可以重复而set是无序的元素不可以重复的.而HashCode()可以直接找到代插元素的位置在用equal()来判断是否与被插元素相同如果相同就不插入元素.不同就阵列后面的元素.插入元素.
void notify();//与线程有关
void notifyAll();//同上
void wait();//与线程有关
void wait(long milliseconds;//与线程有关
void wait(long milliseconds,int nanoseconds);// 与线程有关
}

Object类equals,toString两个方法,我们可以在自己的类中进行重写,而达到自己想要的效果.
String类,Java已经重写了equals方法,从而使String类的equals方法是比较两个字符串的值是否相等.我们要比较自已定义的两个类是否相等,记住要重写equals方法.
toString()方法可以表示对象的字符串形式,默认打印的是内存中的地址,我们也可以重写此方法来达到我们自己想要的效果.


最后请看综合运用示例Student,访问控制与继承的综合运用.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值