Java语言程序设计 第四部分 封装、继承和多态
封装、继承和多态 第一讲 类的继承性
类的继承性 继承是软 件重用的一种形式,在创建新类时吸收现有类的成员,然后赋予其新能力,或修改原有的能力。通过继承,程序员在程序开发中利用己验证和调试过的高质量软件,可节省开发时间。 当程序员创建类时,可以指定新类从现有类中继承某些成员,而不需要完全从头开始声明新的 成员。这个现有类称为超类( superclass ),新创建的类称为子类( subclass)。子类也可以成为其他类的超类。 通常子类会添加自己的变量和方法,因此子类比其超类更详细,表示更为特定的对象。典型的 情况是,子类既有其超类的行为,又有其专门的行为。
类的继承性 软件系统的开发经验表明,一个程序中往往有许多类是紧密相关的,而且它们需要用大量代码 来处理。如果程序员过分关注特定事例就会在细节上耗费大量时间和精力。使用面向对象编程方 法,程序员可以关注系统中对象之间的共性,而不必过乏地纠缠于特定的事件。 新类可以由类库中的类继承。许多开发组织都开发了自己的类库,并可以利用 其他可用的类库。这样有利于开发出更强大、丰富并且 更经济的软件。
继承的概念 由一个己有的类定义一个新类,称为新类继承已有的类,被继承的类称为父 类或超类,通过继承产生的新类称为子类或派生类。 继承性在父类和子类之间建立起了联系,子类自动拥有父类的全部成员,包括成员变量 和成员方法,使父类成员得以传承和延续;子类可以更改父类的成员,使父类成员适应新的 需求;子类也可以增加自己的成员,使类的功能得以扩充。但是,子类不能删除父类的成员 。
继承的概念
继承的概念
继承原则 子类继承父类的成员变量,包括实例成员变量和类成员变量。 子类继承父类除构造方法以外的成员方法,包括实例成员方法和类成员方法。 子类不能继承父类的构造方法。因为父类构造方法创建的是父类对象,子类必须声明自己的构造方法,创建子类自己的对象。 子类不能删除父类成员。 子类可以增加自己的成员变量和成员方法。 子类可以重定义父类成员。
继承原则 子类对象对父类成员的访问权限 类中成员的访问权限体现了类封装的信息隐蔽原则: 子类对父类的私有成员(private)没有访问权限; 子类对父类的公有成员(public)和保护成员(protected)具有访问权限。 子类对父类中缺省权限成员的访问分两种情况,对同一包中父类的缺省权限成员具有访问权限,而对不同包中父类的缺省权限成员则没有访问权限。 类中成员的访问权限体现了类封装的信息隐蔽原则: 如果类中成员仅限于该类自己使 用,则声明为private; 如果类中成员允许子类使用,则声明为protected; 如果类中成员没有权限限制,所有类均可使用,则声明为public。
继承原则 如果父类成员适用于子类,则子类不需要重新定义父类成员,此时子类继承了父类的成 员变量和成员方法,子类对象引用的是父类定义的成员变量,调用的是父类定义的成员方法。 如果从父类继承来的成员不适合于子类,子类不能删除它们,但可以重定义它们,扩充父类成员方法的功能,使父类成员能够适应子类新的需求。 子类重定义父类成员,同名成员之间不会产生冲突和混乱。程序运行时,父类对象引用父类成员,子类对象引用子类成员。
class Cleanser { private String s = new String("Cleanser"); public void append(String a) { s += a; } public void dilute() { append(" dilute()"); } public void apply() { append(" apply()"); } public void scrub() { append(" scrub()"); } public void print() { System.out.println(s); } public static void main(String[] args) { Cleanser x = new Cleanser(); x.dilute(); x.apply(); x.scrub(); x.print(); }
public class Detergent extends Cleanser { public void scrub() { append(" Detergent.scrub()"); super.scrub(); } public void foam() { append(" foam()"); public static void main(String[] args) { Detergent x = new Detergent(); x.dilute(); x.apply(); x.scrub(); x.foam(); x.print(); System.out.println("Testing base class:"); Cleanser.main(args);
super引用 子类自动继承父类的成员,当子类没有重定义父类成员时,则不存在同名成员问题。子 类对象访问的都是父类声明的成员变量,调用的也都是父类定义的成员方法,所以不需要使 用super引用。 当子类重定义了父类成员时,则存在同名成员问题。此时,在子类方法体中,成员变量 和成员方法均默认为子类的成员变量或成员方法。如果需要引用父类的同名成员,则需要使 用super引用。 super.成员变量 super.成员方法([参数列表])
class superclass{ int x; superclass(){ x=3; System.out.println("in superclass x="+x);} void doSomething(){ System.out.println("in superclass doSomething()");} } class subclass extends superclass{ subclass(){ super(); x=5; System.out.println("in subclass x="+super.x); System.out.println("in subclass x="+x); } super.doSomething(); System.out.println("in subclass doSomething()");}
public class Inheritance { public static void main(String[] args) subclass subc=new subclass(); subc.doSomething(); }
class Game { Game(int i) { System.out.println("Game constructor"); } class BoardGame extends Game { BoardGame(int i) { super(i); System.out.println("BoardGame constructor"); public class Chess extends BoardGame { Chess() { super(11); System.out.println("Chess constructor"); public static void main(String[] args) { Chess x = new Chess();
类之间的关系 组合 has a 继承 is a
class Art { Art() { System.out.println("Art constructor"); } class Drawing extends Art { Drawing() { System.out.println("Drawing constructor"); public class Cartoon extends Drawing { Cartoon() { System.out.println("Cartoon constructor"); public static void main(String[] args) { Cartoon x = new Cartoon();
封装、继承和多态 第二讲 类的封装性
类的封装性 封装性是面向对象的核心特征之一,它提供一种信息隐藏技术。 类的封装性包含两层含义 一是将数据和对数据的操作组合起来构成类,类是一个不可分割的独立单位。 二是类中既要提供与外部联系的方法,同时又要尽可能隐藏类的实现细节 。
类的封装性 如果外面的程序可以随意修改一个类的成员变量,会造成不可预料的程序错误,就像一个人的身高,不能被外部随意修改,只能通过各种摄取营养的方法去修改这个属性。 在定义一个类的成员(包括变量和方法)时,使用private关键字说明这个成员的访问权限,这个成员就成了类的私有成员,该类能被这个类的其他成员方法使用,而不能不其他的类中的方法所调用。
类的封装性 封装的意义 封装的实现 合理的访问(权限) 屏蔽实现的细节,便于修改 private (default) protected public
类的封装性 public——说明该类成员可被所有类的对象访问。pubilc指定最大访问权限范围。 ·缺省——当没有使用访问权限修饰符声明成员时,说明该类成员能被同一类中的其他 成员访问或被同一包中的其他类访问,不能被包之外的其他类访问。缺省权限以包为界划定访问权限范围,使同一包中的类具有访问权限,其他包中的类则没有访问权限。 private——说明该类成员只能被同一类中的其他成员访问,不能被其他类的成员访问, 也不能被子类成员访问。private指定最小访问权限范围,对其他类隐藏类的成员,防 止其他类修改该类的私有成员。
类的封装性 为了实现更好的封装性,我们通常将类的成员变量声名为private,在通过public的方法来对这个变量进行访问。对一个变量的操作,一般都有读取和赋值操作,我们分别定义两个方法来实现这两种操作,一个是getXxx()(Xxx表示要访问的成员变量的名字),用来读取这个成员变量操作,另外一个是setXxx()用来对这个成员变量赋值。 一个类通常就是一个小的模块,我们应该让模块仅仅公开必须要让外界知道的内容,而隐藏其他一切内容。我们在进行程序的详细设计时,应尽量避免一个模块直接修改或操作另一个模块的数据,模块设计追求强内聚(许多功能尽量在类的内部独立完成,不让外面干预),弱耦合(提供给外部尽量少的方法调用)。
数据隐藏总结 public protected default private 同一个类中 √ 同一包中 × 不同包中子类 不同包中非子类
封装、继承和多态 第三讲 多态性
多态性 多态性是面向对象的核心特征之一。类的多态性提供类中方法设计的灵活性和执行多样性。 在面向对象语言中,多态性是指一个方法可以有多种实现版本,即“一种定义,多种实现”。对于一个方法有多种实现方式,程序 运行时,系统会根据方法的参数或调用方法的对象自动选择一个方法执行,不会产生混淆或混乱。 通过多态,就能“对通用情况进行编程”,而不是“对特定情况编程”。多态的特别之处是,使程序能够处理类层次 中共享同一超类的对象,就好像它们都是超类的对象一样。
多态性 创建模拟几种动物运动的一个程序。类Fish, Frog 和Bird是要研究的三类动物。 设想这些类都是从超类Animal扩展而来的, Animal包含方法 move,并以x-y坐标表示动物的当前位置。每个子类都实现方法move。 不同类 型的Animal对move消息做出不同的响应, Fish可能游3尺远, Frog可能跳5尺远,而 Bird可能飞10尺高。 程序维护一个不同Animal 子类对象的引用数组。程序对所有的动物对象发布相同的消息(move ),而各对象知道如何根据 其特定的运动类型修改其x-y坐标。 对于同样的方法调用,依靠对象自己来了解如何"正确地处理" ,这是多态的关键概念。同样的消息(在本例中是move) ,得到"许多形态"的结果,这就是术语多态的由来。
多态性 利用多态可以设计和实现可扩展的系统,只要新类也在继承层次中。新的类对程序的通用部分 只需进行很少的修改,或不做修改。程序中唯一必须修改的是有关新类的直接知识。例如,如果通 过扩展Animal创建类Tortoise (它对move消息的响应可能是爬行1寸),就只需写出 Tortoise类以及模拟运行的部分,而各Animal的公共处理部分保持不变。
方法覆盖 覆盖( override)是指子类重定义了父类中的同名方法。 如果一个父类方法不适用于子类,子类可以 重新定义它,即声明并实现父类中的同名方法并且参数列表也完全相同,则父类和子类具有 两个同名方法,此时称子类方法覆盖了父类方法。子类方法覆盖父类方法时,既可以完全重 新定义,也可以在父类方法的基础上进一步增加功能。 程序运行时,究竟执行覆盖同名方法中的哪一个,取决于调用该方法的对象所属的类是 父类还是子类。Java寻找执行方法的原则是:从对象所属的类开始,寻找匹配的方法执行; 如果当前类中没有匹配方法,则逐层向上依次在父类或祖先类寻找匹配方法,直到Object类。
方法覆盖 继承的过程 继承全部内容 增加内容 改变内容 覆盖方法 一致的声明(名称、参数) 不低于基类的存取权限
多态性 编译时的多态 方法的重载 同类对象之间 运行时的多态 方法的覆盖 父类和子类之间
多态性---向上映射 向上映射 类图 子类是父类的特例 Shapes.java
class Triangle extends Shape { void draw() { System.out.println("Triangle.draw()"); } void erase() { System.out.println("Triangle.erase()"); class Shape { void draw() {} void erase() {} } class Circle extends Shape { void draw() { System.out.println("Circle.draw()"); void erase() { System.out.println("Circle.erase()"); class Square extends Shape { System.out.println("Square.draw()"); System.out.println("Square.erase()");
public class Shapes { public static Shape randShape() { switch((int)(Math.random() * 3)) { default: // To quiet the compiler case 0: return new Circle(); case 1: return new Square(); case 2: return new Triangle(); } public static void main(String[] args) { Shape[] s = new Shape[9]; for(int i = 0; i < s.length; i++) s[i] = randShape(); s[i].draw();
向下转换 对象转换 强制类型转换 instanceof运算符
封装、继承和多态 第四讲 抽象类、final类
抽象类 通常,当谈到类型时,人们总是认为程序将会创建该类型的对象。但是,有时也需要声明永远 不会被实例化的类。这样的类称为抽象类,因为它们只作为继承层次中的超类使用,所以又称为抽象超类。不能实例化抽象类的对象,因为它们是不完整的。子类必须声明出 "缺少的部分"。 抽象类的基本目的提供合适的超类,使其他类可以继承它的共享公共设计。 不是所有继承层次中都有抽象类,但程序员的确经常编写只使用抽象超类类型的客户代码,以 减少客户代码对特定子类的依赖。
抽象类 抽象方法不提供实现,如果一个类中有抽象方法,那么它必须声明为抽象类。 构造函数和 static方法不能声明为abstract。构造函数不能继承,因此永远不会实现抽象构造函数口同样, 子类不能覆盖static方法,因此也永远不会实现abstract和static方法。 抽象类声明类层次中所有类的共有属性和行为。抽象类通常包括一个或多个抽象方法,具体子类必 须覆盖这些抽象方法。 实例化抽象类的对象是编译错误。 尽管不能实例化抽象超类的对象,但是可以使用抽象超类声明变量,并用它来保存由该抽象超 类派生的任何具体类的对象引用。程序中经常使用这样的变量来多态地操作子类对象。
抽象类 无法实例化的类 代表一些基本行为 abstract关键字 抽象方法 只含有一个声明,没有方法主体 包含抽象方法的类一定是抽象类 抽象类中可包含非抽象方法 public abstract class PlaneGraphics1 //平面图形类,抽象类 { public abstract double area(); //计算面积,抽象方法,分号";"必不可少 }
class Triangle extends Shape { void draw() { System.out.println("Triangle.draw()"); } void erase() { System.out.println("Triangle.erase()"); abstract class Shape { int color,width; void draw() ; void erase() ; } class Circle extends Shape { void draw() { System.out.println("Circle.draw()"); void erase() { System.out.println("Circle.erase()"); class Square extends Shape { System.out.println("Square.draw()"); System.out.println("Square.erase()");
final类 修饰方法 final类 不能被覆盖的方法 类String是final类,它不能被扩展,因此所有使用String的程序使用的都是 Java API所声明的String对象的功能。声明final类还能防止程序员创建绕过安全限制的子类。
final类 声明最终类,不能被继承 声明最终方法,不能被覆盖。 public final class Math extends Object //数学类,最终类 声明最终方法,不能被覆盖。 public class Circle1 extends Graphics1 { public final double area() //最终方法,不能被子类覆盖 return Math.PI*this.radius*this.radius; }
例题 设计一个抽象类CompareObject,里面有抽象方法compareTo用于比较两个对象。然后设计一个类Position从CompareObject派生,有x和y属性表示其坐标,该类实现compareTo方法,用于比较两个对象距离原点(0,0)的距离之差。
封装、继承和多态 第五讲 接口、内部类
接口 接口定义事物之间的交互途径,并使之标准化。 例如,收音机上的控制钮就是收音机内部元件与用户之间的接口,控制钮使用户能够进行有限的操作(例如调台、调节音量以及在AM和FM 间切换) 。 接口必须说明收音机允许用户进行哪些操作,但不指定如何实现这些操作。
接口 “interface”(接口)关键字使抽象的概念更深入了一层。我们可将其想象为一个“纯”抽象类。 接口中的所有方法都是没有方法体的抽象方法,代表一些基本行为。 接口也可以包含基本数据类型的数据成员,但它们都默认为static和final。
接口 接口这样描述自己:“对于实现我的所有类,看起来都应该象我现在这个样子”。因此,采用了一个特定接口的所有代码都知道对于那个接口可能会调用什么方法。所以常把接口用于建立类和类之间的一个“协议”。 为创建一个接口,使用interface关键字。 为了生成与一个特定的接口(或一组接口)相符的类,要使用implements关键字。 如果一个类没有实现接口的所有方法,那么它是个抽象类,必须声明 为abstract类型。
接口
class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly { public void swim() {} public void fly() {} } public class Adventure { static void t(CanFight x) { x.fight(); } static void u(CanSwim x) { x.swim(); } static void v(CanFly x) { x.fly(); } static void w(ActionCharacter x) { x.fight(); } public static void main(String[] args) { Hero i = new Hero(); t(i); // Treat it as a CanFight u(i); // Treat it as a CanSwim v(i); // Treat it as a CanFly w(i); // Treat it as an ActionCharacter interface CanFight { void fight(); } interface CanSwim { void swim(); interface CanFly { void fly(); class ActionCharacter { public void fight() {}
interface Vampire extends DangerousMonster, Lethal { void drinkBlood(); } class HorrorShow { static void u(Monster b) { b.menace(); } static void v(DangerousMonster d) { d.menace(); d.destroy(); public static void main(String[] args) { DragonZilla if2 = new DragonZilla(); u(if2); v(if2); interface Monster { void menace(); } interface DangerousMonster extends Monster { void destroy(); interface Lethal { void kill(); class DragonZilla implements DangerousMonster { public void menace() {} public void destroy() {}
接口 接口通常用于需要在不相关的类之间共用方法或常数的情况下。 能够对不相关的类对象进行多态处理 实现相同接口的不同类对象可以响应同样的方法调用。 程序员可以创建接口来描述所需的功能,然后在需要该功能的类中实现这一接口。
接口与抽象类的区别 相同点: 不同点: 两者都包含抽象方法。 两者都不能被实例化。 抽象类约定多个子类之间共同使用的方法;接口约定多个互不相关类之间共同使用的方法。 抽象类与子类之间采用单重继承机制;一个类实现多个接口则实现了多重继承的功能。 抽象类及其类中成员具有与普通类一样的访问权限;接口中成员的访问权限均是public。 抽象类中可以包含非抽象方法,也可以声明构造方法;接口中的方法全部是抽象方法,不能声明构造方法。 抽象类中可以声明成员变量,子类可以对该成员变量赋值;接口中只能声明常量。
例题 设计一个抽象类CompareObject,里面有抽象方法compareTo用于比较两个对象。然后设计一个类Position从CompareObject派生,有x和y属性表示其坐标,该类实现compareTo方法,用于比较两个对象距离原点(0,0)的距离之差。 将上题的功能改用接口来实现。
内部类 类与类之间除了继承关系,还存在嵌套关系。即一个类可以声明包含另一个类,被包含 的类称为内部类( inner class) ,包含内部类的类称为外部类,此时内部类成为外部类的成员。
内部类 public class Line //直线类,外部类 { protected Point p1,p2; //直线的起点和终点 protected class Point //点类,内部类 protected int x,y; //内部类的成员变量 protected Point(int x,int y) //内部类的构造方法 this.x = x; this.y = y; }
内部类 内部类既有类的特性,也有类中成员的特性。 内部类的类特性 内部类的成员特性 内部类不能与外部类同名。 内部类具有封装性。 内部类具有继承性。 内部类具有抽象性。 内部类的成员特性 使用点运算符“.”引用内部类。例如: Line.Point 内部类具有4种类中成员的访问权限。 内部类具有静态特性。
public class Parcel1 { class Contents { private int i = 11; public int value() { return i; } } class Destination { private String label; Destination(String whereTo) { label = whereTo; String readLabel() { return label; } public void ship(String dest) { Contents c = new Contents(); Destination d = new Destination(dest); public static void main(String[] args) { Parcel1 p = new Parcel1(); p.ship("Tanzania");
内部类 匿名内部类 内部类的工作方式
interface Contents { int value(); } public class Parcel6 { public Contents cont() { return new Contents() { private int i = 11; public int value() { return i; } }; // Semicolon required in this case public static void main(String[] args) { Parcel6 p = new Parcel6(); Contents c = p.cont();
事件与匿名内部类 IDE采用匿名内部类生成事件处理程序
封装、继承和多态 第六讲 Java常用类2
Object类 Object类是Java中所有类的根,所有其他的类都是由Object类派生出来的,因此,根据继承的特点,在Object类中定义的成员变量和方法,在其他类中都可以使用。 提供了对所有对象统一处理的解决方案
Object类 protected void finalize() String toString() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。 String toString() 返回该对象的字符串表示。 boolean equals(Object obj) 指示某个其他对象是否与此对象“相等”。 与Java运算符“= =”的含义相同。
== 与equals() public class Equivalence { public static void main(String[] args) { Integer n1 = new Integer(47); Integer n2 = new Integer(47); System.out.println(n1 == n2); System.out.println(n1 != n2); } public class EqualsMethod { public static void main(String[] args) { Integer n1 = new Integer(47); Integer n2 = new Integer(47); System.out.println(n1.equals(n2)); }
== 与 equals() class Value { int i; } public class EqualsMethod { public static void main(String[] args) { Value v1 = new Value(); Value v2 = new Value(); v1.i = v2.i = 100; System.out.println(v1.equals(v2));
创建类时应注意 是否进行对象的比较 equals方法 compareTo方法 是否进行对象的复制 clone方法
System类 System类是java.lang包中一个非常重要的类,提供了许多获取或重新设置系统资源的静态方法。 static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。 static Properties getProperties() 确定当前的系统属性。 static String getProperty(String key) 获得指定键指示的系统属性。
例题 创建一个person类,包含属性年龄、身高、性别,注意对象的比较问题。