美文网首页
Java 学习基础篇 ---- Java集合

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

作者: 瀑月流光 | 来源:发表于2020-06-07 10:21 被阅读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);
        }
    }
    

    相关文章

      网友评论

          本文标题:Java 学习基础篇 ---- Java集合

          本文链接:https://www.haomeiwen.com/subject/rwfbahtx.html