Java 学习基础篇 ---- Java集合
一、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);
}
}