Programming blog

This is a programming blog


  • Home

  • Tags

  • Categories

  • Archives

  • Sitemap

  • Search

[Java] Object equality in Java

Posted on 2016-12-09 | Edited on 2016-12-13 | In Java |

在Java中,要比較兩個基本型態的變數,要使用==。
要比較兩個物件型態的變數,要使用equals。

因為所有的物件都是繼承Object,加上Object的equals()是使用==判定,所以需要Override Object的equals()函式。

Object的equals()函式:

1
2
3
public boolean equals(Object paramObject) {
return (this == paramObject);
}

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public 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。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public 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));
}
}

  1. 計算point2的hashCode
  2. 計算point1的hashCode
  3. 若hashCode一樣代表落在同一個bucket
  4. 再透過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

[Java] Multiple parameters in a constructor

Posted on 2016-12-09 | Edited on 2016-12-13 | In Java |

在開發的過程中很常會碰到需要傳入大量的參數到建構子的情況,這樣做法會讓其他人不易理解,造成維護和測試上的困難。在Clean Code: A Handbook of Agile Software Craftsmanship有提到:函式的參數最好不要超過三個。

那這樣的情況應該要怎麼做會比較好呢?可以利用Effective Java (2nd Edition)中所提到的Builder pattern來解決。

並不是每種情況都適合使用Builder pattern來解決,Builder pattern是解法之一。

範例:

還沒有使用Builder pattern:

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
public 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:

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
public 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:

1
2
NutritionFacts sodaDrink = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();

比較:

優點:

  1. 增加可讀性。
  2. 讓物件為immutable。

缺點:

  1. builder和object的程式碼重複性很高。
  2. 新增或刪除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

[Java] 4 types of Java inner classes

Posted on 2016-12-09 | Edited on 2016-12-13 | In Java |

Java中有四種inner class,分別是:

  1. static inner class
  2. member inner Class
  3. local inner class
  4. anonymous inner class

Static inner class:

1
2
3
4
5
6
7
8
9
10
11
12
public 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public 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();
}
}
  1. inner class可以自由存取outter class的member或method。
  2. 通常會用private修飾inner class,這樣可以避免公佈太多的細節,違反封裝原則。

    Local inner class:

    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
    public 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();
    }
    }
  3. inner class可以自由存取outter class的member和method。

  4. inner class要存取所在method的變數,必須使用final修飾該變數。
    原因:以上述例子為例,當呼叫到outterClass.print(),變數y就失去作用了。然而InnerClass要再存取y就會出錯,所以才會使用final修飾變數y。即複製一份變數y給InnerClass使用,且值都不會再改變。Java 8有支援Closure就可以解決此問題。

    Anonymous inner class:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public 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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public 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);
}
}

結果:

1
2
3
x = 23
this.x = 1
ShadowTest.this.x = 0

參考資料:

  • 4 types of Java inner classes
  • 認識 Lambda/Closure(2)什麼是 Closure?
  • 內部類別(Inner class)
  • Shadowing

[Java] The SerialVersionUID

Posted on 2016-12-09 | In Java |

當實作了Serializable interface時,Eclipse總是會提醒你

1
The serializable class xxx does not declare a static final serialVersionUID field of type long

serialVersionUID相當於這個Object的版本控管,若不宣告serialVersionUID,JVM會協助產生。但是因為演算法對Object內容有相當高的敏感度和不同的JVM有不同的實作方式,所以有可能Object都一樣,但是在不同JVM上算出的serialVersionUID卻是不同,丟出InvalidClassException。

範例:

1
2
3
4
5
6
7
public class Person implements Serializable{
private static final long serialVersionUID = 1L;

private int age;

private String addres;
}

接收方將Person Object序列化存到硬碟上。

之後Person Object做了修改

1
2
3
4
5
public 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)

[Java] Enum in Java

Posted on 2016-12-09 | Edited on 2016-12-13 | In Java |

enum類別可以單獨宣告或是宣告在其他類別裡面。

單獨宣告:

1
2
3
public enum Direction {
EAST,WEST,SOUTH,NORTH;
}

在編譯時期compiler會自動:

  • 加入final修飾enum和繼承Enum:

    1
    2
    3
    public final enum Direction extends Enum{
    EAST,WEST,SOUTH,NORTH;
    }
  • 加入private Constructor:

    1
    2
    3
    4
    5
    6
    public final enum Direction extends Enum{
    EAST,WEST,SOUTH,NORTH;
    private Direction(String s, int i){
    super(s, i);
    }
    }
  • 加入public static final修飾列舉內容值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public 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);
    }
    }
  • 初始化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public 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類別內容值的陣列。

宣告在其他類別:

1
2
3
4
5
public class DirectionOutter {
public enum Direction {
EAST,WEST,SOUTH,NORTH;
}
}

在編譯時期compiler會自動:

  • 加入static final修飾enum和繼承Enum:
    1
    2
    3
    4
    5
    public class DirectionOutter {
    public static final enum Direction extends Enum {
    EAST,WEST,SOUTH,NORTH;
    }
    }

其餘行為和單獨宣告一樣。


當宣告一個enum類別,compiler會”自動”做那麼多事,會有以下特性:

  1. 自動繼承Enum抽象類別,因為Enum抽象類別實做了Comparable和Serializable,所以enum物件之間是可以比較和enum物件是可以序列化。
  2. enum類別不能繼承其他類別或被繼承。
  3. enum物件為Singleton,所以可以使用==或equals進行比較。
  4. enum物件為immutable物件。
  5. 建構子存取權限一定是private。

只有compiler可以繼承Enum。


增加屬性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public 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。


實作抽象方法:

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
public 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

    1
    Direction.valueOf("WEST");
  • 當使用者輸入180度時,要轉換成Direction.WEST:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public 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:

    1. 用EnumSet來存放enum物件的時間和空間效能較好。
    2. 在多執行緒實行下是不安全。
    3. enum物件在EnumSet的順序為在宣告時候的順序。
  • EnumMap:
    1. 用EnumSet來存放enum物件的時間和空間效能較好。
    2. 在多執行緒實行下是不安全。

      參考資料:

  • Guide for understanding enum in java

[Java] Heap space vs. Stack

Posted on 2016-12-09 | Edited on 2016-12-13 | In Java |

在理解Java是否為Pass by Value(參考這篇文章)之前,建議先了解heap和stack的功用。

  • Heap space:
    1. 當建立物件時,會在heap space分配一個空間給物件使用。
    2. 所有在heap space上的物件,都可以被其他的thread存取。
    3. Garbage Collection會清除heap space內沒有被使用到的物件。
    4. 可透過-Xms參數調整heap space的開始大小。
    5. 可透過-Xmx參數設定heap space的最大值。
    6. 假如heap space滿了,Java會丟出
      Java Heap Space```
      1
      2
      3
      4
      5
      6
      7
        
      * Stack:
      1. 若變數是基本型態,就存放它的值。
      2. 若變數是物件,就存放它在heap space的位置。
      3. stack不能被其他thead存取。
      4. 可透過-Xss參數調整stack大小。
      5. 假如stack滿了,Java會丟出```java java.lang.StackOverFlowError

範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public 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

}

  1. 當開始執行程式時候,JVM會載入相關的class到heap space。
  2. 產生main thread並且建立它的stack。
  3. 執行Line2,因為i是基本型態,所以push其值到stack。
  4. 執行Line3,因為obj是物件,所以push它在heap space的位置到stack。
  5. 執行Line4,因為mem是物件,所以push它在heap space的位置到stack。
  6. 執行Line6,push obj在heap space的位置到stack。
  7. 執行Line7,push obj.toString()在heap space的位置到stack。
  8. 執行Line8,pop str和param。
  9. 執行Line9,pop mem、obj和i,程式執行結束。

如何透過pop屬於這個method的變數,推測實作方式或許是類似於四則運算。

例如:

  1. 執行Line1,push “{“。
  2. 執行Line6,push “{“。
  3. 執行Line8,一直pop,直到遇到”{“停止。
  4. 執行Line9,一直pop,直到遇到”{“停止。

參考資料:

  • Java Heap Memory vs Stack Memory Difference
  • How will you go about explaining the following Java concepts to a beginner?

[LeetCode] 277. Find the Celebrity

Posted on 2016-12-09 | Edited on 2018-05-07 |

Question

Suppose 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. 編號1認識編號2:那麼編號1一定不是名人,編號2有可能是名人。
  2. 編號1不認識編號2:編號2一定不是名人,編號1有可能是名人。

每問一人就會淘汰掉一人,接著繼續問可能是名人的人選和下一個編號的關係,直到問完N個人就可以找出名人。時間複雜度是 O(N)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public 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

  1. Celebrity Problem
  2. 名人問題 (Celebrity problem)

[Java] The details of Inheritance

Posted on 2016-12-09 | Edited on 2016-12-13 | In Java |

在Java使用繼承時要注意幾個小細節,不然很容易出錯。

  • 建構子的呼叫順序
  • compiler會自動產生無參數的建構子,會自動初始化field變數

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class 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()。

來看一個例子:

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
public 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);
}
}

會印出什麼樣的結果呢?

分析:

  1. new Extension()的時候會呼叫Base的建構子(public Base()),呼叫add(2)。
  2. 因為Extension有覆寫(override)Base的add function,所以Base建構子所呼叫的add(1)是呼叫Extension中的add function。
  3. 因為多型的關係,當Base建構子呼叫add(1),裡面的i是Extension的i,並不是Base的i。
  4. 當Base建構子呼叫add(1)完畢,此時Extension的i為2。
  5. 接著呼叫Extension的建構子,此時會先初始化field變數,所以i會被重設為3。
  6. 之後是呼叫add(2),此時i為7。Extension的初始化完畢。
  7. 呼叫add(8)時,可以得出i = 7 +16 = 23,所以印出23。

假如將

1
private static void exec(Extension base)

改成

1
private static void exec(Base base)

結果還是一樣嗎?
結果是0,因為此時的i指的是Base的i。

[LeetCode] 56. Merge Intervals

Posted on 2016-12-09 | Edited on 2018-05-07 | In LeetCode |

Question

Given 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.

1
2
i :     |--------|
i': |-----|

2.

1
2
i :     |--------|
i': |-----|

3.

1
2
i :     |--------|
i': |-----|

4.

1
2
i :     |--------|
i': |--------------|

以上為兩個interval重疊的情況,可以歸納出以下情況:

  • i.start <= i'.end
  • i'.start <= i.end

知道上述規則後合併就變得很簡單,因為給定的陣列是沒有排序過的,先依照 interval.start 排序。

  1. 初始化 merge = interval.get(0)
  2. 依序檢查 interval.get(i) 是否和 merge 重疊
  3. 若重疊,merge = 合併 interval.get(i) 和 merge
  4. 若不重疊,把 merge 寫入到結果,merge = interval.get(i)

有點像貪食蛇,就一直吃直到不能吃為止。

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
public 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;
}

[Java] lock、tryLock和lockInterruptibly的差別

Posted on 2016-12-09 | Edited on 2016-12-13 | In Java |

在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():
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
import 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():
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
import 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():
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
import 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的區別

1234

pandaforme

This is a programming blog

33 posts
7 categories
17 tags
RSS
GitHub Linkedin
© 2018 pandaforme
Powered by Hexo v3.7.1
|
Theme — NexT.Gemini v6.2.0