作者dasea (植栽鸡肉饭)
看板ncyu_phyedu
标题[讨论] 封装
时间Sat Jan 30 14:55:33 2010
封装的意义
物件导向程式设计的原则之一, 是让实作和界面分开, 以便让同一界面但不同的实作的物
件能以一致的面貌让外界存取, 为了达到此目标, java允许设计人员规范类别成员以及类
别本身的存取限制。
类别成员的存取
所谓封装(Encapsulation),是指class A的设计者可以指定其他的class能否存取A的某个
member。Java定义了四种存取范围:
private:只有A自己才可以存取, 使用keyword private
package:只有和A同一个package的class才可以存取, 没有相对应的keyword
protected:只有同一个package或是A的子类别才可以存取, 使用keyword protected
public:所有的class都可以存取, 使用keyword public
public class EncapsulationExample {
private int privateVariable;
int packageVariable;
protected int protectedVariable;
public int publicVariable;
private int privateObjectMethod() {}
int packageObjectMethod() {}
protected int protectedObjectMethod();
public int publicObjectMethod();
static private int privateClassMethod() {}
static int packageClassMethod() {}
static protected int protectedClassMethod();
static public int publicClassMethod();
}
如果member的前面没有private,protected,public其中一个修饰字,则该member的存取范
围就是package。从以上的叙述,读者可以推知这四种存取范围的大小是public >
protected > package > private。
Package的定义
所谓package,可以想成是在设计或实作上相关的一群class。要宣告某class属於某
package,其语法为
package myPackage;
public class MyClass {
}
如果没有宣告package的话,如下面这两个class,就会被归类为「匿名」的package:
// in file ClassA.java
public class ClassA {
public static void main(String[] argv) {
ClassB x = new ClassB();
}
}
// in file ClassB.java
public class ClassB {
}
为了让JVM在执行期间能够找到所需要的class,同一个package的class会放在同一个目录
下。不过只知道目录的名称还不够,还需要指定该目录在档案系统内的路径。classpath这
个环境变数是由多个以分号隔开的路径所组成,JVM透过classpath配合package的名称就可
以找到所需要的class。如果我们只用到Java标准的程式库,则不需要指定classpath。指
定classpath环境变数时,要特别注意的是,不要忘了把.(代表目前的工作目录)放到最前面
,否则就找不到「匿名」package里的class(别忘了绝大部分简单的范例都没有宣告
package,所以都是匿名的)。
classpath=.;n:\计网中心系统组\project;
classpath里除了路径外,也可以指定zip或jar(java archive format)格式的档案。zip和
jar可以把目录及其子目录内的档案都压缩起来,因此可以透过这类档案抓到所需的class
档。
classpath=.;c:\mylib.zip;c:\otherlib.jar;n:\计网中心系统组\project;
package的宣告可以用.号构成复杂的package tree。
package mylib.package1
public class A {}
package mylib.package2
public class B {}
属於mylib.package1的class会放在mylib目录下的package1目录内。Java所提供的标准应
用程式介面(Application Programming Interface, API)就是一个复杂的package tree。
同一个.java档里面, 可以定义好几个class, 但最多只能有一个宣告为public class。此
限制是因为java希望每一个编译的单元(.java档)都有唯一的界面宣告。那麽public
class和class的区别何在? non public class只能给同一个package内的其他class引用,
public class则可以给任何class引用。
Package的引用
假如Class A用到package myPackage里的Class B, 为了检查A使用到B的部分是否符合B的
原始定义, 诸如方法存不存在, 参数正不正确等问题, Compiler必须引入class B的定义
, 以便进行编译时期的检查。引入的语法为
import myPackage.B;
这里要强调的是, import指令告知Compiler在Compiler time所要检查的类别定义在哪里
。但有时候我们编译的环境和执行的环境可能不同, 例如编译时用JDK 1.4, 执行时却用
JDK 1.2, 若程式使用到JDK 1.4才有的API, 那麽会在执行期间产生错误。
有时候我们会引用相当多个同属某package的类别, 如果要一个一个import, 会很烦人,
因此Java允许我们使用万用字元*来代表某package里的所有class:
import myPackage.*;
public class A {
public static void main(String[] argv) {
B x = new B();
}
}
眼尖的读者会发现我们并没有import String的定义啊, 怎麽都没有问题? 由於写程式多
多少少都会用到一点系统提供的程式库, 如果连很简单的程式都要import一堆class, 也
真烦人。因此Java Compiler会自动帮我们引入java.lang.*
public class Hello {
public static void main(String[] argv) {
System.out.println("Hello World.");
}
}
就等同
import java.lang.*;
public class Hello {
public static void main(String[] argv) {
System.out.println("Hello World.");
}
}
由於class是放在类似树状结构的package tree里面, 因此引用的class应该加上完整的
package路径才是全名, 例如
public class Hello {
public static void main(java.lang.String[] argv) {
java.lang.System.out.println("Hello World.");
}
}
只要不会造成混淆, 一般我们都使用省略package路径的class简称。但是如果我们
import P1和P2两个package, 而这两个package碰巧都定义了同名的class A, 则用到A的
地方就比需以P1.A和P2.A来区别了。
下面的程式码哪里有错?
package p1;
public class Access {
private static void f1() {}
static void f2() {}
protected static void f3() {}
public static void f4() {
Access.f1();
}
}
package p1;
public class Example1 {
public static void main(String[] argv) {
Access.f1();
Access.f2();
Access.f3();
Access.f4();
}
}
package p2;
import p1.*;
public class Example2 {
public static void main(String[] argv) {
Access.f1();
Access.f2();
Access.f3();
Access.f4();
}
}
package p1;
public class Example3 extends Access {
public static void main(String[] argv) {
Access.f1();
Access.f2();
Access.f3();
Access.f4();
}
}
package p2;
import p1.*;
public class Example4 extends Access {
public static void main(String[] argv) {
Access.f1();
Access.f2();
Access.f3();
Access.f4();
}
}
Java档和Class档的相依性
传统程式开发的流程是Compile个别Source Code,然後Link所有的Object Code成为执行档
。对大型的应用程式来说,常见的问题之一是如何确定Link时所需的Object Code是由最新
的Source编译而来?尤其模组间存在相依性,如模组A可能用到模组B里的函数,如果B的函数
有修改参数,则A模组也要重新编译。换句话说单看source code和object code的产生时间
是不行的。最简单的方法就是在Link前将所有的Source Code重新编译一次,但这样做有以
下几个问题:
重新编译大型专案全部的程式码可能会浪费不少时间
要对每个Source File下达编译指令,不但费时,而且容易遗漏。即使写个批次程式,也要随
时记得纳入新的原始档
由於这些问题的存在,某些原始码管理系统便因应而生,例如UNIX上的SCCS(Source Code
Control System)。Java Compiler具有下面两个功能,可以在没有原始码管理系统的情况
下,也能解决上述问题:
可使用javac *.java来编译目前目录下的所有java档案
编译A.java时,会自动检查A所用到的其他class B,比较B.java和B.class的产生时间,如果
B.java比较新则B.java就会被重新编译
如果应用软体只有单一的进入点,例如class A的public static void main(String[]
argv),则只要编译A.java就会自动编译其他需要重新编译的.java档。如果应用软体有两
个以上的进入点,如网路程式的client端和server端的进入点会不一样,只要写个批次档编
译相关进入点的.java档即可。
用Link List实作Stack
Link List Stack 示意图(点图放大)
public class Stack {
private Node head;
private int size;
class Node {
Object data;
Node next;
}
public void push(Object s) {
Node tmp = new Node();
tmp.next = head;
tmp.data = s;
size++;
head = tmp;
}
public Object pop() {
Object tmp = head.data;
head = head.next;
size--;
return tmp;
}
}
--------------------------------------------------------------------------------
Link List Stack Push Step 1(点图放大)
Link List Stack Push Step 2(点图放大)
Link List Stack Push Step 3(点图放大)
Link List Stack Pop(点图放大)
--------------------------------------------------------------------------------
public class Example {
public static void main(String[] argv) {
Stack s1 = new Stack();
Stack s2 = new Stack();
s1.push("abc");
s1.push("def");
s2.push("123");
s2.push("456");
}
}
用Link List实作Queue
public class Queue {
private Node head, tail;
private int size;
class Node {
Object data;
Node next;
}
public void put(Object s) {
Node tmp = new Node();
tmp.data = s;
if (tail != null) {
tail.next = tmp;
} else {
head = tmp;
}
tail = tmp;
size++;
}
public Object get() {
Object tmp = head.data;
head = head.next;
if (head == null) {
tail = null;
}
size--;
return tmp;
}
}
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 61.58.22.74