---
# System prepended metadata

title: Java - Collections(List & Set)
tags: [Java, Basic Java]

---

# Java - Collections(List & Set)
###### tags: `Java` `Basic Java`

## List - ArrayList
幾個特性：
1. 有排序
2. 可塞重複值
3. 可利用索引尋找元素

### 1. 添加元素 - add()
用的方法是add()，但是有以下注意點
1. 若要在List當中添加元素，則add()永遠返回true。(因為List允許元素重複)
2. 若要在Set當中添加元素，元素存在回返回false，元素不存在才會返回true。(因為Set當中不允許重複值出現)

### 2. 獲取元素 - get()
此為子類別list的方法(Collection沒有)
參數放索引值

### 3. 清空 - clear()

### 4. 移除元素 - remove()
1. 參數裡面放的是**物件，不是索引**
2. 刪除成功(元素存在)返回true，刪除失敗(元素不存在)返回false
3. 此方法只會移除第一次出現的元素，又因為刪除元素後list會自動將後續元素往前補，故若要將某重複元素全部都刪除乾淨，需要從後面往前面開始刪(詳見Iteration)

### 5. 是否包含物件 - contains()
底層source code使用equals()來判斷物件是否包含，**故如果需要判斷自定義的物件，需要重寫contains的方法**
舉例說明：
我們先創建一個student的java bean
```
public class Student {

    private String name;

    private int age;


    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
```
然後使用collection進行添加
```
    public static void main(String[] args) {

        Collection<Student> collections = new ArrayList<>();

        Student stu1 = new Student("John", 23);
        Student stu2 = new Student("Max", 24);
        Student stu3 = new Student("Bob", 25);

        collections.add(stu1);
        collections.add(stu2);
        collections.add(stu3);

        Student stu4 = new Student("John", 23);

        System.out.println(collections.contains(stu4));


    }
```
**此時返回結果為false**
因為原本的equals()的方法是從Object Class來的，比較的是物件的地址是否相同，而並非比較裡面的值是否相同。而stu1跟stu4因為是不同的物件，所以具有不同的地址

**但如果我們需要將stu1跟stu4視為重複的物件，讓contains返回true，則需要在student的java bean中改寫equals()方法**
```
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }
```
ps. 這段用IDEA直接生成即可(右鍵generate > equals() and hashCode())，重點不是code本身而是這個重寫的概念

改寫完之後再次執行System.out.println(collections.contains(stu4));就會返回true了

### 6. 是否為空 - isEmpty()
底層為判斷size()是否為0

### 7. 集合長度 - size()

### 8. 轉成陣列 - toArray()
建議用Object[]去接，因為可能會含有其他類型的data

### 9. Iteration:
**注意點：普通for loop只能遍歷有索引的List，不能遍歷沒有索引的Set**
#### 1. Iterator：
默認索引值為0

兩個常用方法：
hasNext() -> 判斷目前的位置是否有元素，有true無false
next() -> 取得目前位置的元素，並將iterator移到下一個索引位置
```
    public static void main(String[] args) {
        Collection<Integer> coll = new ArrayList<>();
        coll.add(0);
        coll.add(1);
        coll.add(2);
        coll.add(3);
        coll.add(4);
        coll.add(5);
        coll.add(6);
        coll.add(7);
        coll.add(8);
        coll.add(9);
            
        // 創建指針
        Iterator<Integer> it = coll.iterator();
        
        // 用hasNext當作循環的判斷
        // 若有指針返回true代表有東西就繼續跑
        // 返回false代表跑完了就結束循環
        while(it.hasNext()) {
            // 獲取元素，賦值給變數後，將指針往後移
            int number = it.next();
            System.out.print(number + "\t");
        }

    }
```

細節：
1. 指針遍歷後不會自動復位，要重新遍歷的話需要重新new一個新的
2. 指針跑到最後若繼續使用next()就會有NoSuchElementException (不是out of bound，因為不是用索引遍歷)
3. **循環中只能使用一個next()，若用兩次以上，因為移動多次所以一定會超過集合的範圍，就會出現NoSuchElementException**
4. 使用指針遍歷時無法使用collection的add()或是remove() -> ConcurrentModificationException
要刪除元素只能使用iterator的remove()

#### 2. for each (只適用於單集合)
承上點範例
```
        for (int i: coll) {
            // i = 100;
        }
        System.out.print(coll);
```
1. int的部分可以換成其他type，只要跟集合的type一致即可
2. 即便對循環中的變數做賦值，也不會改變原集合的內容。


#### 3. Lambda Expression
先來看一下inner class的表達方式
```
        coll.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
```
> 輸出結果為
> 0
> 1
2
3
4
5
6
7
8
9

底層source code原理：
遍歷集合後把元素逐一交給accept的方法，然後透過我們的改寫把元素print出來

正確的lambda expression
```
coll.forEach(integer -> System.out.println(integer));
```

#### 4. 如何選擇???
1. 如果在遍歷過程中需要刪除元素，請用Iterator
2. 如果只需要遍歷元素，用for each或lambda expression即可

## List - LinkedList
幾個特點：
1. 底層為**雙向鏈接串列**
2. 查詢各元素的速度較慢(因為一定要從頭開始查)
3. 但對於頭尾元素的操作(增加刪除)具有優勢

因為對頭尾元素的操作有優勢，所以有些特別的API能配合使用：
* addFirst() == push() / offer()
* addLast() == pop() / poll()
* getFirst()
* getLast()
* removeFirst()
* removeLast()

**通常的應用場景：Stack與Queue**

### 1. Stack
```
        LinkedList<String> stack = new LinkedList<>();

        stack.addFirst("First");
        stack.addFirst("Second");
        stack.push("Third");
        stack.push("Forth");

        System.out.println(stack);
```
> [Forth, Third, Second, First]

```
        stack.removeFirst();
        System.out.println(stack);
```
> [Third, Second, First]


### 2. Queue
```
        LinkedList<String> queue = new LinkedList<>();
        
        queue.addLast("First");
        queue.addLast("Second");
        queue.offer("Third");
        queue.offer("Forth");

        System.out.println(queue);
```
> [First, Second, Third, Forth]

```
        queue.removeFirst();
        System.out.println(queue);
```
> [Second, Third, Forth]

## Set:
幾個特點：
1. 沒有排序(LinkedHashSet TreeSet除外)
3. 不能放重複(不能放重複物件)
4. 沒有索引值可以查詢

**若要讓set任為兩個一樣內容的物件是重複物件，則需要改寫hasCode()與equals()的方法(跟底層原始碼有關)**

舉例說明：

我們先建立一個學生類別的標準java bean，並順便改寫toString方法
```
    static class Student {
        private String name;
        private int age;
        private char sex;

        public Student() {
        }

        public Student(String name, int age, char sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public char getSex() {
            return sex;
        }

        public void setSex(char sex) {
            this.sex = sex;
        }

        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", sex=" + sex +
                    '}';
        }
```

建立hashSet之後放入學生物件
```
    public static void main(String[] args) {

        Set<Student> studentSet = new HashSet<>();
        Student s1 = new Student("JoJo", 50, 'M');
        Student s2 = new Student("JoJo", 50, 'M');
        Student s3 = new Student("GaGa", 40, 'F');
        Student s4 = new Student("Zaza", 35, 'M');

        studentSet.add(s1);
        studentSet.add(s2);
        studentSet.add(s3);
        studentSet.add(s4);

        System.out.println(studentSet);

    }
```
這時候print出來會發現有四個學生物件，但其實裡面s1跟s2的內容是一樣的，Java還是會把它當作不同的物件(因為reference不同)

要讓Java判斷s1與s2是同一個物件，就要進行改寫
```
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return age == student.age && sex == student.sex && Objects.equals(name, student.name);
        }

        @Override
        public int hashCode() {
            return Objects.hash(name, age, sex);
        }
```

改寫完之後再print一次，就只剩下3個學生物件了(重複物件已被排除)
> [Student{name='GaGa', age=40, sex=F}, Student{name='JoJo', age=50, sex=M}, Student{name='Zaza', age=35, sex=M}]
