日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

学习Java中遇到的问题积累_1

發(fā)布時(shí)間:2024/7/5 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 学习Java中遇到的问题积累_1 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1.奇數(shù)性

看下面代碼時(shí)候是否能判斷參數(shù) i 是奇數(shù)?

public static boolean isOdd(int i){ return i % 2 == 1; }

答案是: NO
看似正確的判斷奇數(shù), 但是如果 i 是負(fù)數(shù), 那么它返回值都是false
造成這種現(xiàn)象的是 => 從思想上固化, 認(rèn)為奇數(shù)只在正數(shù)范圍, 故判斷負(fù)數(shù)將報(bào)錯(cuò), 在C++中也是, 負(fù)數(shù)取余還是負(fù).
在Java中取余操作定義產(chǎn)生的后果都滿足下面的恒等式:

int數(shù)值a, 與非零int數(shù)值b 都滿足下面的等式: (a / b) * b + (a % b) == a

從上面就可以看出, 當(dāng)取余操作返回一個(gè)非零的結(jié)果時(shí), 左右操作數(shù)具有相同的正負(fù)號(hào), 所以當(dāng)取余在處理負(fù)數(shù)的時(shí)候, 以及會(huì)考慮負(fù)號(hào).
而上面的這個(gè)問題, 解決方法就是避免判斷符號(hào):

public static boolean isOdd(int i){ return i % 2 != 0; }

讓結(jié)果與0比較, 很容易避免正負(fù)號(hào)判斷.

思考:
1.在使用取余操作的時(shí)候要考慮符號(hào)對(duì)結(jié)果的影響
2.在運(yùn)算中, 嘗試使用0解決符號(hào)問題, 在一定程度上避免符號(hào)對(duì)結(jié)果的影響

2.浮點(diǎn)數(shù)產(chǎn)生的誤差

看下面代碼會(huì)打印出什么樣的結(jié)果?

public class Change{ public static void main(String args[]){ System.out.println(2.00 - 1.10); } }

從主觀上看, 打印的結(jié)果必然是0.90, 然后這卻是一個(gè)主觀錯(cuò)誤.
對(duì)于1.10這個(gè)數(shù), 計(jì)算機(jī)只會(huì)使用近似的二進(jìn)制浮點(diǎn)數(shù)表示, 產(chǎn)生精度影響.
從上面的例子中來看, 1,10在計(jì)算機(jī)中表示為1.099999, 這個(gè)1.10并沒有在計(jì)算機(jī)中得到精確的表示.
針對(duì)這個(gè)精度問題, 我們可能會(huì)選擇: System.out.printf("%.2f%n", 2.00 - 1.10);解決, 盡管打印出來的是正確答案, 但是依舊會(huì)暴露出一個(gè)問題: 如果精度控制在2.00 - 1.0010; 那么精度誤差依舊會(huì)出現(xiàn).
這里也說明了: 使用printf, 計(jì)算機(jī)底層依舊是使用二進(jìn)制的方式來計(jì)算, 只不過這種計(jì)算提供了更好的近似值而已.
那么應(yīng)該怎么解決這個(gè)問題呢?
首先想到是使用int模擬小數(shù)每一位, 然后計(jì)算, 最后將結(jié)果又轉(zhuǎn)化為小數(shù);
以此想到的就是使用BigDecimal類, 它主要用于精確小數(shù)運(yùn)算.

import java.math.BigDecimal; public class Change1{ public static void main(String args[]){ System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.10"))); } }

通過上面的代碼就能得到一個(gè)精確的值.
注: 使用BigDecimal的時(shí)候, 不要使用BigDecimal(double d)的構(gòu)造方法, 在double與double之間傳值的時(shí)候依舊會(huì)引起精度損失. 這是一個(gè)嚴(yán)重的問題.
BigDecimal底層采用的就是int[], 使用String的時(shí)候, 會(huì)將String不斷取每一位存入int[], 使用double的時(shí)候, 同理將數(shù)字的每一位存入int[], 但是double本身存在誤差, 導(dǎo)致存入的數(shù)據(jù)會(huì)出現(xiàn)誤差,例: 0.1存入double就表示為0.1000000099999999, 因此不使用double類型的構(gòu)造函數(shù)

思考:
當(dāng)然對(duì)于精確要求不高的地方, 完全可以使用float/double, 但是對(duì)于要求精度的計(jì)算, 比如貨幣 一定要使用int, long, BigDecimal.

3.長整數(shù)造成數(shù)據(jù)溢出

看下面的代碼會(huì)打印什么?

public class LongDivision{ 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; System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY); } }

整個(gè)過程, 除數(shù)與被除數(shù)都是long型, 很容易保存這兩個(gè)數(shù), 結(jié)果一定是1000, 但是結(jié)果讓你失望了, 結(jié)果是5.
這又是怎么回事呢?
首先這個(gè)表達(dá)式: 24606010001000總是在int類型的基礎(chǔ)上進(jìn)行計(jì)算. 即表達(dá)式是按照int的規(guī)則計(jì)算
很容易看出這個(gè)表達(dá)式計(jì)算的范圍早已超出int的取值范圍, 縱然使用long去存儲(chǔ)計(jì)算結(jié)果, 但是在計(jì)算的過程中就已經(jīng)出現(xiàn)計(jì)算數(shù)據(jù)溢出, 這是一個(gè)隱藏錯(cuò)誤.
Java目標(biāo)確定類型的特性 => 如上例子, 不同通過 long 去確定24606010001000按照long進(jìn)行存儲(chǔ).
必須指定數(shù)據(jù)類型, 才能按照指定的規(guī)則進(jìn)行運(yùn)算.
就用前面這個(gè)例子來看, 當(dāng)指定24為24L就能防止數(shù)據(jù)計(jì)算溢出, 在進(jìn)行乘法運(yùn)算的時(shí)候就已經(jīng)是在long的基礎(chǔ)上進(jìn)行計(jì)算.

public class LongDivision{ 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; System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY); } }

思考:
這個(gè)問題給了我一個(gè)深刻的教訓(xùn), 當(dāng)操作數(shù)很大的時(shí)候, 要預(yù)防操作數(shù)溢出, 當(dāng)無法確定計(jì)算數(shù)會(huì)不會(huì)溢出, 所要做的就是用儲(chǔ)存范圍最大的類型: long 來進(jìn)行計(jì)算

4.long的 “L” 與 “l(fā)” 所引發(fā)的錯(cuò)誤

從上面 “長整數(shù)運(yùn)算造成數(shù)據(jù)溢出” 引發(fā)又一個(gè)問題, 看下面例子:

public class Elementary{ public static void main(String[] args){ System.out.println(12345+5432l); } }

乍一看, 這很簡單, 計(jì)算結(jié)果時(shí)是 6666, 但是打印的結(jié)果是 17777, 我開始頭暈了, 這很不合理.
思考過后, 發(fā)現(xiàn)了一個(gè)問題:
我把 “l(fā)” 看作是 “1”, “l(fā)” 只是用于標(biāo)識(shí)5432是一個(gè)long類型, 這個(gè)視覺上的錯(cuò)誤將會(huì)引發(fā)更嚴(yán)重的問題.
思考:
小寫字母 l 與 1 很容易造成混淆, 為了避免這種錯(cuò)誤, 在表示long類型數(shù)據(jù)的, 要做的就是將 “l(fā)” 換做 “L”, 掐斷產(chǎn)生混亂的源頭.

5.多重類型轉(zhuǎn)換引發(fā)的數(shù)值變化

看這樣的一個(gè)例子:

public class Multicast{ public static void main (String[] args){ System.out.println((int)(char)(byte) -1); } }

看似結(jié)果是 -1, 但是運(yùn)行之后, 結(jié)果變?yōu)?65535
分析一下:
byte下的-1 => 1111,1111,1111,1111,1111,1111,1111,1111 32位(4個(gè)字節(jié)) 首位1表示負(fù)號(hào).
byte到char => 變?yōu)?0000,0000,1111,1111 16位(2個(gè)字節(jié))首位0, 就此負(fù)號(hào)變正號(hào).
char到int => 變?yōu)?0000,0000,0000,0000,0000,0000,1111,1111 32位(4個(gè)字節(jié))
由此可見, 在byte到char的變化過程中出現(xiàn)符號(hào)轉(zhuǎn)換的問題. char首位總是0使得負(fù)號(hào)變正號(hào)

類型轉(zhuǎn)換的過程存在這樣的簡單規(guī)則:
如果最初的數(shù)值類型是有符號(hào)的,那么就執(zhí)行符號(hào)擴(kuò)展;如果它是 char,那么不管它將要被轉(zhuǎn)換成什么類型,都執(zhí)行零擴(kuò)展。因此這也就解釋了為什么byte到char的過程存在負(fù)號(hào)變正號(hào).

為了在轉(zhuǎn)換的過程中保留符號(hào), 就使用位掩碼進(jìn)行限制, 例如: char c = (char)(b & 0xff); 這樣就能保證符號(hào)具有保留

思考:
在對(duì)有符號(hào)與無符號(hào)之間的轉(zhuǎn)換, 一定要注意上面的轉(zhuǎn)換規(guī)則, 如果不能確定轉(zhuǎn)換符號(hào)是否正確, 那么就避免出現(xiàn)有符號(hào)到無符號(hào)之間的轉(zhuǎn)換.

6.避免所謂聰明的編程技巧

對(duì)于交換兩個(gè)變量的內(nèi)容, 在C/C++中存在一種這樣的編程技巧:

int x = 1111; int y = 2; x^=y^=x^=y; cout<<x<<" "<<y;

這樣一個(gè)簡單的連續(xù)異或就能解決變量的交換問題, 這種寫法在很久以前是為了減少臨時(shí)變量的使用, 所以這種做法在現(xiàn)在也得到了保留.
首先看這樣一個(gè)問題, 表達(dá)式x^=y, 在C/C++的編譯器中是先計(jì)算出y的值, 然后再獲取x的值, 最后再計(jì)算表達(dá)式. 但在Java中的做法是先獲得x的值, 再獲得y的值, 最后再計(jì)算.
Java的語言規(guī)范描述: 操作符的操作數(shù)是從左往右求值.
這使得在計(jì)算 x^ =y^ =x^ =y表達(dá)式中的第二個(gè)x的時(shí)候是在計(jì)算x^ =y之前的值( x的值依舊是1111 ), 并不是x^=y后的值, 這就導(dǎo)致了計(jì)算上的錯(cuò)誤.
所以在Java中準(zhǔn)確的寫法是:

y = ( x^=( y^=x ) )^y

思考:
上面的這種寫法極其容易引起錯(cuò)誤, 程序的可讀性受到很大的影響, 所以在寫代碼的時(shí)候要思考一個(gè)問題, 除非編譯器能確定操作數(shù)的運(yùn)算順序, 不然不要讓編譯器去確定操作數(shù)的計(jì)算順序, 就比如這樣的表達(dá)式: x=a[i]+±a[j]++. 很容易導(dǎo)致錯(cuò)誤.

7.避免使用混合運(yùn)算

看如下代碼:

public class DosEquis{ public static void main(String[] args){ char x = 'X'; int i = 0; System.out.println(true ? x : 0); System.out.println(false ? i : x); } }

看似將打印: XX, 但是結(jié)果卻是X88. 這是一個(gè)出乎意料的結(jié)果.
思考之后, 將可能得出這樣的結(jié)論: 出現(xiàn)這樣問題的原因是操作數(shù)的類型自動(dòng)提升, char=>int.
但是又有一個(gè)問題就是為什么第一個(gè)運(yùn)算不是88. 找其根本原因還是在于條件表達(dá)式的運(yùn)算規(guī)則:

A ? B : C B, C為相同類型, 那么表達(dá)式的計(jì)算結(jié)果就是B, C的類型 B, C不是相同類型的時(shí)候, 那么計(jì)算結(jié)果就按照B的類型(此時(shí)B必須是式子中最高類型).此時(shí)C的類型就自動(dòng)上升為式子中最高的類型, 例: false ? x : i, 輸出是0, 而不是0對(duì)應(yīng)的字符.

上面的規(guī)則決定了, 將調(diào)用哪一個(gè)print的重載函數(shù).
這種條件表達(dá)式返回值, 很容易受B, C類型影響. 當(dāng)根據(jù)返回值作條件判斷的時(shí)候, 這種性質(zhì)也將導(dǎo)致一個(gè)嚴(yán)重的問題.
思考:
上面的問題說明了, 在條件表達(dá)式中, 最后再后兩個(gè)操作數(shù)使用相同類型的操作數(shù), 以此避免返回值類型不確定的問題, 并且在其他的表達(dá)式計(jì)算中, 一定要理清楚數(shù)值之間的類型轉(zhuǎn)換.

8.發(fā)現(xiàn)隱藏的類型轉(zhuǎn)換

在這樣的表達(dá)式: x += i; 按照平常的理解, 它一定是x = x + i; 可是這樣的運(yùn)算表達(dá)式是建立在x與i具有相同的類型的基礎(chǔ)上的, 如果當(dāng)x, i類型不相同的時(shí)候, 將會(huì)引發(fā)一個(gè)問題就是精度損失.
就比如:

short x = 0; int i = 99999; x += i;

現(xiàn)在的x不是99999, 而是-31073.
當(dāng) x += i 的時(shí)候, 出現(xiàn)的問題就是i自動(dòng)轉(zhuǎn)型為short, 此時(shí)x的值就不再是99999. 而當(dāng)你將表達(dá)式寫為: x = x + i 的時(shí)候, 這是一種顯式的轉(zhuǎn)型, 自然需要強(qiáng)轉(zhuǎn)操作. 從而避免了隱藏的類型轉(zhuǎn)換.
思考:
復(fù)合運(yùn)算會(huì)隱藏出現(xiàn)轉(zhuǎn)型操作, 這種轉(zhuǎn)型操作很有可能出現(xiàn)精度丟失.
所以在進(jìn)行復(fù)合運(yùn)算的時(shí)候, 避免兩邊的操作數(shù)是不同的類型, 防止編譯器出現(xiàn)危險(xiǎn)的窄化類型, 或者不使用復(fù)合運(yùn)算, 人為進(jìn)行類型轉(zhuǎn)換.

9.字符串的"+"運(yùn)算符

看如下代碼:

public class LastLaugh{public static void main(String[] args){ System.out.print("H"+"a"); System.out.print('H'+'a'); } }

由于長期受 “+” 運(yùn)算符的影響, 上面的代碼, 很容易把 ‘H’+‘a(chǎn)’ 也看作是字符串的連接, 這是一種慣性的思考方式.
在 ‘H’+‘a(chǎn)’ 表達(dá)式的運(yùn)算中, 是將 ‘H’, ‘a(chǎn)’, 上升為int, 進(jìn)行數(shù)值運(yùn)算.
如果想讓兩個(gè)字符連接在一起, 可以采用:

1.使用 StringBuffer/StringBuild 做 append 運(yùn)算.StringBuild s = "".append('H'); 2.使用String s = "" + 'H' +'a'; 使用字符串連接.String s1 = "" + 'H' + 'a';String s2 = 'a' + 'H' + "";System.out.println(s1);System.out.println(s2);注: 避免 s2 的寫法, 這樣寫 'a'+'H' 依舊做 int 的數(shù)值運(yùn)算

思考:
在使用 “+” 運(yùn)算符一定要注意操作數(shù)的類型, 以防止慣性思維導(dǎo)致的運(yùn)算錯(cuò)誤. 在某些場合這種錯(cuò)誤有可能是致命性的.

看完字符的 “+” 運(yùn)算符, 現(xiàn)在再來字符數(shù)組的 "+"運(yùn)算符 :

public class A{ public static void main(String[] args){ String letters = "ABC"; char[] numbers = {'1', '2', '3'}; System.out.println(letters + " easy as " + numbers); } }

上面的代碼, 最終的打印結(jié)果不是 ABC easy as 123, 而是ABC easy as [C@16f0472.
如果想到的打印結(jié)果是ABC easy as 123, 那么犯的錯(cuò)誤還是上面相同的錯(cuò)誤.
在打印結(jié)果的時(shí)候, 首先會(huì)進(jìn)行字符串連接, 當(dāng) “easy as” 這個(gè)字符串連接 char[] 的時(shí)候, 那么調(diào)用的是char[] 的toString(), 而系統(tǒng)并沒有重寫toString(), 所以最后調(diào)用的就是Object的toString();
為了修正這樣的錯(cuò)誤, 給出如下解決方式:

1.使用String.valueOf(number); 轉(zhuǎn)字符串后再進(jìn)行連接操作. 2.使用System.out.println(number); 調(diào)用重載的println(char[] c);

而在C/C++中, char numbers[4] = {‘1’, ‘2’, ‘3’, ‘\0’ }; 代表的就是一個(gè)字符串.
思考:
牢記, 數(shù)組類型的toString都沒有重寫, 如果想獲得數(shù)組中的值, 避免調(diào)用數(shù)組類型的toString, 或者讓系統(tǒng)隱藏調(diào)用, 而是直接遍歷數(shù)組獲得其中的值.

10."=="運(yùn)算符進(jìn)行比較

  • 問題1:
    這里先說明第一個(gè)問題, 就是Java中的 “==” 運(yùn)算符: 在比較基本類型的時(shí)候, 是比較基本類型值的關(guān)系; 在比較數(shù)組, 或者對(duì)象的時(shí)候是比較對(duì)象之間的引用值關(guān)系. 但是這里要注意的是:
    在比較Integer, Long(本人親測)這兩種的時(shí)候, 比較-128~127的時(shí)候是從緩存池中拿取數(shù)據(jù).
Integer中的equals方法 public boolean equals(Object obj) {if (obj instanceof Integer) {return value == ((Integer)obj).intValue();}return false;} 這個(gè)過程中實(shí)現(xiàn)的是將Integer拆包,-128~127不需要拆包,可直接使用==比較. Integer的緩存池-128~127: 自動(dòng)裝箱過程中使用valueOf創(chuàng)建對(duì)象,因此直接會(huì)使用緩存池中對(duì)象.

思考:
這里我想表達(dá)的意思就是, 如果要進(jìn)行對(duì)象內(nèi)容之間的比較, 務(wù)必重寫equals, 然后使用equals. 還有避免在基本類型與包裝類型混合狀態(tài)的基礎(chǔ)上使用 “==”, 就比如 Integer. 這個(gè)很容易導(dǎo)致錯(cuò)誤.

  • 問題2
    當(dāng)看到這樣的代碼的時(shí)候:
public class AnimalFarm{ public static void main(String[] args){ final String pig = "length: 10"; final String dog = "length: " + pig.length(); System.out. println("Animals are equal: " + pig == dog); } }

我想去比較pig與dog引用值關(guān)系, pig 與 dog 的引用值肯定是相同的, 但是最后的輸出結(jié)果卻是false. 在這里忽略了一個(gè)問題, 那就是前面的 “+” 的運(yùn)算級(jí)比 “==” 的運(yùn)算級(jí)高, 看似是比較pig與dog的引用值, 最后卻是比較"Animals are equal: length: 10"與dog的引用值關(guān)系.
現(xiàn)在給出下面的修正方案:

1.System.out.println("Animals are equal: " + (pig == dog)); 2.System.out.println("Animals are equal: " + pig.equals(dog));

思考:
從這里也看出, 比較對(duì)象內(nèi)容的時(shí)候, 務(wù)必使用已經(jīng)重載后equals, 除非刻意比較兩個(gè)對(duì)象的引用值, 否則千萬別使用"==".

上面有錯(cuò), 還請(qǐng)指出, 如果認(rèn)為我寫的還不錯(cuò), 還請(qǐng)點(diǎn)個(gè)贊, 多多支持一下, O(∩_∩)O~~

總結(jié)

以上是生活随笔為你收集整理的学习Java中遇到的问题积累_1的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。