760 likes | 899 Views
接口与包. 什么是包( package ). 包的作用类似于 C 的函数库,但是 C 的函数库很容易出现重名的问题,包在一定程度上解决了这个问题。 一个包通常包含很多个功能相近的类。 JDK 里面已经包含了 N 个功能强大的包,所以当你要实现一个功能,首先记得去查一下 JDK 的文档,看看有否对应功能的包。 编程语言的优劣都是相对而言的,大家觉得 Java 功能很强大,其实是因为 SUN 公司的 JDK 免费赠送了众多功能强大的包。. 包- 1. 包的命名 SUN 公司建议用域名的逆序,因为域名是唯一的。如: cn.edu.xmu 包的结构
E N D
什么是包( package ) • 包的作用类似于C的函数库,但是C的函数库很容易出现重名的问题,包在一定程度上解决了这个问题。 • 一个包通常包含很多个功能相近的类。 • JDK里面已经包含了N个功能强大的包,所以当你要实现一个功能,首先记得去查一下JDK的文档,看看有否对应功能的包。 • 编程语言的优劣都是相对而言的,大家觉得Java功能很强大,其实是因为SUN公司的JDK免费赠送了众多功能强大的包。
包-1 • 包的命名 • SUN公司建议用域名的逆序,因为域名是唯一的。如:cn.edu.xmu • 包的结构 • 一个包被映射为一个目录,包里面的每个类,则映射到目录中的某一个文件,如:com.prenhall.mypackage这个包被展开后,就是
包-2 • 打包 • package packagename; • 上面这一行一般出现在Java源文件的第一行,表示这个文件的所有类,都将被打包到packagename中。 • 不同源文件的packagename可以相同,Java会根据packagename将相同包的class压缩在一起,生成一个扩展名为jar的包。 • 顺便说一下,jar包其实是zip格式的压缩文件
包-3 • 使用包 • 要使用一个包里的某个类,必须首先导入 • import javax.swing.JOptionPane; • 如果要使用一个包里的多个类,用这个 • import javax.swing.*; • *表示导入这个包的所有类 • 注意包之间没有嵌套关系,例如java.awt和java.awt.geom是两个完全独立的包。这个其实很好理解,因为java.awt这个包,仅仅包含java/awt这个目录下的class文件,而java/awt/geom则是另外一个目录了。
包的命名规范 • 包的名称一般全部采用小写 • 包名的前缀一般是域名单词序列的逆序 • 实例 • com.sun.eng • com.apple.quicktime • edu.cmu.cs.bovik.cheese
文件的命名规范 • 必须与该文件中public类的类名同名 • 后缀必须是 .java
类/接口的命名规范 • 类名一般是名词/名词词组:每个单词的首字母大写,其它字母小写 • 类名应当尽量简单,而且其含义能够尽量准确地刻画该类的含义 • 一般采用全称—尽量少用缩写词 (除非该缩写词被广泛使用) • 实例 • Clock • Time • ImageSprite
方法的命名规范 • 方法名一般是 • 动词/动词性词组 • 首字母小写 • 中间单词的首字母大写,其它字母小写 • 尽量应用简单的、常用的单词 • 实例 • run( ); • getBackground( ); • getTime( );
变量的命名规范 • 变量名的首字母小写 • 中间单词的首字母大写,其它字母小写 • 变量名的首字母尽量不要用字符: _或$ • 变量名应当简短、有含义、且便于记忆 • 变量名常常由表示其所扮演的角色与数据类型组成 • 实例 • int i; char c; double widthBox; • Point startingPoint, centerPoint; • Name loginName;
常量的命名规范 • 常量名一般全大写,单词之间用下划线分隔 (“_”) • 实例: • static final int MIN_WIDTH = 4; • static final int MAX_WIDTH = 999;
源文件编辑 • 尽量不要用TAB排版 • 行数/每行的字符数不宜过多 • 缩排方式(Indentation) • 多用空白符/行 • 友情提示:通常Java的开发环境都会提供源代码的格式化/重排功能,如果使用记事本编辑源代码,建议去下载一个叫astyle的源代码重排工具(DOS命令行工具)。UltraEdit有捆绑这个工具,并提供了GUI界面的调用方式。
文件组织 • 源程序文件一般采用如下的组织顺序: • 最开始一般是注释 • package 和 import 语句 • 类和接口的定义
搞一个复杂一点的例子先热身 • 我们想做一个类Circle表示平面直角坐标系上的一个圆(解析几何还记得吧...)。 • 然后我们想做一个求面积的成员函数。 • 我们还要假定所有的Circle的成员变量都是private的,所有的成员函数都是public的,下面开始动手设计这个类。。。
操作步骤 1 • 先搭一个空架子总是没有问题的: class Circle{ }
操作步骤 2 • 设计一下成员变量,这里三个足够了: class Circle{ private double r; //r是半径 private double x,y; //(x,y)是圆心坐标 }
操作步骤 3 • 然后是构造函数,这里先搞两个: class Circle{ private double r; private double x,y; public Circle() { x=0; y=0; r=1; } public Circle(double x, double y, double r) { this.x=x; this.y=y; this.r=r; //一不小心用到了this,阿门,this是啥知道不? } }
操作步骤 4 • 然后是面积函数: class Circle{ private double r; private double x,y; public Circle() { x=0; y=0; r=1; } public Circle(double x, double y, double r) { this.x=x; this.y=y; this.r=r; } public double getArea() { return (Math.PI*r*r); } }
Eclipse测试程序 • 综合一下以上的所有步骤: • 先建一个工程,名字随意,再建一个类,名字叫Circle,然后把Circle的代码贴上; • 再建一个类,名字随意,例如叫Test,勾上自动生成main函数的选项; • 现在你的工程里应该有两个java源文件,两个类,其中一个是用来测试的类,所以它有main函数,Circle类由于没有main函数,所有它是不能运行的。因此启动运行的主类是Test。
Test类的写法 public class Test{ public static void main(String args[]) { Circlec1 = new Circle(); System.out.println(c1.getArea()); Circlec2 = new Circle(3.0,4.0,5.0); System.out.println(c2.getArea()); } }
Circle的改进 • Circle类显然不完善,例如你在Test类里,如果想知道c1的x,y,r三个成员分别是多少,可能吗?如果你想在Test类里,随时修改c1的半径,可能吗? • 答案显然不行,所以,Circle其实还有很多的成员函数需要添加。 • 这里就不演示了,自己动手试试。。。
研究一下对象的引用 • 一个类的成员函数(static除外),是不是一定要new才能被使用呢?例如GUI编程中经常有这种语句: • Container container = getContentPane(); container.add(new Button("hello button")); • 这个container好像就没有new对吧?怎么就可以抓来用呢?难道随便get一下也能用? • 其实这个container已经被new过了,只是你没有看见而已。get一下相当于做对象的引用。这么说好像很复杂,看一个例子:
对象的引用 I 这个例子的输出是:a.x=1 b.x=10 a.x=10 a.x=100 b.x=100 • class A{ • public int x; • } • public class Test{ • public static void main(String args[]) { • A a = new A(); • a.x = 1; System.out.println("a.x="+a.x); • A b = a; //注意这里b不是new出来的 • b.x = 10; System.out.println("b.x="+b.x); • System.out.println("a.x="+a.x); • a.x = 100; System.out.println("a.x="+a.x); • System.out.println("b.x="+b.x); • } • }
对象的引用 II • 总结一下,如果一个对象不是new出来的(例子中的b),而是直接用一个已经new出来的对象(例子中的a)给它赋值,那么这个对象b就叫做a的引用。 • 引用的对象之所以可以使用,不需要new,其实是因为它所指向的对象和赋值给它的对象是同一个!!!也就是它只是给原来的对象又起了一个名字而已。 • 顺便再提一次,对象作为函数参数的时候,实参都是通过引用传递的方式传给形参的。
当继承发生的时候,多态性就来了 • 运行时多态性是面向对象程序设计代码重用的一个最强大机制。 • 动态性的概念也可以被说成“一个接口,多个方法”。 • Java实现运行时多态性的基础是动态方法调度,它是一种在运行时而不是在编译期调用重载方法的机制。 • 顺带提一下,静态的多态性就是函数重载。
方法覆盖 • 当子类和超类定义了同样的函数时,就产生了方法覆盖,如: public class Circle extends GeometricObject { /** Override the toString method defined in GeometricObject */ public String toString() { return super.toString() + "\nradius is " + radius; } } • 当覆盖发生时,在子类中,超类中的函数会被自动屏蔽;如果非要调用超类的函数,只能借助于super关键字。
覆盖和重载是不同的 • 下图中的函数p,左边是覆盖,右边是重载 • 覆盖的特征:函数名相同,参数列表相同 • 重载的特征:函数名相同,参数列表不同
来一个例子 class A{ protected int x; public void f() { x--; } } class B extends A{ int x; void f() {//覆盖了超类的f() this.x++; //这里用的是自己的x,也可以直接写x++ super.x = 100; //这里用的是超类继承来的x super.f(); //这里用的是超类继承来的f() } void f(int i) { //重载了继承来的函数f(),注意二者的参数列表不同 i++; } }
有个复杂点的例子 class A { void fun() { System.out.println(“A”); } } class B extends A { void fun() { System.out.println(“B”); } } class C extends B { void fun() { System.out.println(“C”); } } class Test { public static void main(String[] args) { A a; B b = new B(); C c = new C(); a=b; a.fun(); // 此处将输出Ba=c; a.fun(); // 此处将输出C } }
解释一下 • Java 的动态多态性这种机制遵循一个原则:当超类(父类)对象的引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法; • 当然,这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。你可能会问,如果这个方法没有被覆盖呢?Good question……
还有个例子 class A { void fun() { System.out.println(“A”); } } class B extends A { void funB() { System.out.println(“B”); } } class C extends B { void funC() { System.out.println(“C”); } } class Test { public static void main(String[] args) { A a; B b = new B(); C c = new C(); a=b; a.fun(); // 此处将输出Aa=c; a.fun(); // 此处将输出A } }
抽象类 • 假设我们用GeometricObject这个类作为几何形状的基类,以便可以在GeometricObject的基础上派生出Circle类、Rectangle类等等具体的几何图形。 • 显然,GeometricObject这个类应该包含几何形状的一些共性:颜色、是否填充、创建日期、toString()函数、可以被求周长、可以被求面积…… • 下面我们来设计这个类:
面积和周长的困惑 • 颜色、是否填充、创建日期等函数不难写出相应的Java代码。但是这里有两个函数是没法写代码的,这就是面积和周长函数。这是因为面积和周长跟具体形状有关,在没有具体的形状之前,这两个属性没法计算。 • 现在的问题是,作为一个基类GeometricObject,如果没有设计getArea和getPerimeter函数,显然属于重大设计失误。但是这个函数又确实没法写,怎么办呢?Java说,好办,你可以把那两个函数做成抽象函数嘛。
抽象函数 • 抽象函数是一种特殊函数,它只定义了函数原型,但是不需要实现这个函数。抽象函数用关键字abstract修饰。 • 包含抽象函数的类叫做抽象类,也必须用abstract修饰。 • 我们现在知道,抽象类只是一个半成品,所以Java中,抽象类不可以被new出来。
GeometricObject类的部分代码 1 public abstract class GeometricObject { 2 private String color = "white"; 3 private boolean filled; 4 private java.util.Date dateCreated; 5 6 /** Construct a default geometric object */ 7 protected GeometricObject() { 8 dateCreated = new java.util.Date(); 9 } ... 43 /** Abstract method getArea */ 44 public abstract double getArea(); 45 46 /** Abstract method getPerimeter */ 47 public abstract double getPerimeter(); 48 } 注意这两个抽象函数是没有函数体的。
抽象类的派生类 • 刚才看到,抽象类有几个函数其实是没有实现的;于是Java规定,凡是抽象类的派生类,一定要完成父类的未竟事业。 • 例如从GeometricObject类派生两个类,一个叫做Circle,一个叫做Rectangle。那么这两个类都一定要覆盖(实现)getArea和getPerimeter函数。换句话说,这两个类可以没有其它成员,但是一定要实现这两个成员函数。
Circle类的部分代码 3 public class Circle extends GeometricObject { 4 private double radius; ... 23 /** Return area */ 24 public double getArea() { 25 return radius * radius * Math.PI; 26 } ... 33 /** Return perimeter */ 34 public double getPerimeter() { 35 return 2 * radius * Math.PI; 36 } ... 43 } 注意这两个GeometricObject的抽象函数在这里一定要被实现。
Rectangle类的部分代码 3 public class Rectangle extends GeometricObject { 4 private double width; 5 private double height; ... 35 /** Return area */ 36 public double getArea() { 37 return width * height; 38 } 39 40 /** Return perimeter */ 41 public double getPerimeter() { 42 return 2 * (width + height); 43 } 44 } 注意这两个GeometricObject的抽象函数在这里一定要被实现。
抽象类小结 • 抽象函数只能在抽象类中出现,这些抽象函数在派生类中一定要被实现。 • 抽象类不能被new出来。 • 抽象类可以没有抽象函数,但是即便如此,它也不能被new出来。 • 即使父类不是抽象类,子类也可以定义成抽象类。 • 父类中的非抽象函数,可以被子类覆盖,并修改成抽象函数。 • 抽象类不能被new,但是抽象类是一个合法的数据类型。如: GeometricObject[] objects = new GeometricObject[10];
一个测试程序 1 public class TestGeometricObject { 2 /** Main method */ 3 public static void main(String[] args) { 4 // Declare and initialize two geometric objects 5 GeometricObject geoObject1 = new Circle(5); 6 GeometricObject geoObject2 = new Rectangle(5, 3); 7 8 System.out.println("The two objects have the same area? " + 9 equalArea(geoObject1, geoObject2)); 10 11 // Display circle 12 displayGeometricObject(geoObject1); 13 14 // Display rectangle 15 displayGeometricObject(geoObject2); 16 } 17 18 /** A method for comparing the areas of two geometric objects */ 19 public static boolean equalArea(GeometricObject object1, 20 GeometricObject object2) { 21 return object1.getArea() == object2.getArea(); 22 } 23 24 /** A method for displaying a geometric object */ 25 public static void displayGeometricObject(GeometricObject object) { 26 System.out.println(); 27 System.out.println("The area is " + object.getArea()); 28 System.out.println("The perimeter is " + object.getPerimeter()); 29 } 30 }
接口-1 • 当一个抽象类没有成员变量,并且其全部函数都被做成抽象函数时,Java会建议你使用interface这个关键字来替换class。所以,本质上,接口其实是一个抽象类,它的全部函数都是抽象函数并且没有成员变量(可以有静态常量)。 • 接口由于不带有成员变量,所以它特别适合用来描述一种通用的操作。例如同一个类的两个实例A、B比较大小这个操作,无论A、B是什么类的实例,你都可以定义A>B时,取正值;A=B时,取零;A<B时,取负值。因此比较大小就特别适合被设计成接口。
接口-2 • 设计接口 public interface Comparable { public int compareTo(Object o); } • 上面的interface约等于abstract class。 • 这里的函数compareTo是抽象函数,所以Comparable并不需要把这个函数具体实现(当然也没法实现)。
接口-3 • 我们知道,抽象类在被继承时,派生类必须实现所有的抽象函数。类似的规定在接口上也是如此。 • 接口当然不能被继承,但是它可以被实现implements。当一个接口被实现时,所有它定义的函数都必须被实现(也就是新类必须覆盖这些函数)。 • 由于对象基本上都有可比性,其实很多Java类其实都实现了Comparable接口。
接口-4 • String类和Date类的部分源代码: • 可见String的不同引用之间是可比的。注意下面的取值都是true,所以很大程度上,实现一个接口,跟继承一个类,没有什么区别。
接口-5 • 实现一个接口,下面是一个可比较大小的矩形类: 1 public class ComparableRectangle extends Rectangle 2 implements Comparable { 3 /** Construct a ComparableRectangle with specified properties */ 4 public ComparableRectangle(double width, double height) { 5 super(width, height); 6 } 7 8 /** Implement the compareTo method defined in Comparable */ 9 public int compareTo(Object o) { 10 if (getArea() > ((ComparableRectangle)o).getArea()) 11 return 1; 12 else if (getArea() < ((ComparableRectangle)o).getArea()) 13 return -1; 14 else 15 return 0; 16 } 17 }
接口-6 • 上例中,ComparableRectangle类继承了Rectangle类,并实现了Comparable接口。 • 在Java中,由于仅仅支持单一继承,所以每次只能extends一个类(这样就大大限制了OOP的功能);为了能够实现C++中多重继承的功能,Java允许你一次实现多个接口。