這是 Stack Overflow 第三周周報,本周加入了 Python 的內容,原計划兩篇 Java、兩篇 Python。但明天過節所以今天就先把周報發了,兩篇 Java、一篇 Python。公眾號「渡碼」為日更,歡迎關注。
DAY1. 使用隨機數打印"hello world"
今天我們看一個有意思的例子,看看下面的代碼為什么每次運行都能輸出 "hello world"。
public static String randomString(int i) { Random ran = new Random(i); StringBuilder sb = new StringBuilder(); while (true) { int k = ran.nextInt(27); System.out.println(k); if (k == 0) { break; } sb.append((char)('`' + k)); } return sb.toString(); }
調用代碼:
System.out.println(randomString(-229985452) + " " + randomString(-147909649));
我們不禁會想,生成隨機數不是隨機嗎,為什么每次運行結果都一樣。
要解釋這個問題,我們需要了解 “偽隨機” 的概念。只要隨機數是由確定的算法生成的,那就是偽隨機,也就是說它的生成看似隨機但是有一定規律的。而“真隨機”需要真實的隨機事件取得,所以計算機只能生成偽隨機數。
知道了“偽隨機”概念,今天的例子就好解釋了。在之前的文章中我們看過生成隨機數的代碼,是根據當前種子(seed)通過特定的規則(算法)生成隨機數並更新種子,因此 Random 生成的隨機數是“偽隨機”數。此外我們都知道算法有個特性叫確定性,也就是說相同的輸入只能得出相同的輸出。對於生成隨機數這個算法來說,每次調用只要初始種子(seed)不變,那么一定會生成相同的隨機數。這就解釋了,為什么每次執行生成的隨機數序列都是一樣的。
最后,生成的 int 類型隨機數 k,通過 (char)('`' + k) 這行代碼轉成字符,這里用到的是 ASCII 碼相關的知識,不再贅述。
今天通過這個簡單的例子了解了偽隨機的概念。歡迎交流,關注公眾號每天分享一個知識點。
DAY2. Java 嵌套類的兩種形式
Java 中嵌套類有兩種形式,官方定義為:如果嵌套類為靜態的,則稱為靜態嵌套類,如果是非靜態的則稱為內部類。設計嵌套類有以下的好處:
- 代碼組織:如果我們定義的某個類只服務於當前類,而不會在其他命名空間中使用,那么將它定義在當前類的命名空間中是明智的。就像成員變量,在面向對象編程思想下,並不是所有的成員都要定義為全局的
- 訪問權限:嵌套類可以直接訪問外部類的成員,即便是 private 修飾的
- 便利性:沒必要為每一個類創建一個文件
下面看看這兩種類的寫法以及優勢。先看看靜態嵌套類:
class OuterClass { private static int a =10; class StaticNestedClass { } } // 用法 OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
靜態類初始化時除了類前面加“外部類."外,其他的跟使用一個普通類相同。並且在 nestedObject 對象上使用跟普通對象一樣。靜態類最大的優勢在於可以直接訪問外部類私有的靜態成員。
再看看內部類:
class OuterClass { private int a = 10; class InnerClass { } } // 用法 OuterClass outerObject = new outerObject(); OuterClass.InnerClass innerObject = outerObject.new InnerClass();
與非靜態成員類似,InnerClass 都綁定在一個外部對象上。因此初始化 InnerClass 時,需要先創建 OuterClass 對象。內部類除了可以訪問外部類的靜態私有成員外,還可以直接訪問外部類的非靜態私有成員。當然,內部類使用時有個限制,不能聲明 static 成員。
DAY3. 是 Python 的設計缺陷嗎
先看一個問題,猜猜下面代碼的輸出結果是什么?
def a(): print("a executed") return [] def b(x=a()): x.append(5) print(x) b() b() b()
輸出結果如下:
a executed [5] [5, 5] [5, 5, 5]
可以看到,每次調用 b() 時代碼的輸出都不一樣。並且,a 函數只執行了一次。因此,我們可以確定,b 函數的默認參數是在函數定義時僅被計算了一次,而不是函數每次調用都會計算。這個問題可以這樣看:Python 中函數也是一個對象而默認參數是對象的成員,所以,它會被保存、更新。但你可能會想,這是不是 Python 的設計缺陷。答案是否定的,如今的 Python 如此流行,如果僅僅是設計缺陷這么簡單的問題,那么會很快被修復。那么,你可能還會想是不是可以讓函數執行時再確定默認參數值,這樣就可以避免上面的問題了。然而這樣同樣會有問題,看下面的例子:
fruits = ("apples", "bananas", "loganberries") def eat(food=fruits): print(food) def some_random_function(): global fruits fruits = ("blueberries", "mangos")
假設,全局變量 fruits 被修改了, 然后我們調用 eat 時,她的默認參數值就跟之前的調用不一致了, 同樣會令我們疑惑。而 Python 的設計者認為這種情況給語言使用者造成的疑惑更大,因此他們采用第一種的設計方式,在函數定義時確定默認值。
其實這樣的問題不光 Python 存在,任何語言都存在, 我們下面看一個 Java的例子。
StringBuffer s = new StringBuffer("Hello World"); Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>(); counts.put(s, 5); s.append("!!!!"); for (Map.Entry<StringBuffer, Integer> entry : counts.entrySet()) { System.out.println(entry.getKey() + "\t" + entry.getValue()); }
我們寫入 counts 的字符串是 "Hello World",而輸出是變成了 "Hello World!!!!"。這跟我們剛剛討論的 Python 的問題類似,這里沒有對錯,無論使用什么方式,都會有人提出不同意見。到底使用哪種方式是語言的設計者考慮的問題,而每種方式有什么坑,我們作為語言的使用者應該提前了解且避免。
以上便是 Stack Overflow 的第二周周報,希望對你有用,后續會繼續更新,如果想看日更內容歡迎關注公眾號。
公眾號「渡碼」,分享更多高質量內容