Java中的面向对象特性
本章主要内容 Java程序结构 “开-闭”原则:抽象与封装 类和对象 继承 多态
Java程序结构(一) 包(package)声明:0 or 1 ,in the beginning of the whole file 类的引入(import):0 or more,in the beginning of the all classes, import standard class and existed classes 公开类声明(public classDefinition): 0 or1 ,the file’s name must be same as this class nameclassDefinition: 0 or more ,the number of the classes is not limited interfaceDefinition: 0 or more ,the number of the interfaces is not limited
Java程序结构(二) Example: package project.gui.view; import java.util.Calendar; import java.awt.*; import java awt.event.*; import mywork.form; public class ButtonExample{…} class A{…} interface ButtonInterface{…} …… } class B{…}
简单的Java面向对象程序 Initially each of our Java programs will consist of two classes: a program class and a runner class. Program class: an object of this class defines what our program should do. Runner class: tells the Java interpreter how to run our program.
Program class声明
Runner class声明
Main函数 When you give a class to the Java interpreter it looks for a main method in the class that has the prototype public static void main(String[] args) and begins executing the statements in it.
示例:圆的计算 program constructs a CircleCalculator object called circle program asks circle to read value for its radius program asks circle to calculate its area and circumference program asks circle to display its radius, area and circumference
CircleCalculatorRunner类
构建一个对象 new CircleCalculator() causes these statements to be executed public CircleCalculator() { } Statements
CircleCalculator类
CircleCalculator构造符
readInput方法
doCalculations方法
displayResults方法
对象与消息(一)
对象与消息(二)
构造符声明 Here is a general template for a constructor declaration modifiers ClassName ( formalArgumentList ) { local declarations and other statements }
方法声明 Here is a general template for a method declaration modifiers returnType methodName ( formalArgumentList ) { local declarations and other statements }
编译与运行(一) 这个应用程序需要有三个文件: 每一个java文件都要编译成class文件(bytecode)。 KeyboardReader.java CircleCalculatorRunner.java CircleCalculator.java 每一个java文件都要编译成class文件(bytecode)。
编译与运行(二) 编译完成后,执行 java CircleCalculatorRunner 运行整个程序,结果如下: Enter radius 3 Radius: 3.0 Area: 28.274333882308138 Circumference: 18.84955592153876
课间习题 自己上机实践以上的示例; 能完全读懂程序的流程结构; 根据KeyboardReader.java文件中的readDouble()方法,实现类似的readInt();readLong()等方法; 提高:参考javadoc学习KeyboardReader.java文件中用到的几个IO类和方法。
"开-闭"原则(OCP) “开-闭”原则(Open-Closed Principle)讲的是:一个软件实体应当对扩展开放,对修改关闭[MEYER88],英文原文是: Software entities should be open for extension,but closed for modification.
"开-闭"原则(OCP) 满足"开-闭"原则的设计可以给一个软件系统两个无可比拟的优越性: 1、通过扩展已有的软件系统,可以提供新的行为,以满足对软件的新需求,使变化中的软件系统有一定的适应性和灵活性; 2、已有的软件模块,特别是最重要的抽象层模块不能在修改,这就使变化中的软件系统有一定的延续性。 具有这两个优点的软件系统是在一个层次上实现了复用的系统,也是一个易于维护的系统。
怎样做到“开-闭”原则?(一)
怎样做到“开-闭”原则?(二) 抽象化是关键! 解决问题的关键在于抽象化。在面向对象的编程语言中,可以给系统定义出一个一劳永逸、不再更改的抽象设计,此设计运行有无穷无尽的行为在实现层被实现。在Java语言中,可以给出一个或多个抽象类或接口,规定出具体类必须提供的方法的特征(signature)作为系统设计的抽象层。这个抽象层预见了所有的可能扩展,因此,在任何扩展情况下都不会改变。这使得系统的抽象层不需修改,从而满足了“开-闭”原则的第二条:对修改关闭。 同时,由于从抽象层导出一个或多个新的具体类可以改变系统的行为,因此系统的设计对扩展是开放的,这就满足了“开-闭”原则的第一条:对扩展开放。
Java的接口(一) 可插入性:如果可以动态的将一个构件移走,并以另一个构件取而代之,这就叫构件的可插入性。 接口(电源插座)、可插入构件(各种电器) 接口是实现构件的可插入性的关键。 Java接口是一些方法特征的集合,这些方法特征当然来自于具体方法,但它们都是一些很重用的方法。接口只有方法的声明,没有方法的实现,因此这些方法在不同的地方被实现时,可以完全具有不同的行为。
Java的接口(二) Java源代码示例: package java.lang; public interface Runnable { public abstract void run(); }
Java的抽象类(一) 抽象类仅提供一个类型的部分实现。抽象类可以有实例变量,以及一个或多个构造符。抽象类可以同时有抽象方法和具体方法。 关键字 abstract 一个抽象类不会有实例,这些构造符不能被客户端调用来创建实例。但抽象类的构造符可以被其他子类调用,从而使一个抽象类的所有子类都可以有一些共同的实现(共性),而不同的子类可以在此基础上有其自己的实现(个性)。
Java的抽象类(二) 抽象类通常代表一个抽象概念,它提供一个继承的出发点。抽象类一定是用来继承的,具体类不是用来继承的。换言之,在一个以继承关系形成的等级结构里面,树叶节点均应当是具体类,而树枝节点均应当是抽象类(或者Java接口)。这样的设计是所有的Java设计师都应当努力做到的! 抽象类应当拥有尽可能多的共同代码;抽象类应当拥有尽可能少的数据。
Java的抽象类(三) Java源代码示例: InputStream接口的源代码 package java.io; public abstract class InputStream { public abstract int read() throws IOException; public int read(byte b[], int off, int len) throws IOException { …… }
怎样做到“开-闭”原则?(三) 对可变性的封装原则:讲的是找到一个系统的可变因素,将之封装起来。这个原则意味着两点: 一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里面。同一种可变性的不同表象意味着同一个继承等级机构中的具体子类,因此,读者可以期待在设计模式中看到继承关系; 一种可变性不应当与另一种可变性混合在一起。一般的设计模式的类图的继承机构都不会超过两层,不然就意味着两种不同的可变性混合在一起了。
类和对象 类的组成 Fields Constructors Methods
类声明 类声明 类修饰符(class modifier) [class modifier] class classname [extends superclass name] [implements interfacename{,interfacename} ] {class body} 类修饰符(class modifier) Access Modifier abstract final
Fields/Variables 实例变量 类变量 Instance Variables Final Instance Variables Non-Static Fields Private state of an object(each object has its own copy of instance variables) Final Instance Variables Not modifiable(constant instance variables) Must be initialized 类变量 Static Fields: shared by all objects of the class Final (static) variables must be initialized(constants) Use static class variables to save storage when a single copy of the data will suffice
示例 class Body {//天体类 public long idNum; public String nameFor; public Body orbits; public static long nextID = 0; } —Instance variables: idNum, nameFor, Orbits —Class variable: nextID —Body a,b; a.idNum and b.idNum are independent, but a and b share nextID
Fields修饰符
Static Field Only one copy of such members in the class/ shared by all objects of the class Exist even when no objects of the class exist Also called class variables and class methods Example: nextID
静态初始化块 静态初始化块的存在往往是需要在类载入时完成一些初始化的工作。 class Body { public long idNum; public String nameFor; public Body orbits; public static long nextID; } static { nextID = 20; } Body() { idNum = nextID ++; } … }
Can’t change reference Final Field Class A { int i = 1; public void g(); } Class B { final int j = 9; public static final int k = 20; final int m = (int)(Math.random()*20) final A a1 = new A(); final A a2; … void q(final A x) { x = new A(); x.g(); } public statuc void main(String[] args){ B b = new B(); b.a1.i++; b.a1 = new A(); } …} No initializer X X is final X Can’t change reference X
创建对象 Creation by new Memory Allocation Reference General Format: new objecttype (arglists); Body sun = new Body(); Memory Allocation Automatic Reference Declaration(initialization:null) Object reference is valid only if explicitly created Initialization after Creation sun.idNum = Body.nextID++; sun.nameFor = “Sol”;
构造符 构造符和类具有相同的名字; 一个类可以有多个构造符(重载); 构造符可以有0..n个参数; 构造符没有返回值; 构造符总是和new运算符一起被调用。
构造符的示例 class Body { public long idNum; public String name = “<unnamed>”; public Body orbits = null; private static long nextID = 0; Body() { // constructor without argument idNum=nextID++; }// every Body object have a unique idNum } Body sun = new Body(); // idNum=0; sun.name=”Sol”; sun.orbits=null;
构造符重载 Body(String bodyName, Body orbitsAround) { this(); name = bodyName; orbits = orbitsAround; } This(): call another constructor of the class, Body() Creation: Body sun = new Body(“Sol”,null); Body earth = new Body(“Earth”,sun);
初始化的顺序 在一个类里变量初始化顺序由定义顺序决定的,在任何方法(包括构造器)之前得到初始化 首先初始化static对象(如果它们尚未由前一次对象创建过程初始化),然后初始化非static对象 例:创建一个对象的过程: 类型为A的一个对象首次创建或A类的static成员首次访问时,JAVA解释器必须找到A.class A.class的所有初始化模块都会运行( static 初始化仅发生一次) 创建一个new A()时,首先在内存堆中为其分配足够空间;然后清零,将A中所有基本类型设为默认值 显式初始化、执行构造器
初始化的示例 Class Card{ Tag t1 = new Tag(1); Tag(1) Card() { System.out.println(“Card()”); t3 = new Tag(33); } Tag t2 = new Tag(2); void f() {System.out.println(“f()”); } Tag t3 = new Tag(3); } … Card t = new Card(); t.f(); Tag(1) Tag(2) Tag(3) Card() Tag(33) f()
方法的声明 Modifier1 Modifier2…Return-type MethodName(Formal-Parameter List) throw [ Exception-list] { Method Body}
示例 public String toString() { String desc = idNum + “ (” + name + “)”; return desc; } toString is a special method with String as the return type, and without any parameter toString is automatically invoked when an object is used in ‘+’ or ‘+=’ of string operations System.out.println(“Example:”+ sun) Example:0(Sol)
方法的调用 格式: reference. method( actual-parameter-list ) 形参与实参: 传值调用: formal parameters can be changed in methods, but do not affect the actual parameters 传引用:
示例1: public static void halveIt(double arg ) { arg /=2.0; System.out.println(“halved:arg = ” + arg); } …… double one = 1.0; System.out.println(“before:one=” + one); halveIt(one); System.out.println(“after:one=” + one); one=1 arg=0.5 one=1
示例2: name changed public static void commonName(Body bodyRef) { bodyRef.name = “Dog Star”; bodyRef = null; } …… Body sirius = new Body(“Sirius”,null); System.out.println(“before:”+sirius); commonName(sirius); System.out.println(“after:”+sirius); name changed before: 0(Sirius) after: 0(Dog Star)
示例2分析: Body sirius = new Body(“Sirius”,null) main() sirius Refrence IdNum: 0 Name Orbit:null OBJECT Dog Star “Sirius” commonName(sirius) bodyRef.name = “Dog Star” commonName() bodyRef Refrence public static void commonName(Body bodyRef) bodyRef = null
关键字this(一) 当你位于某个函数之内,想取得当前的对象引用(object reference)。这时this关键字因此而生。它仅用于函数之内,能取得“唤起此一函数”的那个对象引用。 Example class Moose { String hairdresser ; Moose(String hairdresser) { this.hairdresser = hairdresser; } field parameter
关键字this(二) Class B { void f( int i ) { ….}} B a = new B(), b = new B(); a.f(1); b.f(2); How does f() work? B.f(a,1); B.f(b,2); Java Compile pass the reference of the current object Within a method, the refrence of current object ——this
方法的重载 Signature: name, number of parameters, types of parameters Overloading: by multiple methods with different signatures Example public Body orbitsAround() { return orbits } public void orbitsAround(Body around) { orbits = around; }
静态方法 静态方法是不对对象施加操作的方法。即它没有隐式参数。我们可以把静态方法看作是没有this参数的方法。 因为静态方法并不操作对象,所以我们不能在一个静态方法中访问实例字段,但可以访问自己类中的静态字段。 最好使用类的名字来调用静态方法。 B.f(a,1); int n = People.getId(); int n = harry.getId(); 不推荐
继承 子类/父类 直接父类/间接父类 单继承/多继承 继承层次(tree-like/network-like) Object: 是所有类的父类,它定义了Java系统中每一个类都具有的行为(方法)。
None_Number_PhoneCard 电话卡 PhoneCard 剩余金额 拨打电话 查询余额 继承 继承 None_Number_PhoneCard 有卡号电话卡 无卡号电话卡 卡号、密码、接入号码、接通 对应电话机型号 Number_PhoneCard 获得电话机型号 登录交换机 继承 继承 继承 继承 IP卡 200卡 电话磁卡 电话卡 失效日期 附加费 使用地域 拨打电话 拨打电话 修改密码 拨打电话 拨打电话 IP_Card magCard IC_Card D200Card
abstract class PhoneCard { double balance; abstract boolean performDial(); double getBalance() { return balance; } } abstract class None_Number_PhoneCard extends PhoneCard { String phoneSetType; String getSetType() { return phoneSetType; } }
class magCard extends None_Number_PhoneCard { String usefulArea; boolean performDial() if (balance > 0.9) balance -= 0.9; return true; } else return false;
class IC_Card extends None_Number_PhoneCard { boolean performDial() if (balance > 0.5) balance -= 0.9; return true; } else return false;
abstract class Number_PhoneCard extends PhoneCard { long cardNumber; int password; String connectNumber; boolean connected; boolean performConnection(long cn,int pw) { if (cn == cardNumber && pw == password) { connected = true; return true; } else return false; } }
class IP_Card extends Number_PhoneCard { Date expireDate; boolean performDial() if (balance > 0.3 && expireDate.after(new Date())) balance -= 0.3; return true; } else return false;
class D200_Card extends Number_PhoneCard { double balance; double additoryFee; boolean performDial() { if (balance >(0.5+additoryFee)) { balance -= (0.5 + additoryFee); return true; } else return false; }
Field继承 Field Hiding(字段遮蔽) 注意点: A field with the same name as a superclass’s filed is declared in subclass 注意点: The hidden field in the superclass still exists The hidden field may be accessed by super, or referencing a superclass’s object When a field of an object is accessed, the declared type of the object determine either super- or sub-class’s field will be used
示例 Public class TestHiddenField { public static void main(String args[ ] ) { D200_Card my200 = new D200_Card(); my200.balance = 50.0; System.out.println(“Supper:”+my200.getBalance()); if (my200.performDial()) System.out.println(“Subclass:”+my200.balance); } 0.0 49.5
Method继承(一) 覆写(override) 要求 Replace a superclass’s method implementation by a new one in the subclass 要求 The old method and the new method must have the same signature and return type. (if different, such a redefinition is not overriding, but is simply an example of overloading) The method must be non-static
Method继承(二) 注意点: Super may be used to refer to the overridden method of the superclass Dynamic Method Call: when a method of an object is called, the actual type of the object determine which implementation will be used
class SuperShow { public String str = “SuperStr”; public void show() { System.out.println(“Super.show:” + str); } } class ExtendShow extends SuperShow { public String str = “ExtendStr”; public void show() { System.out.println(“Extend.show” + str); } public static void main(String[] args) { ExtendShow ext = new ExtendShow(); SuperShow sup = ext;
sup.show();//show of actual type, i.e. ExtendShow ext.show(); System.out.println(“sup.str=”+sup.str); System.out.println(“ext.str =”+ext.str); } Two kinds of reference: actual type(ext), super type(sup) Result: Extend.show:ExtendStr // actual type: Extend Extend.show:ExtendStr sup.str = SuperStr // declared type: SuperShow ext.str = ExtendStr // declared type: ExtendShow
抽象方法和抽象类 抽象方法 抽象类 A method has not been specified(signature only) Abstract methods must be implemented in non-abstract subclasses 抽象类 one or more abstract methods Abs. classes may have concrete data and methods
abstract class GraphicObject { int x, y; . . . void moveTo(int newX, int newY) { . . . } abstract void draw(); } class Circle extends GraphicObject { void draw() { . . . } } …
abstract class A { abstract void callme(); void metoo() { System.out.println(“A’s metoo”) } } class B extends A { void callme() { System.out.println(“B’s callme”) } A a=new B(); a.callme(); /*B’s callme*/ a.metoo(); A a=new C(); a.callme(); /*C’s callme*/ a.metoo();
多态(一) 方法(Method) 类型(Types) Overloading(methods with different signatures) Overriding(inheritance with redefinition) 类型(Types) Subclass-object-is-a-superclass-object, so an object can be referenced by the super types A class’s constructor usually invokes a superclass’s constructor, because creating an object also leads to creating another object of the superclass A variable of type Object may refer to any object
多态(二) Casting Up GraphicObject g = new Circle(); g.draw(); Circle.draw() 在C++中 ,用虚函数实现动态绑定; 在JAVA中,缺省情况就是动态绑定;否则可用final定义
多态(三) Polymorphism as implemented with dynamic method binding (also called late binding)is efficient Polymorphism promotes extensibility: new object types can be added without modifying the base system By polymorphism, programs contain less branching (switch) logic in favor of simpler sequential code. This facilitates testing, debugging, and maintenance
实习题 1、编写表示二维点(x, y)的类Point2D,现要求两点间的距离的方法distance。再编写Point2D的子类Point3D表示三维点(x, y, z), 改写 distance方法。并增加求两点内积的方法innerProduct。编写main 方法,来使用Point2D,Point3D及其方法。 2、创建一名为Rational有理数的类执行数学中的分数运算,并编写驱动程序测试此类。 要求用整数变量表示类的private实例变量:
实习题 numerator和denominator。提供一个构造器,使声明类的对象时可以对它们进行初始化。此构造器可按简化形式存储分数,例如分数2/4将1存储在numerator中,2存储在denominator中。提供带缺省值的无参数构造器以防不提供初始值。为下列运算提供public 方法: (1)两个Rational数相加,结果以简化形式存储; (2)两个Rational数相减,结果以简化形式存储; (3)两个Rational数相乘,结果以简化形式存储; (4)两个Rational数相除,结果以简化形式存储; (5)以a/b的形式打印Rational数,其中a为numerator,b为denominator; (6)以浮点格式打印Rational数(可按用户的要求确定小数点后的数)。
实习题 3、 《Java编程思想》第四、六、七章,这三章习题选作3道。 樊闻斌 uliyas@mes.nju.edu.cn 13813901977