Java 学习基础篇 ---- Java集合

2020-06-07  本文已影响0人  瀑月流光

一、Java 集合简介

(一) Java集合简介

1、Java 集合定义:
(1)一个 Java 对象可以在内部持有若干其他 Java 对象,并对外提供访问接口,我们把这种 Java 对象称为集合。
(2)Java 的数组可以看成是一种集合,如下:

String[] ss = new String[10];    // 可以持有 10 个 String 对象
ss[0] = "Hello";         // 可以放入 String 对象
String first = ss[0];         // 可以获取 String 对象

2、数组和集合类的区别
(1)数组初始化后大小不可变
(2)数组只能按索引顺序存取

(3)集合类可变大小的顺序链表
(4)集合类保证无重复元素的集合

(二) 集合类 --- 由JDK自带的 java.util 包提供

1、Collection 集合类的根接口
(1)List:一种有序列表。例如:按索引排列的 Student 的 List
(2)Set:一种保证没有重复元素的集合。例如:所有无重复 Student 的 Set

2、Map:一种通过 Key 查找 Value 的映射表集合。例如:根据 name 查找对应 Student 的 Map

3、Java 集合设计特点
(1)接口和实现相分离:List 接口:ArrayList、LinkedList
(2)支持泛型:List<Student> list = new ArrayList<>();
(3)访问集合有统一的方法:迭代器(Iterator),即 Java 集合使用统一 Iterator 遍历集合。

4、JDK的部分集合类是遗留类,不应该继续使用:
(1)Hashtable:一种线程安全的 Map 实现
(2)Vector:一种线程安全的 List 实现
(3)Stack:基于 Vector实现的 LIFO 的栈

5、JD卡的部分接口是遗留接口,不应该被继续使用:
(1)Enumeration<E>:已经被 Iterator<E> 取代。

二、List

(一) 使用 List

1、List<E> 是一种有序链表,其特点如下:
(1)List 内部按照放入元素的先后顺序存放
(2)每个元素都可以通过索引确定自己的位置
(3)类似数组,但大小可变
(4)List 的元素可以重复,且可以为 null

2、List<E> 常用方法:
(1)void add(E e) 在末尾添加一个元素
(2)void add(int index, E e) 在指定索引添加一个元素
(3)int remove(int index) 删除指定索引的元素
(4)int remove(Object e) 删除某个元素
(5)E get(int index) 获取指定索引的元素
(6)int size() 获取链表大小(包含元素的个数)

3、List<E> 有 ArrayList 和 LinkedList 两种实现,和数组区别如下:
(1)数组也是有序结构,但是大小固定,且删除元素时需要移动后续元素:


数组.png

(2)ArrayList<E>: 内部使用数组存储所有元素


ArrayList.png
(3)LinkedList<E>:内部每个元素都指向下一个元素
LinkedList.png

4、创建 List<E>:
(1)常规方式:List<String> languages = new ArrayList<>() 或者 List<String> languages = new LinkedList<>()

List<String> languages = new ArrayList<>();
languages.add("Java");
languages.add("Python");
System.out.println(language);

(2)使用 java.util.Arrays 工具类:List<String> number = new ArrayList<>(Arrays.asList(xx, xx)) 或者 List<String> number = new LinkedList<>(Arrays.asList(xx, xx))

List<String> number = new LinkedList<>(Arrays.asList("AA", "BB"));
number.add("cc");
System.out.println(number);

(3)使用 java.util.stream.Collectors 和 java.util.stream.Stream 工具类:

import java.util.stream.Collectors;
import java.util.stream.Stream;

List<String> colors = Stream.of("blue", "red", "yellow").collect(Collectors.toList());
System.out.println(colors);

5、ArrayList 和 LinkedList 的对比:


两种 List 对比.png

6、遍历一个 List<E>:
(1)通过 get(int index): 对于 ArrayList 来说效率高,对于 LinkedList 来说效率低。

List<String> list = ...
for (int i=0; i<list.size; i++){
    String s = list.get(i);
}

(2)使用 Iterator<E> 遍历:

List<String> list = ...
for (Iterator<String> it = list.iterator();        // 使用 iterator()方法,获取 list 的 Iterator 对象
    it.hasNext();){
    String s = it.next();
}

(3)使用 foreach 来遍历:

List<String> list = new LinkedList<>{};
list.add("ad");
list.add("fd");
list.add("eh");
list.add("rt");
for (String s : list){
}

注意:除了数组 Array 以外,所有实现了 Iterable 接口的对象都可以使用 foreach 来遍历,List 实现了 Iterable 接口所以可以使用 foreach 来遍历,编译器在执行 foreach 代码时,会自动将其转化为使用 Interator 的遍历。如下图:


foreach执行原理.png

7、List<E> 和 Array 的相互转换:
(1)把一个 List<E> 变为一个 Array:
方法一:使用 Object[] toArray() 方法(Object[] 是返回值)

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Object[] array = list.toArray();     // {1, 2, 3}

方法二:使用 <T> T[] toArray(T[] a) 方法(<T> T[] 是返回值),传入什么类型的T,就返回什么类型的T.

传入指定类型的 T 数组,返回 T类型 的数组

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Integer[] array = list.toArray(new Integer[3]);    // Integer[3] 即传入的是 Integer 类型的 T;
                                                   // 返回值就是 Integer[] 即 Integer 类型的 T;
Number[] ns = list.toArray(new Number[3]);    // 传入 Number类型的数组,返回 Number 类型数组

String[] ss = list.toArray(new String[3]);  // ArrayStoreException   抛出异常,因为 list 是 List<Integer> 类型,即 list 的元素是 Integer ,所以无法传入 String 类型


// 如果一个 List 有三个元素,但是我们传入的数组只能容纳两个元素,toArray 方法会直接扔调传入的数组,创建一个新的数组来接收这三个元素
Integer[] array = list.toArray(new Integer[2]);

// 如果我们传入的是能容纳五个元素的数组,则 toArray 方法会用 null 补充空位
Integer[] array2 = list.toArray(new Integer[5]);         // {1, 2, 3, null, null}

(2)把一个 Array 变为一个 List<E> :
方法:<T> List<T> Arrays.asList(T... a)

Integer[] array = {1, 2, 4};

// 注意返回的 list 并不是 ArrayList
List<Integer> list = Arrays.asList(array);       // asList() 方法返回的这个 List<Integer> 对象是只读的,不能调用 add、remove 方法(会抛出异常)
list.add(4);    // UnsupportedOperationException!


// 
Integer[] array = {1, 2, 3};
List<Integer> list = Arrays.asList(array); 
List<Integer> arrayList = new ArrayList<>();
arrayList.addAll(list);


Integer[] array = {1, 2, 3};
List<Integer> arrayList = new ArrayList<>(Arrays.asList(array));

8、实际应用

import java.util.LinkedList;
import java.util.List;

public class Main{
    public static void main(String[] arg){
        List<String> list = new LinkedList<>();
        list.add("apple");
        list.add("pear");
        list.add("Orange");
        String[] ss = list.toArray(new String[list.size()]);
        for (String s : list){
            System.out.println(s);
        }
    } 
}

(二) 编写 equals 方法

1、List 中使用 contains() 和 indexOf() 判断元素是否存在或者查找元素索引:
(1)boolean contains(Object o) 是否包含某个元素
(2)int indexOf(Object o) 查找某个元素的索引,不存在返回-1

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.contains("C");    // true
list.contains("X");    // false
list.indexOf("C");    // 2
list.indexOf("x");   // -1

2、List 内部使用 equals() 方法判断两个元素是否相等,而不是通过 == 来判断。所以要正确调用 contains / indexOf 方法,放入的元素必须正确覆写 equals 方法,JDK 提供的 String、Integer等已经覆写了 equals 方法,我们自定义的类型要自己实现 equals 方法(例如:Person 类)。

package com.test;

import java.util.Objects;

public class Person{
    private final String name;
    private final int agenum;

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

    public String getName(){
        return name;
    }

    // 覆写 toString 方法,便于方便的打印 参数
    @Override
    public String toString(){
        return "(Person: " + name + ", " + agenum + ")";
    }

    // 手动实现 equals 方法
    public boolean equals(Object o){
        if (this == o){
            return true;
        }
        if (o instanceof Person){
            Person p = (Person) o;
            // 引用类型字段借助Objects.equals()判断;  基本类型字段用==直接比较 
            return Objects.equals(p.name, this.name) &&p.agenum == this.agenum;  
        }
        return false;
    }
}
package com.test;

import java.util.ArrayList;
import java.util.List;

public class Main{
    public static void main(String[] arg){
        List<Person> list = new ArrayList<>();
        list.add(new Person("Ming", 12));
        list.add(new Person("Hong", 15));
        list.add(new Person("Jun", 18));
        System.out.println(list);
        System.out.println(list.contains(new Person("Jun", 18)));    // true  因为 Person 实现了 equals 方法
    }
}

注意:如果不实现 equals() 方法,则以上代码中:

list.add(new Person("Jun", 18));
System.out.println(list);
System.out.println(list.contains(new Person("Jun", 18)));

两次 new Person("Jun", 18) 使不同对象,contains 返回 false。

3、equals()编写方法:
(1)判断this==o
(2)判断o instanceof Person
(3)强制转型,并比较每个对应的字段
   a. 基本类型字段用==直接比较
   b. 引用类型字段借助Objects.equals()判断

4、总结
(1)如果要在 List 中查找元素:
   a. List 的实现类通过元素的 equals 方法比较两个元素
   b. 放入的元素必须正确覆写 equals 方法:JDK 提供的 String、Integer等已经覆写了 equals 方法
   c. 编写 equals 方法可记住 Objects.equals() 判断
(2)如果不再 List 中查找元素:不必覆写 equals 方法

三、Map

(一) 使用 Map

1、Map是一种键值映射表,可以通过Key快速查找Value。

public class Student{
    public String name;
    public String address;
    public int grade;
    public int score;
}

Map<String, Student> map = ..
Student target = map.get("Xiao Ming");

2、常用方法:
(1)V put(K key, V value):把Key-Value放入Map
(2)V get(K key):通过Key获取Value
(3)boolean containsKey(K key):判断Key是否存在

3、遍历Map:用for...each循环
(1)循环Key:keySet()

Map<String, Integer> map = ...
for (String key : map.keySet()){
    Integer value = map.get(key);
}

(2)循环Key和Value:entrySet()

Map<String, Integer> map = ...
for (Map.Entry<String, Integer> entry : map.entrySet()){
    String key = entry.getKey();
    Interger value = entry.getValue();
    System.out.println("key:" + key);
    System.out.println("value:" + value);
}

4、常用的实现类(关系如下图):


Map实现关系图.png

(1)HashMap:不保证有序。
(2)SortedMap:保证按Key排序,实现类有TreeMap。即 TreeMap 实现了 SortedMap 接口(如代码块一)。
(3)TreeMap: 按元素顺序排序,可以自定义排序算法(如代码块二),注意排序算法只能作用于 key,和 value 没有任何关系。

package com.test;

import java.util.Map;
import java.util.TreeMap;

// 代码块一
Map<String, Integer> map = new TreeMap<>();
map.put("orange", 1);
map.put("apple", 2);
map.put("banana", 3);
for (String key:map.keySet()){
    System.out.println(key);
}

// apple, banana, orange
// 代码块二(按照字母顺序倒叙排序)
package com.test;

import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

public class Main{
    public static void main(String[] args){
        Map<String, Integer> map = new TreeMap<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return -o1.compareTo(o2);
            }
        });
        map.put("orange", 14);
        map.put("apple", 2);
        map.put("banana", 3);
        for (String key:map.keySet()){
            System.out.println(key);
        }
    }
}
// orange banana apple

5、实例:

package com.test;

import java.util.*;

public class Main{
    public static void main(String[] arg){
        List<Person> list = Arrays.asList(new Person("Hing", 12), new Person("Mou", 14));
        System.out.println(list);
        Map<String, Person> map = new HashMap<>();
        for (Person p : list){
            map.put(p.getName(), p);
        }
        for (String key : map.keySet()) {
            System.out.println(key + " --> " + map.get(key));
        }
        System.out.println("============== 倒序 ===============");
        Map<String, Person> map1 = new TreeMap<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return - o1.compareTo(o2);
            }
        });
        for (Person p : list){
            map1.put(p.getName(), p);
        }
        System.out.println(map1);
        for (String key : map1.keySet()){
            System.out.println(key + " --> " + map1.get(key));
        }
    }
}
package com.test;

import java.util.Objects;

public class Person{
    private final String name;
    private final int agenum;

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

    public String getName(){
        return name;
    }

    // 覆写 toString 方法,便于方便的打印 参数
    @Override
    public String toString(){
        return "(Person: " + name + ", " + agenum + ")";
    }

    public boolean equals(Object o){
        if (this == o){
            return true;
        }
        if (o instanceof Person){
            Person p = (Person) o;
            return Objects.equals(p.name, this.name) &&p.agenum == this.agenum;
        }
        return false;
    }
}

(二) 编写 equals 和 hashCode

package com.test;
import java.util.*;

public class Main{
    public static void main(String[] arg){
    Map<String, Integer> map = new HashMap<>();
    map.put("aa", 10);
    map.put("cc", 27);
    map.put("bb", 20);
    System.out.println(map.get("aa"));       // 10
    System.out.println(map.get("xx"));      // null
    }
}

在Map 内部对 key 做比较是通过 equals 方法实现的

HashMap 通过计算 Key 的 hashCode() 定位 Key 的存储位置,继而获得 Value
任何一个对象都有一个 hashCode 方法,他是从 Object 继承下来的,hashMap 通过对 haskcode 进行计算,映射某一个位置,进而得到 value

1、正确使用Map必须保证:
(1)作为Key的对象必须正确覆写equals()方法,例如:String、Integer、Long
(2)作为Key的对象必须正确覆写hashCode()方法

2、覆写hashCode:
(1)如果两个对象相等,则两个对象的hashCode()必须相等
(2)如果两个对象不相等,则两个对象的hashCode()尽量不相等(可以相等,会造成效率下降)

3、如果一个对象覆写了 equals() 方法,就必须正确覆写 hashCode() 方法:
(1)如果 a.equals(b) == true, 则 a.hashCode() == b.hashCode()
(2)如果 a.equals(b) == false, 则 a 和 b 的 hashCode() 尽量不要相等

4、hashCode可以通过Objects.hashCode()辅助方法实现

5、实例

package com.test;

import java.util.Objects;

public class Person{
    private final String name;
    private final int agenum;

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

    public String getName(){
        return name;
    }

    // 覆写 toString 方法,便于方便的打印 参数
    @Override
    public String toString(){
        return "(Person: " + name + ", " + agenum + ")";
    }


    @Override
    public int hashCode() {
        return Objects.hash(this.agenum, this.name);       // 保证 name 和 agenum 相同的情况下,生成的 hashcode 是相同的
    }

    public boolean equals(Object o){
        if (this == o){
            return true;
        }
        if (o instanceof Person){
            Person p = (Person) o;
            return Objects.equals(p.name, this.name) &&p.agenum == this.agenum;
        }
        return false;
    }
}
package com.test;

import java.util.*;

public class Main{
    public static void main(String[] arg){
        List<Person> list = Arrays.asList(new Person("Hing", 12), new Person("Jun", 18));
        Map<Person, String> map = new HashMap<>();
        for (Person p : list){
            map.put(p, p.getName());
        }
        // Jun
        System.out.println(map.get(new Person("Jun", 18)));
    }
}
// null 因为 Person类 没有实现 hashcode 方法,所以 new Person("Jun", 18) 和定义时传入的 new Person("Jun", 18) 被认为不是同一个对象

(三) 使用 Properties

1、Properties文件特点
(1).properties文件只能使用ASCII编码* 可以从文件系统和ClassPath读取
(2)读取多个.properties文件,后读取的Key-Value会覆盖已读取的Key-Value

2、Properties文件格式

3、Properties用于读取配置
(1)从文件系统读取 .properties 文件

String f = "C:\\conf\\setting.properties";
Properties props = new Properties();     // 创建一个 Properties 对象
props.load(new FileInputStream(f));      // 使用 Properties 对象加载 .properties 配置文件

String url = props.getProperty("url");
// default description 为默认值,当获取的参数不存在时,返回该默认值
String desc = props.getProperty("description", "default description");   

(2)可以从 ClassPath 读取 .properties 文件

Properties props = new Properties();
props.load(getClass().getResourceAsStream("/common/setting.properties"));
props.load(new FileInputStream("C:\\conf\\setting.properties"));

String url = props.getProperty("url");
String desc = props.getProperty("description", "no description");

4、Properties实际上是从 Hashtable 派生,但只需调用getProperty和setProperty

5、实例:
(1)setting.properties

# comment
url=https://www.baidu.com/
language=Java
course.title=Java\u96c6\u5408\u7c7b

(2) Main.java

package com.test;
import java.util.*;

public class Main{
    public static void main(String[] arg) throws Exception {
        Properties props = new Properties();
        props.load(Main.class.getResourceAsStream("/com/test/setting.properties"));   // Class.getResourceAsStream
        String url = props.getProperty("url");
        System.out.println(url);                // https://www.baidu.com/
        String lang = props.getProperty("language");
        System.out.println(lang);             // Java
        String title = props.getProperty("course.title");
        System.out.println(title);                // Java集合类
    }
}

6、Class.getResourceAsStream() 与 ClassLoader.getResourceAsStream() 的区别
(1)Class.getResourceAsStream() 会指定要加载的资源路径与当前类所在包的路径一致。
例如:你写了一个MyTest类在包com.test.mycode 下,那 MyTest.class.getResourceAsStream("name") 会在com.test.mycode包下查找相应的资源。如果这个name是以 '/' 开头的,那么就会从classpath的根路径下开始查找。

(2)ClassLoader.getResourceAsStream() 无论要查找的资源前面是否带'/' 都会从classpath的根路径下查找。所以: MyTest.getClassLoader().getResourceAsStream("name") 和 MyTest.getClassLoader().getResourceAsStream("name") 的效果是一样的。

四、Set

(一) 使用 Set

1、Set用于存储不重复的元素集合,所以利用Set可以去除重复元素,常用方法如下:
(1)boolean add(E e)
(2)boolean remove(Object o)
(3)boolean contains(Object o)
(4)int size()

Set<String> set = ...
set.add("abc");     // true
set.add("xyz");     // true
set.size();    // 2
set.add("abc");     // false
set.size();    // 2
set.contains("abc");   // true
set.remove("abc");    // true
set.size();        // 1

2、Set 相当于没有 value 的 Map,放入 Set<E> 的元素 E 要正确实现 equals() 和 hashCode() 方法

3、Set不保证有序:
(1)HashSet是无序的
(2)TreeSet是有序的 实现了SortedSet接口的是有序Set


Set关系.png
// HashSet 无序
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("orange");
for (String s : set){
    System.out.println(s);
}
// banana  orange  apple
// TreeSet 无序
Set<String> set = new TreeSet<>();
set.add("apple");
set.add("banana");
set.add("orange");
for (String s : set){
    System.out.println(s);
}
// apple  banana  orange

4、TreeSet 和 TreeMap 类似,可以自定义排序算法

public class Main{
    public static void main(String[] arg){
        Set<String> set = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return -o1.compareTo(o2);
            }
        });
        set.add("apple");
        set.add("banana");
        set.add("orange");
        for (String s : set){
            System.out.println(s);
        }
    }
}

5、实例

package com.test;
import java.util.*;

public class Main{
    public static void main(String[] arg){
        List<String> list1 = Arrays.asList("pear", "apple", "banana", "orange", "apple");
        List<String> list2 = removeDuplicate(list1);
        System.out.println(list2);
    }
    static List<String> removeDuplicate(List<String> list){
        Set<String> set = new HashSet<>(list);
        return new ArrayList<String>(set);
    }
}

五、Queue

(一) 使用 Queue

1、队列(Queue)是一种先进先出(FIFO)的数据结构

2、Queue 的实现类:ArrayDeque,LinkedList 操作Queue的元素的方法:
(1)获取队列长度:size()
(2)添加至队尾压栈:add() / offer()

Queue<String> q = ...
if (q.offer("abc")){
    // add ok
} else{
    // add failed
}

(3)获取队列头部元素并删除:E remove() / E poll()

Queue<String> q = ...
if (q.isEmpty()){
    // cannot poll
} else{
    String first = q.poll();
}

(4)获取队列头部元素但不删除:E element() / E peek()
(5)两组方法的区别:是否抛出Exception,如下图:


两组方法对比.png

3、避免把 null 添加到队列
4、实例:

public class Main{
    public static void main(String[] arg) {
        Queue<Person> queue = new LinkedList<>();  // 定义一个 LinkedList,将其向上转型为 Queue
        queue.offer(new Person("Ming", 12));
        queue.offer(new Person("Hong", 15));
        queue.offer(new Person("Jun",17));
        System.out.println(queue.poll());      // (Person: Ming, 12)
        System.out.println(queue.poll());      // (Person: Hong, 15)
        System.out.println(queue.poll());      // (Person: Jun, 17)
        if (!queue.isEmpty()){
            System.out.println(queue.remove());
        }
    }
}

(二) 使用 PriorityQueue

1、PriorityQueue 特点
(1)PriorityQueue<E> 具有 Queue<E> 接口,可以使用 Queue<E> 的 添加/删除 操作
(2)PriorityQueue 的出队顺序与元素的优先级有关
(3)从队首获取元素时,总是获取优先级最高的元素
(4)默认按元素比较的顺序排序(必须实现Comparable接口)
(5)可以通过Comparator自定义排序算法(不必实现Comparable接口)

2、实例
(1)默认按元素比较的顺序排序(必须实现Comparable接口)

package com.test;
import java.util.*;

public class Main{
    public static void main(String[] arg) {
        Queue<Person> queue = new PriorityQueue<>();
        queue.offer(new Person("Ming", 12));
        queue.offer(new Person("Hong",15));
        queue.offer(new Person("Jun",17));
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}
package com.test;

public class Person implements Comparable<Person>{
    private final String name;
    private final int agenum;

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

    public String getName(){
        return name;
    }

    // 覆写 toString 方法,便于方便的打印 参数
    @Override
    public String toString(){
        return "(Person: " + name + ", " + agenum + ")";
    }
    
    @Override
    public int compareTo(Person o) {
        // 按照 name 字段进行排序
        return this.name.compareTo(o.name);
    }
}

(2)通过Comparator自定义排序算法(不必实现Comparable接口)

package com.test;
import java.util.*;

public class Main{
    public static void main(String[] arg) {
        Queue<Person> queue = new PriorityQueue<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return - o1.getName().compareTo(o2.getName());
            }
        });
        queue.offer(new Person("Ming", 12));
        queue.offer(new Person("Hong",15));
        queue.offer(new Person("Jun",17));
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}
package com.test;

public class Person{
    private final String name;
    private final int agenum;

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

    public String getName(){
        return name;
    }

    // 覆写 toString 方法,便于方便的打印 参数
    @Override
    public String toString(){
        return "(Person: " + name + ", " + agenum + ")";
    }
}

(三) 使用 Deque

1、Deque<E> 实现的是一个双端队列(Double Ended Queue):
(1)既可以添加到队尾,也可以添加到队首;
(2)既可以从队首获取,又可以从队尾获取

2、Deque<E> 继承于 Queue<E>类,即:

public interface Deque<E> extends Queue<E>{
    ...
}

3、使用方法(特点:总是调用xxxFirst / xxxLast以便与Queue的方法区分开)
(1)添加元素到队首 / 队尾:addFirst(E e) / offerFirst(E e) addLast(E e) / offerLast(E e)
(2)取队首 / 队尾元素并删除:E removeFirst() / E pollFirst() E removeLast() / E pollLast()
(3)取队首 / 队尾元素但不删除:E getFirst() / E peekFirst() E getLast() / E peekLast()

4、与 Queue<E> 方法对比


方法对比.png

5、Deque 的实现类:ArrayDeque、LinkedList(LinkedList 是一个全能选手,是多种实现类身份,如 List 的实现类)
(1)把 LinkedList 作为 Deque 使用:

Deque<String> obj = new LinkedList<>();
obj.offerLast("z");

(2)把 LinkedList 作为 List 使用:

List<String> obj = new LinkedList<>();
obj.add("z");

6、避免把 null 添加到队列

7、实例

package com.test;
import java.util.*;

public class Main{
    public static void main(String[] arg) {
        Deque<String> deque = new LinkedList<>();
        deque.offerLast("end");        // "end"
        deque.offerLast("c");           // "end", "c"
        deque.offerFirst("A");         // "A", "end", "c"
        System.out.println(deque.pollLast());       // "c"
        System.out.println(deque.pollFirst());     // "A"
    }
}

六、Stack

(一)

1、栈(Stack)是一种后进先出(LIFO)的数据结构

2、操作栈的元素的方法:
push(E e):压栈
pop():出栈*
peek():取栈顶元素但不出栈

3、Java使用Deque实现栈实现 Stack 的功能
(1)push(E e) 对应 addFirst(E e)
(2)pop() 对应 removeFirst()
(3)peek() 对应 peekFirst()
注意:只调用 push/pop/peek 方法,避免调用 Deque 的其他方法 (addFirst/removeFirst/peekFirst)

4、递归调用就是一个压栈的过程,但是方法函数嵌套调用过会造成栈溢出 StackOverflowError

static void main(String[] args){
    increase(1);
}
static int increase(x){
    return increase(x) + 1;        // 无限递归,会造成栈溢出
}

5、案例:编译器在执行中缀表达式的时候,会将其编译为后缀表达式去执行(压栈)
(1)中缀表达式:1 + 2 *(9 - 5)
(2)后缀表达式:1 2 9 5 - * +

6、不要使用遗留类 Stack

七、实际应用

(一) 使用 Iterator -- Java 的迭代模式

1、Java 的集合类都可以使用 for ... earch 循环:List、Set、Queue、Deque

2、Java 编译器在执行 for ... earch 循环时,实际上是通过 Iterator 将 for ... earch 循环改写为普通的 for 循环。即:for...each循环是编译器实现的Iterator模式。

3、如何让自己编写的集合类可以使用 for ... earch 循环?
(1)实现 Iterable 接口
(2)返回 iterator 实例对象,一个集合通过调用 iterator() 方法,会返回一个 Iterator 对象.
(3)用 Iterator 对象跌代

package com.test;

import java.util.Iterator;

public class ReadOnlyList<E> implements Iterable<E>{       // 1、实现 Iterable  这个接口
    E[] array;          // 使用数组来存储需要存储的元素

    @SafeVarargs
    public ReadOnlyList(E... array){
        this.array = array;
    }

    @Override
    public Iterator<E> iterator(){                // 2、实现一个 Iterator 方法,他返回一个 Iterator 对象
        return new ReadOnlyIterator();
    }  // 覆写 iterator 方法,返回 Iterator 对象


    class ReadOnlyIterator implements Iterator<E>{          // 实现了 Iterator 接口,返回 
        int index = 0;
        // 要正确的实现 hasNext方法 和 next方法

        // 是否还有下一个元素
        @Override
        public boolean hasNext(){
            // class ReadOnlyIterator 定义在 ReadOnlyList 内部,所以可以直接访问 ReadOnlyList 这个实例
            return index < ReadOnlyList.this.array.length;
        }

        // 如何获取下一个元素
        @Override
        public E next(){
            E e = array[index];
            index++;
            return e;
        }
    }
}
package com.test;

public class Main{
    public static void main(String[] arg){
        ReadOnlyList<String> list = new ReadOnlyList<String>("apple", "pear", "orange");
        for (String s : list){
            System.out.println(s);
        }
    }
}

4、使用 Iterator 模式进行迭代的好处:
(1)对任何集合都采用同一种访问模型
(2)调用者对集合内部结构一无所知
(3)集合类返回的Iterator对象知道如何迭代
(4)Iterator是一种抽象的数据访问模型

(二) 使用 Collections(JDK提供的集合工具类)

1、为 Java 集合添加元素
(1)boolean addAll(Collection<?super T> c, T... elements)

2、使用 Collections 创建空集合(创建的集合不可变,即不能添加元素,空集合):
(1)List<T> emptyList()
(2)Map<K, V> emptySet()
(3)Set<T> emptyMap()

3、创建单元素集合(创建的集合不可变,即不能添加元素,只有一个元素):
(1)Set<T> singleton(T o)
(2)List<T> singletonList(T o)
(3)Map<K, V>singletonMap(K key, V value)

4、sort 方法可对List排序(必须传入可变的 List):
(1)void sort(List<T> list)
(2)void sort(List<T> list, Comparator<? super T> c):

5、shuffle 随机重置 List 的元素(随机打乱 List 内部元素的顺序)
(1)void shuffle(List<?> list)

6、把可变集合变为不可变集合(创建不可变集合):
(1)List<T> unmodifiableList(List<? extends T> list)
(2)Set<T> unmodifiableSet(Set<? extends T> set)
(3)Map<K, V> unmodifiableMap(Map<? extends K,? extends V> m)

7、把线程不安全的集合变为线程安全的集合(已不推荐使用):
(1)List<T> synchronizedList(List<T> list)
(2)Set<T> synchronizedSet(Set<T> s)
(3)Map<K, V> synchronizedMap(Map<K, V> m)

8、实例
(1)错误使用

package com.test;
import java.util.*;

// 错误使用
public class Main{
    public static void main(String[] arg){
        List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
        List<Object> readOnlyhList = Collections.unmodifiableList(list);   // 把 list 变成不可变的集合
        System.out.println(readOnlyhList);
        list.add("X");
        // readOnlyhList.add("XX");   // 抛出异常,因为 readOnlyhList 为不可变集合
        System.out.println(readOnlyhList);       // list 引用的修改会改变 readOnlyhList 的值,所有此种使用方法错误
    }
}

(2)正确使用

package com.test;
import java.util.*;

// 正确使用
public class Main{
    public static void main(String[] arg){
        // 创建完 ArrayList 对象后,不创建引用,直接将其转化为不可变集合,防止了引用篡改数据
        List<Object> readOnlyhList = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("A", "B", "C")));
        System.out.println(readOnlyhList);
        // readOnlyhList.add("XX");   // 抛出异常,因为 readOnlyhList 为不可变集合
        readOnlyhList.add("XX");
        System.out.println(readOnlyhList);
    }
}
上一篇下一篇

猜你喜欢

热点阅读