-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
368 lines (368 loc) · 151 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[[Java] Flow control in try catch finally]]></title>
<url>%2F2018%2F05%2F25%2FJava-Flow-control-in-try-catch-finally%2F</url>
<content type="text"><![CDATA[在正常情況下,當執行完 try 的最後一行或是要把控制權轉交給它的呼叫者 (return 或丟出 exception)時,會執行 finally ,當 finally 執行完畢,再把控制權轉交給 try。 但是當在 finally 裡面寫下 return, throw exception, continue, break 會沒有辦法把控制權轉交給 try,造成不可以預期的結果。所以千萬不要在 finally 裡面 return 或丟出 exception,雖然現在的 IDE 都會聰明到幫你檢查出來。 主要原因 try-finally 轉成 Java byte code 時候,finally 是一個副程式,當 try 執行完畢時候就會去呼叫 finally 副程式。當 finally 副程式執行完畢,會跳到 try 的記憶體位置。但在 finally 裡面加了這些return, throw exception, continue, break,它就提前跳出程式回不到正確的位置。 另外,當 try 裡面強制中斷 JVM 或是非預期的情況發生提早結束 (這裡所指的非預期的情況不是丟出 exception,而是當下的 thread 突然死掉或是 JVM 突然有問題這類的。),finally 是不會執行的。 Try-finally clause1234567try { // Block of code with multiple exit points}finally { // Block of code that is always executed when the try block is exited, // no matter how the try block is exited} Example 1123456789101112int i = 0; try { i = i + 1; System.out.println(i);}finally{ i = i + 2; System.out.println(i);}//1//3 Example 21234567891011121314public static void main(String[] args){ System.out.println(doSomeThing());}private static int doSomeThing(){ try { return 1; } finally{ return 2; }}//2 Example 31234567891011121314151617public static void main(String[] args) throws Exception { System.out.println(doSomeThing(true));}private static int doSomeThing(boolean bVal) throws Exception { try { if (bVal) { return 1; } return 0; } finally { System.out.println("Got old fashioned."); }}// Got old fashioned.// 1 Example 412345678910111213public static void main(String[] args) throws Exception { System.out.println(doSomeThing());}private static int doSomeThing() throws Exception { try { throw new Exception("from try"); } finally { throw new Exception("from finally"); }}// Exception in thread "main" java.lang.Exception: from finally Example 512345678910111213141516171819202122public static void main(String[] args) throws Exception { System.out.println(doSomeThing(true));}private static int doSomeThing(boolean bVal) throws Exception { int i = 0; while (true) { try { try { i = 1; } finally { // the first finally clause i = 2; } i = 3; return i; // this never completes, because of the continue } finally { // the second finally clause if (i == 3) { continue; // this continue overrides the return statement } } }} Example 612345678910111213public static void main(String[] args) throws Exception { System.out.println(doSomeThing());}private static int doSomeThing() throws Exception { try { System.exit(1); } finally { throw new Exception("from finally"); }}// terminates JVM and doesn't execute finally block ReferencesTry-finally clauses defined and demonstrated]]></content>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Scala] The importance of recursion]]></title>
<url>%2F2017%2F04%2F26%2FScala-The-importance-of-recursion%2F</url>
<content type="text"><![CDATA[遞迴在functional programming世界中是不准許有mutable變數,意味著我們熟悉的 while, for 迴圈都是禁止使用。那我們應該怎麼辦呢? 這邊會舉個數個例子,來解釋是透過遞迴方式來完成 while 或 for 迴圈。 從一個串列中找出最大值如果在 Java 我們會怎麼做呢? 宣個一個變數(max)存放目前的最大值 透過while 或 for 迴圈,將串列中的每個元素跟我們宣告的變數(max)比大小2.1 若元素的值大於max,max的值就改為此元素的值2.2 若元素的值小於或等於max,不做任何事 123456var max = Int.MinValuefor(i <- List(1, 2, 3, 4, 5)){ if(i > max) max = i} 透過遞迴方式我們會怎麼解決呢? 12345678910111213findMax(List(1, 2, 3, 4, 5))def findMax(list: List[Int]): Int = { list match{ // 假如是空的串列,我們回傳0 case Nil => 0 // 假如只有一個元素的串列,那最大值就為該元素 case h :: Nil => h // 主要邏輯 case h :: t => Math.max(h, findMax(t)) }} 在迴圈版本中,逐一每個元素跟某一個mutable變數比大小。 在迴遞版本中,第一個元素跟去除第一個元素的串列的最大值比大小。 迴遞的寫法更是簡潔和表達出整個程式主要邏輯,我們再來多試看看幾個題目。 從一個串列中找出最小值做法跟找出最大值類似,第一個元素跟去除第一個元素的串列的最小值比大小。 12345678910111213findMin(List(1, 2, 3, 4, 5))def findMin(list: List[Int]): Int = { list match{ // 假如是空的串列,我們回傳0 case Nil => 0 // 假如只有一個元素的串列,那最小值就為該元素 case h :: Nil => h // 主要邏輯 case h :: t => Math.min(h, findMin(t)) }} 檢查一個串列中是否存在某個特定值檢查第一個元素是否等於目標值或是去除第一個元素的串列有目標值 12345678910111213find(List(1, 2, 3, 4, 5), 5)def find(list: List[Int], target: Int): Boolean = { list match{ // 假如是空的串列,回傳false case Nil => false // 假如只有一個元素的串列,而且該元素等於target,回傳true case h :: Nil if (h == target) => true // 主要邏輯 case h :: t => (h == target) || find(t, target) }} 反轉一個字串先反轉去除第一個字元的串列,之後再將第一字元放在最後面 12345678910111213reverse("I should learn scala seriously!".toList)def reverse(list: List[Char]): String = { list match { // 假如是空的串列,回傳false case Nil => "" // 假如只有一個元素的串列,回傳該元素 case h :: Nil => h.toString // 主要邏輯 case h :: t => reverse(t) + h }} 判斷一個字串是否為另外一個字串的子字串子字串的定義:字數,內容和順序需要符合,空字串為任何字串的子字串例如: “abc” 為 “XXXYYYabc”的子字串 “XXXY” 為 “XXXYYYabc”的子字串 “” 為 “XXXYYYabc”的子字串 “ABC” 不為 “XXXYYYabc”的子字串 “QQWW” 不為 “XXXYYYabc”的子字串 “XXXaYYY” 不為 “XXXYYYabc”的子字串 先比較主要字串和子字串的第一個字母是否一樣, 若一樣則這兩個字串去除第一個字母繼續比 若不一樣,主要字串去除第一個字母和子字串繼續比 例如:主要字串: “abcdefghi”子字串: “abc”檢查主要字串前三個字母(abc)是否和子字串(abc)一樣,若一樣就回傳true 主要字串: “abcdefghi”子字串: “defgh”檢查主要字串前五個字母(abcde)是否和子字串(defgh)一樣,若不一樣,則 “bcdefghi” 和 “defgh” 繼續比 1234567891011121314println(isSubString("I should learn scala seriously!".toList, "scala".toList))println(isSubString("I should learn scala seriously!".toList, "XXOO".toList))println(isSubString("I should learn scala seriously!".toList, "scaxxyyla".toList))def isSubString(original: List[Char], target: List[Char]): Boolean = { (original, target) match { case (Nil, _) => false case (_, Nil) => true case (o :: Nil, t :: Nil) if (o == t) => true case (o :: Nil, t :: Nil) if (o != t) => false case (o, t) if (o.take(t.length) == t) => true case (oh :: ot, t) => isSubString(ot, t) }} 將一個串列由小排到大我們來實作Quicksort 從數列中挑出一個元素,稱為 “基準”(pivot)。 重新排序數列,所有比基準值小的元素擺放在基準前面,所有比基準值大的元素擺在基準後面(相同的數可以到任一邊)。在這個分割結束之後,該基準就處於數列的中間位置。這個稱為分割(partition)操作。 遞迴地(recursively)把小於基準值元素的子數列和大於基準值元素的子數列排序。 123456789quicksort(List(4, 3, 2, 9, 10))def quicksort(list: List[Int]): List[Int] = { list match { case Nil => Nil case h :: Nil => List(h) case h :: t => quicksort(t.filter(_ <= h)) ::: h :: quicksort(t.filter(_ > h)) }} 寫出費氏數列何謂費氏數列:費氏數列 123456789fib(6)def fib(n: Int): Int = { n match { case 0 => 0 case 1 => 1 case _ => fib(n - 1) + fib(n - 2) }} 經過幾次的練習,我們應該大概可以慢慢掌握到怎麼運用遞迴來達到迴圈目的。 先把主要邏輯寫出來 再把邊界條件設好,沒有邊界條件就會無窮的跑下去…遞迴主要是透過自我呼叫,把每一步的狀態放在 stack,等走到邊界回傳邊界值或完成邊界條件後,再回溯回去。 把問題透過Divide and conquer方式,大問題分解成數個小問題,若小問題規模較小且易於解決時,則直接解。否則,遞歸地解決各小問題。最後再將每個小問題的結果逐一合併起來。 遞迴的寫法很簡潔,但是最大的問題是效能不好和容易 Stack Overflow。主要原因是會去嘗試走所有可能的路徑而且把每一個狀態放在stack,當狀態一多就爆了。去嘗試走所有可能的路徑也是造成效能不好的原因。 假如: 去嘗試走所有可能的路徑不多,但是每一個步計算很花時間,遞迴容易轉換成非同步程式。 以排序為例,一個串列數量小於一千,我們可以使用Insertion Sort;大於一千改使用Quicksort: 12345678910111213def sort(list: List[Int]): List[Int] = { if(list.length < 1000){ // 需要大量時間計算才可以得出結果 insertionSort(list) } else{ list match { case Nil => Nil case h :: Nil => List(h) case h :: t => sort(t.filter(_ <= h)) ::: h :: sort(t.filter(_ > h)) } }} 我們可以輕易將它轉換成非同步程式。12345678910111213def sort(list: List[Int]): Future[List[Int]] = { if(list.length < 1000){ // 需要大量時間計算才可以得出結果 Future(insertionSort(list)) } else{ list match { case Nil => Future(Nil) case h :: Nil => Future(List(h)) case h :: t => Future(sort(t.filter(_ <= h)) ::: h :: sort(t.filter(_ > h))) } }} 非遞迴的版本就很難改了,因為主要存在共享的mutable變數,多個thread會共享同一個變數,就需要做同步處理。同步問題沒有小心處理,結果很容易出錯。 嘗試走所有可能的路徑很多 例如:計算第五十個費氏數列根據計算費氏數列的遞迴公式,n = 50 的樹狀結構會相當大。我們可以透過 Tail recursion 來解決。把上一個狀態的結果直接當成帶入參數,而不是依賴上一個狀態的結果。 因為 fib(n) = fib(n - 1) + fib(n - 2),假如可以把 fib(n - 1) 和 fib(n - 2) 當參數帶入,這樣就可以馬上得出 fib(n),而不是等算完 fib(n - 1) 和 fib(n - 2) 後才可以得出 fib(n)。 import scala.annotation.tailrec fib(0, 1, 5) @tailrec def fib(previous: Int, current: Int, n: Int): Int = { n match { case 0 => previous case 1 => current case _ => fib(current, previous + current, n - 1) } } 有興趣的讀者可以到 ScalaKitchen 試玩看看,也有提供中文版本的Scala入門教學。]]></content>
<categories>
<category>Scala</category>
</categories>
<tags>
<tag>Scala</tag>
<tag>Recursion</tag>
<tag>Tail recursion</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Scala] Use Either to hand error cases]]></title>
<url>%2F2017%2F02%2F15%2FScala-Use-Either-Try-to-hand-error%2F</url>
<content type="text"><![CDATA[在OOP的語言中,我們exception來表達程式錯誤或是系統crash.Exception又可以分為Checked Exception 和 UnChecked Excpetion. Checked Exception: 會將要丟出的excpetion寫在function的signature.例如: 1interface Foo() throws XXXException, YYYException UnChecked Excpetion: 要丟出的excpetion不會寫在function的signature,呼叫function的人需要知道有exception要處理. Checked Exception會違反Open/Closed Principle,當有新增丟出的excpetion,需要修改interface signature;UnChecked Excpetion則是需要知道有哪些exception需要處理. 這兩種exception孰好孰壞,在 Clean code: a handbook of agile software craftsmanship, Robert C. Martin 有提到:123456The debate is over. For years Java programmers have debated over the benefits and liabilities of checked exceptions. When checked exceptions were introduced in the first version of Java, they seemed like a great idea. The signature of every method would list all of the exceptions that it could pass to its caller. Moreover, these exceptions were part of the typeof the method. Your code literally wouldn't compile if the signature didn't match what your code could do.At the time, we thought that checked exceptions were a great idea; and yes, they can yield some benefit. However, it is clear now that they aren't necessary for the production of robust software. C# doesn't have checked exceptions, and despite valiant attempts, C++ doesn't either. Neither do Python or Ruby. Yet it is possible to write robust software in all of these languages. Because that is the case, we have to decide—really—whether checked exceptions are worth their price.Checked exceptions can sometimes be useful if you are writing a critical library: You must catch them. But in general application development the dependency costs outweigh the benefits 在Java中建議使用UnChecked Excpetion,但是這會導致另外一個災難… 呼叫者不知道會丟出exception 呼叫者不知道有哪些excpetion要處理,當call chain很深的時候更是糟糕… 到處充滿了 try{...} catch{...} 相信使用Java開發的工程師應該感觸良多,尤其是在維護舊專案.一個call chain長達十幾層,每一層丟的excpetion都不一樣,有些層會處理exception,有些又不會,最後的大絕招就是每一層都加 try{...} catch{...}. 例外處理在OOP是一個很重要的議題,沒有謹慎處理很容易造成維護困難和發生問題不容易找到問題點.因為Exception是有side effect,在FP是不被准許的.所以在pure FP程式中是看不到excpetion和 try{...} catch{...}. Either我們可以透過 Either 來達成,Left 放錯誤的物件,Right 放正確的物件:123456789sealed abstract class Either[A, B]final case class Left[+A, +B](a: A) extends Either[A, B]final case class Right[+A, +B](b: B) extends Either[A, B]// for exmpletype Error = Stringval result = Right[Error, Int](123)val error = Left[Error, Int]("Something wrong!!!") 我們可以把正確的結果或錯誤放到 Either 這個容器,呼叫者可以清楚知道有需要處理錯誤情況,再來程式碼不再到處充斥 try{...} catch{...}. 我們使用四則運算來當範例,如何使用 Either123456789101112131415161718192021222324252627282930313233343536373839404142def div(a: Double, b: Double): Either[String, Double] = { if(b == 0) Left[String, Double]("Can't divide by 0 !!!") else Right[String, Double](a / b)}def add(a: Double, b: Double): Either[String, Double] = { Right[String, Double](a + b)}def minus(a: Double, b: Double): Either[String, Double] = { Right[String, Double](a - b)}def mul(a: Double, b: Double): Either[String, Double] = { Right[String, Double](a * b)}// ((((1 + 2) * 3) / 4) - 5)val result = for { r1 <- add(1, 2) r2 <- mul(r1, 3) r3 <- div(r2, 4) r4 <- minus(r3, 5)} yield { r4}// result: scala.util.Either[String,Double] = Right(-2.75)// ((((1 + 2) * 3) / 0) - 5)val result = for { r1 <- add(1, 2) r2 <- mul(r1, 3) r3 <- div(r2, 0) r4 <- minus(r3, 5)} yield { r4}// result: scala.util.Either[String,Double] = Left(Can't divide by 0 !!!) 根據上面簡單的範例,這樣的寫法很明顯優於傳統OOP用excpetion來處理錯誤: 不再有 try{...} catch{...} 充斥在每個地方. 呼叫者可以根據fucntion signature知道是否會產生錯誤. 更可以專心在商業邏輯,而不用花多餘的心力在處理例外. Try12345sealed abstract class Try[+T]final case class Failure[+T](exception: Throwable) extends Try[T] final case class Success[+T](value: T) extends Try[T] Try 也是類似於 Either 的context,有興趣的讀者可以試用看看.在實務上大部分都採用 Either,不採用 Try的原因是: 沒有辦法對exception做 exhaustive pattern match,當是Failure的時候,呼叫者不知道有哪些exception會丟出,只知道會丟出一個 Throwable class. 使用 Either 我們可以自訂Error type,當失敗時候就可以對Error type做 exhaustive pattern match,依據不同的錯誤做不同的處理.]]></content>
<categories>
<category>Scala</category>
</categories>
<tags>
<tag>Scala</tag>
<tag>Exception</tag>
<tag>Either</tag>
<tag>Try</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Scala] How to use Option correctly]]></title>
<url>%2F2016%2F12%2F20%2FScala-How-to-use-Option-correctly%2F</url>
<content type="text"><![CDATA[對於Scala我一直把它當成進階版本的Java,當越來越了解Functional Programming的精神,才發現之前想法還蠻天真的. 可以從如何正確使用 Option ,讓我們慢慢來進入Functional Programming的世界. 先從一個簡單的API實例來開始: 假如有一個API,Admin使用者可以找出某個國家的所有使用者;不是Admin使用者就回傳空字串. 使用者會帶上 userName , password 和 country 呼叫此API,這個API會 檢查格式是否正確 查詢資料庫是否有使用者 若有,檢查 userType 使否為 Admin 根據 country 去資料庫撈取該國家的所有使用者 將結果轉成 json 放到 http response 先定義 Data Type:123456789101112131415161718192021sealed trait Namefinal case class UserName(name: String) extends Namesealed trait Passwordfinal case class MD5Password(plainText: String) extends Passwordsealed trait Countryfinal case object TAIWAN extends Countryfinal case object CHINA extends Countrysealed trait UserTypefinal case object ADMIN extends UserTypefinal case object EMAIL extends UserTypefinal case object PHONE extends UserTypesealed trait Account { val name: String}final case class AccountWithCredential(name: UserName, password: Password, userType: UserType) extends Accountfinal case class AccountWithProfile(name: UserName, country: Country, age: Int) extends Account 這樣的 Data Type 也是所謂的 Algebraic Type,以 Account 為例: 它有兩種 subtype AccountWithCredential 和 AccountWithProfile,為 Account 的 Sum Type 各種不同 subtype 有它們的參數,也稱之 Product Type 這種定義方式有點像Java世界中的 Value Object,把資料和行為分開是有好處的,避免資料和行為耦合過緊.Design Pattern 也是透過pattern去分離資料和行為. 再來我們來定義行為:1234567891011trait Repo { def login(name: UserName, password: Password): Option[AccountWithCredential] def getAccountsByCountry(country: Country): Option[List[AccountWithProfile]]}object Repo extends Repo { def login(name: UserName, password: Password): Option[AccountWithCredential] = ??? def getAccountsByCountry(country: Country): Option[List[AccountWithProfile]] = ???} login 的回傳值為一個 Option context,裡面裝著 AccountWithCredential. 若有找到就回傳 Some[AccountWithCredential] 若沒有找就回傳 None 在 Java 7 之前的版本會定義找不到會回傳 Null,Null 會造成語意不清和程式碼充斥著一堆檢查是否為 Null 的程式碼.我們可以透過 Null Object pattern 來解決這個問題.在 Scala 或 Java 8 則是透過 Option / Optional data type來解決之. 我們可以實作API:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748object Api extends App { val name: Option[UserName] = parse(UserName(args(0))) val password: Option[MD5Password] = parse(MD5Password(args(1))) // Step1: login // Step2: check isAdmin == true // Step3: get accounts by country // Step4: convert accounts to json // Step5: add json to HttpResponse if (name.isEmpty) throw new RuntimeException("bad id") if (password.isEmpty) throw new RuntimeException("bad password") val json: Option[String] = getJsonProfile(name.get, password.get, CHINA) if (json.isDefined) { returnHttpResponse(json.get) } else { returnHttpResponse("") } private def parse[A](a: => A): Option[A] = ??? private def getJsonProfile(name: UserName, password: Password, country: Country): Option[String] = { val accountWithCredential: Option[AccountWithCredential] = Repo.login(name, password) if (accountWithCredential.isDefined) { accountWithCredential.get.userType match { case ADMIN => val accounts: Option[List[AccountWithProfile]] = Repo.getAccountsByCountry(country) if (accounts.isDefined) { listToJson(accounts.get) } else { None } case _: UserType => None } } else { None } } // convert a list of Account to json String private def listToJson[A](list: List[A]): Option[String] = ??? private def returnHttpResponse(message: String): Unit = ???} 這個版本利用 isDefined 去判定是否為空值,這樣的寫法跟使用 Null 當回傳值一樣,在這個版本完全看不出使用 Option 好處,反而顯得冗餘. 我們改寫另外一個版本 getJsonProfile :123456789private def getJsonProfile(name: UserName, password: Password, country: Country): Option[String] = { Repo.login(name, password).flatMap( account => { if (account.userType == ADMIN) { Repo.getAccountsByCountry(country).flatMap(profiles => listToJson(profiles)) } else None }) } 我們利用 flatMap 來幫串接,而不是嘗試取得 Option 裡面的值,Option 的 flatMap 的 signature 為 A => Option[B]. 例如: getAccountsByCountry 需依賴 login 的結果,才可以進行之後的動作.我們是透過 flatMap 將這兩個動作串接起來,這樣就可以避免一堆冗餘 if else 檢查. 再舉一個抽象的例子:1234567def A(a: A): Option[B] = ???def B(b: B): Option[C] = ???def C(c: C): Option[D] = ???def total(input: Option[A]): Option[D] = { input.flatMap(A(_).flatMap(B(_).flatMap(C(_))))} 可以透過 flatMap 串接 A, B, C 這三個function,假如用 isDefined 來串接,程式碼的可讀性和維護性會大幅下降.在 Function Programming中,常見的pattern會將值放到一個context裡面,在使用時候並不會將值取出來,而是透過 flatMap 或 map 來轉換context裡面的值. 我們可以傳入 parse 過後的結果,將所有的function串接在一起:12345678910111213141516171819202122232425262728object Api extends App { val name: Option[UserName] = parse(UserName(args(0))) val password: Option[MD5Password] = parse(MD5Password(args(1))) val json: Option[String] = getJsonProfile(name, password, CHINA) if (json.isDefined) { returnHttpResponse(json.get) } else { returnHttpResponse("") } private def getJsonProfile(name: Option[UserName], password: Option[Password], country: Country): Option[String] = { name.flatMap(name1 => password.flatMap(password1 => Repo.login(name1, password1).flatMap( account => { if (account.userType == ADMIN) { Repo.getAccountsByCountry(country).flatMap(profiles => listToJson(profiles)) } else None }) )) } // other functions // ...} 可以發現串接的function越多,會越寫越右邊,有點類似 Callback hell ,或許也可以稱為 flatMap hell…好險Scala有提供很好的 syntax sugar for comprehension 來解決 flatMap hell 1234567891011121314151617object Api extends App { val name: Option[UserName] = parse(UserName(args(0))) val password: Option[MD5Password] = parse(MD5Password(args(1))) for { name1 <- name password1 <- password account <- Repo.login(name1, password1).filter(a => a.userType == ADMIN) profiles <- Repo.getAccountsByCountry(CHINA) json <- listToJson(profiles) } yield { returnHttpResponse(json) } // other functions // ...} 比較第一個版本和最後一個版本的程式碼,最後一個版本可以很清楚表達整個程式意圖,而不會被一堆 if else 檢查而干擾.透過正確使用 Option 我們可以學習到: 分離資料和行為 將資料放入context 利用 flatMap, map 轉換context裡面的值 衍生需求最後一個版本可以發現payload不正確或某個國家的使用者為零 結果竟然都是None,這樣會造成使用者體驗不佳和維護上的困難.那我們可以怎麼改善呢? 可以改用 Try 或 Either 來表達錯誤,在這邊使用 Option 來表示parse完的結果不太洽當,主要是展示可以透過compse方式來串接多個function,下一篇將改用 Try 或 Either ,讓前端可以清楚知道錯誤訊息. 參考: Introduction to Algebraic Types in Scala]]></content>
<categories>
<category>Scala</category>
</categories>
<tags>
<tag>Scala</tag>
<tag>Option</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] Java is Pass by Value and Not Pass by Reference]]></title>
<url>%2F2016%2F12%2F09%2FJava-Java-is-Pass-by-Value-and-Not-Pass-by-Reference%2F</url>
<content type="text"><![CDATA[Java到底是pass by value還是pass by reference?說法眾說紛紜,後來看到這篇文章Java is Pass by Value and Not Pass by Reference後,觀念才整個釐清。 Java是pass by value! 範例:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950public class Balloon { private String color; public Balloon() { } public Balloon(String c) { this.color = c; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public static void main(String[] args) { Balloon red = new Balloon("Red"); Balloon blue = new Balloon("Blue"); swap(red, blue); System.out.println("red color=" + red.getColor()); System.out.println("blue color=" + blue.getColor()); foo(blue); System.out.println("blue color=" + blue.getColor()); } private static void foo(Balloon balloon) { balloon.setColor("Red"); balloon = new Balloon("Green"); balloon.setColor("Blue"); } public static void swap(Object o1, Object o2) { Object temp = o1; o1 = o2; o2 = temp; } public static void swap1(Balloon o1, Balloon o2) { String temp = o1.getColor(); o1.setColor(o2.getColor()); o2.setColor(temp); }} 結果:123456789//使用swap()red color=Redblue color=Blueblue color=Red//使用swap1()red color=Blueblue color=Redblue color=Red 分析: 尚未執行swap(): 執行swap(),o1指向red,o2指向blue: swap()執行結束,原本的物件並沒有互相交換: 執行foo(),ballon指向blue: 執行foo()第一行,透過原本物件的setter method修改值: 執行foo()第二行: 執行foo()第三行: 因為Java是採用pass by value作法,當以物件作為參數傳入到method,在method裡面想要修改物件的值,需透過物件的setter method,這樣原本物件的值才會連帶一起變更。透過assign(=)或new等方式,原本物件的值都不會有變化。 參考資料: Java is Pass by Value and Not Pass by Reference]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] Java String Pool]]></title>
<url>%2F2016%2F12%2F09%2FJava-Java-String-Pool%2F</url>
<content type="text"><![CDATA[假如沒有string pool這樣的機制,遇到string就建立一個物件,這樣記憶體就很快就會爆掉了。 在java中採取Flyweight pattern作法,可以共享同樣的string object。 建立string object有兩種方式: String string = “Cat”; String string = new String(“Cat”); 採用第一種方法,會先檢查string pool是否有相同的string。若有就共用,沒有則建立之。 採用第二種方法﹐不會使用到string pool機制,而是在heap建立一個新的string object。若之後想要使用string pool機制,可以使用intern。 範例:12345678910111213public class StringExample { public static void main(String args[]) { String s1 = "Cat"; String s2 = "Cat"; String s3 = new String("Cat"); System.out.println("s1 == s2 ?" + (s1 == s2)); System.out.println("s1 == s3 ?" + (s1 == s3)); System.out.println("s1 == s3.intern() ?" + (s1 == s3.intern())); System.out.println("s1 equals s3 ?" + s1.equals(s3)); }} 結果:1234s1 == s2 ?trues1 == s3 ?falses1 == s3.intern() ?trues1 equals s3 ?true 參考資料: What is Java String Pool?]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[SQL] How to get rank using ANSI SQL]]></title>
<url>%2F2016%2F12%2F09%2FSQL-How-to-get-rank-using-ANSI-SQL%2F</url>
<content type="text"><![CDATA[要對分數做排名,最直接的想法就對它們做ORDER BY。但是當分數有重複時候排名是要一樣,這時候ORDER BY就發揮不了作用。 不同的廠商的資料庫有提供不同的函數可以解決這個問題,假如沒有使用函數該如何做到呢? 建立資料表:123456CREATE TABLE `golf` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) DEFAULT NULL, `score` int(11) DEFAULT NULL, PRIMARY KEY (`id`)); 新增資料:1234567INSERT INTO `golf`(`name`,`score`) VALUES (A,74);INSERT INTO `golf`(`name`,`score`) VALUES (B,79);INSERT INTO `golf`(`name`,`score`) VALUES (C,79);INSERT INTO `golf`(`name`,`score`) VALUES (D,82);INSERT INTO `golf`(`name`,`score`) VALUES (E,89);INSERT INTO `golf`(`name`,`score`) VALUES (F,89);INSERT INTO `golf`(`name`,`score`) VALUES (G,98); 查詢:12345678910111213141516SELECT tmp.name, tmp.score, (SELECT COUNT(*) + 1 FROM (SELECT golf.score FROM golf GROUP BY golf.score) AS tmp1 WHERE tmp1.score > tmp.score) AS rankFROM golf AS tmpORDER BY tmp.score DESC; 結果:12345678name score rankG 98 1E 89 2F 89 2D 82 3B 79 4C 79 4A 74 5 想法:先從排名這個概念下手,假如有三個人的分數(這三人分數都不一樣)大於我的分數,那麼我就是排名第四。因為分數重複的排名是一樣的,需要先對分數做一次GROUP BY,再根據上述的概念去算出排名。]]></content>
<categories>
<category>SQL</category>
</categories>
<tags>
<tag>SQL</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] Synchronize in Java]]></title>
<url>%2F2016%2F12%2F09%2FJava-Synchronize-in-Java%2F</url>
<content type="text"><![CDATA[在了解Java的synchronized機制之前,先來複習一下Monitor。 可以把Monitor想像成它就是一個類別,裡面存放著共享變數、方法和建構子。Monitor會確保只有一個Process可以進入,其他也想要進入的Process,就必須在queue裡面等待。程式設計師可以根據不同的情況,決定是否要讓正在Monitor的Process進入waiting state或是喚醒其他在waiting state的Process。 例如:當某個Process取得Monitor的執行權利,在執行過程中發現不符合x情況,必須進入waiting state,可以使用x.wait()。想要喚醒x queue中的waiting Process,可以透過x.signal()。詳細可以參考Operating System Concepts。 Java的synchronized就是實作了Monitor,Object的wait()和notify()就相當於wait()和signal()。有了這樣的概念後,在使用synchronized就比較不會發生鎖錯對象的問題。 synchronized使用方式可以分為兩種: synchronized method synchronized block 範例TargetObject.java:1234567891011121314151617181920212223public class TargetObject { private static int count = 0; public void method1() { count++; System.out.println(Thread.currentThread().getName() + " in method1 and count = " + count); } public void method2() { count++; System.out.println(Thread.currentThread().getName() + " in method2 and count = " + count); } public static void staticMethod1() { count++; System.out.println(Thread.currentThread().getName() + " in static method1 and count = " + count); } public static void staticMethod2() { count++; System.out.println(Thread.currentThread().getName() + " in static method2 and count = " + count); }} Thread1.java:1234567891011121314151617181920public class Thread1 implements Runnable { private TargetObject targetObject; public Thread1(TargetObject targetObject) { this.targetObject = targetObject; } @Override public void run() { while (true) { this.targetObject.method1(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }} Thread2.java:1234567891011121314151617181920public class Thread2 implements Runnable { private TargetObject targetObject; public Thread1(TargetObject targetObject) { this.targetObject = targetObject; } @Override public void run() { while (true) { this.targetObject.method2(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }} Run.java:123456789public class Run { public static void main(String[] args) { TargetObject targetObject = new TargetObject(); Thread thread1 = new Thread(new Thread1(targetObject), "Thread1"); Thread thread2 = new Thread(new Thread2(targetObject), "Thread2"); thread1.start(); thread2.start(); }} 執行結果:沒有做同步處理,會存在race condition。123456Thread1 in method1 and count = 2Thread2 in method2 and count = 2Thread2 in method2 and count = 3Thread1 in method1 and count = 4Thread2 in method2 and count = 5Thread2 in method2 and count = 6 synchronized method在TargetObject.java中的method1和method2加上 synchronized 就可以鎖定由TargetObject類別所實體化的物件targetObject。 TargetObject.java1234567891011121314151617181920212223public class TargetObject { private static int count = 0; public synchronized void method1() { count++; System.out.println(Thread.currentThread().getName() + " in method1 and count = " + count); } public synchronized void method2() { count++; System.out.println(Thread.currentThread().getName() + " in method2 and count = " + count); } public static void staticMethod1() { count++; System.out.println(Thread.currentThread().getName() + " in static method1 and count = " + count); } public static void staticMethod2() { count++; System.out.println(Thread.currentThread().getName() + " in static method2 and count = " + count); }} 執行結果:123456789Thread1 in method1 and count = 1Thread2 in method2 and count = 2Thread2 in method2 and count = 3Thread1 in method1 and count = 4Thread2 in method2 and count = 5Thread2 in method2 and count = 6Thread1 in method1 and count = 7Thread2 in method2 and count = 8Thread2 in method2 and count = 9 為了更清楚了解鎖定的對象,現在先 移除 method1的synchronized,將同步機制移到Thread1.java。 Thread1.java12345678910111213141516171819202122public class Thread1 implements Runnable { private TargetObject targetObject; public Thread1(TargetObject targetObject) { this.targetObject = targetObject; } @Override public void run() { while (true) { synchronized (targetObject) { this.targetObject.method1(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }} 執行結果:123456789Thread1 in method1 and count = 1Thread2 in method2 and count = 2Thread2 in method2 and count = 3Thread1 in method1 and count = 4Thread2 in method2 and count = 5Thread2 in method2 and count = 6Thread1 in method1 and count = 7Thread2 in method2 and count = 8Thread2 in method2 and count = 9 從執行結果可以看出上鎖的對象是在Run.java中實體化的 targetObject。 若將 Thread1.java中的 this.targetObject.method1() 改成 TargetObject.staticMethod1()。 Thread2.java中的 this.targetObject.method2() 改成 TargetObject.staticMethod2()。 必須對TargetObject的staticMethod1和staticMethod2做 synchronized 。透過上述的方式可以知道synchronized static method是鎖定 TargetObject.class。 synchronized block123synchronized(想要鎖定的物件或是class literal){ //do something} synchronized block較有彈性,可以選擇鎖定的對象。Thread1.java:123456789101112131415161718192021222324public class Thread1 implements Runnable { private TargetObject targetObject; private byte[] lock; public Thread1(TargetObject targetObject, byte[] lock) { this.targetObject = targetObject; this.lock = lock; } @Override public void run() { while (true) { synchronized (this.lock) { this.targetObject.method1(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }} Thread2.java:123456789101112131415161718192021222324public class Thread2 implements Runnable { private TargetObject targetObject; private byte[] lock; public Thread2(TargetObject targetObject, byte[] lock) { this.targetObject = targetObject; this.lock = lock; } @Override public void run() { while (true) { synchronized (this.lock) { this.targetObject.method2(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }} Run.java:1234567891011public class Run { public static void main(String[] args) { TargetObject targetObject = new TargetObject(); final byte[] lock = new byte[0]; Thread thread1 = new Thread(new Thread1(targetObject, lock), "Thread1"); Thread thread2 = new Thread(new Thread2(targetObject, lock), "Thread2"); thread1.start(); thread2.start(); }} 在使用synchronized的時候,務必要搞清楚鎖定的對象,沒有搞清楚反而等同於沒有同步。]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[SQL] 5 types of SQL JOIN]]></title>
<url>%2F2016%2F12%2F09%2FSQL-5-types-of-SQL-JOIN%2F</url>
<content type="text"><![CDATA[ANSI-standard SQL有五種JOIN: INNER JOIN LEFT OUTER JOIN RIGHT OUTER JOIN FULL OUTER JOIN CROSS JOIN A Visual Explanation of SQL Joins用Venn diagrams方式圖解JOIN。 範例:1234567TableA TableBid name id name-- ---- -- ----1 Pirate 1 Rutabaga2 Monkey 2 Pirate3 Ninja 3 Darth Vader4 Spaghetti 4 Ninja INNER JOIN: SQL:123SELECT * FROM TableAINNER JOIN TableBON TableA.name = TableB.name 結果:12345TableA TableBid name id name-- ---- -- ----1 Pirate 2 Pirate3 Ninja 4 Ninja Venn diagram: 列出兩個Table共有的資料,即兩個Table的交集。 LEFT OUTER JOIN: SQL:123SELECT * FROM TableALEFT OUTER JOIN TableBON TableA.name = TableB.name 結果:1234567TableA TableBid name id name-- ---- -- ----1 Pirate 2 Pirate2 Monkey null null3 Ninja 4 Ninja4 Spaghetti null null Venn diagram:以左邊Table為主,若沒有配對到資料,顯示null。 RIGHT OUTER JOIN: 跟LEFT OUTER JOIN大同小異,結果改成以Table B為主,不再贅述。 FULL OUTER JOIN: SQL:123SELECT * FROM TableAFULL OUTER JOIN TableBON TableA.name = TableB.name 結果:123456789TableA TableBid name id name-- ---- -- ----1 Pirate 2 Pirate2 Monkey null null3 Ninja 4 Ninja4 Spaghetti null nullnull null 1 Rutabaganull null 3 Darth Vader Venn diagram:為兩個Table的聯集,若沒有配對到資料以null顯示。 CROSS JOIN:即是Cartesian product,會產生兩個Table所有的組合。SQL:12SELECT * FROM TableACROSS JOIN TableB 結果:4*4 = 16種組合 因為CROSS JOIN會將所有組合列出來,所以當資料量龐大時候,效能會變得很差。 總結:可以利用以上的五種JOIN和過濾方式,對兩個集合進行交集、聯集、差集等操作。 參考資料: A Visual Explanation of SQL Joins Join (SQL)) 圖解SQL的Join  Visual Representation of SQL Joins]]></content>
<categories>
<category>SQL</category>
</categories>
<tags>
<tag>SQL</tag>
</tags>
</entry>
<entry>
<title><![CDATA[The Law of Demeter]]></title>
<url>%2F2016%2F12%2F09%2FThe-Law-of-Demeter%2F</url>
<content type="text"><![CDATA[更詳細的說法是“Law of Demeter for Functions/Methods” (LoD-F),簡單化Object之間的互動,讓多個Object不要互相相依。 定義:The Law of Demeter for functions requires that a method M of an object O may only invoke the methods of the following kinds of objects: O itself M’s parameters Any objects created/instantiated within M O’s direct component objects A global variable, accessible by O, in the scope of M 範例:123456789101112131415161718192021222324252627282930313233343536/** * A Law of Demeter example in Java. * Created by Alvin Alexander, <a href="http://devdaily.com" title="http://devdaily.com">http://devdaily.com</a>. * This is an adaptation of the source code example from the book * The Pragmatic Programmer. */public class LawOfDemeterInJava{ private Topping cheeseTopping; /** * Good examples of following the Law of Demeter. */ public void goodExamples(Pizza pizza) { Foo foo = new Foo(); // (1) it's okay to call our own methods doSomething(); // (2) it's okay to call methods on objects passed in to our method int price = pizza.getPrice(); // (3) it's okay to call methods on any objects we create cheeseTopping = new CheeseTopping(); float weight = cheeseTopping.getWeightUsed(); // (4) any directly held component objects foo.doBar(); } private void doSomething() { // do something here ... }} 參考資料: Law_of_Demeter Law of Demeter - Java examples]]></content>
</entry>
<entry>
<title><![CDATA[[Java] Object equality in Java]]></title>
<url>%2F2016%2F12%2F09%2FJava-Object-equality-in-Java%2F</url>
<content type="text"><![CDATA[在Java中,要比較兩個基本型態的變數,要使用==。要比較兩個物件型態的變數,要使用equals。 因為所有的物件都是繼承Object,加上Object的equals()是使用==判定,所以需要Override Object的equals()函式。 Object的equals()函式:123public boolean equals(Object paramObject) { return (this == paramObject);} 範例:123456789101112131415161718192021222324public class Point { public final int x; public final int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object that) { if (that instanceof Point) { Point point = (Point) that; return this.x == point.x && this.y == point.y; } return false; } @Override public int hashCode() { return Objects.hash(x,y); } } 注意:當Override equals時,hashCode最好也要Override。 例如:1234567891011121314151617public class PointTest { private Object point1; private Point point2; @Before public void setUp() { point1 = new Point(1, 1); point2 = new Point(1, 1); } @Test public void testCase() { Set<Object> set = new HashSet<Object>(); set.add(point1); assertTrue(set.contains(point2)); }} 計算point2的hashCode 計算point1的hashCode 若hashCode一樣代表落在同一個bucket 再透過equals判別是否相等 hashCode可以透過Objects.hash()協助計算,或是自行設計公式。例如:String的hashCode)。 有蠻多類似的文章提到為什麼要用31當乘數呢?例如:Why does Java’s hashCode() in String use 31 as a multiplier? 查了相關的討論幾乎都是31式質數並且jvm可以對其做最佳化,個人推測主要原因31可以大幅降低collision的機會,或許演算法或是有相關的論文會有更詳細的說明。 相關資料:物件相等性(上) 範例的原始碼:https://github.com/pandaforme/ObjectEqual]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] Multiple parameters in a constructor]]></title>
<url>%2F2016%2F12%2F09%2FJava-Multiple-parameters-in-a-constructor%2F</url>
<content type="text"><![CDATA[在開發的過程中很常會碰到需要傳入大量的參數到建構子的情況,這樣做法會讓其他人不易理解,造成維護和測試上的困難。在Clean Code: A Handbook of Agile Software Craftsmanship有提到:函式的參數最好不要超過三個。 那這樣的情況應該要怎麼做會比較好呢?可以利用Effective Java (2nd Edition)中所提到的Builder pattern來解決。 並不是每種情況都適合使用Builder pattern來解決,Builder pattern是解法之一。 範例:還沒有使用Builder pattern:1234567891011121314151617181920212223242526272829303132333435363738public class NutritionFacts { private final int servingSize; // (mL) required private final int servings; // (per container) required private final int calories; // optional private final int fat; // (g) optional private final int sodium; // (mg) optional private final int carbohydrate; // (g) optional public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat) { this(servingSize, servings, calories, fat, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) { this(servingSize, servings, calories, fat, sodium, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) { this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbohydrate = carbohydrate; }} 使用Builder pattern:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { // Required parameters private final int servingSize; private final int servings; // Optional parameters - initialized to default values private int calories = 0; private int fat = 0; private int carbohydrate = 0; private int sodium = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } private NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; }} initialize NutritionFacts:12NutritionFacts sodaDrink = new NutritionFacts.Builder(240, 8). calories(100).sodium(35).carbohydrate(27).build(); 比較:優點: 增加可讀性。 讓物件為immutable。 缺點: builder和object的程式碼重複性很高。 新增或刪除object的attribute,需要回頭修改builder程式碼。例如:同時要Override object和builder的equals(),hashCode()。 這裡的Builder pattern和Design Patterns: Elements of Reusable Object-Oriented Software中所提到的Builder pattern似乎不一樣,但是概念上很相近。後者會有一個共用組裝和建構Product的interface;前者因為組裝和建構流程差異過大,所以沒有共用的interface。所以前者很難做到抽換不同的Concrete Builder就可以產生不同的的Product。 參考資料: Book Review: Effective Java 2nd Edition Creating and Destroying Java Objects]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] 4 types of Java inner classes]]></title>
<url>%2F2016%2F12%2F09%2FJava-4-types-of-Java-inner-classes%2F</url>
<content type="text"><![CDATA[Java中有四種inner class,分別是: static inner class member inner Class local inner class anonymous inner class Static inner class:123456789101112public class OutterClass { public static class InnerClass { public void print() { System.out.println("I am a static inner class"); } } public static void main(String[] args) { OutterClass.InnerClass innerClass = new OutterClass.InnerClass(); innerClass.print(); } } static inner class不能存取outter class的memeber或method!若是一定要存取,則memeber或method要用static修飾。 Member inner Class:123456789101112131415161718192021222324public class OutterClass { private int x = 0; private InnerClass innerClass; public OutterClass() { innerClass = new InnerClass(); } private class InnerClass { private void print() { x = x++; System.out.println("Outer x is " + x); } } public void print() { innerClass.print(); } public static void main(String[] args) { OutterClass outterClass = new OutterClass(); outterClass.print(); }} inner class可以自由存取outter class的member或method。 通常會用private修飾inner class,這樣可以避免公佈太多的細節,違反封裝原則。 Local inner class:12345678910111213141516171819202122232425262728293031public class OutterClass { private int x = 0; public void print() { final int y = 10; class InnerClass implements Runnable { @Override public void run() { while (true) { System.out.println("The value of x in OutterClass is " + x); System.out.println("The value of y in print() is " + y); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } new Thread(new InnerClass()).start(); } public static void main(String[] args) { OutterClass outterClass = new OutterClass(); outterClass.print(); }} inner class可以自由存取outter class的member和method。 inner class要存取所在method的變數,必須使用final修飾該變數。原因:以上述例子為例,當呼叫到outterClass.print(),變數y就失去作用了。然而InnerClass要再存取y就會出錯,所以才會使用final修飾變數y。即複製一份變數y給InnerClass使用,且值都不會再改變。Java 8有支援Closure就可以解決此問題。Anonymous inner class:123456789public static void main(String[] args) { new Runnable() { @Override public void run() { System.out.println("I am a anonymous class"); } };} 性質和local inner class相同。 如果多個變數為相同名稱時,預設會選用距離inner class最近的變數。不過實務上開發者很少會把變數名稱取為相同,有可能會讓讀者困惑。請參考Shadowing。123456789101112131415161718192021public class ShadowTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x) { System.out.println("x = " + x); System.out.println("this.x = " + this.x); System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); } } public static void main(String... args) { ShadowTest st = new ShadowTest(); ShadowTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); }} 結果:123x = 23this.x = 1ShadowTest.this.x = 0 參考資料: 4 types of Java inner classes 認識 Lambda/Closure(2)什麼是 Closure? 內部類別(Inner class) Shadowing]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] The SerialVersionUID]]></title>
<url>%2F2016%2F12%2F09%2FJava-The-SerialVersionUID%2F</url>
<content type="text"><![CDATA[當實作了Serializable interface時,Eclipse總是會提醒你1The serializable class xxx does not declare a static final serialVersionUID field of type long serialVersionUID相當於這個Object的版本控管,若不宣告serialVersionUID,JVM會協助產生。但是因為演算法對Object內容有相當高的敏感度和不同的JVM有不同的實作方式,所以有可能Object都一樣,但是在不同JVM上算出的serialVersionUID卻是不同,丟出InvalidClassException。 範例:1234567public class Person implements Serializable{ private static final long serialVersionUID = 1L; private int age; private String addres;} 接收方將Person Object序列化存到硬碟上。 之後Person Object做了修改12345public class Person implements Serializable{ private static final long serialVersionUID = 2L; private String name;} 此時,接收方嘗試Person Object反序列化,就會丟出InvalidClassException。 那些情況屬於不可相容的改變,請參考:Incompatible Changes 那些情況屬於可相容的改變,請參考:Compatible Changes 當Object內容改變,可以根據上述連結來判定屬於可相容的改變或是不可相容的改變。若是不可相容的改變就要修正serialVersionUID的值。 參考資料: Understand The SerialVersionUID Serializable (Java Platform SE 7)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] Enum in Java]]></title>
<url>%2F2016%2F12%2F09%2FJava-Enum-in-Java%2F</url>
<content type="text"><![CDATA[enum類別可以單獨宣告或是宣告在其他類別裡面。 單獨宣告:123public enum Direction { EAST,WEST,SOUTH,NORTH;} 在編譯時期compiler會自動: 加入final修飾enum和繼承Enum: 123public final enum Direction extends Enum{ EAST,WEST,SOUTH,NORTH;} 加入private Constructor: 123456public final enum Direction extends Enum{ EAST,WEST,SOUTH,NORTH; private Direction(String s, int i){ super(s, i); }} 加入public static final修飾列舉內容值: 12345678910public final enum Direction extends Enum{ public static final Direction EAST, public static final Direction WEST, public static final Direction SOUTH, public static final Direction NORTH; private Direction(String s, int i){ super(s, i); }} 初始化: 1234567891011121314151617public final enum Direction extends Enum{ public static final Direction EAST, public static final Direction WEST, public static final Direction SOUTH, public static final Direction NORTH; private Direction(String s, int i){ super(s, i); } static{ EAST = new Direction("EAST", 0); WEST = new Direction("WEST", 1); SOUTH = new Direction("SOUTH", 2); NORTH = new Direction("NORTH", 3); }} override Enum的valueOf method。 產生一個values method,回傳值為enum類別內容值的陣列。 宣告在其他類別:12345public class DirectionOutter { public enum Direction { EAST,WEST,SOUTH,NORTH; }} 在編譯時期compiler會自動: 加入static final修飾enum和繼承Enum:12345public class DirectionOutter { public static final enum Direction extends Enum { EAST,WEST,SOUTH,NORTH; }} 其餘行為和單獨宣告一樣。 當宣告一個enum類別,compiler會”自動”做那麼多事,會有以下特性: 自動繼承Enum抽象類別,因為Enum抽象類別實做了Comparable和Serializable,所以enum物件之間是可以比較和enum物件是可以序列化。 enum類別不能繼承其他類別或被繼承。 enum物件為Singleton,所以可以使用==或equals進行比較。 enum物件為immutable物件。 建構子存取權限一定是private。 只有compiler可以繼承Enum。 增加屬性:12345678910111213141516public enum Direction { WEST(180), EAST(0), NORTH(90), SOUTH(270); private Direction(final int angle) { this.angle = angle; } private int angle; public int getAngle() { return angle; }} 建構子的存取權限可以不加(compiler會自動加入private)或是加入privae。 實作抽象方法:12345678910111213141516171819202122232425262728293031323334353637public enum Direction { WEST(180) { @Override public void shout() { System.out.println("WEST"); } }, EAST(0) { @Override public void shout() { System.out.println("EAST"); } }, NORTH(90) { @Override public void shout() { System.out.println("NORTH"); } }, SOUTH(270) { @Override public void shout() { System.out.println("SOUTH"); } }; public abstract void shout(); Direction(final int angle) { this.angle = angle; } private int angle; public int getAngle() { return angle; }} 將值轉換成enum根據使用情境,可以使用valueOf轉換或是自行設計轉化方式。 若想將字串WEST轉換成Direction.WEST:使用valueOf 1Direction.valueOf("WEST"); 當使用者輸入180度時,要轉換成Direction.WEST: 1234567891011121314151617181920212223public enum Direction { WEST(180),EAST(0),NORTH(90),SOUTH(270); Direction(final int angle) { this.angle = angle; } private int angle; public int getAngle() { return angle; } private static final Map<Integer, Direction> lookup = new HashMap<Integer, Direction>(); static { for (Direction s : Direction.values()) lookup.put(s.getAngle(), s); } public static Direction get(int angle) { return lookup.get(angle); } 透過map物件進行轉換。 將enum物件存放到容器: EumSet: 用EnumSet來存放enum物件的時間和空間效能較好。 在多執行緒實行下是不安全。 enum物件在EnumSet的順序為在宣告時候的順序。 EnumMap: 用EnumSet來存放enum物件的時間和空間效能較好。 在多執行緒實行下是不安全。參考資料: Guide for understanding enum in java]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] Heap space vs. Stack]]></title>
<url>%2F2016%2F12%2F09%2FJava-Heap-space-vs-Stack%2F</url>
<content type="text"><![CDATA[在理解Java是否為Pass by Value(參考這篇文章)之前,建議先了解heap和stack的功用。 Heap space: 當建立物件時,會在heap space分配一個空間給物件使用。 所有在heap space上的物件,都可以被其他的thread存取。 Garbage Collection會清除heap space內沒有被使用到的物件。 可透過-Xms參數調整heap space的開始大小。 可透過-Xmx參數設定heap space的最大值。 假如heap space滿了,Java會丟出Java Heap Space```1234567 * Stack: 1. 若變數是基本型態,就存放它的值。 2. 若變數是物件,就存放它在heap space的位置。 3. stack不能被其他thead存取。 4. 可透過-Xss參數調整stack大小。 5. 假如stack滿了,Java會丟出```java java.lang.StackOverFlowError 範例:123456789101112131415public class Memory { public static void main(String[] args) { // Line 1 int i=1; // Line 2 Object obj = new Object(); // Line 3 Memory mem = new Memory(); // Line 4 mem.foo(obj); // Line 5 } // Line 9 private void foo(Object param) { // Line 6 String str = param.toString(); //// Line 7 System.out.println(str); } // Line 8 } 當開始執行程式時候,JVM會載入相關的class到heap space。 產生main thread並且建立它的stack。 執行Line2,因為i是基本型態,所以push其值到stack。 執行Line3,因為obj是物件,所以push它在heap space的位置到stack。 執行Line4,因為mem是物件,所以push它在heap space的位置到stack。 執行Line6,push obj在heap space的位置到stack。 執行Line7,push obj.toString()在heap space的位置到stack。 執行Line8,pop str和param。 執行Line9,pop mem、obj和i,程式執行結束。 如何透過pop屬於這個method的變數,推測實作方式或許是類似於四則運算。 例如: 執行Line1,push “{“。 執行Line6,push “{“。 執行Line8,一直pop,直到遇到”{“停止。 執行Line9,一直pop,直到遇到”{“停止。 參考資料: Java Heap Memory vs Stack Memory Difference How will you go about explaining the following Java concepts to a beginner?]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[LeetCode] 277. Find the Celebrity]]></title>
<url>%2F2016%2F12%2F09%2FCelebrity-Problem%2F</url>
<content type="text"><![CDATA[QuestionSuppose you are at a party with n people (labeled from 0 to n - 1) and among them, there may exist one celebrity. The definition of a celebrity is that all the other n - 1 people know him/her but he/she does not know any of them. Now you want to find out who the celebrity is or verify that there is not one. The only thing you are allowed to do is to ask questions like: “Hi, A. Do you know B?” to get information of whether A knows B. You need to find out the celebrity (or verify there is not one) by asking as few questions as possible (in the asymptotic sense). You are given a helper function bool knows(a, b) which tells you whether A knows B. Implement a function int findCelebrity(n), your function should minimize the number of calls to knows. Note: There will be exactly one celebrity if he/she is in the party. Return the celebrity’s label if there is a celebrity in the party. If there is no celebrity, return -1. Solution先對N個人作編號,從1到N。先從編號1的人開始,問他認不認識編號2的人。情況有兩種: 編號1認識編號2:那麼編號1一定不是名人,編號2有可能是名人。 編號1不認識編號2:編號2一定不是名人,因為名人要有N-1人認識他。 根據以上結果,可以得到結論: 編號1認識編號2:那麼編號1一定不是名人,編號2有可能是名人。 編號1不認識編號2:編號2一定不是名人,編號1有可能是名人。 每問一人就會淘汰掉一人,接著繼續問可能是名人的人選和下一個編號的關係,直到問完N個人就可以找出名人。時間複雜度是 O(N)。 12345678910111213141516public int findCelebrity(int n) { int celebrity = 0; for (int i = 1; i < n; i++) { if (knows(celebrity, i)) celebrity = i; } for (int i = 0; i < n; i++) { if (i != celebrity) { if (knows(celebrity, i) || !knows(i, celebrity)) return -1; } } return celebrity;} References Celebrity Problem 名人問題 (Celebrity problem)]]></content>
<tags>
<tag>LeetCode</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] The details of Inheritance]]></title>
<url>%2F2016%2F12%2F09%2FJava-The-details-of-Inheritance%2F</url>
<content type="text"><![CDATA[在Java使用繼承時要注意幾個小細節,不然很容易出錯。 建構子的呼叫順序 compiler會自動產生無參數的建構子,會自動初始化field變數 例如:123456789101112131415161718192021222324class Parent1{ int i; String s; /* compiler會自動產生無參數的建構子, * 並且初始化field變數 * public Parent1() { * i = 0; * s = null; * } */}class Parent2 extend Parent1{ public Parent2(int i){ //因為沒有指定要呼叫父類別的哪一個建構子, //compiler會自動呼叫super() }}class Son extend Parent2{ public Son(){ super(1); } } new Son()的時候會呼叫Parent2的建構子(public Parent2(int i)),Parent2的建構子會呼叫Parent1的建構子(public Parent1())。 注意:當沒有寫建構子,compiler會自動產生一個無參數的建構子。在繼承過程中若沒有特別指定要呼叫父類別的哪一個建構子,compiler會自動呼叫super()。 來看一個例子:1234567891011121314151617181920212223242526272829303132public class Base { int i; public Base() { add(1); } public void add(int j) { i += j; }}public class Extension extends Base { int i = 3; public Extension() { add(2); } public void add(int j) { i += j * 2; } public static void main(String[] args) { exec(new Extension()); } private static void exec(Extension base) { base.add(8); System.out.println(base.i); }} 會印出什麼樣的結果呢? 分析: new Extension()的時候會呼叫Base的建構子(public Base()),呼叫add(2)。 因為Extension有覆寫(override)Base的add function,所以Base建構子所呼叫的add(1)是呼叫Extension中的add function。 因為多型的關係,當Base建構子呼叫add(1),裡面的i是Extension的i,並不是Base的i。 當Base建構子呼叫add(1)完畢,此時Extension的i為2。 接著呼叫Extension的建構子,此時會先初始化field變數,所以i會被重設為3。 之後是呼叫add(2),此時i為7。Extension的初始化完畢。 呼叫add(8)時,可以得出i = 7 +16 = 23,所以印出23。 假如將1private static void exec(Extension base) 改成1private static void exec(Base base) 結果還是一樣嗎?結果是0,因為此時的i指的是Base的i。]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[LeetCode] 56. Merge Intervals]]></title>
<url>%2F2016%2F12%2F09%2FLeetCode-Merge-Intervals%2F</url>
<content type="text"><![CDATA[QuestionGiven a collection of intervals, merge all overlapping intervals. Example 1: Input: [[1,3],[2,6],[8,10],[15,18]] Output: [[1,6],[8,10],[15,18]] Explanation: Since intervals [1,3] and [2,6] overlaps, merge them into [1,6]. Example 2: Input: [[1,4],[4,5]] Output: [[1,5]] Explanation: Intervals [1,4] and [4,5] are considerred overlapping. Soultion這題的主要在考如何判斷兩個interval為重疊的演算法。 怎麼判斷兩個interval為重疊:1.12i : |--------|i': |-----| 2.12i : |--------|i': |-----| 3.12i : |--------|i': |-----| 4.12i : |--------|i': |--------------| 以上為兩個interval重疊的情況,可以歸納出以下情況: i.start <= i'.end i'.start <= i.end 知道上述規則後合併就變得很簡單,因為給定的陣列是沒有排序過的,先依照 interval.start 排序。 初始化 merge = interval.get(0) 依序檢查 interval.get(i) 是否和 merge 重疊 若重疊,merge = 合併 interval.get(i) 和 merge 若不重疊,把 merge 寫入到結果,merge = interval.get(i) 有點像貪食蛇,就一直吃直到不能吃為止。 123456789101112131415161718192021222324252627public List<Interval> merge(List<Interval> intervals) { List<Interval> result = new ArrayList<>(); if (intervals.size() == 0 || intervals.size() == 1) return intervals; intervals.sort(Comparator.comparingInt(o -> o.start)); Interval merge = intervals.get(0); for (int i = 1; i < intervals.size(); i++) { if (isOverlap(merge, intervals.get(i))) { merge = new Interval( Math.min(merge.start, intervals.get(i).start), Math.max(merge.end, intervals.get(i).end)); } else { result.add(merge); merge = intervals.get(i); } } result.add(merge); return result;}private boolean isOverlap(Interval interval1, Interval interval2) { return interval1.start <= interval2.end && interval2.start <= interval1.end;}]]></content>
<categories>
<category>LeetCode</category>
</categories>
<tags>
<tag>Algorithm</tag>
<tag>LeetCode</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] lock、tryLock和lockInterruptibly的差別]]></title>
<url>%2F2016%2F12%2F09%2FJava-lock%E3%80%81tryLock%E5%92%8ClockInterruptibly%E7%9A%84%E5%B7%AE%E5%88%A5%2F</url>
<content type="text"><![CDATA[在Java中的Lock介面有lock、tryLock和lockInterruptibly三個功能很相近的method,看完javadoc後還是搞不太清楚它們之間的差異性,終於找到一篇解釋清楚的文章了。 lock():若lock被thread A取得,thread B會進入block狀態,直到取得lock。 tryLock():若當下不能取得lock,thread就會放棄。 lockInterruptibly():跟lock()情況一下,但是thread B可以透過interrupt被喚醒處理InterruptedException。 範例: lock(): 1234567891011121314151617181920212223242526import java.util.Date;import java.util.concurrent.locks.ReentrantLock;public class LockDemo { private final ReentrantLock lock; public LockDemo() { lock = new ReentrantLock(); } public static void main(String[] args) throws InterruptedException { LockDemo lockDemo = new LockDemo(); Runnable runnable = new Runnable() { @Override public void run() { lockDemo.lock.lock(); System.out.println(String.format("%s %s locked", new Date(System.currentTimeMillis()), Thread.currentThread().getName())); } }; Thread threadA = new Thread(runnable, "Thread A"); Thread threadB = new Thread(runnable, "Thread B"); threadA.start(); threadB.start(); }} 可以發現ThreadB呈現block狀態,一直在等待Thread A釋放lock。 tryLock(): 123456789101112131415161718192021222324252627import java.util.Date;import java.util.concurrent.locks.ReentrantLock;public class TryLockDemo { private final ReentrantLock lock; public TryLockDemo() { lock = new ReentrantLock(); } public static void main(String[] args) throws InterruptedException { TryLockDemo lockDemo = new TryLockDemo(); Runnable runnable = new Runnable() { @Override public void run() { if (lockDemo.lock.tryLock()) { System.out.println(String.format("%s %s locked", new Date(System.currentTimeMillis()), Thread.currentThread().getName())); } } }; Thread threadA = new Thread(runnable, "Thread A"); Thread threadB = new Thread(runnable, "Thread B"); threadA.start(); threadB.start(); }} 若一開始lock被Thread A取得,Thread B透過tryLock()當下若沒有取得到lock,就會放棄。 lockInterruptibly(): 12345678910111213141516171819202122232425262728293031323334import java.util.Date;import java.util.concurrent.locks.ReentrantLock;public class LockInterruptiblyDemo { private final ReentrantLock lock; public LockInterruptiblyDemo() { lock = new ReentrantLock(); } public static void main(String[] args) throws InterruptedException { LockInterruptiblyDemo lockDemo = new LockInterruptiblyDemo(); Runnable runnable = new Runnable() { @Override public void run() { try { lockDemo.lock.lockInterruptibly(); System.out.println(String.format("%s %s locked", new Date(System.currentTimeMillis()), Thread.currentThread().getName())); } catch (InterruptedException e) { System.out.println(String.format("%s %s interrupted", new Date(System.currentTimeMillis()), Thread.currentThread().getName())); } } }; Thread threadA = new Thread(runnable, "Thread A"); Thread threadB = new Thread(runnable, "Thread B"); threadA.start(); Thread.sleep(1000); threadB.start(); threadB.interrupt(); }} Thread A先取得lock,Thread B無法取得lock進入block狀態,可以透過發出interrupt方式喚醒Thread B。 Lock和synchronized幾乎是大同小異,但是Lock可以做更細微的同步方式。 參考資料:用通俗的語言說說lock和lockInterruptibly的區別]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] Semaphore的使用時機]]></title>
<url>%2F2016%2F12%2F09%2FJava-Semaphore%E7%9A%84%E4%BD%BF%E7%94%A8%E6%99%82%E6%A9%9F%2F</url>
<content type="text"><![CDATA[Java所提供的Semaphore跟Operating System Concepts上所講的Semaphore一樣。 可以用它來解決: Producer and Consumer Problem The Readers-Writers Problem The Dining-Philosophers Problem Pool的實作 控制critical region ####範例:12345678910111213141516171819202122232425262728293031323334353637383940import java.util.concurrent.Semaphore;public class SemaphoreDemo { Semaphore pool = new Semaphore(10); public static void main(String args[]) { final SemaphoreDemo demo = new SemaphoreDemo(); for (int i = 0; i < 30; i++) { new Thread(new Runnable() { @Override public void run() { demo.mutualExclusion(); } }, "Thread" + i).start(); } } private void mutualExclusion() { try { pool.acquire(); System.out.println(Thread.currentThread().getName() + " inside mutual exclusive region"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { pool.release(); System.out.println(Thread.currentThread().getName() + " outside of mutual exclusive region"); } }} 當pool為空的時候,想要使用pool資源的thread會進入blocking狀態,直到有其他thread執行release()釋放pool資源,喚醒其中在blocking狀態的thread。 參考資料:Counting Semaphore Example in Java 5 – Concurrency Tutorial]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] Difference between synchronized vs ReentrantLock]]></title>
<url>%2F2016%2F12%2F09%2FJava-Difference-between-synchronized-vs-ReentrantLock%2F</url>
<content type="text"><![CDATA[根據參考資料整理了ReentrantLock和synchronized的比較表: ReentrantLock synchronized 鎖定對象 可以對任何對象上鎖 class literal, the instance of class 效能 較好 較差 中斷 可以放棄取鎖 一旦嘗試獲取鎖就會一直等待直到獲取到鎖 lock的鎖定和釋放 程式設計師控制 JVM控制 ReentrantLock的控制程度比較大,synchronized的控制程度比較小。 正確使用的情況下,使用ReentrantLock會比使用synchronized的效能較好。 若對同步問題不熟悉,鎖定和釋放ReentrantLock的順序不對容易造成deadlock。 參考資料ReentrantLock Example in Java, Difference between synchronized vs ReentrantLock]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] ThreadLocal]]></title>
<url>%2F2016%2F12%2F09%2FJava-ThreadLocal%2F</url>
<content type="text"><![CDATA[當多個thread共享某些變數,可以透過synchronized或是lock來保護共享變數,避免產生race condition問題。這樣的作法安全但是效能會變慢,是以時間換空間的方法。 將共享變數各複製一份給thread,這樣既達安全性並且提升效能,是以空間換時間的方法。可以使用ThreadLocal來達到這樣的效果。 例如: 多個thread共享SimpleDateForamt變數,因為SimpleDateForamt不是ThreadSafe,因此會有同步問題。可以使用ThreadLocal解決。 Database connection pool中的thread會共享某些變數,假如透過lock方式避免同步問題發生,但會導致效能較差。可以使用ThreadLocal解決,以空間來換取時間。 Java Application Server利用ThreadLocal管理transaction和security資訊。 參考資料: Design Pattern: Thread-Specific Storage 模式 ThreadLocal in Java - Example Program and Tutorial Java Thread Local – How to Use and Code Sample]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Find running median from a stream of integers]]></title>
<url>%2F2016%2F12%2F09%2FFind-running-median-from-a-stream-of-integers%2F</url>
<content type="text"><![CDATA[依序輸入多個數字,如何有效率找出中位數(median)? 例如:依序輸入7、13、2、6、9,中位數為7。 解法: 將所有數字排序,即可以得出中位數。時間複雜度為$O(nlogn)$ 利用max heap和min heap: 新增數字到heap: 若max heap為空,數字新增到min heap。 若max heap不為空並且數字小於max heap的root,數字新增到max heap。 若max heap不為空並且數字大於max heap的root,數字新增到min heap。 rebalance: 若max heap size - min heap size > 1, 刪除max heap的root並且新增到min heap。 若min heap size - max heap size > 1, 刪除min heap的root並且新增到max heap。 計算中位數: 若max heap size > min heap size,中位數為max heap的root。 若max heap size < min heap size,中位數為min heap的root。 若max heap size == min heap size,中位數為$(max\ heap的root + min\ heap的root) / 2$ (根據定義決定)。 時間複雜度為$O(1)$ 此方法是利用中位數的特性: 左半部的數字一定小部右半邊。 例如:2、3、7、13、15 或是2、3、7、13、15 左半部和右半部的大小差值不能超過1,否則不能透過上述方法找出中位數。 左半部的數字(包含中位數)建成max heap,max heap的root即為中位數。 例如:2、3、7、13、15 2、3、7建成max heap,13、15建成min heap 右半部的數字(包含中位數)建成min heap,min heap的root即為中位數。 例如:2、3、7、13、15 2、3建成max heap,7、13、15建成min heap 程式碼:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374import java.util.Collections;import java.util.Comparator;import java.util.LinkedList;import java.util.PriorityQueue;import java.util.Queue;public class FindMedian<E> { private Queue<E> queue; private Queue<E> maxHeap; private Queue<E> minHeap; private Comparator<E> comparator; public FindMedian(Comparator<E> comparator) { queue = new LinkedList<>(); maxHeap = new PriorityQueue<>(Collections.reverseOrder(comparator)); minHeap = new PriorityQueue<>(comparator); this.comparator = comparator; } public void enqueue(E e) { if (e == null) throw new IllegalArgumentException(); this.queue.add(e); addElementToHeap(e); rebalance(); } public E dequeue() { E removeObject = this.queue.remove(); this.maxHeap.remove(removeObject); this.minHeap.remove(removeObject); return removeObject; } public E peekMedian() { if (maxHeap.size() == minHeap.size()) { // Depends on definition return maxHeap.peek(); } else if (maxHeap.size() > minHeap.size()) { return maxHeap.peek(); } else { return minHeap.peek(); } } private void addElementToHeap(E e) { E tmp = maxHeap.peek(); if (tmp == null) { minHeap.add(e); } else if (comparator.compare(tmp, e) > 0) { maxHeap.add(e); } else { minHeap.add(e); } } private void rebalance() { int sizeDiff = maxHeap.size() - minHeap.size(); if (sizeDiff > 1) { minHeap.add(maxHeap.remove()); } else if (sizeDiff < -1) { maxHeap.add(minHeap.remove()); } }} FindMedian 參考資料:Find running median from a stream of integers]]></content>
<categories>
<category>Algorithm</category>
</categories>
<tags>
<tag>Algorithm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] How do I switch between Java8, Java 7 and Java 6 on OS X]]></title>
<url>%2F2016%2F12%2F09%2FOS-X-How-do-I-switch-between-Java-7-and-Java-6%2F</url>
<content type="text"><![CDATA[解決方法:將以下程式碼加入到.bash_profile123456789101112131415function setjdk() { if [ $# -ne 0 ]; then removeFromPath '/System/Library/Frameworks/JavaVM.framework/Home/bin' if [ -n "${JAVA_HOME+x}" ]; then removeFromPath $JAVA_HOME fi export JAVA_HOME=`/usr/libexec/java_home -v $@` export PATH=$JAVA_HOME/bin:$PATH fi echo JAVA_HOME set to $JAVA_HOME java -version } function removeFromPath() { export PATH=$(echo $PATH | sed -E -e "s;:$1;;" -e "s;$1:?;;") } 使用方式:1setjdk 1.8 參考資料:How do I switch between Java 7 andJava 6 on OS X 10.8.2?]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Java] Test Java mail by GreenMail]]></title>
<url>%2F2016%2F12%2F09%2FJava-Test-Java-mail-by-GreenMail%2F</url>
<content type="text"><![CDATA[要如何對JavaMail-API做單元測試呢? 在Stack Overflow上不少人推薦 GreenMail,GreenMail支援SMTP, POP3, IMAP,可以對sender和receiver進行測試,就來試用看看。 Sender12345678910111213141516171819202122232425262728293031323334353637383940414243public class MailSenderTest { private GreenMail greenMail; private MimeMessage message; @Before public void setUp() throws Exception { greenMail = new GreenMail(ServerSetupTest.SMTP); greenMail.start(); Properties properties = System.getProperties(); properties.put("mail.smtp.host", "localhost"); properties.put("mail.smtp.port", ServerSetupTest.SMTP.getPort()); Session session = Session.getInstance(properties); message = new MimeMessage(session); message.setFrom(new InternetAddress("test@test.com")); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("test1@test.com", false)); message.setSubject("subject"); message.setText("text"); message.setSentDate(new Date()); } @After public void tearDown() throws Exception { greenMail.stop(); } @Test public void testSend() throws Exception { // [Start] Replace this with your send code // Sender.send(message); // [End] MimeMessage[] messages = greenMail.getReceivedMessages(); Assert.assertNotNull(messages); Assert.assertEquals(1, messages.length); MimeMessage m = messages[0]; Assert.assertEquals("subject", m.getSubject()); Assert.assertTrue(String.valueOf(m.getContent()).contains("text")); Assert.assertEquals("test@test.com", m.getFrom()[0].toString()); }} Receiver12345678910111213141516171819202122232425262728293031323334public class MailReceiverTest { private GreenMail greenMail; private String subject; private String body; @Before public void setUp() throws Exception { greenMail = new GreenMail(ServerSetupTest.SMTP); greenMail.start(); subject = GreenMailUtil.random(); body = GreenMailUtil.random(); GreenMailUtil.sendTextEmailTest("test@localhost.com", "test1@localhost.com", subject, body); } @After public void tearDown() throws Exception { greenMail.stop(); } @Test public void testReceiver() throws Exception { // [Start] Replace this with your receive code // Message message = Receiver.receive(); // [End] Assert.assertNotNull(message); Assert.assertEquals(message.getTo(), "test@localhost.com"); Assert.assertEquals(message.getFrom(), "test1@localhost.com"); Assert.assertEquals(message.getSubject(), subject); Assert.assertEquals(message.getBody(), body); }} 使用GreenMail進行測試,嚴格來說並不屬於unit test,某種程度來說它也是屬於mail server,否則就必須mock Transport和Session。是否能透過mock Transport和Session來達到測試效果,這跟你的mail class設計有關。以實用性、方便性和功能性,GreenMail算是一個不錯測試mail的framework。 參考資料: GreenMail Integration Testing IMAP, SMTP and POP3 with GreenMail How to test… Java Mail]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[LOGBack] "hidden" cost of parameter construction]]></title>
<url>%2F2016%2F12%2F09%2FLOGBack-hidden-cost-of-parameter-construction%2F</url>
<content type="text"><![CDATA[以往寫程式的時候,對於log的使用通常像這樣:1logger.debug("The new entry is "+entry+"."); 但是這樣的做法會有效能影響。不管log debug模式是否有打開,都會將"The new entry is "+entry+"."轉成字串。 當entry是一個很大的物件,且在production環境(log debug模式是關閉),很有可能造成效能低落。 較好的作法如下(只有在log debug模式是打開的時候,才會將"The new entry is "+entry+"."轉成字串):1logger.debug("The new entry is {}.", entry); 有多個參數的時候:12Object[] paramArray = {newVal, below, above};logger.debug("Value {} was inserted between {} and {}.", paramArray); 參考資料:http://logback.qos.ch/manual/architecture.html]]></content>
<categories>
<category>LOGBack</category>
</categories>
<tags>
<tag>LOGBack</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Jersey] RESTful service starter-kit (1)]]></title>
<url>%2F2016%2F12%2F09%2FJersey-RESTful-serveice-starter-kit-1%2F</url>
<content type="text"><![CDATA[JAX-RS有提供類似AOP功能,我們可以集中處理所有的Exception。 在Clean Code說到: The short chapter seven of «Clean Code» is dedicated to error handling. The one point it stresses with vigor is:Do not use checked exceptions!The price of checked exceptions is an Open/Closed Principle violation. If you throw a checked exception from a method in your code and the catch is three levels above, you must declare that exception in the signature of each method between you and the catch. This means that a change at a low level of the software can force signature changes on many higher levels.—Robert C. Martin, «Clean Code», page 107我會習慣將checked exception轉成runtime exception,統一集中處理。 例如:只要沒有處理到的exception,一律回Internal Server Error 只要實作ExceptionMapper,選擇要捕捉exception放到generic,最後再加入@Provider就可以統一處理想要處理的Exception。1234567891011@Providerpublic class GenericExceptionMapper implements ExceptionMapper<Throwable> { @Override public Response toResponse(Throwable exception) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(exception.getMessage()) .type(MediaType.APPLICATION_JSON) .build(); }} Glassfish有提供LoggingFilter,可以自動log每個request和response。假如對於輸出格式比較要求,可以實作屬於自己的LoggingFilter,實作方式可以參考LoggingFilter.java。 使用LoggingFilter可以在web.xml或程式碼中開啟。可以參考Registering Resources and Providers in Jersey 2。 例如: client端發出:http://localhost:8080/jersey-starterkit/hello```123456789101112server端會紀錄相關資訊:```bash15:34:40.087 [qtp1671915608-80] INFO application.MyApplication - 3 * Server has received a request on thread qtp1671915608-801 > GET http://localhost:8080/jersey-starterkit/hello1 > Accept: */*1 > Host: localhost:80801 > User-Agent: curl/7.37.115:34:40.091 [qtp1671915608-80] INFO application.MyApplication - 4 * Server responded with a response on thread qtp1671915608-802 < 2002 < Content-Type: application/jsonhello 程式碼:https://github.com/pandaforme/jersey-starterkit 參考資料: Error handling in REST API with Jersey Testing error handling in RESTful application with jersey and jbehave Registering Resources and Providers in Jersey 2]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Jersey</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Scala] Tail recursion]]></title>
<url>%2F2016%2F12%2F09%2FScala-Tail-recursion%2F</url>
<content type="text"><![CDATA[在學習Functional programming過程中,學到遞迴可以分為兩類: Tail recursion 1If a function calls itself as its last action, the function’s stack frame can be reused. This is called tail recursion. Tail call 1If the last action of a function consists of calling a function (which may be the same), one stack frame would be sufficient for both functions. Such calls are called tail-calls. 分別以計算最大公因數(gcd)和階層(factorial)為例: gcd 123456def gcd(a: Int, b: Int): Int = { if (b == 0) a else gcd(b, a % b)} factorial 123456def factorial(n: Int): Int = { if (n == 0) 1 else n * factorial(n - 1)} 例如: 1gcd(21, 14) -> gcd(14, 7) -> gcd(7, 0) -> 7 123456factorial(5) -> 5 * factorial(4) -> 5 * 4 * factorial(3) -> 5 * 4 * 3 * factorial(2) -> 5 * 4 * 3 * 2 * factorial(1) -> 5 * 4 * 3 * 2 * 1 * factorial(0) -> 5 * 4 * 3 * 2 * 1 * 1 可以發現: 在gcd範例中,每一步不會依賴上一步的結果,上一步的結果是以參數方式傳入到函式裡面。 在factorial範例中,每一步會依賴上一步的結果,所以需要 stack 來記錄每一步的狀態。等走到盡頭後,再取出 stack 內的元素並且計算之,逐一合併結果。 假如執行 factorial(100000) 可以預期會發生 stack overflow,我們可以將原本的程式改成Tail recursion版本,就不會有 stack overflow 發生。在Scala中會對Tail recursion做最佳化,或是可以透過 @tailrec 標註此函數是Tail recursion。 測試範例: 123456789101112131415161718192021222324252627282930import java.util.concurrent.TimeUnitimport com.google.common.base.Stopwatchdef factorial(n: Int): Int = { if (n == 0) 1 else n * factorial(n - 1)}def factorialTailrec(n: Int, result: Int): Int = { if (n == 0) result else factorialTailrec(n - 1, n * result)}val sw = Stopwatch.createUnstarted()sw.elapsed(TimeUnit.MILLISECONDS)sw.start()factorial(15)sw.stop()println(sw.toString)sw.reset()sw.start()factorialTailrec(15, 1)sw.stop()println(sw.toString) 測試結果:121.611 ms677.4 μs 可以看出來改成Tail recursion版本效率會大幅改善。 參考: What is tail recursion? Tail recursion]]></content>
<categories>
<category>Scala</category>
</categories>
<tags>
<tag>Scala</tag>
<tag>Recursion</tag>
<tag>Tail Recursion</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Git] Moving A Git Repository To A New Server]]></title>
<url>%2F2016%2F12%2F09%2FGit-Moving-A-Git-Repository-To-A-New-Server%2F</url>
<content type="text"><![CDATA[[更新]有更簡潔的做法,請參考 Duplicating a repository 在工作上遇到要將git repository從舊的server轉移到新的sever,參考這篇文章Moving A Git Repository To A New Server進行轉移. Step 1: fetch所有的remote branch1git fetch origin Step 2: 將所有remote branch clone到本地端12345for branch in `git branch -a | grep remotes | grep -v HEAD`; do git branch --track ${branch##*/} $branchdonegit fetch --allgit pull --all Step 3: 新增新的repository,名稱為new-origin1git remote add new-origin git@[new server url]:[group]/[project].git Step 4: push所有的branch和tag到新的repository12git push --all new-origingit push --tags new-origin Step 5: 砍掉舊的repository,並且新的repository改名為origin12git remote rm origingit remote rename new-origin origin 將以上的操作步驟寫成script:123456789101112131415#!/bin/bashrm -rf $1git clone git@old-repo:old-group/$1.gitcd $1git fetch originfor branch in `git branch -a | grep remotes | grep -v HEAD`; do git branch --track ${branch##*/} $branchdonegit fetch --allgit pull --allgit remote add new-origin git@new-repo:new-group/$1.gitgit push --all new-origingit push --tags new-origingit remote rm origingit remote rename new-origin origin 參考: Moving A Git Repository To A New Server Clone all remote branches with Git? Duplicating a repository]]></content>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[AOP] Monitor Legacy System]]></title>
<url>%2F2016%2F12%2F09%2FAOP-Monitor-Legacy-System%2F</url>
<content type="text"><![CDATA[最近要在一個舊的scala系統加入一些監控的程式碼,找出影響效能的地方. 想了兩個做法可以解決: 方法一: 在懷疑的程式碼地方上下夾log,計算出執行時間. 方法二: 利用AOP方式攔截每個function執行時開始點和結束點,計算出執行時間. 來分析一下這兩個方法的優缺點: 方法一: 優點:不用大腦就可以解決 缺點:需要在所有的點插入log,造成程式充滿一堆log 方法二: 優點:可以不用動到原本的程式碼 缺點:需要寫一些程式碼 後來採用Aspectj來解決這個問題. 新增一個Monitor Aspect 12345678910111213141516171819202122232425262728package aspectj;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.slf4j.Logger;import org.slf4j.LoggerFactory;@Aspectpublic class MonitorAspect { private long startTime; private Logger logger = LoggerFactory.getLogger(MonitorAspect.class); @Before(value = "execution (* *(..))", argNames = "joinPoint") public void before(JoinPoint joinPoint) { startTime = System.currentTimeMillis(); } @After(value = "execution (* *(..))", argNames = "joinPoint") public void after(JoinPoint joinPoint) { long executionTime = System.currentTimeMillis() - startTime; logger.debug("Signature : {}, Source Line : {}, Execute Time : {}", joinPoint.getSignature(), joinPoint.getSourceLocation(), executionTime); }} 加入aspectj jar 12"org.aspectj" % "aspectjweaver" % "1.8.7","org.aspectj" % "aspectjrt" % "1.8.7" 利用load time weaving方式.需要在resource資料夾下新增META-INF/aop.xml 1234567891011<aspectj> <aspects> <aspect name="aspectj.MonitorAspect"/> </aspects> <weaver options="-verbose -showWeaveInfo"> <include within="xxx.yyy.*"/> <include within="xxx.zzz.*"/> <include within="aspectj.*"/> </weaver></aspectj> 詳細參數可以參考:Chapter 5. Load-Time Weaving 因為舊系統是用java啟動,所以需要加入javaagent參數,指定aspectjweaver.jar的位置,例如:java -javaagent=/jars/aspectjweaver-1.8.7.jar ... 在實作過程中遇到了不少的坑,紀錄一下: 第一個坑: 在實作Aspect時,Annotation中可以准許不用對每個參數給值,但是會噴錯… 以下寫法是准許的,但是load time weaving時會噴錯1@Before("execution (* *(..))") 後來改成以下寫法就沒事了1@Before(value = "execution (* *(..))", argNames = "joinPoint") 第二個坑: 為了可以動態修改aop.xml,必須將這個檔案從jar拉出來放.可以透過-Dorg.aspectj.weaver.loadtime.configuration=file:{aop.xml位置}來指定aop位置. 但是啟動程式時卻又噴錯java.lang.RuntimeException: Cannot register non aspect:...,原因是javax.management.remote.rmi.NoCallStackClassLoader不能載入我寫好的aspect.(這邊日後有時間需要來研究一下,應該是執行順序的問題…) 透過-Daj.weaving.loadersToSkip=javax.management.remote.rmi.NoCallStackClassLoader略過它就可以執行了.詳細可參考:AspectJ: ClassLoading issue when trying to use external aop.xml file 第三個坑: 一開始沒有在aop.xml的weaver tag中加入MonitorAspect的位置,又噴錯java.lang.NoSuchMethodError: aspectj.MonitorAspect.aspectOf()Laspectj/MonitorAspect加入MonitorAspect的位置就正常了!詳細可參考:AspectJ java.lang.NoSuchMethodError: aspectOf 第四個坑: 為了可以在aop.xml動態指定要waving的type,千萬不能在annotation的value欄位寫死. 例如:1@Before(value = "execution (* xxx.yyy.*(..))", argNames = "joinPoint") 1234567891011<aspectj> <aspects> <aspect name="aspectj.MonitorAspect"/> </aspects> <weaver options="-verbose -showWeaveInfo"> <include within="xxx.yyy.*"/> <include within="xxx.zzz.*"/> <include within="aspectj.*"/> </weaver></aspectj> 結果:只能waving到xxx.yyy.*,不能waving xxx.zzz.* 改成以下:1@Before(value = "execution (* *(..))", argNames = "joinPoint") 1234567891011<aspectj> <aspects> <aspect name="aspectj.MonitorAspect"/> </aspects> <weaver options="-verbose -showWeaveInfo"> <include within="xxx.yyy.*"/> <include within="xxx.zzz.*"/> <include within="aspectj.*"/> </weaver></aspectj> 結果:可以waving到xxx.yyy.*和xxx.zzz.* 心得:AOP的概念真的很強大,可以讓你不費吹灰之力去監控Legacy System.不用更改舊有程式,也不會有多餘且重複的程式碼,之後可以來想看看有什麼地方可以利用AOP. 因為scala版本太舊了(2.9.1, jdk 6),利用compile weaving方式失敗了,只好用load time weaving方式.假如採用較新的scala版本或許可以考慮 sbt-aspectj. 參考: AspectJ with Akka and Scala AspectJ: ClassLoading issue when trying to use external aop.xml file AspectJ java.lang.NoSuchMethodError: aspectOf Chapter 5. Load-Time Weaving sbt-aspectj]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>AOP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Jersey] RESTful service starter-kit (2)]]></title>
<url>%2F2016%2F12%2F09%2FJersey-RESTful-service-starter-kit-2%2F</url>
<content type="text"><![CDATA[如何設定Custom Injection場景:client發送get request,會在header帶一個userId,server會根據userId去資料庫查詢相關user.例如:1curl -H 'userId: 89ce5a60-a88f-11e5-a837-0800200c9a66' http://localhost:8080/user PS:正規的做法是把userId放在uri path中,這邊只是模擬從header抓取資料進行處理,之後可以透過annotation方式將結果放到resource 定義annotation 1234@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})public @interface UserParam {} 定義ValueFactoryProvider 123456789101112131415161718192021222324252627282930313233343536373839@Singleton@Slf4j //lombok annotationpublic final class UserParamValueFactoryProvider extends AbstractValueFactoryProvider { @Singleton public static final class InjectionResolver extends ParamInjectionResolver<UserParam> { public InjectionResolver() { super(UserParamValueFactoryProvider.class); } } @Inject public UserParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator injector) { super(mpep, injector, Parameter.Source.UNKNOWN); } @Override public AbstractContainerRequestValueFactory<?> createValueFactory(Parameter parameter) { Class<?> classType = parameter.getRawType(); if (classType == null || (!classType.equals(User.class))) { log.warn("UserParam annotation was not placed on correct object type; Injection might not work correctly!"); return null; } return new AbstractContainerRequestValueFactory<User> (){ @Context private ResourceContext context; @Inject private Repo repo; public User provide() { final ContainerRequestContext context = this.context.getResource(ContainerRequestContext.class); final UUID userId = UUID.fromString(context.getHeaderString("userId")); return repo.getUser(userId); } }; }} Repo123public interface Repo { User getUser(UUID uuid);} RepoFactory1234567891011public class RepoFactory implements Factory<Repo> { @Override public Repo provide() { return new DummyRepo(); } @Override public void dispose(Repo instance) { }} bind UserParamValueFactoryProvider bind(UserParamValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);12bind(UserParamValueFactoryProvider.InjectionResolver.class).to(new TypeLiteral<InjectionResolver<UserParam>>() {}).in(Singleton.class);bindFactory(RepoFactory.class).to(Repo.class).in(Singleton.class); 透過annotation取得User 123456@GET@ApiOperation(value = "Return user data", response = User.class)public Response user(@NotNull @Valid @UserParam User user) { return Response.ok(user, MediaType.APPLICATION_JSON) .build();} 如何設定Swagger API因為是透過ResourceConfig去設定JAX-RS,所以只要將以下程式碼加入到ResourceConfig就完成Swagger API設定123456789101112register(ApiListingResourceJSON.class);register(SwaggerSerializers.class);BeanConfig beanConfig = new BeanConfig();beanConfig.setVersion("1.0.1");beanConfig.setResourcePackage("rest"); // replace with your packagesbeanConfig.setBasePath("http://localhost:8080/jersey-starterkit/");beanConfig.setDescription("My RESTful resources");beanConfig.setTitle("My RESTful API");beanConfig.setFilterClass(ApiAuthorizationFilterImpl.class.getName());beanConfig.setPrettyPrint(true);beanConfig.setScan(true); 假如想要用apiKey限制使用者存取swagger.json,可以繼承SwaggerSpecFilter,在beanConfig.setFilterClass設定路徑即可. 呼叫swagger.json:1curl http://localhost:8080/swagger.json 程式碼:jersey-starterkit 參考資料: How to inject an object into jersey request context? Chapter 22. Custom Injection and Lifecycle Management How to bind custom context to Jersey request Using HK2 with Jersey (with auto-scanning!) Swagger]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Jersey</tag>
</tags>
</entry>
<entry>
<title><![CDATA[[Jersey] Using HK2 to inject services into background tasks]]></title>
<url>%2F2016%2F12%2F09%2FJersey-Using-HK2-to-inject-services-into-background-tasks%2F</url>
<content type="text"><![CDATA[Jersey採用HK2作為Dependency Injection framework,可以透過簡單的設定將實作的程式inject到interface.有使用過這樣功能的使用者會發現到,僅限於Restful layer可以做inject,非Restful layer就不能inject. 例如: Interface:1234@Contractpublic interface Bar { void bar();} Implement Bar1234567@Servicepublic class BarImpl implements Bar { @Override public void bar() { log.info("Bar"); }} Background task123456789101112public class CrazyThread { @Inject Bar bar; public void run() { new Thread(() -> { while (true) { bar.bar(); } }).start(); }} Inject BarImpl into Bar123456789101112public class MyApplication extends ResourceConfig { public MyApplication() { this.register(new AbstractBinder() { @Override protected void configure() { bind(BarImpl.class).to(Bar.class).in(Singleton.class); }); new CrazyThread().run(); }} 會發現沒有辦法順利將BarImpl注射到Bar,因為CraztThread並不在container中,所以沒有辦法順利注射. 首先,我們可以透過HK2 Metadata Generator幫我們達成,它會產生注射的設定檔到META-INF/hk2-locator/default下.要確認Jersey使用的hk2版本,再匯入相關的jar檔.例如:jersey 2.22.1使用的hk2版本為2.4.0-b31,hk2-utils,hk2-api,hk2-metadata-generator一定要為2.4.0-b31.1234compile 'javax.inject:javax.inject:1'compile group: 'org.glassfish.hk2', name: 'hk2-utils', version: '2.4.0-b31'compile group: 'org.glassfish.hk2', name: 'hk2-api', version: '2.4.0-b31'compile group: 'org.glassfish.hk2', name: 'hk2-metadata-generator', version: '2.4.0-b31' 修改CrazyThread,加入@Service12345678910111213@Servicepublic class CrazyThread { @Inject Bar bar; public void run() { new Thread(() -> { while (true) { bar.bar(); } }).start(); }} 最後修改MyApplication,透過ServiceLocator取得CrazyThread1234567public class MyApplication extends ResourceConfig { public MyApplication() { ServiceLocator serviceLocator = ServiceLocatorUtilities.createAndPopulateServiceLocator(); final CrazyThread crazyThread = serviceLocator.getService(CrazyThread.class); crazyThread.run(); }} 大功告成!!! 後記在Jersey中使用HK2這個問題困惱我很久,很多沒有在resource層使用到的程式,就沒有辦法透過hk2注入.Using HK2 with Jersey (with auto-scanning!)這篇文章有提到怎麼解決這個問題,可能是早期版本的hk2必須手動去hack,嘗試他的做法不能順利執行.後來參考了jersey + grizzly + hk2: Dependency injection, but not into resource和khasunuma/hk2-sample才解決這個問題… 參考資料: Using HK2 with Jersey (with auto-scanning!) jersey + grizzly + hk2: Dependency injection, but not into resource khasunuma/hk2-sample HK2 Metadata Generator]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Jersey</tag>
<tag>HK2</tag>
</tags>
</entry>
</search>