1. 程式人生 > >import在java中的用法

import在java中的用法

import與package機制相關,這裡先從package入手,再講述import以及static import的作用。

package

C/C++ 的 #include會把所包含的內容在編譯時新增到程式檔案中,而java的import則不同。

這裡我們先了解一下Java 的 package 到底有何用處。

package名稱就像是我們的姓,而class名稱就像是我們的名字 。package和package的附屬關係用”.”來連線,這就像是複姓。比如說 java.lang.String就是複姓 java.lang,名字為 String 的類別;java.io.InputStream 則是複姓 java.io,名字為 InputStream的類別。

Java 會使用 package 這種機制的原因也非常明顯,就像我們取姓名一樣 ,光是一間學校的同一屆同學中,就有可能會出現不少同名的同學,如果不取姓的話,那學校在處理學生資料,或是同學彼此之間的稱呼,就會發生很大的困擾。相同的,全世界的 Java 類數量,恐怕比日本人還多,如果類別不使用package名稱,那在用到相同名稱的不同類時, 就會產生極大的困擾。所以package這種方式讓極大降低了類之間的命名衝突。

Java 的package名稱我們可以自己取,不像人的姓沒有太大的選擇 ( 所以出現很多同名同姓的情況 ),如果依照 Sun 的規範來取套件名稱,那理論上不同人所取的套件名稱不會相同 ( 需要的話請參閱 “命名慣例” 的相關文章 ),也就不會發生名稱衝突的情況。

可是現在問題來了,因為很多package的名稱非常的長,在程式設計時,要使用一個類要將多個包名.類名完全寫出,會讓程式碼變得冗長,減低了簡潔度。例如

java.io.InputStream is = java.lang.System.in;
java.io.InputStreamReader isr= new java.io.InputStreamReader(is);
java.io.BufferedReader br = new java.io.BufferedReader(isr);
  • 1
  • 2
  • 3

顯得非常麻煩,於是Sun公司就引入了import。

import

import就是在java檔案開頭的地方,先說明會用到那些類別。 
接著我們就能在程式碼中只用類名指定某個類,也就是隻稱呼名字,不稱呼他的姓。

首先,在程式開頭寫:

import java.lang.System;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
  • 1
  • 2
  • 3
  • 4

於是我們就可以在程式中這樣寫到:

InputStream = System.in;
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
  • 1
  • 2
  • 3

一個java檔案就像一個大房間,我們在門口寫著在房間裡面的class的姓和名字,所以在房間裡面提到某個class就直接用他的名字就可以。例如:

System 就是指 java.lang.System,而 InputStream 就是指 java.io.InputStream。

但是如果一個java檔案裡面有多個同個“姓”,即包名相同的類(例如上面的InputStream,InputStreamReader,BufferedReader都是java.io中的類),我們一一寫出顯得比較繁雜,所以Sun就讓我們可以使用

import java.lang.*;
import java.io.*;
  • 1
  • 2

表示檔案裡面說到的類不是java.lang包的就是java.io包的。編譯器會幫我們選擇與類名對應的包。

那我們可不可以再懶一點直接寫成下面宣告呢?

import java.*;
  • 1

歷史告訴我們,這樣是不行的。因為那些類別是姓 java.io 而不是姓 java。就像姓『諸葛』的人應該不會喜歡你稱他為『諸』 先生吧。這樣寫的話只會將java包下的類宣告,而不不會宣告子包的任何類。

這裡注意,java.lang包裡面的類實在是太常太常太常用到了,幾乎沒有類不用它的, 所以不管你有沒有寫 import java.lang,編譯器都會自動幫你補上,也就是說編譯器只要看到沒有姓的類別,它就會自動去lang包裡面查詢。所以我們就不用特別去 import java.lang了。

一開始說 import 跟 #include 不同,是因為import 的功能到此為止,它不像#include 一樣,會將其他java檔案的內容載入進來。import 只是讓編譯器編譯這個java檔案時把沒有姓的類別加上姓,並不會把別的檔案程式寫進來。你開心的話可以不使用import,只要在用到類別的時候,用它的全部姓名來稱呼它就行了(就像例子一開始那樣),這樣跟使用import功能完全一樣。

import的兩種匯入宣告

  • 單型別匯入(single-type-import) 
    (例:import java.util.ArrayList; )
  • 按需型別匯入(type-import-on-demand) 
    (例:import java.util.*;)

有如下屬性:

  1. java以這樣兩種方式匯入包中的任何一個public的類和介面(只有public類和接口才能被匯入)

  2. 上面說到匯入宣告僅匯入宣告目錄下面的類而不匯入子包,這也是為什麼稱它們為型別匯入宣告的原因。

  3. 匯入的類或介面的簡名(simple name)具有編譯單元作用域。這表示該型別簡名可以在匯入語句所在的編譯單元的任何地方使用.這並不意味著你可以使用該型別所有成員的簡名,而只能使用型別自身的簡名。 
    例如: java.lang包中的public類都是自動匯入的,包括Math和System類.但是,你不能使用它們的成員的簡名PI()和gc(),而必須使用Math.PI()和System.gc().你不需要鍵入的是java.lang.Math.PI()和java.lang.System.gc()。

  4. 程式設計師有時會匯入當前包或java.lang包,這是不需要的,因為當前包的成員本身就在作用域內,而java.lang包是自動匯入的。java編譯器會忽略這些冗餘匯入宣告(redundant import declarations)。即使像這樣 
    import java.util.ArrayList; 
    import java.util.*; 
    多次匯入,也可編譯通過。編譯器會將冗餘匯入宣告忽略.

static import靜態匯入

在Java程式中,是不允許定義獨立的函式和常量的。即什麼屬性或者方法的使用必須依附於什麼東西,例如使用類或介面作為掛靠單位才行(在類裡可以掛靠各種成員,而接口裡則只能掛靠常量)。

如果想要直接在程式裡面不寫出其他類或介面的成員的掛靠單元,有一種變通的做法 : 
將所有的常量都定義到一個接口裡面,然後讓需要這些常量的類實現這個介面(這樣的介面有一個專門的名稱,叫(“Constant Interface”)。這個方法可以工作。但是,因為這樣一來,就可以從“一個類實現了哪個介面”推斷出“這個類需要使用哪些常量”,有“會暴露實現細節”的問題。

於是J2SE 1.5裡引入了“Static Import”機制,藉助這一機制,可以用略掉所在的類或介面名的方式,來使用靜態成員。static import和import其中一個不一致的地方就是static import匯入的是靜態成員,而import匯入的是類或介面型別

如下是一個有靜態變數和靜態方法的類

package com.assignment.test;

public class staticFieldsClass {
    static int staticNoPublicField = 0; 
    public static int staticField = 1;
    public static void staticFunction(){}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

平時我們使用這些靜態成員是用類名.靜態成員的形式使用,即staticFieldsClass.staticField或者staticFieldsClass.staticFunction()。

現在用static import的方式:

//**精準匯入**
//直接匯入具體的靜態變數、常量、方法方法,注意匯入方法直接寫方法名不需要括號。
import static com.assignment.test.StaticFieldsClass.staticField;
import static com.assignment.test.StaticFieldsClass.staticFunction;

//或者使用如下形式:
//**按需匯入**不必逐一指出靜態成員名稱的匯入方式
//import static com.assignment.test.StaticFieldsClass.*;

public class StaticTest {
    public static void main(String[] args) {
        //這裡直接寫靜態成員而不需要通過類名呼叫
        System.out.println(staticField);
        staticFunction();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

這裡有幾個問題需要弄清楚:

  1. Static Import無權改變無法使用本來就不能使用的靜態成員的約束,上面例子的StaticTest和staticFieldsClass不是在同一個包下,所以StaticTest只能訪問到staticFieldsClass中public的變數。使用了Static Import也同樣如此。

  2. 匯入的靜態成員和本地的靜態成員名字相同起了衝突,這種情況下的處理規則,是“本地優先。

  3. 不同的類(介面)可以包括名稱相同的靜態成員。例如在進行Static Import的時候,出現了“兩個匯入語句匯入同名的靜態成員”的情況。在這種時候,J2SE 1.5會這樣來加以處理:

    1. 如果兩個語句都是精確匯入的形式,或者都是按需匯入的形式,那麼會造成編譯錯誤。
    2. 如果一個語句採用精確匯入的形式,一個採用按需匯入的形式,那麼採用精確匯入的形式的一個有效。

大家都這麼聰明上面的幾個特性我就不寫例子了。

static import這麼叼那它有什麼負面影響嗎?

答案是肯定的,去掉靜態成員前面的型別名,固然有助於在頻繁呼叫時顯得簡潔,但是同時也失去了關於“這個東西在哪裡定義”的提示資訊,理解或維護程式碼就呵呵了。 
但是如果匯入的來源很著名(比如java.lang.Math),這個問題就不那麼嚴重了。

按需匯入機制

使用按需匯入宣告是否會降低Java程式碼的執行效率?

絕對不會!

一、import的按需匯入

import java.util.*;

public class NeedImportTest {
    public static void main(String[] args) {
        ArrayList tList = new ArrayList();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

編譯之後的class檔案 :

//import java.util.*被替換成import java.util.ArrayList
//即按需匯入編譯過程會替換成單型別匯入。
import java.util.ArrayList;

public class NeedImportTest {
    public static void main(String[] args) {
        new ArrayList();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

二、static import的按需匯入

import static com.assignment.test.StaticFieldsClass.*;
public class StaticNeedImportTest {
    public static void main(String[] args) {
        System.out.println(staticField);
        staticFunction();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面StaticNeedImportTest 類編譯之後 :

//可以看出 : 
//1、static import的精準匯入以及按需匯入編譯之後都會變成import的單型別匯入
import com.assignment.test.StaticFieldsClass;

public class StaticNeedImportTest {
    public static void main(String[] args) {
    //2、編譯之後“打回原形”,使用原來的方法呼叫靜態成員
        System.out.println(StaticFieldsClass.staticField);
        StaticFieldsClass.staticFunction();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

附加

這是否意味著你總是可以使用按需匯入宣告? 
是,也不是!

在類似Demo的非正式開發中使用按需匯入宣告顯得很有用。

然而,有這四個理由讓你可以放棄這種宣告:

  1. 編譯速度:在一個很大的專案中,它們會極大的影響編譯速度.但在小型專案中使用在編譯時間上可以忽略不計。
  2. 命名衝突:解決避免命名衝突問題的答案就是使用全名。而按需匯入恰恰就是使用匯入宣告初衷的否定。
  3. 說明問題:畢竟高階語言的程式碼是給人看的,按需匯入看不出使用到的具體型別。
  4. 無名包問題:如果在編譯單元的頂部沒有包宣告,Java編譯器首選會從無名包中搜索一個型別,然後才是按需型別宣告。如果有命名衝突就會產生問題。

Sun的工程師一般不使用按需型別匯入宣告.這你可以在他們的程式碼中找到: 
在java.util.Properties類中的匯入宣告:

import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.BufferedWriter;
import java.security.AccessController;
import java.security.PrivilegedAction;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

可以看到他們用單型別匯入詳細的列出了需要的java.io包中的具體型別。