目錄
謎題一:奇數性" >謎題一:奇數性
謎題二:找零時刻" >謎題二:找零時刻
谜题三:长整数" >谜题三:长整数
谜题四:初级问题" >谜题四:初级问题
谜题五:十六进制的趣事" >谜题五:十六进制的趣事
首頁 Java java教程 整理分享Java語言表達式的五個謎題

整理分享Java語言表達式的五個謎題

May 19, 2022 am 11:49 AM
java

本篇文章為大家帶來了關於java的相關知識,其中主要介紹了關於語言表達式的相關問題,其中包括了奇數性、找零、長整數等等內容,下面一起來看一下,希望對大家有幫助。

整理分享Java語言表達式的五個謎題

推薦學習:《java影片教學

謎題一:奇數性

下面的方法意圖確定它那唯一的參數是否是一個奇數。這個方法能夠正確運作嗎?

public static boolean isOdd(int i){ 
returni%2==1:
}
登入後複製

        奇數可以定義為被2整除餘數為1的整數。表達式i%2計算的是i整除2時所產生的餘數,因此看起來這個程式應該可以正確運作。遺憾的是,它不能;它在四分之一的時間裡返回的都是錯誤的答案。

        為什麼是四分之一?因為在所有的int數值中,有一半都是負數,而isOdd方法對於所有負奇數的判斷都會失敗。在任何負整數上呼叫該方法都會回傳回false,不管該整數是偶數還是奇數。這是Java對取餘運算子(%)的定義所產生的後果。這個運算子被定義為對所有的int數值a和所有的非零int數值b,都滿足下面的恆等式:

(a/b)*b+(a%b)==a
登入後複製

整理分享Java語言表達式的五個謎題

         換句話說,如果你用b整除a,將商數乘以b,然後加上餘數,那麼你就得到了最初的值a。此恆等式具有正確的意義,但是當與Java的截尾整數整除運算子結合時,它就表示:當取餘運算傳回非零的結果時,它與左運算元具有相同的正負符號。

        當i為負奇數時,i%2等於-1而非1,因此isOdd方法將錯誤地傳回false。為了防止這種意外,請測試你的方法在為每一個數值型參數傳遞負數、零和正數值時,其行為是否正確。這個問題很容易訂正。只要將i%2與0而不是與1比較,並且反轉比較的含義即可:

public static boolean isOdd(inti){ 
returni%2!=0;
}
登入後複製

        如果你正在在一個性能臨界(performance-critical)環境中使用isOdd方法,那麼用位元運算子AND(&)來取代取餘運算子會顯得更好:

public static boolean isOdd(inti){ 
return(i&1)!=0;
}
登入後複製

        總之,無論何時使用至了取餘運算符,都要考慮到運算元和結果的符號。該運算符的行為在其運算元非負時是一目了然的,但是當一個或兩個運算元都是負數時,它的行為就不那麼顯而易見

了。

謎題二:找零時刻

請考慮這句話所描述的問題:

        Tom在汽車配件商店購買了一個價值$1.10的火星塞,但是他錢包裡都是兩美元一張的鈔票。如果他用一張兩美元的鈔票支付這個火星塞,那麼應該找給他多少零錢呢?

下面是一個試圖解決上述問題的程序,它會打印出什麼?

public class Change{
public static void main(String args[]){ 
Systemoutprintln(2.00-1.10);
}
}
登入後複製

        你可能會很天真地期望該程式能夠列印出0.90但是它如何才能知道你想要列印小數點後兩位小數呢?

        類型的值轉換為字串的規則有所了解你就會知道該程式列印出來的小數,是足以將 double類型的值與最靠近它的臨近值區分出來的最短的小數,它在小數點之前和之後都至少有一位。因此,看起來,該程式應該列印0.9是合理的。

這麼分析可能顯得很合理,但並不正確。如果你運行該程序,你會發現它打印的是:

0.89999999999999999

整理分享Java語言表達式的五個謎題

         问题在于1.1这个数字不能被精确表示成为一个 double,因此它被表示成为最接近它的double值。该程序从2中减去的就是这个值。遗憾的是,这个计算的结果并不是最接近0.9的double值。表示结果的double值的最短表示就是你所看到的打印出来的那个可恶的数字。

        更一般地说,问题在于并不是所有的小数都可以用二进制浮点数来精确表示的。

        如果你正在用的是JDK5.0或更新的版本,那么你可能会受其诱惑,通过使用printf工具来设置输出精度的方订正该程序:

//拙劣的解决方案-仍旧是使用二进制浮点数 

System.out.printf("%.2f%n",2.00-1.10);
登入後複製

        这条语句打印的是正确的结果,但是这并不表示它就是对底层问题的通用解决方案:它使用的仍日是二进制浮点数的double运算。浮点运算在一个范围很广的值域上提供了很好的近似,但是它通常不能产生精确的结果。二进制浮点对于货币计算是非常不适合的,因为它不可能将0.1-或者10的其它任何次负幂--精确表示为一个长度有限的二进制小数解决该问题的一种方式是使用某种整数类型,例如int或long,并且以分为单位来执行计算。如果你采纳了此路线,请确保该整数类型大到足够表示在程序中你将要用到的所有值。对这里举例的谜题来说,int就足够了。下面是我们用int类型来以分为单位表示货币值后重写的println语句。这个版本将打印出正确答案90分:

Systemoutprintln((200-110)+"cents")
登入後複製

        解决该问题的另一种方式是使用执行精确小数运算的BigDecimal。它还可以通过JDBC与SQL DECIMAL类型进行互操作。这里要告诫你一点:一定要用BigDecimal(String)构造器,而千万不要用BigDecimal(double)。后一个构造器将用它的参数的精确”值来创建一个实例:new BigDecimal(1)将返回一个表示0100000000000000055511151231257827021181583404541015625BigDecimal。通过正确使用BigDecimal,程序就可以打印出我们所期望的结果0.90:

import java.math.BigDecimal; 
public class Changel {
public static void main(String args[]){
System.out.println(newBigDecimal(2.00")
subtract(new BigDecimal("1.10")));
}
}
登入後複製

        这个版本并不是十分地完美,因为Java并没有为 BigDecimal提供任何语言上的支持。使用

BigDecimal的计算很有可能比那些使用原始类型的计算要慢一些,对某些大量使用小数计算的程序来说,这可能会成为问题,而对大多数程序来说,这显得一点也不重要。

        总之,在需要精确答案的地方,要避免使用 float和double;对于货币计算,要使用int、long或BigDecimal。对于语言设计者来说,应该考虑对小数运算提供语言支持。一种方式是提供对操作符重载的有限支持,以使得运算符可以被塑造为能够对数值引用类型起作用,例如BigDecimal。另一种方式是提供原始的小数类型,就像COBOL与PL/I所作的一样。

谜题三:长整数

        这个谜题之所以被称为长整除是因为它所涉及的程序是有关两个long型数值整除的。被除数表示的是一天里的微秒数;而除数表示的是一天里的毫秒数。这个程序会打印出什么呢?

public class Longpision{
public static void main(String args[]){
final long MICROS PER DAY=24*60*60*1000*1000;
final long MILLIS PER DAY=24*60*60*1000;
Systemoutprintln(MICROS PER DAY/ MILLIS PER DAY);
}
}
登入後複製

        这个谜题看起来相当直观。每天的毫秒数和每天的微秒数都是常量。为清楚起见,它们都被表示成积的形式。每天的微秒数是(24小时/天*60分钟/小时*60秒/分钟*1000毫秒/秒*1000微秒/毫秒)。而每天的毫秒数的不同之处只是少了最后一个因子1000。当你用每天的毫秒数来整除每天的微秒数时,除数中所有的因子都被约掉了,只剩下1000,这正是每毫秒包含的微秒数。

        除数和被除数都是long类型的,long类型大到了可以很容易地保存这两个乘积而不产生溢出。因此,看起来程序打印的必定是1000。遗憾的是,它打印的是5。这里到底发生了什么呢?

        问题在于常数MICROS PER DAY的计算确实”溢出了。尽管计算的结果适合放入long中,并且其空间还有富余,但是这个结果并不适合放入 int中。这个计算完全是以int运算来执行的,并且只有在运算完成之后,其结果才被提升到long,而此时已经太迟了:计算已经溢出了,它返回的是一个小了200倍的数值。从int提升到 long是一种拓宽原始类型转换(widening primitive conversion),它保留了(不正确的)数值。这个值之后被MILLIS PER DAY整除,而MILLIS PER DAY的计算是正确的,因为它适合int运算。这样整除的结果就得到了5。

        那么为什么计算会是以int运算来执行的呢?为所有乘在一起的因子都是int数值。当你将两个int数值相乘时,你将得到另一个int数值。Java不具有目标确定类型的特性,这是一种语言特性,其含义是指存储结果的变量的类型会影响到计算所使用的类型。

        通过使用long常量来替代int常量作为每一个乘积的第一个因子,我们就可以很容易地订正这个程序。这样做可以强制表达式中所有的后续计算都用long运作来完成。尽管这么做只在MICROS PER DAY表达式中是必需的,但是在两个乘积中都这么做是一种很好的方式。相似地,使用long作为乘积的“第一个”数值也并不总是必需的,但是这么做也是一种很好的形式。在两个计算中都以long数值开始可以很清楚地表明它们都不会溢出。下面的程序将打印出我们所期望的1000:

整理分享Java語言表達式的五個謎題

public class Longpision{

public static void main(String args[)

final long MICROS PER DAY=24L*60*60*1000*1000:

final long MILLIS PER DAY=24L*60*60*1000;

SystemoutprintlnMICROS PER DAY MILLIS PER DAY);

}

}
登入後複製

        这个教训很简单:当你在操作很大的数字时,千万要提防溢出--它可是一个缄默杀手。即使用来保存结果的变量已显得足够大,也并不意味着要产生结果的计算具有正确的类型。当你拿不准时,就使用long运算来执行整个计算。

        语言设计者从中可以吸取的教训是:也许降低默溢出产生的可能性确实是值得做的一件事。这可以通过对不会产生缄默溢出的运算提供支持来实现。程序可以抛出一个异常而不是直接溢出。就像Ada所作的那样,或者它们可以在需要的时候自动地切换到一个更大的内部表示上以防止溢出,就像Lisp所作的那样。这两种方式都可能会遭受与其相关的性能方面的损失。降低缄默溢出的另一种方式是支持目标确定类型,但是这么做会显著地增加类型系统的复杂度。

谜题四:初级问题

        得啦,前面那个谜题是有点棘手,但它是有关整除的,每个人都知道整除是很麻烦的。那么下面的程序只涉及加法,它又会打印出什么呢?

public class Elementary{

public static void main(String]args) {

Systemoutprintln(12345+54321);

}

}
登入後複製

        从表面上看,这像是一个很简单的谜题--简单到不需要纸和笔你就可以解决它。加号的左操作数的各个位是从1到5升序排列的,而右操作数是降序排列的。因此,相应各位的和仍然是常数,程序必定打印66666。对于这样的分析,只有一个问题:当你运行该程序时,它打印出的是17777。难道是Java对打印这样的非常数字抱有偏见吗?不知怎么的,这看起来并不像是一个合理的解释。

        事物往往有别于它的表象。就以这个问题为例,它并没有打印出我们想要的输出。请仔细观察+操作符的两个操作数,我们是将一个int类型的12345加到了long类型的54321上。请注意左操作数开头的数字1和右操作数结尾的小写字母1之间的细微差异。数字1的水平笔划(称为“臂(arm)”)和垂直笔划(称为“茎(stem)”)之间是一个锐角,而与此相对照的是,小写字母l的臂和茎之间是一个直角。

整理分享Java語言表達式的五個謎題

         在你大喊“恶心!”之前,你应该注意到这个问题确实已经引起了混乱,这里确实有一个教训:在 long型字面常量中,一定要用大写的L,千万不要用小写的1。这样就可以完全掐断这个谜题所产生的混乱的源头。

System.out.println(12345+5432L);
登入後複製

        相类似的,要避免使用单独的一个1字母作为变量名。例如,我们很难通过观察下面的代码段来判断它到底是打印出列表1还是数字1。

List l=new ArrayList<string>() ;

l.add("Foo");

System.outprintln(1);</string>
登入後複製

整理分享Java語言表達式的五個謎題

         总之,小写字母l和数字1在大多数打字机字体中都是几乎一样的。为避免你的程序的读者对二者产生混淆,千万不要使用小写的1来作为long型字面常量的结尾或是作为变量名。Java从C编程语言中继承良多,包括long型字面常量的语法。也许当初允许用小写的1来编写long型字面常量本身就是一个错误。

谜题五:十六进制的趣事

下面的程序是对两个十六进制(hex)字面常量进行相加,然后打印出十六进制的结果。这个程序会打印出什么呢?

public class JoyOfHex{

public static void main(String[] args){ 

System.out.println(

Long.toHexString(0x100000000L+0xcafebabe));

}

}
登入後複製

        看起来很明显,该程序应该打印出1cafebabe。毕竟,这确实就是十六进制数字10000000016与 cafebabe16的和。该程序使用的是long型运算,它可以支持16位十六进制数,因此运算溢出是不可能的。

        然而,如果你运行该程序,你就会发现它打印出来的是cafebabe,并没有任何前导的1。这个输出表示的是正确结果的低32位,但是不知何故第33位丢失了。

        看起来程序好像执行的是int型运算而不是long型运算,或者是忘了加第一个操作数。这里到底发生了什么呢?

        十进制字面常量具有一个很好的属性,即所有的十进制字面常量都是正的,而十六进制和八进制字面常量并不具备这个属性。要想书写一个负的十进制常量,可以使用一元取反操作符(-)连接一个十进制字面常量。以这种方式,你可以用十进制来书写任何int或long型的数值,不管它是正的还是负的,并且负的十进制常数可以很明确地用一个减号符号来标识。但是十六进制和八进制字面常量并不是这么回事,它们可以具有正的以及负的数值。如果十六进制和八进制字面常量的最高位被置位了,那么它们就是负数。在这个程序中,数字Oxcafebabe是一个int常量,它的最高位被置位了,所以它是一个负数。它等于十进制数值-889275714。

整理分享Java語言表達式的五個謎題

         该程序执行的这个加法是一种“混合类型的计算(mixed-type computation)左操作数是long类型的,而右操作数是int类型的。为了执行该计算,Java将int类型的数值用拓宽原始类型转换提升为一个long类型,然后对两个long类型数值相加。因为int是一个有符号的整数类型,所以这个转换执行的是符合扩展:它将负的int类型的数值提升为一个在数值上相等的long类型数值。这个加法的右操作数0xcafebabe被提升为了long类型的数值0xffffffffcafebabeL。这个数值之后被加到了左操作数0x100000000L上。当作为int类型来被审视时,经过符号扩展之后的右操作数的高32位是-1,而左操作数的高32位是1,将这两个数相加就得到了0,这也就解释为什么在程序输出中前导1丢失了。下面所示是用手写的加法实现。(在加法上面的数字是进位。)

1111111

0xffffffffcafebabeL

+0x0000000100000000L

0x00000000cafebabeL
登入後複製

        订正该程序非常简单,只需用一个long十六进制字面常量来表示右操作数即可。这就可以避免了具有破坏力的符号扩展,并且程序也就可以打印出我们所期望的结果1cafebabe:

public class JoyOfHex{

public static void main(String[] args){ 

System.outprintln(

LongtoHexString(0x100000000L+0xcafebabeL));

}

}
登入後複製

        这个谜题给我们的教训是:混合类型的计算可能会产生混淆,尤其是十六进制和八进制字面常量无需显式的减号符号就可以表示负的数值。为了避免这种窘境,通常最好是避免混合类型的计算。对于语言的设计者们来说,应该考虑支持无符号的整数类型,从而根除符号扩展的可能性。可能会有这样的争辩:负的十六进制和八进制字面常量应该被禁用,但是这可能会挫伤程序员,他们经常使用十六进制字面常量来表示那些符号没有任何重要含义的数值。

推荐学习:《java视频教程

以上是整理分享Java語言表達式的五個謎題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Java Spring 面試題 Java Spring 面試題 Aug 30, 2024 pm 04:29 PM

在本文中,我們保留了最常被問到的 Java Spring 面試問題及其詳細答案。這樣你就可以順利通過面試。

突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP與Python:了解差異 PHP與Python:了解差異 Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

Java程序查找膠囊的體積 Java程序查找膠囊的體積 Feb 07, 2025 am 11:37 AM

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHP與Python:核心功能 PHP與Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP與其他語言:比較 PHP與其他語言:比較 Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

創造未來:零基礎的 Java 編程 創造未來:零基礎的 Java 編程 Oct 13, 2024 pm 01:32 PM

Java是熱門程式語言,適合初學者和經驗豐富的開發者學習。本教學從基礎概念出發,逐步深入解說進階主題。安裝Java開發工具包後,可透過建立簡單的「Hello,World!」程式來實踐程式設計。理解程式碼後,使用命令提示字元編譯並執行程序,控制台上將輸出「Hello,World!」。學習Java開啟了程式設計之旅,隨著掌握程度加深,可創建更複雜的應用程式。

See all articles