我有一個朋友,叫老劉,戴著度數比我還高的近視鏡,顯得格外的“程式設計師”;穿著也非常“不拘一格”,上半身是襯衣西服,下半身是牛仔褲運動鞋。
我和老劉的感情非常好,每週末我們都要在一起吃頓飯。這周,我們吃的是洛陽有名的吳家刀削麵,席間他聊了一件蠻有趣的面試經歷;我聽得津津有味。
散席的時候,老劉特意叮囑我把他和麵試者的對話整理一下發出來,因為他覺得這段對話非常的精彩,值得推薦給更多初學Java的年輕人。
注:以下是老劉和麵試者東豐的真實對話。如有雷同,請勿對號入座。
老劉:“東豐,你長期從事金融軟體的開發,記錄存款和金額之類的有關資料用哪種資料型別啊?”
東豐:“當然用float啊,精確度比double高嘛。”
老劉:“東豐,你確定double精度比float低嗎?”
東豐:“那當然啊,double只精確到小數點後兩位,double這個單詞的意思不就是二的意思嗎?”
老劉:“東豐,你右手邊剛好有一本《Java核心技術卷1》,你翻到第35頁,看一下。”
東豐:“......哦,劉經理,不用了。不好意思,剛剛開個玩笑,為了緩和一下面試的緊張氣氛。看您厚厚的眼鏡片下藏著一雙深邃的眼睛,我覺得您一定大有學問。在金融計算中,必須要使用BigDecimal,double和float都不適合。因為單單一個精度問題就能把人整暈了。”
“我記得有一次,我碰巧要計算一個表示式a - b
,a的值為2,b的值為1.1,我侄女五歲半都知道答案應該是0.9,結果程式算出來的結果竟然是0.89999...,我當時又氣又激動,氣的是計算機還沒有我侄女靠譜,激動的是我竟然第一次找到了Java的bug。”
“我趕緊把這個bug反饋到了沉默王二的青銅時代群,以為我要被大家點贊表揚了。結果收到了大佬們一致的無情的嘲笑!”
“好在,群主二哥及時地安慰了我。他發我私信說:‘首先,計算機進行的是二進位制運算,我們輸入的十進位制數字會先轉換成二進位制,進行運算後再轉換為十進位制輸出。double和float提供了快速的運算,然而問題在於轉換為二進位制的時候,有些數字不能完全轉換,只能無限接近於原本的值,這就導致了你看到的不正確的結果。’”
“看到二哥的資訊後,我沮喪的心情得到了很大的安慰。我於是就對使用浮點數和小數中的問題進行了深入地研究。”
“BigDecimal可以表示任意精度的小數,並對它們進行計算。但要小心使用 BigDecimal(double)
建構函式,因為它會在計算的過程中產生舍入誤差。最好要使用基於整數或 String 的建構函式來建立BigDecimal物件。”
老劉:“哇,你回答得很好。那我們來看下一個問題。你應該知道2 / 0
的時候程式會報java.lang.ArithmeticException
的錯誤,那麼你知道2.0 / 0
的結果嗎?”
東豐:“劉經理,您這個問題難不倒我。結果是Infinity
(英菲尼迪),不好意思,我的英語口語能力有限啊。其實就是無窮的意思。不僅有正無窮大,還有負無窮大,甚至還有一個叫做NaN
的特殊值。NaN
代表‘不是一個數字’。這些值的存在是為了在出現錯誤條件時,程式還可以用特定的值來表示所產生的結果。這些錯誤的情況包括算術溢位、給負數開平方根,還有您說的除以 0 等。”
老劉:“東豐啊,你的發音比我好啊,挺準確的。”
東豐:“劉經理您見笑了。”
老劉:“我這還有一道關於陣列的問題,你稍等一下,我在紙上寫一下。”
int[] a = {1, 2, 3, 4}
int[] b = {2, 4}
int[] c = {1, 3}
int[] d = {2}
複製程式碼
“有這樣四個陣列,要求每個陣列只留一個唯一的元素。也就是說,a、b、c、d四個陣列之間的元素不能相同,你打算怎麼做呢?”
東豐:“劉經理,我能用一下您的凌美鋼筆嗎?”
老劉:“可以啊,你請用。”
東豐:“我大致演算了一下。說一下我的思路。d只能是2,b只能是4,a是1或者3,c是3或者1。遍歷長陣列,剔除長陣列中含有的最短陣列的元素。b中剔除d中的2還剩下4,a中剔除d中的2還剩下1、3、4,c中不含d中元素,所以不用剔除。剔除後b中還剩下一個4,d中是一個2。再次遍歷剔除a中的4。最後a和c中只剩下1和3了,再分別剔除互異的數就行了。”
“我覺得比較笨的作法,劉經理您覺得可行嗎?”
老劉:“可行,沒有問題。那,你對變數和方法的命名有什麼看法呢?請隨意發揮啊。”
東豐:“我在部落格園上曾看到一個有意思的投票統計——選出平常工作時自己認為最難的事情,選項大致有:”
- 寫各種文件
- 與客戶溝通
- 預估工作量
- 給變數命名
“投票結果完全出乎我的意料,排在第一的竟然是‘給變數命名’!變數命名實在是軟體開發中最常見的一件事了,但這件事要想做好,還真是不容易啊。”
“阿里巴巴Java開發手冊中「強制」規定,方法名、引數名、成員變數、區域性變數要統一使用lowerCamelCase風格,必須遵從駝峰形式。”
localValue // 變數
getHttpMessage() // 方法
複製程式碼
“有很長一段時間,我總是在糾結究竟是用拼音好還是用英語單詞好的問題。後來我下定了決心:要麼用拼音要麼用英語單詞,只要看到名字就能知道這個變數或者方法的用意就行了。”
“有時候,確實很難給變數取一個好名字。這時候,我就會選擇一種省時省力省心的做法——將變數名命名為型別名。比如說:”
Map map;
List list;
複製程式碼
“最好,變數宣告的地方要離第一次使用的地方近。否則的話,程式碼閱讀起來會很困難,因為人眼睛接受的螢幕高度是有限的。”
老劉:“東豐啊,你非常的優秀。恭喜你,你的面試過了。你回去準備一下,下週一就可以來上班了。”
再注:以上是老劉和麵試者東豐的真實對話。如有雷同,請勿對號入座。
附:陣列的留存程式碼
import java.util.ArrayList;
public class Distinct {
public static void main(String[] args) {
int[] a = {1, 2, 3, 4};
int[] b = {2, 4};
int[] c = {1, 3};
int[] d = {2};
int[][] input = {a, b, c, d};
//記錄每個陣列留下的唯一的元素
ArrayList<Integer> result = new ArrayList<Integer>();
//記錄每個陣列留下的唯一元素在陣列中的位置
ArrayList<Integer> index = new ArrayList<Integer>();
int row = 0;
int column = 0;
do {
boolean isBacktrack = false; //記錄當前狀態,是否是回溯
while(column < input[row].length) {
Integer current = input[row][column];
//當前元素是否已存在結果集中
boolean isContained = result.contains(current);;
//若當前元素不存在結果集中,將該元素加入結果集,並遍歷下一行
if(isContained == false) {
result.add(current);
index.add(column);
column = 0;
row++;
break;
}
//如果當前元素已經存在結果集中,並且已經到達本行最後一個元素,則回溯一行
else if(column + 1 == input[row].length) {
result.remove(result.size() - 1);
column = index.get(index.size() - 1) + 1;
index.remove(index.size() - 1);
row--; //回溯一行
isBacktrack = true;
break;
}
column++;
}
//如果是回溯,判斷列數是否超過該行的界限,如果超過了,再回溯一行
if(isBacktrack && column == input[row].length) {
result.remove(result.size() - 1);
column = index.get(index.size() - 1) + 1;
index.remove(index.size() - 1);
row--; //回溯一行
isBacktrack = true;
}
}while(row < input.length);
//把 result 中的每個元素賦給相應的陣列
for(int i = 0; i < result.size(); i++) {
input[i] = new int[] {result.get(i)};
}
//列印每個陣列的元素
for(int[] i: input) {
System.out.println(i[0]);
}
}
}
複製程式碼
沉默王二(微信ID:qing_gee),一個不止寫程式碼的程式設計師;還寫有趣有益的文字,給不喜歡嚴肅的你。