參考鏈接: 在Java中將預定義的類名用作類或變量名
0. 前言?
下面是本篇的內容提綱:??
1. 類?
Java 中類的聲明形式如下所示,變量的聲明和方法的定義意味著只能聲明變量、初始化、方法定義等,而不能在方法外進行賦值等操作。?
class 類名 {
? ? 變量的聲明;
? ? 方法的定義;
}
?
?
?Java 中的類名推薦使用大駝峰命名法,也就是首字母大寫,然后每個單詞都大寫,比如 ChinaMade。?
?
1.1. 變量的聲明?
1.1.1. 實例成員變量?
這種是實例成員變量的聲明,聲明的變量在類內都可以使用。可以聲明的類型包括:整型、浮點型、字符型、邏輯類型、數組、對象、接口等。?
?
?Java 中的成員變量名字推薦使用小駝峰命名法,也就是首字母小寫,后面的每個單詞都大寫,如 chinaMade。另外一行只聲明一個變量。?
?
1.1.2. 類變量?
使用 static 關鍵字聲明類變量,如?
class Point {
? ? int w;? ? ? ? ? ? // 實例變量
? ? static int h;? ? // 類變量
}
?
類變量和實例變量的區別:?
類變量是多個對象共享的,但是實例變量是每個實例對象私有的,每個對象的實例變量互不相同。也就是說當用 new 創建多個不同的對象時,這些對象是共用一個類變量的,假如此時一個對象改變了這個類變量,那么其他對象中的這個類變量自然也是變了的。類的字節碼文件被加載到內存后,類中的類變量也被加載到內存了。類變量的訪問可以通過某個對象訪問,也可以通過類名訪問。?
1.2. 方法的定義?
1.2.1. 實例方法?
返回值類型 方法名(參數列表) {
? ? ......
}
?
int test() {
? ? ......
}
?
void test() {? ? // 不返回任何數據時,使用 void
? ? ......
}
?
方法定義中聲明的變量(包括括號內聲明的變量和參數列表中的變量)稱為局部變量,局部變量具有以下這些性質:?
只在方法中有效;從聲明它的位置之后開始都是有效的;復合語句或循環中的語句聲明的變量只在相應的語句中有效;局部變量和成員變量的名字相同的話,則成員變量會被隱藏。想要使用被隱藏的成員變量,可以使用 this 關鍵字;類變量類似,如果類變量和局部變量相同的話,那么類變量會被隱藏,要想使用隱藏的成員變量,可以使用 ”類名.“ 的方式來調用類變量。成員變量有默認值的,但是局部變量是沒有默認值的;?
1.2.2. 類方法?
同樣使用 static 關鍵字聲明類方法,如?
class Point {
? ? float max(flaot x, float y) {? ? //實例方法
? ? ? ? ......
? ? }
? ??
? ? static float jerry() {? ? ? ? ? ? // 類方法
? ? ? ? ......
? ? }
}
?
類方法與實例方法的區別:?
類方法可以通過對象調用,也可以通過類名調用。實例方法中即可訪問實例變量,也可以訪問類變量。但是類方法不可以操作實例變量,因為在類的字節碼被加載時,類方法也會被分配相應的入口地址,但是這時候對象可能還沒有創建,實例成員變量還沒有分配內存,假如可以訪問實例成員變量的話,那么將會出錯。?
1.2.3. 構造方法?
類中的一種特殊方法,創建對象時會調用該類的構造方法。構造方法需要注意以下幾點:?
?方法名必須與類名相同;? 構造方法沒有類型;? 允許存在多個構造方法,但是參數列表要不同(參數的個數不同或者參數的個數相同但參數列表對應位置上的參數類型不同);? 默認情況下類中都會有一個默認的構造方法,該構造方法是無參數、無實現的。假如不手動編寫構造方法,調用的將會是這個默認構造方法;假如編寫了構造方法,那么這個默認方法將會失效。??
class Point{
? ? int h;
? ? int w;
? ??
? ? Point() {
? ? ? ? this.h = 1;
? ? ? ? this.w = 1;
? ? }
? ??
? ? Point(int h, int w){
? ? ? ? this.h = h;
? ? ? ? this.w = w;
? ? }
? ??
? ? public void printAll() {
? ? ? ? System.out.println(this.h + "? " + this.w);
? ? }
}
?
1.2.4. 方法重載?
一個類中可以有多個同名的方法,但是這些方法的參數列表是不一樣的,即參數的個數不同或者參數的個數相同,但是對應位置上的參數類型不同。方法的返回類型和參數的名字不參與比較。?
2. 修飾符?
2.1. 訪問權限?
2.1.1. public — 可修飾類?
該成員可以被任意類中的方法訪問?
2.1.2. private?
只有同一個類中的成員方法才能訪問私有成員,在其他類中不能直接調用。?
2.1.3. protected?
介于 private 和 public 之間,①同一個包內的所有類的所有方法都能訪問該成員;②如果不在同一個包內的類的方法要訪問該成員,則該類必須是該成員所在類的子類。?
2.1.4. default — 可修飾類?
同一個包內的類的方法都能訪問該成員,可以省略不寫。?
2.1.5. 總結?
權限大小排序:public > protected > default > private?
修飾詞同一個類同一個包子類(不同包)不同包中無繼承關系的類public√√√√protected√√√default√√private√
2.2. final 修飾符?
?final 修飾類,表示這個類不能被繼承,即不能有子類? final 修飾方法,表示這個方法不允許被子類重寫? final 修飾成員變量或者局部變量,表示這是一個常量,不允許被修改??
2.3. abstract 修飾符?
abstract 修飾類表示這是一個抽象類,抽象類更多是用作上轉型的對象,即使用 abstract 類的類型聲明引用變量,然后把子類對象實例賦值給該引用變量。注意用 abstract 修飾之后不能再用 final 修飾一個類。abstract 修飾方法,表示這個方法只允許聲明,而沒有具體實現。同樣注意用 abstract 修飾之后不能再用 final 修飾一個方法,也不能再用 static 修飾。?
abstract class Demo {? ? // abstract 類
? ? abstract int min(int x, int y);? ? // abstract 方法
}
?
abstract 類和方法需要注意以下幾點:?
abstract 類可以有 abstract 方法,也可以有非 abstract 方法;而非 abstract 類中不允許有 abstract 方法。因此,當 abstract 的子類不是抽象類的時候,那么必須重寫父類所有的 abstract 方法;當 abstract 的子類是抽象類的時候,那么可以重寫父類的 abstract 方法也可以選擇繼承。對于 abstract 類,不能使用 new 來創建該類的對象。因此,abstract 更多是用作上轉型的對象。?
2.4. static 修飾符?
就是上面的類成員變量和類方法。?
3. 對象?
3.1. 對象的創建 — new 關鍵字?
new 構造方法 之后,將會在堆區創建一個對象,相當于類的實例,然后會返回堆區的地址(引用)。可以將這個y地址(引用)賦值給某個引用變量。?
// 類的名字 引用變量 = new 構造方法
Point p = new Point();
?
3.2. 對象操作 — . 符號?
引用變量指向某個對象實例之后,那么引用變量可以使用 “.” 來訪問對象實例中的內容,比如調用方法、訪問成員變量等。?
p.printAll()
?
3.3. 對象數組?
對象數組,這個數組中實際上引用變量的數組。?
// 創建了一個對象數組,數組中的每一個元素都是引用變量,都可以指向該類型的對象實例。此時創建之后,每個數組元素并沒有指向。
Point[] ps = new Point[10];
?
ps[0] =? new Point();? ? // ps 對象數組中,索引為 0 的數組元素指向了一個對象實例
?
3.4. this 關鍵字?
this 是指調用該方法或者成員變量的當前的對象實例、構造方法正在創建的對象實例。比如下面這段代碼中,當 p 調用 printAll() 方法的時候,該方法中的 this 等同于 p,this 與 p 指向同一個對象實例。Ponit() 這個構造方法中,其實表示將 1、2 賦值給新創建的對象實例。?
class Point() {
? ? int h;
? ? int w;
? ??
? ? Point() {
? ? ? ? this.h = 1;
? ? ? ? this.w = 2;
? ? }
? ??
? ? void printAll() {
? ? ? ? System.out.println(this.h + " " + this.w);
? ? }?
}
Point p = new Point();
p.printAll();
?
this 關鍵字可以出現在實例方法和構造方法中,但是不能出現在類方法中,這是因為類方法可以通過類名調用,這個時候可能沒有對象實例;就算有了,通過類名方式調用的話,this 也不知道指向哪個實例對象。?
一個實例方法正在調用類中另一個方法的時可以省略 this 關鍵字或類名。?
4. 面向對象?
面向對象的三要素是封裝、繼承和多態。?
4.1. 封裝?
封裝就是將事物抽象為類,把對外接口暴露,將實現和內部數據隱藏。?
4.2. 繼承?
繼承是指這樣一種能力:它使用現有類的所有功能,并在無需重新編寫原來類的情況下對這些功能進行擴展。繼承創建的新類稱為“子類”或者“派生類”;被繼承的類稱為“基類”、“父類”或“超類”。實現繼承一般使用“繼承”或者“組合”來實現,Java 就是使用“繼承“來實現的,關鍵字是 extends。在有些 OOP 語言中子類還可以繼承多個基類,但是 Java 中只允許繼承一個類。子類繼承父類的示例代碼如下所示:?
class 子類名 extends 父類名 {
? ? ......
}
?
class Student extends People {
? ? ......
}
?
繼承需要注意以下幾點:?
?子類聲明繼承父類之后,將繼承父類的成員變量或方法,就相當于子類中聲明了父類成員變量或方法一樣。但是,**子類能繼承的成員變量/方法還受到訪問權限和包位置的影響,**具體的話分為以下兩種情況:?
? 子、父類在同一個包:父類的所有都會被繼承,包括父類中不是 private 的成員變量/方法;子、父類不在同一個包:private 和 default 的成員變量不會被繼承;protected 和 public 訪問權限的成員變量/方法可被子類繼承;? 另外,繼承的成員變量和方法還是屬于父類,子類中只相當于存了這些的引用,實際訪問的還是父類中,只是繼承的成員變量/方法可以通過子類訪問,所以當訪問繼承的方法時,該方法中訪問的成員變量其實是父類的成員變量(因為父類在實現的時候,并不知道自己會被哪些類繼承,所以無法知道除自己之外的成員變量,只能操作自己的成員變量)。 比如 B 是 A 的子類,C 是另一個不與 A、B 有任何關系,那么在 C 類中創建了一個 B 類的實例對象,那么在 C 類中訪問 B 類自己聲明的 protected 變量的話,那么 B 類和 C 類需要放在一個包中;假如 C 類中訪問 B 類繼承的 protected 變量的話(實際上訪問的是 A 類 protected 變量),那么 A 類和 C 類要在同一個包才行。? 子類不繼承父類的構造方法。??
4.2.1. 成員變量的隱藏?
當子類聲明的成員變量的名字和從父類那邊繼承來的成員變量的名字相同時,那么子類就會隱藏繼承的成員變量。那么,子類自己定義的方法可以操作子類繼承的成員變量和子類自己生命的變量,但無法直接訪問子類隱藏的成員變量;子類繼承的方法操作的是子類繼承和隱藏的成員變量,也就是父類自己的成員變量。示例代碼如下所示,子類繼承 method 方法,那么 method 操作的是父類 A 中 a、b。?
public class A {
? ? int a;
? ? int b;
? ? public int method() {
? ? ? ? return a*b;????????
? ? }
}
?
public SubA extends A {
? ? int a;
? ? int b;
? ? public int methodSub() {
? ? ? ? return a + b;? ? // 是指 SubA 中的 a、b
? ? }
}
?
4.2.2. 方法重寫?
同成員變量類似,子類繼承父類的某個方法之后,子類有權利去重寫這個方法。重寫是指,子類中重新定義了一個方法,這個方法的返回值類型、名字、參數列表(參數的個數、參數的類型)都跟父類的方法完全相同(返回值類型不相同的話也行,但是需要確保重寫之后的類型是父類方法類型的子類型)。一旦重寫,那么繼承的父類方法將被隱藏起來。重寫的示例代碼如下所示:?
public class Demo {
? ? public float computer (float x, float y) {
? ? ? ? return x + y;
? ? }
}
?
public class SubDemo extends Demo {
? ? public float computer (float x, float y) {
? ? ? ? return x * y;
? ? }
}
?
重寫需要注意以下幾點:?
也就是你要準備重寫了的話,那么上述需要相同的內容都得確保相同,假如返回值類型相同、名字相同、參數列表不相同,那么這種是方法重載了。假如名字相同、參數列表相同,返回值類型不同了,那么這個是不允許。重寫方法的時候,不允許降低方法的訪問權限,但是可以提高訪問權限。?
4.2.3. super 關鍵字?
super 關鍵字主要是用來操作被隱藏的成員變量/成員方法和構造方法。?
?操作隱藏的成員變量/成員方法 如果子類中想使用被子類隱藏的成員變量或者方法,那么使用 super 關鍵字即可。比如 super.x;
super.play();
? 調用父類的構造方法 子類的構造方法在編寫的時候一定先調用父類的某個構造方法。當然,默認情況下已經相當于調用了父類不帶參數的構造方法,即 super(),所以都可以不用再寫。假如自己要調用父類的某個構造方法的話,那么一定要把調用放在構造方法的第一句。 因為,子類的構造方法在默認情況下會調用父類不帶參數的構造方法,因此在實現類的時候,如果實現了帶參數的構造方法,那么一定要添加一個無參數的構造方法,以防子類出錯。??
4.2.4. 子類與對象?
使用子類的構造方法創建一個子類的對象時,子類和父類的成員變量都分配了內存空間,其實通過上述 super 可以看到,父類的構造方法也被調用了,因此相當父類也是被創建了的。?
下面來闡述一下 instanceof 運算符,這是一個二元運算符,左邊是一個引用變量,右邊是一個類,主要是判斷左邊引用變量所指的實例對象是否是右邊類的一個實例對象,如下所示,輸出為 True。這是因為 sp 指向的還是 new Student() 出來的實例對象。?
Student stu = new Student()
SchoolPeople sp = stu;
if (sp instanceof Student) {
? ? System.out.println("True!");
} else {
? ? System.out.println("False!");
}
================================
True
?
4.3. 多態?
多態性是允許你將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之后,父對象就可以根據當前賦值給它子對象的特性以不同的方式運作。簡單的說,就是把子類的對象實例賦值給父類的引用變量,父類的引用變量就可以訪問子類的成員變量或者方法,因為賦值的子類對象實例不同,因此呈現多態。將子類的引用變量賦值給父類引用之后,父類引用變量稱為子類引用變量的上轉型對象,如下所示,a 被稱為 b 的上轉型對象。?
// Tiger 是 Animal 的子類
Animal a;
Tiger b = new Tiger();
a = b;
// 上述等價于:Animal a = new Tiger();
?
那么上轉型對象需要注意以下幾點:?
上轉型對象不能操作子類新增的成員變量或方法。上轉型對象可以訪問子類繼承或隱藏的成員變量,調用子類繼承的方法或子類重寫的方法(就相當于子類對象調用這些方法)。如果子類重寫了父類的靜態方法,那么子類對象的上轉型對象不能調用子類重寫的靜態方法,只能調用父類的靜態方法。?
?
?這幾點總的來說就是上轉型對象中指向的是子類對象實例,但是引用變量的類型還是父類的,所以只能訪問父類中有的內容,比如子類繼承或隱藏的成員變量,子類繼承的方法或子類重寫的方法,這些父類中都有。另外,為什么靜態方法需要那樣呢?因為靜態方法可以通過類名調用的,靜態方法是屬于這個類的方法,所以當你使用上轉型對象調用靜態方法時相當于父類去調用靜態方法,那調用的自然是父類的靜態方法。?
?
那么怎么理解多態呢?個人理解就是,相同方法,在父子類中展現不同行為特征的能力,這個主要是因為賦值給父類引用變量的子類對象實例不同而呈現不同。那么怎么可以讓相同方法的方法被調用時可以不同呢?那就是讓子類重寫父類中的某個方法(static 方法除外)。 如下所示,那么當把 Teacher 的實例對象傳遞給 sp 的時候,sp.work() 調用的是 Teacher 這個類中的 work() 方法,輸出的是 Teacher Work;當把 Student 的實例對象傳遞給 sp 的時候,sp.work() 調用的是 Student 這個類中的 work() 方法,輸出的是 Student Work,也就呈現多態的特性。需要注意的是,sp.joinClub() 是錯誤的,因為 SchoolPeople 中并沒有這個方法,假如有一次傳給 sp 的是 Teacher 實例對象,但是 Teacher 類中并沒有 joinClub() 這個方法,那這樣調用的話就崩潰了。?
public class SchoolPeople {
? ? public void work() {
? ? ? ? System.out.println("SchoolPeople Work");
? ? }
}
?
public class Student extends SchoolPeople {
? ? public void work() {
? ? ? ? System.out.println("Student Work");
? ? }
? ??
? ? public void joinClub() {
? ? ? ? System.out.println("Join Club");
? ? }
}
?
public class Teacher extends SchoolPeople {
? ? public void work() {
? ? ? ? System.out.println("Teacher Work");
? ? }
? ??
? ? public void attendMeeting() {
? ? ? ? System.out.println("Attend Meeting");
? ? }
}
?
public class MainClass {
? ? public void manager(SchoolPeople sp) {
? ? ? ? sp.work();
? ? }
}
?
?
?其實還有一個向下轉型,就是把父類引用變量指向的實例對象轉化為或者賦值給相應子類的引用變量,這個時候是需要強制類型轉換的。比如下面的代碼:?
?// Student 是 SchoolPeople 的子類
Student stu = new Student();
SchoolPeople sp = stu
Student stu1 = (Student)sp;
?
?當然需要注意,當向下轉型時,父類引用變量指向的實例對象的對象類型要與賦值的子類引用變量的類型是一致的,下面這樣的代碼就是不對了的:?
?// Cat、Dog 是 Pet 的子類
Cat cat = new Cat();
Pet pet = cat;
Dog dog = (Dog)pet;? ? // 這就錯了
?
?
5. 內部類?
5.1. 實名內部類?
實名內部類是指在類中再嵌套一個類的定義。內部類的修飾詞可以是 public、protected、default、private;并且內部類可以訪問外嵌類的成員變量和方法。這邊我將內部類分為兩類:非靜態實名內部類和靜態實名內部類。?
5.1.1. 非靜態實名內部類?
非靜態實名內部類其實也就是沒有 static 關鍵字修飾的內部類,那么這個類類似于一個成員變量。在內部類中需要注意以下幾點:?
?如果成員域具有 static 屬性,那么必須要有 final 屬性,即 final static;? 不能含有 static 屬性的成員方法;? 創建該內部類的對象實例時,需要先創建外部類的對象,然后通過外部類的引用變量創建內部類的對象。如下 public class Test {
? ? public static void main(String[] args) {
? ? ? ? OutClass out = new OutClass();
? ? ? ? OutClass.InnerClass inner = out.new InnerClass();
? ? }
}
?
public class OutClass {
? ? class InnerClass {
? ? ? ? final static int value = 4;
? ? }
}
? 成員的訪問:對于靜態成員,則使用 外部類名.內部類名.成員名;對于非靜態成員,引用變量(指向內部類的對象實例).成員名??
5.1.2. 靜態實名內部類?
靜態實名內部類也就是有 static 關鍵字修飾的內部類,類似于類成員變量。在靜態實名內部類中,需要注意以下幾點:?
?static 內部類不能操作外嵌類的實例成員變量(可以想想類方法,static 內部類在二進制文件被加載的時候就已經分配了,然而此時實例成員變量可能還分配內存等);? 相比非靜態實名內部類來說,靜態實名內部類可以有 static 方法。那么,也就說明靜態實名內部類,其實跟普通類一樣,只是需要注意第一點。? 創建靜態內部類的對象實例時,采用如下方法: public class Test {
? ? public static void main(String[] args) {
? ? ? ? OutClass.InnerClass inner = new OutClass.InnerClass();
? ? }
}
public class OutClass {
? ? static class InnerClass {
? ? ? ? final static int value = 4;
? ? }
}
? 成員的訪問:對于靜態成員,則使用 外部類名.內部類名.成員名;對于非靜態成員,引用變量(指向內部類的對象實例).成員名??
5.2. 匿名內部類?
匿名內部類是沒有類名,在 Java 中經常被用到。匿名內部類如下所示,表示定義了一個沒有名字的子類,并同時創建該子類的一個實例對象。?
new 父類名(父類構造方法參數列表) {
? ? 子類自己的實現;
}
?
父類名也可以是接口名,只是假如是接口的話則不能有參數列表;子類實現中可以繼承父類的方法也可以重寫;父類有抽象方法的話,子類的實現中必須實現抽象方法;不能具有抽象方法或屬性,不能有 static 屬性的成員變量和方法(假如成員變量具有 static 屬性,那么必須要有 final 屬性);匿名類實例對象的方法,通過它父類型的引用變量來訪問。常用在圖形用戶界面設計中,進行各種事件的處理。?
6. 接口?
使用 interface 來定義一個接口,如下所示?
interface Printable {
? ? int MAX = 100;
? ? void add();
? ? float sum(float x, float y);
}
?
接口中只能包含常量的聲明,而沒有變量,所有常量都是 public final static,為了方便這三個修飾符在接口中都可以省略不寫。接口中只有抽象方法,沒有普通的方法,所有的抽象方法都是 public abstract ,為了方便這兩個修飾符在接口中都可以省略不寫。?
6.1. 實現接口?
一個類使用 implements 關鍵字表示實現了某個接口,如?
class Demo implements Printable {}
?
一個類可以實現多個接口,如 class Demo implements Printable, Addable {}父類實現了某接口,子類自然也是實現了該接口,因此子類中不必再顯示地使用 implements 聲明實現了這個接口。并且子類繼承的是父類中已重寫的接口方法和其他方法。接口可以有自己的子接口,用于類的那些修飾符也可以修飾接口,并且效果一樣。由于接口中的方法都是抽象方法,而且是 public。因此,當一個非抽象類實現了某個接口之后,那么這個類必須重寫這個接口中的所有方法,并且重寫的方法的訪問權限都得是 public;假如是一個抽象類實現了某個接口,那么由于抽象類中可以包含抽象方法,因此抽象類既可以重寫接口中的方法,也可以直接擁有接口中的方法。?
6.2. 接口回調?
首先,闡述一下什么是接口變量?接口變量也就是用接口類型聲明的變量。可以將實現了該接口的類的對象實例的引用賦值給該接口變量。如下?
interface Com {
? ? ......
}
?
class ImpleCom implements Com {
? ? ......
}
?
Com com = new ImpleCom();
ImpleCom obj = new ImpleCom();
Com com = obj;
?
接口回調就是指可以通過接口變量調用被類實現的接口的方法,但是類中其他的非接口方法是無法通過接口變量調用的。?
7. Package?
Java 中的 package 類似于 C 語言中的頭文件的概念。在 Java 中將相似功能的類都放在同一個包中,該包的包名就是該類所在的目錄路徑,比如這個類所在的目錄路徑是 com/edu/cn,那么包名就應該是 com.edu.cn。?
7.1. package 和 import 關鍵字?
Java 使用 package 關鍵字顯式得將一個類放入到某個包中,如在一個類的開頭使用 package com.edu.cn 則表示將這個類放到 com.edu.cn 這個包中,com.edu.cn 其實是一個邏輯路徑,表示該類位于邏輯路徑 com/edu/cn 中,為了 import 的正確性,還需確保這個邏輯路徑要跟類所在的物理路徑一樣。那么,當一個類有了包名之后,那么這個時候這個類的全稱應該是 包名.類名。?
package com.edu.cn
public class DemoClass {
? ? ......
}
?
當你想要導入一個包中的某個類的時候,使用 import 關鍵字,import ******。比如該類存放在物理路徑 com/edu/cn 中,該類所處的邏輯路徑也應該是 com/edu/cm,即類所處包名應該是 com.edu.cn,那么則使用 import com.edu.cn.類名 即可正確導入該類。這個時候必須確保 import 導入的類所在的物理路徑和類的邏輯路徑是相同,這是因為 import 將會根據包名去相應的物理路徑中尋找這個類(全稱),比如 import com.edu.cn.A,則會去 com/edu/cn 目錄中尋找 com.edu.cn.A 這個類文件。但是,假如包名和類所處的物理路徑不一致,將會無法正確加載。?
下面我們來看一個例子,有兩個類 DemoClass1 和 DemoClass2,這兩個類所在的 java 文件位于同一物理路徑下,DemoClass2 會用到 DemoClass1,那么 DemoClass2 會去 classpath 設置的環境變量中尋找叫 DemoClass1 的類,但是當前目錄下的類不叫 DemoClass1,而是叫 com.edu.cn.DemoClass1,所以將無法正確加載。?
// DemoClass1 類
package com.edu.cn
public class DemoClass1 {
? ? ......
}
?
// DemoClass2 類
public class DemoClass2 {
? ? public static void main(String args[]) {
? ? ? ? DemoClass1 demo = new DemoClass1();
? ? }
}
?
可以將 DemoClass2 也 package 到 com.edu.cn 中,那么可以正確加載?
package com.edu.cn
public class DemoClass2 {
? ? public static void main(String args[]) {
? ? ? ? DemoClass1 demo = new DemoClass1();
? ? }
}
?
7.2. 導入機制?
Java 中有兩種包的導入機制:?
單類型導入(single-type-import),例如 import java.io.File。單類型導入是僅僅導入一個 public 類或者接口。按需類型導入(type-import-on-demand),例如 import java.io.*。按需導入不是導入一個包下的所有類,而是導入當前類需要使用的類。?
單類型導入和按需類型導入對類文件的定位算法是不一樣的。Java 編譯器會從啟動目錄,擴展目錄和用戶類路徑下去定位需要導入的類,而這些目錄/路徑僅僅給出了類的頂層目錄。編譯器的類文件定位方法大致可以理解為如下公式:頂層路徑名/包名/文件名.class == 絕對路徑。?
?對于單類型導入很簡單,因為包名和文件名都已經確定,所以可以一次性查找定位。? 對于按需類型導入則比較復雜,編譯器會把包名和文件名進行排列組合,然后對所有的可能性進行類文件查找定位。如下面代碼所示: package com;
import java.io.*;
import java.util.*;
?當你的類文件中用到了 File 類,那么可能 File 類的地方如下:?
? File。這個 File 類屬于無名包,就是說 File 類 沒有 package 語句,編譯器會首先搜索無名包com.File。File 類屬于當前包java.lang.File。編譯器會自動導入 java.lang 包java.io.File。java.util.File 然而編譯器找到 java.io.File 類之后并不會停止下一步的尋找,而是把所有的可能性都查找完以確定是否有類導入沖突。假設此時的頂層路徑有三個,那么可能所處的位置會有 3*5=15 處,也就是 15 次查找。如果,查找完成后,編譯器發現了兩個同名的類(個人認為不是類的全稱),那么就會報錯。