numberformat_解决NumberFormat的解析问题
入門程序員很快發(fā)現(xiàn),數(shù)字的文本表示形式與程序可以在其上執(zhí)行數(shù)學(xué)運(yùn)算的數(shù)字變量明顯不同。 例如, "123"與真實(shí)數(shù)值123或十六進(jìn)制0x7B 。 程序必須使用算法或轉(zhuǎn)換例程來從文本中獲取數(shù)字-尤其是當(dāng)文本使用分組或小數(shù)點(diǎn)分隔符(例如美國數(shù)字格式的逗號和小數(shù)點(diǎn))格式化時。 文本到數(shù)字的轉(zhuǎn)換首先是交互式編程的一個問題,但是HTML,XML以及許多其他將數(shù)據(jù)作為文本處理的文件和通信格式也經(jīng)常遇到這種情況。
在Java SE API提供了類似的方法Integer.parseInt()和Double.parseDouble()進(jìn)行轉(zhuǎn)換,但這些方法預(yù)計在Java語言規(guī)范文本(見定義的形式他們的論據(jù)相關(guān)主題 )。 就本文而言,它主要查看整數(shù)和雙精度數(shù),該格式基本上僅由以下字符組成:
- 前導(dǎo)減號(ASCII值45或十六進(jìn)制0x2D )
- 數(shù)字0到9(ASCII值48到57或十六進(jìn)制0x30到0x39 )
- 對于浮點(diǎn)值,用點(diǎn)或句點(diǎn)表示的小數(shù)點(diǎn)(ASCII值46或十六進(jìn)制0x2E )
該要求對于程序員和代碼是合理的,但是用戶希望以其本地文化的通用格式輸入和查看數(shù)字。 Java SE API的java.text.NumberFormat類包含一個方便的parse(String source)方法,大多數(shù)程序員都使用該方法將特定于語言環(huán)境的格式化文本解析為數(shù)字值。 不幸的是,該方法可能會產(chǎn)生意想不到的錯誤結(jié)果。 本文介紹了NumberFormat的原理,回顧了其功能,公開了類的解析陷阱,并提供了可靠使用它的指導(dǎo)原則。
無需驗(yàn)證即可解析
本文的示例程序(請參閱下載 ) NumberInput (圖1所示)是一個Swing應(yīng)用程序,可讓您探索幾種將文本輸入轉(zhuǎn)換為數(shù)值的方法。 除輸入字段外,該程序還顯示默認(rèn)的語言環(huán)境名稱,原始鍵值,原始長度和已解析的位置數(shù)(如果適用)。 在啟動時,它將分別使用雙123456.7值123456.7和整數(shù)值1234567加載輸入字段。 這兩個值均按照本機(jī)用戶期望的格式設(shè)置為默認(rèn)語言環(huán)境。 因?yàn)槲易≡诿绹?#xff0c;程序會顯示"123,456.7"的雙重價值, "1,234,567"的整數(shù)值。
圖1. NumberInput初始顯示
當(dāng)您單擊NoCheck按鈕時,該程序使用Double.parseDouble()和Integer.parseInt()進(jìn)行直接解析,而無需進(jìn)行驗(yàn)證。 請注意,在調(diào)用任何其他方法之前,會在actionPerformed()從輸入字符串中刪除前導(dǎo)和尾隨空格。 圖2顯示了結(jié)果:
圖2. Double.parseDouble()為格式化的文本拋出NumberFormatException
錯誤的原因是逗號用作美國語言環(huán)境的分組分隔符。 除去逗號后,輸入為"123456.7" ,程序?qū)⒔邮躣ouble值。
負(fù)值呢? 鍵入前導(dǎo)符號(并除去逗號)可使程序滿意,但后綴符號的結(jié)果為: 輸入字符串"123456.7-" NumberFormatException 。
出于相同的原因,整數(shù)分析顯示類似的行為。 為NoCheck按鈕調(diào)用的代碼在NumberInput的noCheckInput()方法中。 它使用Double.parseDouble()從JTextField jtD輸入,并使用Integer.parseInt()從JTextField jtI輸入。 根據(jù)規(guī)則,這些結(jié)果是正常的,并且超出絕對初學(xué)者階段,對于大多數(shù)Java程序員來說,應(yīng)該是預(yù)期的行為。
搶救NumberFormat
除了錯別字和其他用戶輸入錯誤外,在顯示帶格式的數(shù)字文本和接收來自同一字段的數(shù)字文本輸入之間始終存在不安的關(guān)系。 我們可能已經(jīng)知道所有程序員,他們認(rèn)為解決方案僅是顯示一條消息,上面寫著:“沒有逗號且前導(dǎo)減號的關(guān)鍵數(shù)字”。 頭腦冷靜的數(shù)據(jù)輸入人員通常對此方法沒有太大的問題,但是用戶通常希望看到格式化的數(shù)字,然后通常直接在格式化的顯示器上鍵入內(nèi)容,從而保持分隔符和分組。 通常,經(jīng)過一番抱怨之后,美國程序員解決該問題的第一步是編寫一個例程,該例程去除逗號并將任何尾隨的減號移動到輸入值的前面。 以這種方式編寫的許多程序在生產(chǎn)中都有很長的生命。 從某種意義上說,這是連程序員首次進(jìn)軍國際化(I18N)和本地化(localization)(見相關(guān)主題 )。 問題在于,這種代碼只能針對一個或一組有限的語言環(huán)境有效地本地化程序。
Java程序被譽(yù)為能夠在任何啟用的平臺上運(yùn)行,并且許多人也以熟悉的方式將其表示為任何國家和語言。 Java SE SDK提供了API來使這種期望成為現(xiàn)實(shí)。 但是,像我剛才描述的第一個努力那樣編寫的程序在其假定范圍之外使用時很快就會崩潰。 在其他國家/地區(qū)中,值123456.7可以設(shè)置為格式或鍵為"123.456,7" , "123456,7"或"123'456,7"或其他鍵。 假設(shè)所有區(qū)域設(shè)置都使用相同的分組分隔符和十進(jìn)制分隔符(同樣,在美國示例中分別使用“,”和“。”)的任何程序都將不起作用。 預(yù)料到此問題,API包含java.text.NumberFormat 。 該類提供了外部簡單的parse()和format()方法,這些方法可以自動進(jìn)行區(qū)域設(shè)置,包括格式化符號的知識。 實(shí)際上, NumberInput使用NumberFormat來格式化輸入字段中顯示的值。
Java Locale對象表示并標(biāo)識語言和地區(qū)或國家/地區(qū)的特定組合。 它本身不提供局部行為; 類必須自己提供本地化。 但是,Java平臺確實(shí)支持一致的語言環(huán)境集,并且許多標(biāo)準(zhǔn)類都實(shí)現(xiàn)一致的本地化行為。 這些類通常具有兩種版本的方法:一種采用Locale參數(shù),而另一種采用默認(rèn)方法。 默認(rèn)語言環(huán)境在程序啟動時自動確定,或由傳遞給Java運(yùn)行時的參數(shù)覆蓋。
NumberFormat是一個抽象類,但它提供了靜態(tài)的工廠get XXX Instance()方法,用于獲取具有預(yù)定義的本地化格式的具體實(shí)現(xiàn)。 基礎(chǔ)實(shí)現(xiàn)通常是java.text.DecimalFormat的實(shí)例。 本文中的代碼和討論使用NumberFormat.getNumberInstance()返回的默認(rèn)值來格式化和解析雙NumberFormat.getIntegerInstance()值,使用NumberFormat.getIntegerInstance()來獲取整數(shù)值。
值得一提的是,完全本地化分析所需的代碼很少。 這些步驟是:
付出很少的努力的好處是巨大的,每個Java程序員都應(yīng)該使用NumberFormat來處理格式化的數(shù)字轉(zhuǎn)換。 要在不同的語言環(huán)境中進(jìn)行嘗試,請使用以下命令行調(diào)用NumberInput應(yīng)用程序,其中l(wèi)c是ISO-639語言代碼, cc是ISO-3166國家代碼:
java -Duser.language=lc -Duser.region=cc NumberInput從JDK 1.4開始,可以使用user.country系統(tǒng)屬性代替user.region 。 要確定Java平臺支持的語言環(huán)境,請參閱JDK文檔的國際化部分中的支持的語言環(huán)境(請參閱參考資料 )。 程序可以使用java.util.Locale的靜態(tài)getAvailableLocales()方法在運(yùn)行時確定語言環(huán)境支持。
清單1顯示了NumberInput的NFInput()方法的相關(guān)代碼,單擊NF按鈕時將調(diào)用該代碼。 該方法使用NumberFormat.parse(String)進(jìn)行驗(yàn)證和轉(zhuǎn)換。
清單1. NFInput()方法使用NumberFormat.parse(String)
... NumberFormat nfDLocal = NumberFormat.getNumberInstance(),nfILocal = NumberFormat.getIntegerInstance(); ...public void NFInput( String sDouble, String sInt ){ // "standard" NumberFormat parsingdouble d;int i;Number n;try{n = nfDLocal.parse( sDouble );d = n.doubleValue();...n = nfILocal.parse( sInt );i = n.intValue();...}catch( ParseException pe ) { ...}} // end NFInputNumberFormat實(shí)例的實(shí)現(xiàn)
在這一點(diǎn)上,簡要回顧一下NumberFormat被要求get XXX Instance()時發(fā)生的情況以及DecimalFormatSymbols類的角色都是有用的。 討論基于對J2SE 1.4附帶的參考實(shí)現(xiàn)源代碼的回顧,并且可能會發(fā)生變化。
事件的基本流程是NumberFormat基于關(guān)聯(lián)的語言環(huán)境向內(nèi)部ListResourceBundle咨詢適當(dāng)?shù)哪J?#xff0c;并返回使用該模式創(chuàng)建的DecimalFormat對象。 如果沒有顯式的負(fù)模式,則將正負(fù)號與正模式組合使用。 在此過程中,將創(chuàng)建適合區(qū)域設(shè)置的DecimalFormatSymbols對象,并且DecimalFormat實(shí)例將獲得對該對象的引用。 因?yàn)镹umberFormat.get XXX Instance()方法基于工廠模式,所以其他實(shí)現(xiàn)或?qū)淼囊脤?shí)現(xiàn)可能返回不同的類。 因此,任何自定義代碼必須確保DecimalFormat嘗試訪問相關(guān)的前實(shí)際返回DecimalFormatSymbols實(shí)例。
DecimalFormatSymbols對象包含信息,例如適當(dāng)?shù)氖M(jìn)制和分組分隔符以及減號。 單擊“信息”按鈕時, NumberInput收集大量此類信息并將其顯示在對話框中。 圖3顯示了使用en_US語言環(huán)境的示例。 此信息對于解析和驗(yàn)證本地化格式的數(shù)字至關(guān)重要。
圖3. DecimalFormatSymbols數(shù)據(jù)
嘗試單擊NF按鈕,您將看到即使使用逗號或其他本地分組分隔符也可以正確接受這些值。 根據(jù)當(dāng)前樣式放置減號也可以接受。 減號在另一個位置時該怎么辦? 這以及其他幾個問題是下一節(jié)的主題,也是本文的動力。
UnexpectedResults.equals(bigTrouble)
關(guān)于Java國際化的大多數(shù)文章都將重點(diǎn)放在NumberFormat的格式化功能上,并在到目前為止我給出的信息有所變化之后結(jié)束對解析的任何討論。 不幸的是,對該類(實(shí)際上是從NumberFormat.get XXX Instance()返回的具體DecimalFormat子類)進(jìn)行的測試和實(shí)驗(yàn)顯示了解析異常,這可能令人驚訝:在許多常見的字段條件下, NumberFormat.parse(String)欣喜地截斷數(shù)據(jù)并在沒有向程序員指示的情況下丟失信號。 以下情況顯示了此行為(除非另有說明,否則使用en_US語言環(huán)境):
- 十進(jìn)制分隔符之前的多個連續(xù)或不規(guī)則插入的分組分隔符將被忽略。
例如, "123,,,456.7" "123,45,6.7"和"123,45,6.7"被接受,并且都返回123456.7 。
經(jīng)過深思熟慮,我得出的結(jié)論是,盡管從技術(shù)上講,這種行為是錯誤的,但不會丟失任何數(shù)據(jù),任何解決方案都會導(dǎo)致超出其價值的工作。 您應(yīng)該了解該行為,但是NumberInput應(yīng)用程序無法糾正它,并且在本文中我將不再對其進(jìn)行詳細(xì)介紹。 - 在十進(jìn)制分隔符之后出現(xiàn)的分組分隔符會導(dǎo)致截斷。
"123,456.7,85"被接受為123456.7 。 - 多個十進(jìn)制分隔符會導(dǎo)致截斷。
"123,456..7"被接受為123456.0 ; "12.3.456.7"被接受為12.3 。 - 對于帶有前減號(負(fù)前綴)的模式,截斷會在非數(shù)字字符的點(diǎn)處發(fā)生,包括嵌入的負(fù)號。
"123,4r56.7"被接受為1234.0 ; "12-3,456.7"被接受為12.0 (正值)。 - 對于帶有尾隨減號(負(fù)后綴)的模式,截斷會在非數(shù)字字符的點(diǎn)處發(fā)生,不包括嵌入的減號。 可接受嵌入的負(fù)號,但任何其他數(shù)據(jù)都將被截斷。
對于沙特阿拉伯語言環(huán)境( ar_SA ), "123,4r56.7"被接受為1234.0 ; 接受"12-3,456.7"作為-12.0 (負(fù)值)。 - 如果模式為負(fù)輸入指定前導(dǎo)負(fù)號,則尾隨負(fù)號將被忽略。
接受"123,456.7-"作為123456.7 (正值),接受"-123.456,7-" (荷蘭語區(qū)域設(shè)置nl_NL )作為-123456.7 (負(fù)值)。
圖4和圖5顯示了單擊NF按鈕時其中一些行為的示例。 盡管可能難以解釋原始條目的意圖,但可以肯定的是,沒有預(yù)料到會出現(xiàn)1234.0的雙重結(jié)果,用戶也不想從整數(shù)輸入中刪除最后兩位。 同樣,不會引發(fā)異常,也沒有跡象表明輸入的部分被忽略了。
圖4. NumberFormat.parse(String)的意外結(jié)果
圖5. NumberFormat.parse(String)接受截斷的值
考慮到實(shí)現(xiàn)NumberFormat和DecimalFormat類所需的工作量,這些結(jié)果在許多使用JDK 1.4和5.0進(jìn)行的測試中都是一致的。 另一方面,代碼比將參數(shù)傳遞給方法并檢查結(jié)果要簡單得多。 唯一的真實(shí)線索是在NumberFormat.parse(String source)的JDK文檔中,該文檔沒有進(jìn)一步說明就說:“該方法可能不會使用給定字符串的整個文本。”
看起來像這樣的異常是很麻煩的,乍一看,似乎最好返回編程的“按自己的方式來做”。 “垃圾進(jìn),垃圾出”在計算領(lǐng)域是老生常談,但這僅意味著程序無法保證數(shù)據(jù)正確無誤 。 程序員的義務(wù)是盡可能確保所有輸入均有效 。 與其說它是一個錯誤,不如說是一種NumberFormat.parse(String)的設(shè)計,它盡可能地從輸入字符串的某個部分返回一個數(shù)字。 不幸的是,該行為包括一個未聲明的假設(shè),即數(shù)據(jù)已經(jīng)通過驗(yàn)證。 最終結(jié)果是程序員無法確定何時輸入無效,這破壞了與用戶和數(shù)據(jù)本身的隱式契約。
幾年前發(fā)現(xiàn)這些問題時,我的第一React是為parse(String)方法編寫相當(dāng)于前端預(yù)處理器的內(nèi)容。 那行得通,但是代價是額外的,部分冗余的代碼和更多的時間來處理數(shù)據(jù)。 幸運(yùn)的是,事實(shí)證明,謹(jǐn)慎使用現(xiàn)有的NumberFormat方法可以解決此問題。
使用ParsePosition進(jìn)行驗(yàn)證
parse(String source, ParsePosition parsePosition)方法是不尋常的,因?yàn)樗粫l(fā)任何異常。 它通常用于從單個字符串中解析多個數(shù)字時使用。 但是,返回方法時, ParsePosition.getIndex()的值是輸入字符串中最后一個解析的位置加一個。 如果代碼始終以索引設(shè)置為零開始,則在處理后,索引值將等于已解析字符的數(shù)量。 使用驗(yàn)證方法的關(guān)鍵是將更新后的索引與原始輸入字符串的長度進(jìn)行比較。
為避免混淆,我應(yīng)該提到ParsePosition也有一個getErrorIndex()方法。 此方法對于此處討論的條件基本上沒有用,因?yàn)闆]有檢測到錯誤。 另外,在使用它時,必須在每次解析操作之前將錯誤索引重置為-1; 否則結(jié)果可能會產(chǎn)生誤導(dǎo)。
單擊NF或NFPP按鈕時, NumberInput應(yīng)用程序?qū)⒃贚ength / PP列下顯示ParsePosition索引。 如果原始值的長度大于零且與索引值匹配,則兩者均顯示為綠色;否則,兩者均顯示為綠色。 否則,這些值將顯示為紅色。 此操作與特定的驗(yàn)證方法分開完成。 如果再次查看圖4 ,即使與NF按鈕關(guān)聯(lián)的NFInput()方法接受了數(shù)據(jù),該值也將顯示為紅色,表明存在錯誤。
對于最終的驗(yàn)證版本,當(dāng)NFPPInput() NFPP按鈕時,將調(diào)用NFPPInput()方法。 此方法使用parse(String, ParsePosition)驗(yàn)證輸入并獲取數(shù)字值。 圖6和圖7顯示在NFPPInput()檢測到來自圖4的無效輸入。 在我的測試中,該方法正確處理了NumberFormat.parse(String)遺漏的所有條件。
圖6.檢測無效的重復(fù)條目
圖7.檢測無效的整數(shù)條目
您必須遵循一些準(zhǔn)則,以確保使用parse(String, ParsePosition)正確的結(jié)果:
- 請記住,該方法永遠(yuǎn)不會引發(fā)異常。
為了清楚和演示起見,此處的代碼僅顯示“ 可接受 / 不可接受”對話框。 在通用情況下,應(yīng)該拋出ParseException使其更符合正常期望。 - 始終在調(diào)用parse(String, ParsePosition)之前將ParsePosition索引重置為零。
必須進(jìn)行重置,因?yàn)槭褂么朔椒?#xff0c;解析ParsePosition輸入字符串中的ParsePosition索引開始。 - 使用NumberFormat.getNumberInstance()解析雙NumberFormat.getIntegerInstance()值,使用NumberFormat.getIntegerInstance()解析整數(shù)值。
如果您不對整數(shù)使用整數(shù)實(shí)例(或者將setParseIntegerOnly(true)應(yīng)用于數(shù)字實(shí)例),則該方法將解析所有小數(shù)點(diǎn)分隔符,直到輸入字符串的末尾。 結(jié)果是長度和索引匹配,并且您接受了無效的輸入。 - 除了比較長度和索引值是否相等之外,還必須在解析后檢查是否為空Number或輸入字符串為空(“”或長度為零)。
清除輸入字段將導(dǎo)致一個空字符串。 在這種情況下,長度和索引值均為零,因此它們匹配。 對于空字符串輸入,parse方法返回null。 此行為與使用NumberFormat.parse(String source)空字符串的結(jié)果不同,后者會拋出“無法解析的數(shù)字” ParseException 。 請記住, parse(String source, ParsePosition parsePosition)永遠(yuǎn)不會引發(fā)異常! 在NumberInput ,清單2中的代碼段用于處理各種可能性:清單2.檢查錯誤情況
if( sDouble.length() != pp.getIndex() || n == null ) { /* error */ }
總而言之,正確輸入處理的步驟為:
清單3顯示了相關(guān)代碼:
清單3. NFPPInput()方法
... NumberFormat nfDLocal = NumberFormat.getNumberInstance(), nfILocal = NumberFormat.getIntegerInstance();ParsePosition pp; ...public void NFPPInput( String sDouble, String sInt ){ // validate NumberFormat with ParsePosition Number n;double d;int i;pp.setIndex( 0 );n = nfDLocal.parse( sDouble, pp );if( sDouble.length() != pp.getIndex() || n == null ){showErrorMsg( "Double Input Not Acceptable\n" + "\"" + sDouble + "\"");}else{d = n.doubleValue();jtD.setText( nfDLocal.format( d ) );showInfoMsg( "Double Accepted \n" + d );}pp.setIndex( 0 );n = nfILocal.parse( sInt, pp );if( sInt.length() != pp.getIndex() || n == null ){showErrorMsg( "Int Input Not Acceptable \n" + "\"" + sInt + "\"");}else{i = n.intValue();jtI.setText( nfILocal.format( i ) );showInfoMsg( "Int Accepted \n" + i );}} // end NFPPInput結(jié)論
Java SE API中已進(jìn)行了大量工作,不僅允許在字節(jié)碼級別“寫一次,在任何地方運(yùn)行”,而且還可以容納國際化和本地化的應(yīng)用程序。 NumberFormat和DecimalFormat是打算編寫世界一流應(yīng)用程序的Java程序員不能沒有的類。 但是,如本文所示,開發(fā)人員也無法使用parse(String source)方法,除非可以假定完美的輸入-在現(xiàn)實(shí)世界中很少出現(xiàn)這種情況。 我在本文中提供的信息和代碼為您提供了另一種使用parse(String source, ParsePosition parsePosition)來確定條目何時無效并獲得正確結(jié)果的技術(shù)。
翻譯自: https://www.ibm.com/developerworks/java/library/j-numberformat/index.html
總結(jié)
以上是生活随笔為你收集整理的numberformat_解决NumberFormat的解析问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: FTP协议及工作原理
- 下一篇: 计算机视觉python入门_计算机视觉应