套件、載入

了解關鍵字 package 及 import 的Java意義及使用方式。

為什麼需要套件? Why Package?

Java是物件導向的程式語言,我們很清楚寫Java就是在設計類別。想想看,如果一個很大的專案、很大的系統,甚至是好幾個團隊一起共同開發的程式,會有多少個類別?

不說別的,光是安裝好JDK,Java提供你使用的類別就有成千上萬個(我也沒數過)。

這些類別如果散落一地,那程式勢必會很凌亂,且類別間彼此負責的部份不容易劃分。更慘的是,如果你開發一個類別叫做Human,那你所有的共同開發者都不能創造一個叫Human名稱的類別,想起來就很不方便。

因此Java設計了一個機制來管理(存放)類別。

套件 Package

一個套件可以存放多個類別,套件被設計成與檔案系統的目錄相對應。

對應到檔案系統上就是,一個目錄可以存放多個類別檔案。

應用上,相似功能的類別可以放在一起,方便管理。或是同一個團隊的類別放在一起,方便資源的存取,套件與存取權限關係重大,清楚的套件劃分可以更容易找出程式錯誤或保護團隊智慧財產。

舉個現實中可能會遇到的例子:

yubin跟tina是團隊的工程師,一起負責同一個專案。我們很清楚物件導向就是把真實世界抽象化成類別,但每個人從不同的角度去思考的結果不一樣。設計類別的方式也不盡相同,所以他們決定先各自開發自己的類別,隨後整合的時候再做進一步討論。

yubin 負責的Human.java:

package yubin;

public class Human {
    String name;
    int age, height, weight;
    public Human(String str) {
        name = str;
    }// end of constructor(String)
    public void setValue(int a, int h, int w) {
        age = a;
        height = h;
        weight = w;
    }// end of setValue(int,int)
}// end of class Human

tina 負責的Human.java:

package tina;

public class Human {
    String name;
    int age,height;
    public Human(String str){
        name = str;
    }// end of constructor(String)
    public void setValue(int a,int h){
        age = a;
        height = h;
    }// end of setValue()
}//end of class Human

整合測試用的Test.java:

package run;

public class Test {
    public static void main(String[] args) {
        tina.Human h1 = new tina.Human("小婷");
        h1.setValue(18, 162);
        yubin.Human h2 = new yubin.Human("小木");
        h2.setValue(22, 178, 63);
    }// end of main(String[])
}// end of class Test

好的,我們可以看到我們的套件關鍵字:package

package 套件名稱;

利用package就可以設定某個類別屬於某個套件。記得必須寫在最外層的class之外,且需置於最頂層(通常放在第一行)。

然而package的結構與檔案目錄相對應,因此若上面三個檔案設了不同package,但放在同一個目錄,編譯會發生錯誤,因為package的設定與該檔案所在位置是對應的。

package目錄就是對應到檔案系統的目錄(資料夾)。

看完檔案系統的結構後,來討論程式的部份。

這個Test.java會分別創造tina套件裡的Human與yubin套件裡的Human物件出來。

package run;

public class Test {
    public static void main(String[] args) {
        tina.Human h1 = new tina.Human("小婷");
        h1.setValue(18, 162);
        yubin.Human h2 = new yubin.Human("小木");
        h2.setValue(22, 178, 63);
    }// end of main(String[])
}// end of class Test

我們可以看到,雖然身處不同套件(也就是不同目錄),但依然可以存取到該類別,創造物件。

要做到這樣需要滿足兩個條件: 1. 目標類別可以被存取 2. 正確的指到該類別

第一點,攸關目標類別的『存取修飾子』設定,仔細看兩個Human類別在最外層的類別定義:

public class Human{
    // code...
}

其中關鍵字public就是存取修飾子,代表這這個類別,可以被其他套件的類別存取。Human類別中的建構子、物件方法也是一樣的意思,詳細存取修飾子會在下一章節討論。

第二點,正確的指到該類別。就是說,雖然身處不同套件中,但類別間彼此還是可以用『完整類別路徑』去做存取。

套件名稱.類別名稱

還是靠那個點運算子『.』,如果套件裡面還有套件,那就繼續點下去。

套件名稱1.套件名稱2.套件名稱3.套件名稱4.套件名稱n.類別名稱

利用『完整類別路徑』就可以存取到相應的類別。但是不覺得麻煩嗎?想建個物件都要打一堆東西(套件名稱又多又長的時候會抓狂)。因此Java就提供了一個機制,讓你可以載入該類別到你撰寫的程式中,就不用一直使用完整類別路徑來使用該類別。

import

關鍵字import,讓你可以把你的目標類別載入你的程式中,你就可以直接使用該類別,彷彿這個目標類別就在你身邊一樣。

舉個例子,假設tina和yubin討論結果,Human類別不需要體重(weight)的欄位,因此決定採用tina套件中的Human.java,那在套件run的Test.java測試程式中,就不用每次都打完整類別路徑,可以利用關鍵字import。

package run;
import tina.Human;

public class Test {
    public static void main(String[] args) {
        Human h1 = new Human("小婷");
        h1.setValue(18, 162);
        Human h2 = new Human("小木");
        h2.setValue(22, 178);
    }// end of main(String[])
}// end of class Test

import 用法:

import 完整類別路徑;

上述Test程式中,在第二行import了tina套件中的Human類別,所以在程式中就可以直接使用Human,這個Human類別被定義在tina套件裡。

這是import關鍵字最主要的功能:把目標套件的某個類別載入程式中。

此外完整路徑的寫法,可以搭配萬用字元『*』:

import tina.*;    // 表示把tina套件中『所有類別』import進來

當然,就算import了某個套件,其他套件還是一樣可以用完整類別路徑做存取:

package run;
import tina.Human;

public class Test {
    public static void main(String[] args) {
        Human h1 = new Human("小婷");
        h1.setValue(18, 162);
        Human h2 = new Human("小木");
        h2.setValue(22, 178);
        yubin.Human h3 = new yubin.Human("阿杉");  
        h3.setValue(10, 100, 100);
        // 物件h3 雖然也叫Human但跟h1、h2是完全不一樣的東西
    }// end of main(String[])
}// end of class Test

上述程式中,物件h3使用的是yubin套件中的Human,這樣是沒問題的。所以就算有類別不會被使用也不用急著刪掉,說不定之後還能派上用場(程式都是工程師的心血阿)。

宣告順序

這個章節我們提到了package、import的使用,但在java的類別定義中,這是有規範好的先後順序。

package 自身套件;
import 目標套件.目標類別;
class 類別名{
    // code...
}

這三個定義順序不能互換,畢竟編譯器是由上往下編譯,不知道package怎麼import東西,沒有先知道import了哪些東西怎麼去解析class裡面的程式碼。

預設套件 Default Package

在講這個章結前,其實我們有寫過一些程式了,但我們都沒有做package的宣告。

沒有放在某個套件中,自然不用宣告package是什麼,在Java中這種沒有存在於任何套件的都被視為在預設套件中(default package)。

※注意,此處題到的根目錄,指的是程式編譯的根目錄,並不是硬碟C槽。

預設已經被import的套件

現在我們知道可以利用import來存取各式各樣的類別了,但回過頭來想,其實我們早就已經用過了!? 還用的很自然XD

來看一下到底是什麼神奇的東西:

System.out.println("Hello Java");

哇塞,這就是我們的第一個程式就說過的println()方法,我們都知道它可以印出東西,也知道System是物件,out是System的一個static欄位,println()是out這個物件的一個方法。

但,這個System是你寫的嗎?沒有在程式裡,也沒有用完整類別路徑存取,怎麼能動?

嗯,當然又是熱心的Java在搞鬼,在 java.lang 套件裡面,定義了很多常常常常會用到的類別,而且都是Java語言的必要基礎類別,所以Java預設都會幫你import java.lang.*;

程式設計師不用自己import,也不用特地用完整類別路徑去存取,直接呼叫就可以。

import java.lang.*; // Java會自動幫你加上這行(你看不到),不寫也沒關係
class Test {
    public static void main(String[] args) {
        java.lang.System.out.println("Hello Java"); //完整類別路徑
        System.out.println("Hello Java");  // 一般寫法
    }// end of main(String[])
}// end of class Test

執行結果:

Hello Java
Hello Java

Last updated