Java 包(學習 Java 編程語言 035)

Java 容許使用包(package)將類組織在一個集合中。藉助包能夠方便地組織本身的代碼,並將本身的代碼與別人提供的代碼庫分開管理。java

標準的 Java 類庫分佈在多個包中,包括 java.long、java.util 和 java.net 等。標準的 Java 包具備一個層次結構。全部標準的 Java 包都處於 java 和 javax 包層次中。程序員

1. 包名

使用包的主要緣由是確保類名的惟一性。事實上,爲了確保包名的絕對惟一性,要用一個因特網域名(這顯然是惟一的)以逆序的形式做爲包名,而後對於不一樣的工程使用不一樣的子包。例如,域名 xiang117.com,逆序來寫,獲得包名 com.xiang117。而後能夠追加一個工程名,如 com.xiang117.corejava。若是在把 Employee 類放在這個包裏,那麼這個類的 「徹底限定」 名就是 com.xiang117.corejava.Employeesql

從編譯器的角度看來,嵌套的包之間沒有任何關係。例如,java.util 包與 java.util.jar 包毫無關係。每個包都是獨立的類集合。ide

2. 類的導入

一個類可使用所屬包中的全部類,以及其餘包中的公共類(public class)。
能夠採用兩種方式訪問另外一個包中的公有類:this

  1. 使用徹底限定名(fully qualified name),即包名後跟着類名。
    java.time.LocalData today = java.time.LocalDate.now();
  2. 使用 import 語句。 .net

    可使用 import 語句導入一個特定的類或者整個包。import 語句應該位於資源文件的頂部(但位於 package 語句的後面)。code

    import 語句是一種引用包中各個類的簡捷方式。一旦使用了 import 語句,在使用類時,就沒必要寫出類的全名了。 資源

    import 語句的惟一好處是簡捷。可使用簡短的名字而不是完整的包名來引用一個類。 get

可使用下面語句導入 java.time 包中的全部類。
import java.time.*;
就可使用
LocalDate today = localDate.now();
無需在前面加上包的前綴。編譯器

java.time.* 的語法比較簡單,對代碼的規模也沒有任何負面影響。固然,若是可以明確地指出所導入的類,將會使代碼的讀者更加準確地知道你使用了哪些類。

須要注意的是,只能使用星號()導入一個包,而不能使用 `import java.import java..` 導入以 java 爲前綴的全部包。

能夠導入一個包中的特定類:
import java.time.LocalDate;

在大多數狀況下,能夠只導入所須要的包,並沒必要過多地考慮它們。但在發生命名衝突的時候,就要注意包了。例如,java.util 和 java.sql 包都有 Date 類。若是在程序中導入了這兩個包:
import java.util.*;
import java.sql.*;
在程序使用 Date 類的時候,就會出現一個編譯錯誤:
Date today; // Error--java.util.Date or java.sal.Date?
此時編譯器沒法肯定程序使用的是哪個 Date 類。能夠採用增長一個特定的 import 語句來解決這個問題:
import java.util.*;
import java.sql.*;
import iava.util.Date;
若是這兩個 Date 類都須要使用,須要在每一個類名的前面加上完整的包名。
java.util.Date deadline = new java.util.Date();
java.sql.Date today = new java.sql.Date();

在包中定位類是編譯器(compiler)的工做。類文件中的字節碼老是使用完整的包名引用其餘類。

3. 靜態導入

有一種 import 語句容許導入靜態方法和靜態字段,而不僅是類。

若是在源文件頂部,添加一條指令:
import static java.lang.System.*;
就可使用 System 類的靜態方法和靜態字段。而沒必要加類名前綴:
out.println("Goodbye, World!"); // i.e., System.out
exit(0); // i.e., System.exit

能夠導入特定的方法或字段:
import static java.lang.System.out;

實際上,是否有不少程序員想要用簡寫 System.out 或 System.exit,這一點讓人懷疑。這樣寫出的代碼看起來不太清晰。不過,
sqrt(pow(x, 2) + pow(y, 2))
看起來比
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
清晰得多。

4. 在包中增長類

要想將類放入包中,就必須將包的名字放在源文件的開頭,即放在定義這個包中各個類的代碼以前。例如在 com.xiang017.corejava 包中增長 Employee 類:

Employee.java

package com.xiang017.corejava;

public class Employee
{
    ...
}

若是沒有在源文件中放置 package 語句,這個源文件中的類就屬於無名包(unnamed package)。無名包沒包名。

PackageTest.java

import com.xiang017.javacore.*;

public class PackageTest {
    public static void main(String[] args) {

        Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
    }
}

將源文件放到與完整包名匹配的子目錄中。例如,com.xiang017.corejava 包中的全部源文件應該放置在子目錄 com/xiang017/corejava 中。編譯器將類文件也放在相同的目錄結構中。例如:

. (基目錄)
│  PackageTest.java
│
└─com
    └─xiang017
        └─javacore
                Employee.java

要想編譯這個程序,只需切換到基目錄,並運行命令:
javac PackageTest.java
編譯器就會自動地查找文件 com/xiang017/corejava/Employee.java 並進行編譯。編譯後的結果以下:

. (基目錄)
│  PackageTest.class
│  PackageTest.java
│
└─com
    └─xiang017
        └─javacore
                Employee.class
                Employee.java

要運行這個程序,須要在基目錄下執行命令:
java PackageTest

假如 PackageTest.java 源文件沒有在基目錄下,在 mycompany 包下,如:

.(基目錄)
|-- com/
     |-- corejava/
     |      |-- Employe.java
     |      |-- Employe.class
     |
     |-- mycompany/
           |-- PackageTest.java
           |-- PackageTest.class

仍然要從基目錄編譯和運行類,即包含 com 目錄的目錄:
javac com/mycompany/PackageTest.java
java com.mycompany.PackageTest

須要注意,編譯器對文件(帶有文件分隔符的擴展名 .java 的文件)進行操做。而 Java 解釋器加載類(帶有 . 的分隔符)。

警告: 編譯器在編譯源文件的時候不檢查目錄結構。例如,假定有一個源文件開頭有如下指令:
package com.mycompany;
即便這個源文件不在子目錄 com/mycompany 下,也能夠進行編譯。若是它不依賴於其餘包,就能夠經過編譯而不會出現編譯錯誤。可是,最終的程序將沒法運行,除非先將全部的類文件移到正確的位置上。若是包與目錄不匹配,虛擬機就找不到類。

PackageTest.java

import com.xiang017.javacore.*;

public class PackageTest {
    public static void main(String[] args) {

        Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
        System.out.println("name=" + harry.getName() +
                ",salary=" + harry.getSalary() +
                ",hireDay=" + harry.getHireDay());
    }
}

com/mycompany/Employee.java

package com.xiang017.javacore;

import java.time.LocalDate;
import java.util.Objects;

public class Employee {

    private String name;

    private double salary;

    private LocalDate hireDay;

    public Employee(String name, double salary, int year, int month, int day) {
        this.name = name;
        this.salary = salary;
        hireDay = LocalDate.of(year, month, day);
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public LocalDate getHireDay() {
        return hireDay;
    }

    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }
}

5. 包訪問

標記爲 public 的部分能夠由任意類使用;標記爲 private 的部分只能由定義它們的類使用。若是沒有指定 public 或 private,這個部分(類、方法或變量)能夠被同一個包中的全部方法訪問。

沒有標記修飾符的類只有在同一個包中其餘類能夠訪問。對於類來講,這種默認方式是合乎情理的。可是,對於變量來講就有些不適宜了,變量必須顯式地標記爲 private,否則的話將默認爲包可見。這樣會破壞封裝性。

在默認狀況下,包不是一個封閉的實體。也就是說,任何人均可以向包中添加更多的類。

從 1.2 版開始,JDK 的實現者修改了類加載器,明確禁止加載包名以 「java.」 開頭的用戶自定義類!固然,用戶自定義的類沒法從這種保護中受益。另外一種機制讓 JAR 文件聲明包爲密封的(sealed),以防止第三方修改,但這種機制已通過時。如今應當使用模塊封裝包。

相關文章
相關標籤/搜索