開源在國(guó)外現(xiàn)在是火的一塌糊涂,Linux、Eclipse、Hibernate、Spring、Python、Ruby、MySQL等等或大或小的開源產(chǎn)品的發(fā)展大大推動(dòng)了軟件開發(fā)這個(gè)行業(yè)的高速發(fā)展,apache、sourceforge等開源社區(qū)中也是百花齊放,開源產(chǎn)品的開發(fā)者、推動(dòng)者、推廣者也得到了各自想得到的回報(bào),更不用說使用這些開源產(chǎn)品進(jìn)行開發(fā)的用戶得到的開發(fā)效率的提升。反觀國(guó)內(nèi)開源的發(fā)展則相對(duì)滯后很多:違背Linux開源協(xié)議進(jìn)行商業(yè)化Linux開發(fā)的廠家被開源社區(qū)罵的狗血噴頭;屈指可數(shù)的幾個(gè)開源愛好者組織的開源團(tuán)隊(duì)在個(gè)人的意志與信念的堅(jiān)守下苦苦支撐著,很多因?yàn)榉N種原因中途夭折;商業(yè)公司對(duì)開源社區(qū)的幾次推廣行動(dòng)也胎死腹中,比如CSDN的開源社區(qū)、Apusic的Operamasks;更有甚者打著開源的旗號(hào)卻從事著根本與開源不搭邊的事情。國(guó)內(nèi)大部分人對(duì)開源更是有著很深的誤解:“你憑什么叫人家開源,不就是想看看Windows的代碼嗎?”、“人家商業(yè)公司做出來的產(chǎn)品是你們幾個(gè)技術(shù)瘋子做出來的東西能比的嗎?”、“把別人寫好的東西拿來抄一抄改一改我的任務(wù)就完成了,錢就到手了,開源真好”、“我做的項(xiàng)目用了很多開源的東西,很牛吧!”。
我認(rèn)為開源對(duì)于整個(gè)軟件行業(yè)有如下幾個(gè)作用:使用開源產(chǎn)品搭建出的產(chǎn)品有更好的安全性和可控性;開源的產(chǎn)品能得到開發(fā)社區(qū)的廣泛支持,從而使得產(chǎn)品能夠成熟的更快;使用開源產(chǎn)品搭建出的產(chǎn)品有更好的安全性和可控性;開發(fā)、學(xué)習(xí)開源產(chǎn)品能夠提升開發(fā)人員的技術(shù)水平。
對(duì)于第一個(gè)作用這里不做過多解釋,相信大部分人都能理解。
開源產(chǎn)品是在全世界所有技術(shù)牛人的手下開發(fā)出來的,并且有千萬雙眼睛盯著它,眾多的使用者可以完善它,這使得開源產(chǎn)品的成熟速度也十分驚人,Linux、Eclipse的成熟就是最典型的證明。各種開源產(chǎn)品層出不窮,相似功能的產(chǎn)品就有幾十種甚至上百種,一個(gè)開源產(chǎn)品如果想要在這些產(chǎn)品中脫穎而出,不僅需要強(qiáng)大的技術(shù)做支撐,更需要非常強(qiáng)的商業(yè)化推廣,否則做出來的產(chǎn)品只能成為少數(shù)技術(shù)人員把玩的玩物。如果沒有IBM對(duì)Eclipse的巨額投入,Eclipse會(huì)發(fā)展成現(xiàn)在的規(guī)模嗎?這些開源項(xiàng)目的后面通常都是有一個(gè)非常強(qiáng)大的開源基金會(huì)在做支撐。遺憾的是,目前國(guó)內(nèi)還沒有一家公司能夠進(jìn)行對(duì)開源社區(qū)如此大的投入,因此國(guó)內(nèi)大部分的開源團(tuán)隊(duì)都是在自己開發(fā)著自己的開源產(chǎn)品,放到網(wǎng)站上供他人下載,然后夢(mèng)想著自己的開源產(chǎn)品能夠有一天會(huì)像Hibernate、Struts一樣成為風(fēng)靡全球的產(chǎn)品,但是如果沒有強(qiáng)大的商業(yè)推廣的話這肯定是一場(chǎng)白日夢(mèng)。
鑒于此,我認(rèn)為目前國(guó)內(nèi)的開源產(chǎn)品商業(yè)化的可能性是非常小的,因此開源的對(duì)于廣大開發(fā)人員的意義更多的在于使用和學(xué)習(xí),而且我們使用開源產(chǎn)品快速的搭建出滿足需求的產(chǎn)品,這本身也是對(duì)開源的學(xué)習(xí)過程。這里提到的“學(xué)習(xí)”包含下面幾個(gè)含義:學(xué)習(xí)開源產(chǎn)品的使用;學(xué)習(xí)開源產(chǎn)品的實(shí)現(xiàn)原理;學(xué)習(xí)模仿開源產(chǎn)品;學(xué)習(xí)開源社區(qū)的運(yùn)營(yíng)。
(1)學(xué)習(xí)開源產(chǎn)品的使用:成熟的開源產(chǎn)品是非常優(yōu)秀的,如果能夠?qū)W會(huì)它們的使用,這對(duì)于開源人員來說就是一種很大的收獲,因?yàn)橥ㄟ^使用這些產(chǎn)品就能認(rèn)識(shí)到這些產(chǎn)品的功能、特性以及優(yōu)缺點(diǎn)。
(2)學(xué)習(xí)開源產(chǎn)品的實(shí)現(xiàn)原理:開源產(chǎn)品的代碼都是開放的,我們可以深入產(chǎn)品的內(nèi)部學(xué)習(xí)其實(shí)現(xiàn)原理,從而提高自身的開發(fā)水平。不得不承認(rèn)的是國(guó)內(nèi)開發(fā)人員的技術(shù)水平還是非常低的,通過學(xué)習(xí)開源產(chǎn)品的代碼,就可以提高整體的技術(shù)水平,從這個(gè)層面來講哪怕是非開源的源代碼開放產(chǎn)品(比如Borland的VCL)對(duì)我們也是同樣有幫助的。值得高興的是,國(guó)內(nèi)很多開發(fā)人員已經(jīng)開始嘗試著深入開源產(chǎn)品的內(nèi)部去探尋這些產(chǎn)品的實(shí)現(xiàn)原理,并把它們的學(xué)習(xí)成果與更多人分享。開源對(duì)開發(fā)人員的提升也是顯而易見的,最明顯的就是微軟開發(fā)社區(qū)中開發(fā)人員的普遍技術(shù)水平是低于Java等開源社區(qū)中開發(fā)人員的普遍技術(shù)水平的(注意,這里說的是“普遍技術(shù)水平”,請(qǐng)微軟社區(qū)中的高人不要?jiǎng)优V袊?guó)計(jì)算機(jī)業(yè)的發(fā)展必須依靠核心技術(shù),而提高技術(shù)水平是擁有核心技術(shù)的大前提!
(3)學(xué)習(xí)模仿開源產(chǎn)品:弄懂了開源產(chǎn)品的實(shí)現(xiàn)原理以后就可以嘗試模仿它們開發(fā)自己的產(chǎn)品,這和“重復(fù)造輪子”是沒有關(guān)系的,模仿是學(xué)習(xí)他人技術(shù)的最佳途徑。因此大家應(yīng)該多多的“造輪子”,越多越好,哪怕造完就扔掉也是可以的。
(4)學(xué)習(xí)開源社區(qū)的運(yùn)營(yíng):國(guó)外很多開源人員都有在開源社區(qū)中開發(fā)的經(jīng)驗(yàn),因此他們對(duì)于開源這種協(xié)同開發(fā)方式就有更多的經(jīng)驗(yàn),因此我們可以加入他們的開發(fā)團(tuán)隊(duì),可以幫他們做文檔、界面的本地化,更可以參與產(chǎn)品功能的完善,從而學(xué)習(xí)他們的協(xié)作方式,更可以和他們做朋友,了解更多“外面的世界”。國(guó)內(nèi)很多朋友都參與了開源產(chǎn)品的文檔中文化、產(chǎn)品的推廣等工作,這都是有深遠(yuǎn)意義的。我們CowNew開源團(tuán)隊(duì)參與JodeEclipse、DWPL等國(guó)外項(xiàng)目就是基于這一點(diǎn)考慮的。
相信經(jīng)過一段時(shí)間學(xué)習(xí)之后,我們的開發(fā)人員將有能力開發(fā)出世界級(jí)的產(chǎn)品,如果我們的民族企業(yè)能夠得到長(zhǎng)遠(yuǎn)的發(fā)展,并且在商業(yè)上幫助國(guó)產(chǎn)開源真正走向強(qiáng)大,從而使得中國(guó)的開源社區(qū)也能躋身“世界開源之林”!
上邊是我一點(diǎn)愚蠢的看法,僅供各位看管參考。希望開源能夠在中國(guó)發(fā)展、壯大,希望中國(guó)早日成為軟件強(qiáng)國(guó)!
用gbk、gb2312 之類的時(shí)候都可能會(huì)出現(xiàn)問題,而utf-8作為一個(gè)標(biāo)準(zhǔn)的多語言字符集則解決亂碼的終極方案。所以推薦大家在其他遇到中文的情況下也使用utf-8。
做系統(tǒng)設(shè)計(jì)的時(shí)候有時(shí)會(huì)碰到一些無法在父類(或者接口)中抽取通用行為的特性,遇到這種情況就可以采用StringConfigure模式,這個(gè)模式我取的名字,不知道是否已經(jīng)有先人做了總結(jié),如果哪位朋友知道這種模式的正確名稱,希望不吝賜教。
以JDBC中取得數(shù)據(jù)庫連接為例,我們可以抽象出數(shù)據(jù)庫的一些公共行為,比如連接數(shù)據(jù)庫都要求提供用戶名和密碼,因此在JDBC中提供設(shè)定連接的用戶名和密碼的方法。但是另外的一些行為則不一定是所有數(shù)據(jù)庫都具備的,比如對(duì)于網(wǎng)絡(luò)型數(shù)據(jù)庫才需要指定網(wǎng)絡(luò)地址,而文件型數(shù)據(jù)庫則不需要,再比如在MySQL中需要指定字符集,而其他數(shù)據(jù)庫則不一定需要。如果為了照顧這些特性,為JDBC提供setHostIP、setDBFilePath、setCharSet等方法的話無疑會(huì)使得接口變得復(fù)雜,會(huì)出現(xiàn)很多用不到的方法,并且這些方法也無法覆蓋所有未來可能出現(xiàn)的情況,比如某個(gè)數(shù)據(jù)庫又增加了允許用戶定制連接超時(shí)的方法,那么JDBC也要為他提供相應(yīng)的setTimeOut方法。為了解決這個(gè)問題,JDBC提出了連接字符串的概念,這樣各個(gè)數(shù)據(jù)庫的JDBC驅(qū)動(dòng)只要規(guī)定好連接字符串的格式即可,用戶把所有的配置信息寫到連接字符串中,如果用戶修改為其他數(shù)據(jù)庫的話只需修改連接字符串即可,不用修改其他的調(diào)用。
使用StringConfigure模式的好處是使得系統(tǒng)中的個(gè)性化配置在一個(gè)參數(shù)中完成,這樣保證系統(tǒng)的不同模塊的行為的一致性,缺點(diǎn)是配置字符串的格式要由各個(gè)實(shí)現(xiàn)模塊來規(guī)定,各個(gè)不同實(shí)現(xiàn)模塊的格式不一致,造成了一定的學(xué)習(xí)成本,而且無法在開發(fā)期發(fā)現(xiàn)配置字符串的問題。
這里再來講一個(gè)StringConfigure模式的應(yīng)用的例子。現(xiàn)在我們要開發(fā)一套對(duì)IC卡讀寫器的類庫,應(yīng)用開發(fā)人員只要調(diào)用不同的IC卡讀寫子類即可實(shí)現(xiàn)操作不同的IC卡讀寫器。各種不同的IC卡讀寫器有兩個(gè)共同的抽象行為:讀卡和寫卡,即readCard和writeCard,但是各個(gè)不同的讀卡器還有自己的特性,比如有的讀卡器需要指定采用ISO格式還是IBM格式來讀寫磁卡,有的讀卡器需要指定讀寫操作的分隔符,這些特性不是各個(gè)讀寫器共有的,因此我們采用StringConfigure模式進(jìn)行設(shè)計(jì),開發(fā)如下的接口:
interface IICCarder
{
public void writeCard(String data);
public String readCard();
public void configure(String configStr);
}
比如需要指定讀寫格式的讀寫器就可以如下實(shí)現(xiàn):
class SomeCarder implements IICarder
{
private FormatEnum format;
public void writeCard(String data)
{
if(format==FormatEnum.IBM)
{
.........
}
else...........
}
public String readCard()
{.............
}
public void configure(String configStr)
{
if(configStr.equls("IBM"))
{
format=FormatEnum.IBM
}
else if(configStr.equls("ISO"))
{
format=FormatEnum.ISO
}
}
}
開發(fā)人員使用的時(shí)候只要如下調(diào)用
IICarder c = new SomeCarder();
c.configure("IBM");
print c.readCard();
如果采用配置文件的話更可以把配置參數(shù)寫到配置文件中,這樣就可以避免修改代碼。
JodeEclipse的網(wǎng)站:http://sourceforge.net/projects/jodeeclipse
http://www.aygfsteel.com/Files/huanzhugege/net.sourceforge.jode_1.0.5.rar
安裝方法:
直接解壓到eclipse的plugins目錄下。
已經(jīng)將修改后的代碼提交給jodeclipse的管理員。
開源萬歲!!!
下面的代碼就演示了為JTextArea、JList增加滾動(dòng)條的代碼:
package com.cownew.Char19;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import javax.swing.DefaultListModel;
import javax.swing.JPanel;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.ListModel;
import java.awt.Rectangle;
import javax.swing.JList;
import javax.swing.JScrollPane;
public class ScrollPaneTest1 extends JFrame
{
private JPanel jContentPane = null;
private JTextArea jTextArea = null;
private JList jList = null;
private JScrollPane jScrollPane = null;
private JScrollPane jScrollPane1 = null;
private JList jList1 = null;
private JTextArea jTextArea1 = null;
private JTextArea getJTextArea()
{
if (jTextArea == null)
{
jTextArea = new JTextArea();
jTextArea.setBounds(new Rectangle(12, 7, 95, 71));
}
return jTextArea;
}
private JList getJList()
{
if (jList == null)
{
jList = new JList();
jList.setBounds(new Rectangle(8, 92, 106, 71));
DefaultListModel listModel = new DefaultListModel();
listModel.addElement("22222");
listModel.addElement("33333333");
listModel.addElement("55555555555555");
listModel.addElement("8888888888");
listModel.addElement("88888888");
listModel.addElement("999999999");
jList.setModel(listModel);
}
return jList;
}
private JScrollPane getJScrollPane()
{
if (jScrollPane == null)
{
jScrollPane = new JScrollPane();
jScrollPane.setBounds(new Rectangle(143, 7, 122, 75));
jScrollPane.setViewportView(getJTextArea1());
}
return jScrollPane;
}
private JScrollPane getJScrollPane1()
{
if (jScrollPane1 == null)
{
jScrollPane1 = new JScrollPane();
jScrollPane1.setBounds(new Rectangle(142, 96, 128, 68));
jScrollPane1.setViewportView(getJList1());
}
return jScrollPane1;
}
private JList getJList1()
{
if (jList1 == null)
{
jList1 = new JList();
DefaultListModel listModel = new DefaultListModel();
listModel.addElement("22222");
listModel.addElement("33333333");
listModel.addElement("8888888888888888888888888888");
listModel.addElement("8888888888");
listModel.addElement("88888888");
listModel.addElement("999999999");
jList1.setModel(listModel);
}
return jList1;
}
private JTextArea getJTextArea1()
{
if (jTextArea1 == null)
{
jTextArea1 = new JTextArea();
}
return jTextArea1;
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run()
{
ScrollPaneTest1 thisClass = new ScrollPaneTest1();
thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
thisClass.setVisible(true);
}
});
}
public ScrollPaneTest1()
{
super();
initialize();
}
private void initialize()
{
this.setSize(300, 200);
this.setContentPane(getJContentPane());
this.setTitle("JFrame");
}
private JPanel getJContentPane()
{
if (jContentPane == null)
{
jContentPane = new JPanel();
jContentPane.setLayout(null);
jContentPane.add(getJTextArea(), null);
jContentPane.add(getJList(), null);
jContentPane.add(getJScrollPane(), null);
jContentPane.add(getJScrollPane1(), null);
}
return jContentPane;
}
}
運(yùn)行效果圖:
圖 17.9
JScrollPane還能為組合界面增加滾動(dòng)條:
package com.cownew.Char19;
import java.awt.Dimension;
import java.awt.Rectangle;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class ScrollPaneTest2 extends JFrame
{
private JPanel jContentPane = null;
private JScrollPane jScrollPane = null;
private JPanel jPanel = null;
private JButton jButton = null;
private JButton jButton1 = null;
private JCheckBox jCheckBox = null;
private JTextField jTextField = null;
private JSlider jSlider = null;
private JScrollPane getJScrollPane()
{
if (jScrollPane == null)
{
jScrollPane = new JScrollPane();
jScrollPane.setBounds(new Rectangle(28, 17, 142, 114));
jScrollPane.setViewportView(getJPanel());
}
return jScrollPane;
}
private JPanel getJPanel()
{
if (jPanel == null)
{
jPanel = new JPanel();
jPanel.setLayout(null);
jPanel.add(getJButton(), null);
jPanel.add(getJButton1(), null);
jPanel.add(getJCheckBox(), null);
jPanel.add(getJTextField(), null);
jPanel.add(getJSlider(), null);
jPanel.setPreferredSize(new Dimension(300,200));
}
return jPanel;
}
private JButton getJButton()
{
if (jButton == null)
{
jButton = new JButton();
jButton.setBounds(new Rectangle(6, 10, 74, 28));
}
return jButton;
}
private JButton getJButton1()
{
if (jButton1 == null)
{
jButton1 = new JButton();
jButton1.setBounds(new Rectangle(102, 9, 82, 30));
}
return jButton1;
}
private JCheckBox getJCheckBox()
{
if (jCheckBox == null)
{
jCheckBox = new JCheckBox();
jCheckBox.setBounds(new Rectangle(17, 56, 93, 21));
jCheckBox.setText("aaaaabbb");
}
return jCheckBox;
}
private JTextField getJTextField()
{
if (jTextField == null)
{
jTextField = new JTextField();
jTextField.setBounds(new Rectangle(126, 57, 99, 22));
}
return jTextField;
}
private JSlider getJSlider()
{
if (jSlider == null)
{
jSlider = new JSlider();
jSlider.setBounds(new Rectangle(20, 111, 205, 25));
}
return jSlider;
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run()
{
ScrollPaneTest2 thisClass = new ScrollPaneTest2();
thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
thisClass.setVisible(true);
}
});
}
public ScrollPaneTest2()
{
super();
initialize();
}
private void initialize()
{
this.setSize(221, 177);
this.setContentPane(getJContentPane());
this.setTitle("JFrame");
}
private JPanel getJContentPane()
{
if (jContentPane == null)
{
jContentPane = new JPanel();
jContentPane.setLayout(null);
jContentPane.add(getJScrollPane(), null);
}
return jContentPane;
}
}
運(yùn)行效果圖:
圖 17.10
對(duì)于這種組合界面必須為界面設(shè)定一個(gè)最佳尺寸(PreferredSize),這樣JScrollPane才知道如何顯示滾動(dòng)條:jPanel.setPreferredSize(new Dimension(300,200))。
JScrollPane中的ViewPort是一種特殊的對(duì)象,通過它就可以查看基層組件,滾動(dòng)條其實(shí)就是沿著組件移動(dòng)“視點(diǎn)”,這樣就可以查看隱藏的部分。
首先演示一個(gè)NumberFormatter的例子,在這個(gè)例子中要求用戶輸入一個(gè)數(shù)字,單擊按鈕后將用戶輸入的數(shù)字乘2以后重新賦值給輸入框。用戶輸入的數(shù)字必須大于0,小于100。
package com.cownew.Char19;
import java.awt.Font;
import java.awt.Rectangle;
import java.math.BigDecimal;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFormattedTextField;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.text.NumberFormatter;
import javax.swing.JTextField;
public class NumberFormaterDialog1 extends JDialog
{
private JPanel jContentPane = null;
private JFormattedTextField numTxtField = null;
private JButton jButton = null;
private JTextField jTextField = null;
private JFormattedTextField getNumTxtField()
{
if (numTxtField == null)
{
NumberFormatter numFormater = new NumberFormatter();
numFormater.setMaximum(new BigDecimal(100));
numFormater.setMinimum(new BigDecimal(0));
numTxtField = new JFormattedTextField(numFormater);
numTxtField.setBounds(new Rectangle(56, 38, 154, 24));
}
return numTxtField;
}
private JButton getJButton()
{
if (jButton == null)
{
jButton = new JButton();
jButton.setBounds(new Rectangle(78, 80, 81, 36));
jButton.setFont(new Font("Dialog", Font.PLAIN, 18));
jButton.setText("計(jì)算");
jButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent e)
{
BigDecimal oldValue = (BigDecimal) getNumTxtField()
.getValue();
if (oldValue != null)
{
getNumTxtField().setValue(
oldValue.multiply(new BigDecimal(2)));
}
else
{
JOptionPane.showMessageDialog(
NumberFormaterDialog1.this,"值非法");
}
}
});
}
return jButton;
}
private JTextField getJTextField()
{
if (jTextField == null)
{
jTextField = new JTextField();
jTextField.setBounds(new Rectangle(176, 83, 76, 22));
}
return jTextField;
}
public static void main(String[] args)
{
NumberFormaterDialog1 dlg = new NumberFormaterDialog1();
dlg.show();
}
public NumberFormaterDialog1()
{
super();
initialize();
}
private void initialize()
{
this.setSize(300, 200);
this.setContentPane(getJContentPane());
this.setTitle("JFrame");
}
private JPanel getJContentPane()
{
if (jContentPane == null)
{
jContentPane = new JPanel();
jContentPane.setLayout(null);
jContentPane.add(getNumTxtField(), null);
jContentPane.add(getJButton(), null);
jContentPane.add(getJTextField(), null);
}
return jContentPane;
}
}
當(dāng)我們輸入-3這個(gè)無效值的時(shí)候是允許輸入的,但是當(dāng)鼠標(biāo)焦點(diǎn)移動(dòng)到另外的控件的時(shí)候,“-3”就會(huì)消失。這個(gè)行為可以通過NumberFormatter 的setAllowsInvalid方法來改變:
圖 17.5
圖 17.6
如果輸入“10”這個(gè)合法的數(shù)字,單擊“計(jì)算”按鈕即可算出正確的值:
圖 17.7
圖 17.8
案例系統(tǒng)中的com.cownew.ctk.ui.swing.JNumberTextField就是為了方便使用而從JFormattedTextField派生的一個(gè)數(shù)字輸入控件。
DateFormatter的使用也是類似的,也允許設(shè)置最大最小值,實(shí)際上只要從InternationalFormatter派生的類,并且數(shù)據(jù)類型實(shí)現(xiàn)了Comparable接口都可以設(shè)置極值。
MaskFormatter允許開發(fā)人員使用掩碼指定更加復(fù)雜的校驗(yàn)規(guī)則。掩碼是一串特殊的字符串,每個(gè)字符的含義如下表所示:
字符
說明
#
匹配任何數(shù)字字符
'
轉(zhuǎn)義符,用來將格式字符當(dāng)成普通字符用
U
任意大寫字母
L
任意小寫字母
A
任意數(shù)字或者字母
?
任何字母
*
任何字符
H
任何十六進(jìn)制字符 (0-9, a-f or A-F).
實(shí)例化時(shí)指定其掩碼即可:
MaskFormatter formatter = new MaskFormatter("0x***");
formatter.setValidCharacters("0123456789abcdefABCDEF");
還允許為掩碼設(shè)定占位符,這樣可用性更好:
MaskFormatter formatter = new MaskFormatter("###-####");
formatter.setPlaceholderCharacter('_');
formatter.getDisplayValue(tf, "123");