1. 程式人生 > >java高階程式設計學習如何使用列印服務 API

java高階程式設計學習如何使用列印服務 API


Java 高階程式設計: 列印 
Java Pro Programming: Printing 
 
學習如何使用列印服務 API 


 
 
 
關鍵詞: Java Programming print PrintJob 
 
 
 
 
摘要 


 
從較高層次上來看,使用 Java 列印服務 API 的步驟是很簡單的: 
  
1. 定位列印服務(印表機) ,可以限制返回的列表,只要那些符合您應用程式需要的打
印機。列印服務由 PrintService 的例項體現。 
 
2. 通過呼叫 PrintService 介面中定義的 createPrintJob() 方法建立一個列印任務。 列印
任務由 DocPrintJob 的一個例項代表。 
 
3. 建立一個 Doc 介面的實現,來描述你想要列印的資料。你也可以建立一個
PrintRequestAttributeSet 的例項,來定義你想要的列印選項。 
 
4. 通過 DocPrintJob 介面定義的 print() 方法來初始化列印,指定你先前建立的 Doc,
指定 PrintRequestAttributeSet 或者空值。 
 
現在你可以檢查每一步,並試著完成它們。 
 

 
注意 
在這篇文章裡,我將交替使用印表機和列印服務,因為在大部
分情況下,列印服務不亞於一臺物理的印表機。 更一般意義上
的列印服務反映了理論上可以傳送到印表機以外的輸出。舉例
來說,列印服務可能根本不列印東西,而是寫入磁碟上的檔案。
換句話說,所有的印表機要表示為列印服務,但是並不是所有
列印服務必須和一臺物理的印表機關聯。儘管如此,實際上你
通常會把你的內容到印表機,這就是我為什麼有時候使用更為
簡便的印表機這個詞,來代替技術上更精確的列印服務。   
 
1.定義列印服務 
Locating print services 
你可以使用在 PrintServiceLookup 類中定義的三種靜態方法中的一種來定義。 最簡單的
一種就是 lookupDefaultPrintService(),正如它的名字一樣,它返回一個服務指向您預設的
印表機: 
PrintService service = PrintServiceLookup.lookupDefaultPrintService();  
雖然用這個辦法簡單而方便,用它來選擇輸出所需的印表機,意味著你默認了使用者預設
的印表機的功能,總是滿足正確輸出您所需的程式資料。實際上,你通常想要選擇的是那種
可以處理您的資料型別,並可以符合您的應用所需特性,例如彩色或者兩面列印。為了從列
表中返回所有已定義的印表機序列, 或滿足您需要功能的印表機序列, 您可以使用餘下兩個
在 PrintServiceLookup 中定義的靜態方法,即 lookupPrintServices()或
lookupMultiDocPrintServices()。 
lookupPrintServices()方法接受兩個引數:一個 DocFlavor 的例項和一個實現
AttributeSet 介面的物件。你馬上將看到,你可以使用兩者中任意一個或同時來限制返回的
印表機列表,但是 lookupPrintServices()允許你指定這兩個引數中的任意一個或同時空值。
如果把兩者都設為空, 那麼你實際要求得到的返回值將是所有可用的印表機列表。 截止目前
為止, 你還沒有真正地檢視過 PrintService 中定義的方法, 其中一個 getName() 方法返回了
一個代表印表機的名字的字串。 你可以通過編譯和執行下面的程式碼, 來列出你的系統可用
的印表機: 
[
PrintService[    ] services = PrintServiceLookup.lookupPrintServices(null, null);    
for (int i = 0; i < services.length; i++) {    
           ;
 System.out.println(services[i].getName());    
}     

例如, 你能訪問到連線在名為 PrintServer 伺服器上的 Alpha, Beta 和 Gamma 印表機,
用以上程式碼可以得到以下輸出: 
\\PrintServer\Alpha    
\\PrintServer\Beta    
\ \\PrintServer\Gamma    
現在讓我們來檢視那些你可以傳給 lookupPrintServices()方法的引數,並觀察如何返回擁有
特殊功能的印表機。 
2.DocFlavor 
在呼叫 lookupPrintService()方法時, 第一個你可以指定的引數是一個 DocFlavor 類的實
例,它描述了將要列印的資料的型別和如何儲存。在大部分情況下,並不需要您去建立一個
新的例項因為 Java 包含了很多預先定義的例項,你只要簡單地傳遞其中一個例項的引用給
lookupPrintServices()。儘管(事情通常都是這樣地簡單) ,我們還是來看一下 DocFlavor 的
構造和方法,來理解列印服務如何使用這個例項。 
建立 DocFlavor 例項需要的兩個引數都是字串, 一個是 MIME (Multipurpose Internet 
Mail Extensions)型別,另一個是表現類的名字。MIME 型別被 DocFlavor 用於描述資料類
型。例如,你要列印一個 gif 檔案,你需要使用 MIME 型別是 image/gif 的 DocFlavor。類
似地, 如果列印文字, 你可能要用 text/plain, 或者是列印 HTML 文件, 你則要用 text/html。  
 
 
 
 
 
3.表現類 
Representation class 
MIME 型別描述將要列印資料的型別,表現類則表示這些資料如何處理並交付列印服
務。DocFlavor 包含了七個靜態的內部類,每一個對應一個表現類及不同的封裝方法。 
 

表 1 中列出了 DocFlavor 中定義的靜態內部類的名稱,及想對應的表現類。注意除了
SERVICE_FORMATTED(一會我會更詳細地解釋) ,每一個類都說明了和"binary"或 
"character"相對應。事實上,這些差別是人為的,因為"character"資料型別本身就是一種特
殊的 binary 型別。這種情況下,我們說的二進位制(binary)資料包括人們可以看懂的字元
以及一些格式化的字元比如 tabs,回車,等等。當然,這些差別很重要,反映出面向字元的
表現類並不適合儲存二進位制列印資料。 
 
例如,你不會將一個表現為 gif 圖片的東西儲存到字元陣列或者 String 物件中,同時也
不會通過實現一個 Reader 介面來訪問它。另一方面,因為字元資料也是一種特殊的二進位制
資料,它完全適合儲存文字資訊到位元組數組裡或者通過 InputStream 或者一個 URL 來訪問
它。 
 
Table 1. DocFlavor 的表現類 
 
 
在 DocFlavor 中定義的每一個靜態內部類對應一個表現類,但是請記住我說過,每一個
DocFlavor 的例項封裝了一個表現類和一個 MIME 來確認要列印的資料的型別。要訪問這
樣一個 DocFlavor 例項,其不僅與表現類,並且與你想要列印的內容的 MIME 型別相關,
你需要參考表 1 列出的一個內部類。例如,我們假設你要列印一個能在網上通過 URL 訪問
的 gif 檔案。這裡,顯然的表現類選擇是 java.net.URL,在 DocFlavor 中對應的靜態類就是
URL 類。如果你檢視那個內部類的文件,你會發現其實它定義了一系列靜態的內部類,每
一個對應一種印表機普遍支援的 MIME 型別。表 2 描述了在 DocFlavor.URL 裡的內部類極
其對應的 MIME。 

 
因為要通過 URL 列印 gif 圖片,你可以用以下程式碼來訪問獲得一個的 DocFlavor 例項 
    


DocFlavor flavor = DocFlavor.URL.GIF;     
 
該程式碼建立了一個 DocFlavor 靜態例項的引用,其代表類是 java.net.URL,MIME 是
image/gif。 
 
表 2 列出的類在 DocFlavor.URL 的類中定義,那麼其他六個在 DocFlavor 內定義的內部類
呢?我們依然會等一下再來討論 SERVICE_FORMATTED,這之前,看看與二進位制資料相關的
所有三種類型(BYTE_ARRAY, INPUT_STREAM, 和 URL)相關的內部類,它們的名字和表
2 中列出的一樣。例如,如果你把 gif 資料儲存到了一個位元組數組裡,那麼你可以用以下程式碼: 
    
DocFlavor flavor = DocFlavor.BYTE_ARRAY.GIF;     
 
正如有三個與二進位制型別關聯的 DocFlavor 有它們自己的內部類一樣,與字元型別相關
的另外三個類,也包含另一種型別的內部類,表 3 中列出。 
 

Table 3. CHAR_ARRAY, READER, and STRING 
 
 
所以,如果你想列印儲存在字串中的文字資料,可用以下程式碼: 
    
DocFlavor flavor = DocFlavor.STRING.TEXT_PLAIN;    
 
類似, 如果文字來自於網頁上的 HTML 文件, 並且你希望打印出和在瀏覽器中看到的一
樣的效果,就用以下程式碼: 
    
DocFlavor flavor = DocFlavor.STRING.TEXT_HTML;    
 
 
 
 
4.選擇正確的印表機 
Choosing the right printer 
 
還記得我們在開始討論 DocFlavor 之時,關於確認您實際使用的印表機,支援需要列印
的資料型別,以及你期望使用的傳送機制(即表現類) 。這步看起來似乎沒有必要,但實際
上,你會對給定印表機所支援的文件型別感到吃驚。例如,剛提到文字型別看起來似乎是最
容易支援的,所以,如果你的程式要列印一個普通文字或者 HTML 文字,你可能會簡單地 
選擇第一個有效的列印服務, 並將輸出送到那臺印表機去。 然而大部分印表機不支援基於文
本的表現類,如果你試圖向列印機發送你選擇的 DocFlavor,但是它卻不支援,就會丟擲下
面的異常: 

    
Exception in thread "main" sun.print.PrintJobFlavorException: invalid flavor    
                
    
at sun.print.Win32PrintJob.print(Win32PrintJob.java:290)    
                
    
at PrintTest.main(PrintTest.java:11)     
 
現在你已經知道了如何得到一個 DocFlavor 的引用,並且我們也討論了選擇支援這個
DocFlavor 的印表機重要性, 接下來我來將告訴你, 如何確定你使用的印表機支援所需特性。
我先前說過 lookupPrintServices()允許你指定一個 DocFlavor 作為第一個引數,如果你指定
的引數非空,那麼方法會返回支援指定 DocFlavor 的列印服務例項。例如,以下程式碼將返回
可以通過 URL 來列印 gif 檔案的印表機的列表: 
DocFlavor flavor = DocFlavor.URL.GIF;    
PrintService[    ] services = PrintServiceLookup.lookupPrintServices(flavor, null);     
 
另外,如果你的程式已經獲得了列印服務的例項,而你想知道它是否支援一種特定的屬
性,你可以呼叫 isDocFlavorSupported()方法。在下面的程式碼裡,將得到一個預設印表機的
引用,如果不支援打印出通過 URL 獲得的 gif,就會出現錯誤資訊: 
    
PrintService service = PrintServiceLookup.;
lookupDefaultPrintService();    
    
DocFlavor flavor = DocFlavor.URL.GIF;    
    
if (!service.isDocFlavorSupported(flavor)) {    
                
    
System.err.println("The printer does not support the appropriate DocFlavor")    ;    
    
}     
 
 
 
 
 
 

5.AttributeSet (屬性集) 
 
如你見,一個 DocFlavor 描述了要列印的資料,並且可以用來確定 PrintService(列印服
務)是否支援該資料型別。然而,您的應用程式也可能需要一種基於印表機特性的選擇機制。
例如,你要列印的圖片需要用不同的顏色來傳遞資訊,你想知道給定的(列印)服務是否支
持彩色列印,如果不是,那麼要麼不使用該印表機,或者轉換成不依賴顏色的圖片演示。 
 
類似彩色列印,兩面列印,以及不同的打印製方向選擇(垂直肖像式或水平風景式)等
特性被稱為印表機屬性,而 javax.print.attribute 包中包含了許多你可以用於描述這些屬性
的類和介面。其中的一個介面是 AttributeSet,可以作為前面提到的 lookupPrintServices()
中第二個引數。正如你預計的那樣,AttributeSet 的一個實現代表了一組屬性的集合,在調
用 lookupPrintServices()時指定一個非空的值,將只返回支援這些屬性的列印服務。換句話
說,如果 DocFlavor 和 AttributeSet 都不為空,那麼方法將返回那些這兩種屬性都支援的
印表機 
 
6.Attribute 
 
給定的一個 AttributeSet 是一組屬性的集合,一個顯而易見的問題是,如何指定組成該
集合的屬性值呢? javax.print.attribute 包裡同時含有一個叫 Attribute 的介面, 你馬上可以
看到通過呼叫 add()方法,來給 AttributeSet 新增若干個 Attribute 例項來獲得這個集合。查
閱 Attribute 介面的文件後,發現在 javax.print.attribute.standard 包裡定義了大量你將要用
到的實現。在你瞭解這些之前,你可以先檢視 javax.print.attribute 這個包裡的其他介面及
其實現,將非常有幫助! 
 
7.屬性角色 
 
目前為止,我們把屬性描述成列印服務的能力,這在大部分上是正確的,至少對於 Java
是如何支援屬性來說,它是某種意義上的單純概括。對應每個不同屬性,java 都將其關聯
到不同的角色上,屬性僅在相關的角色上下文中才有效。換而言之,在不同的位置要使用不
同的 Java 列印服務屬性,不是每個屬性在任何地方都適用。 
10 
為了更好的理解這個,來看一下 javax.print.attribute.standard 包裡定義的
OrientationRequested 和 ColorSupported 實現。建立一個新的列印文件時,應該通過設定
OrientationRequested 屬性來確定列印紙的方向(例如垂直肖像式或水平風景式) 。與此相
反,ColorSupported 是你在呼叫 PrintService 介面的 getAttributes()方法時返回的屬性。換
而言之,OrientationRequested 是一個你用來將資訊傳遞給印表機的屬性,而
ColorSupported 是列印服務用來提供給你關於印表機能力資訊的屬性。你不能在建立列印
文件時把 ColorSupported 作為指定屬性,因為印表機是否支援彩色列印是你的程式不能控
制的。 
 
8.介面和實現 
 
你第一次檢視 javax.print.attribute 包裡的介面和類時, 選擇列表裡的介面和類, 看起來
很令人困惑。 除了Attribute 和AttributeSet介面, 以及實現AttributeSet的HashAttributeSet
類,javax.print.attribute 包裡有 4 個子介面和類,列出在表 4 和圖 1 中。 
Table 4. javax.print.attribute 裡定義的介面和類 
 
圖-1,javax.print.attribute 包的一部分類的層次結構.   
 
11 
為什麼你需要所有這些各式各樣的介面和繼承類呢,特別是已經有了 Attribute, 
AttributeSet, 和 HashAttributeSet?是因為這些特殊的定製是為了確保在角色中使用合適
的有效屬性。比方說,我提到過當你建立列印文件的位置,可以使用屬性;但根據上下文,
一些屬性,例如 ColorSupported 在那裡不能使用。當建立這樣的文件時,你可以使用
DocAttributeSet 介面(或者更明確一點,HashDocAttributeSet 這個實現) ,這個實現只允
許你新增繼承 DocAttribute 這個介面的屬性。這四種不同的角色如下: 
 
? Doc: 在建立列印文件時,描述文件如何列印 
? PrintJob: 從列印任務返回的屬性,描述任務的狀態 
? PrintRequest: 請求初始化列印時,傳給任務的屬性 
? PrintService: 由列印服務返回,用於描述印表機的能力 
 
要知道這些如何工作, 我們來建立一個DocAttributeSet的例項, 然後嘗試為AttributeSet
設定 DocAttributeSet 和 OrientationRequested 屬性。HashDocAttributeSet 定義了一個無
引數的建立結構,所以你可以很容易地建立例項: 
    
Do; 
cAttributeSet attrs = new HashDocAttributeSet();     
 
現在你已經建立了 AttributeSet,你可以呼叫 add()方法,並把 Attribute 的實現傳遞給
它。如果你看了 OrientationRequested 這個類的文件,你會發現它包含了一系列靜態的
OrientationRequest 例項,每一個對應一種紙張列印方向,例如垂直人像或水平風景。要指
定你想要的方向,你所要做的只是利用 add()方法傳遞一個靜態例項的引用: 
 
DocAttributeSet attrs = new HashDocAttributeSet(); 
    
attrs.add(OrientationRequested.PORTRAIT);     
 
ColorSupported 類有一點不同,但一樣很簡單,它定義了兩種靜態例項:一個表示支援
彩色列印, 另一個表示不支援。 你可以試著增加一個ColorSupported 屬性到DocAttributeSet
去,程式碼如下: 
 
DocAttributeSet attrs = new HashDocAttributeSet(); 
 
attrs.add(OrientationRequested.PORTRAIT); 
12 
    
attrs.add(ColorSupported.SUPPORTED);     
 
如前所述,去指定是否支援彩色列印不恰當的,因為這不是程式所能控制的內容。換句
話說,ColorSupported 這個屬性放到一系列文件屬性上下文中並不合適,所以,執行先前
的程式碼,當新增 ColorSupported 屬性時會丟擲一個 ClassCastException 異常。 
 
要這是如何工作的,記住每一個 AttributeSet 子介面(這個例子是 DocAttributeSet)都
有一個相應 Attribute 子介面(DocAttribute)和實現類(HashDocAttributeSet) 。當新增一
個屬性時,實現子類試圖把 Attribute 引數轉換為相應的子介面型別,這樣來確保只有當前
上下文合適的屬性會新增成功。 
 
這個例子中,HashDocAttributeSet 的 add()方法第一次呼叫,是配合一個
OrientationRequested 的例項,併成功將該物件轉換為 DocAttribute。因為,如圖 2 所示,
OrientationRequested 繼承了那個介面。與之相反,傳遞 ColorSupported 例項的時候失敗
了,因為其沒有繼承 DocAttribute。 
 
圖-2. javax.print.attribute 包的部分類層次圖示。 
 
 
這個例子舉例表明,表 4 裡的四個介面和類組確保了只有合適的屬性在合適的上下文中
使用。 注意各種角色和不同的屬性之間有大量的重疊部分, 所以很多屬性與不止一個角色相
關聯。例如,許多屬性繼承了 PrintJobAttribute 和 PrintRequestAttribute,因為大部分維
護和通過列印任務提供給你的屬性, 與你可以在初始化列印時能指定的屬性是相關的。 舉例
來說,你可以通過新增名稱到 PrintRequestAttributeSet 中來指定列印任務名,並且在列印
的時候,通過查詢 PrintJobAttributeSet 來獲得它。因此,JobName 屬性類同時實現了
PrintRequestAttribute 和 PrintJobAttribute。 
13 
9.AttributeSet 和 HashAttributeSet 
 
你已經知道了為什麼會有四個子類,但是 AttributeSet 介面和 HashAttributeSet 父類又
是什麼呢? AttributeSet / HashAttributeSet 在你不能確定要儲存在這個集合中的屬性僅僅
和一個角色相關時使用。記得我以前提到的 lookupPrintServices()方法允許你指定
AttributeSet 引數來限制返回的列印服務。 表面上看來最好指定 PrintServiceAttributeSet 的
例項,但是很多你可能用到的屬性並不繼承 PrintServiceAttribute。 
 
我們假設你想要讓 lookupPrintServices() 方法返回, 支援彩色列印和水平方向列印的打
印機。這些屬性與 ColorSupported 和 OrientationRequested 屬性關聯,但請注意這些類並
不共享角色:前者是一個 PrintServiceAttribute,而 OrientationRequested 與另外三個角色
(Doc,PrintRequest 和 PrintJob)關聯,如圖-2 所示。這意味著不存在單個特定角色的
AttributeSet 介面或類來同時包含 ColorSupported 和 Sides 屬性。 
 
建立一個 AttributeSet,並使其同時包含 OrientationRequested 和 ColorSupported 例項
的簡單方法是使用一個 HashAttributeSet。與它的子類不同,它並不限制你往上加特殊角色
的屬性,所以以下程式碼可以成功執行: 
 
AttributeSet attrs = new HashAttributeSet();    
;
attrs.add(ColorSupported.SUPPORTED);    
attrs.add(OrientationRequested.LANDSCAPE);    
[
[
PrintService[    t
] services = PrintServiceLookup.lookupPrintServices(null, attrs);     
 
10.通過使用者介面的印表機選擇 
 
到當前為止,我假設所需要使用的印表機,應該能夠由應用程式程式設計選擇。事實上,盡
管如此,更為普遍的過程是顯示一個對話方塊,並允許客戶在輸出時,選擇使用哪個印表機。
幸運的是, Java通過使用在javax.print包中定義的ServiceUI類, 中的靜態方法printDialog()
來使得這些操作非常簡單。 
14 
 
除了對話方塊顯示的位置外,在呼叫 printDialog()時必須指定的唯一引數是這些: 
? 一個使用者可選用的 PrintService 例項的陣列; 
? 預設的 PrintService; 
? 一個 PrintRequestAttributeSet 例項。這用來彈出顯示的對話方塊,並在對話方塊消失
之前返回使用者所作的任何更改。 
要演示這些如何運作,可使用下列簡單的程式碼段來顯示一個簡單的列印對話方塊: 
 
    
PrintService[    ] services = PrintServiceLookup.lookupPrintServices(null, null);    
    
l
PrintService svc = PrintServiceLookup.lookupDefaultPrintService(    );    
    
PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet(    );    
    
PrintService selection = ServiceUI.printDialog(        
    
    


null, 100, 100, services, svc, null, attrs);     
執行時,程式碼產生如圖-3 中所示的對話方塊 
圖-3 列印對話方塊 
 
 
15 
正如程式碼所示,從 printDialog()方法返回的值是一個 PrintService 例項,識別使用者所選
的印表機,或在使用者取消印表機對話時標識為空值。此外,PrintRequestAttributeSet 已更
新了,包含使用者通過對話方塊所做出的更改,例如要列印的份數等。 
 
通過使用 printDialog()方法,可讓使用者選擇其輸出要發往的印表機,提供使用者對於專業
應用程式的所期望功能。 
 
11.建立列印任務 
 
這是列印中的最簡單的一個步驟;因為一旦獲得 PrintService 的一個引用,所有你需要
做的就是呼叫 createPrintJob()方法,象這樣: 
    
PrintService service;    
    
.    
    
.    
    
.    
    
DocPrintJob job = service.createPrintJob();     
 
如程式碼所示,從 createPrintJob()返回的值是一個 DocPrintJob 例項,該物件可以讓讓您
控制並監視列印操作的狀態。要初始化列印,您應該呼叫 DocPrintJob 物件的 print()方法,
但是,在這之前,您需要定義待列印的文件,並 PrintRequestAttributeSet。您已經 知道如
何構造並使用 AttributeSet,這些步驟不再重複;接下來,您將瞭解如何定義待列印的文件。  
 
12.定義要列印的文件 
 
列印過程中接下的一步是定義要列印的文件,就是建立一個例項,其實現了在
javax.print 包裡定義的 Doc 介面。每一個 Doc 的例項有兩個必須定義的屬性和一個可選擇
的屬性: 
16 
? 一個 Object 代表要列印的內容; 
? DocFlavor 的一個例項描述資料型別; 
? 一個可選的 DocAttributeSet 包含列印時所需屬性。 
 
查閱 Doc 介面的文件, 可以看出 javax.print 包裡包含了一個叫 SimpleDoc 介面的實現,
它的建構函式包含了與上面三個屬性對應的三個引數。要了解如何構建 SimpleDoc 的例項,
我們假設你要列印兩份存在 http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif
的 gif 檔案拷貝。 
 
構建一個描述所要列印檔案的 SimpleDoc 例項,我們所有要做的是,建立一個指向圖片
的 URL,並獲得一個合適的 DocFlavor 引用,並把這兩個傳給 SimpleDoc 建構函式: 
 
URL url = new     
    
URL("http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif");    
    
;
DocFlavor flavor = DocFlavor.URL.GIF;    
    
SimpleDoc doc = new SimpleDoc(url, flavor, null);    
 
13.啟動列印 
 
列印的最後一個步驟就是呼叫 DocPrintJob 的 print()方法,傳遞給其,待列印資料的
Doc 物件,以及可選的 PrintRequestAttributeSet 例項。為簡單起見,假設預設印表機支援
你所需要的 flavor 和屬性,在此情況下要使用下列程式碼將上一個例子提及的 gif 檔案列印兩
份: 
 
    
PrintService service = PrintServiceLookup.lookupDefaultPrintService();    
    
DocPrintJob job = service.createPrintJob();    
    
URL url = new     
17 
    
URL("http://www.apress.com/ApressCorporate/supplement/1/421/;
;
bcm.gif ");    
    
DocFlavor flavor = DocFlavor.URL.GIF;    
    
Doc doc = new SimpleDoc(url, flavor, null);    
    
PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet(    );    
    
attrs.add(new Copies(2));    
    
, job.print(doc, attrs)    
 
注意,某些情況下,列印是非同步執行的,這可能會在實際列印完成之前,返回對 print()
的呼叫。