廖雪峰java教程学习笔记——集合

Java的java.util包主要提供了三种类型的集合:List、Set、Map

Java集合的设计特点:

  1. 接口和实现类相分离,例如有序表的接口是List,具体的实现类有ArrayList、LinkedList等
  2. 支持泛型
  3. 访问集合总是通过迭代器

注意:避免使用历史遗留类和接口

有一小部分集合类是遗留类,不应该继续使用:

  1. Hashtable:一种线程安全的Map实现;
  2. Vector:一种线程安全的List实现;
  3. Stack:基于Vector实现的LIFO的栈。

还有一小部分接口是遗留接口,也不应该继续使用:

  1. Enumeration:已被Iterator取代。

List 有序列表

List<E>接口主要有以下方法:

在末尾添加一个元素:boolean add(E e)
在指定索引添加一个元素:boolean add(int index, E e)
删除指定索引的元素:E remove(int index)
删除某个元素:boolean remove(Object e)
获取指定索引的元素:E get(int index)
获取链表大小(包含元素的个数):int size()

常用的实现有:ArrayList和LinkedList

ArrayList

ArrayList内部由数组实现,增删元素时会自动将其余元素挪位置,加满时会自动创建一个更大的数组,将原数组的所有元素复制过去,取代旧数组。

LinkedList

LinkedList内部由链表实现。

创建List

方法一:创建空List,用add添加元素

        List<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(3);

或用双括号:

List<Integer> list=new ArrayList<Integer>(){{
                        add(1);
                        add(2);
                        add(3);
                          }};

方法二:使用Arrays.asList()

//创建List
List<Integer> list=Arrays.asList(1, 2, 3);  //注意要用Integer
//创建元素为List的list
List<List<Integer>> list = Arrays.asList(Arrays.asList(1,2), Arrays.asList(3,4));

注意Arrays.asList()创建的List是一个只读对象,不能再用add操作了:

        List<Integer> list = List.of(12, 34, 56);
        list.add(999); // UnsupportedOperationException

asList 是 java.util.Arrays 类的一个方法

public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
}

容易犯的错误:

        int[] array = {1,2,3};
        List myList = Arrays.asList(array);  //将array对象作为list元素了

因为asList的接口是泛型:List<T> asList(T...) ,int作为原始类型不属于Object,所以不能被作为泛类型参数。整个数组倒是一个Object,所以整个数组就用来取代T,所以得到了一个List<int[]>。正确的做法是传入一个Integer[],就能得到一个List<Integer>了。

方法三:从Stream中创建

@Test
public void givenStream_thenInitializeList(){
    List<String> list = Stream.of("foo", "bar")
      .collect(Collectors.toList());
		
    assertTrue(list.contains("foo"));
}

方法四:工厂方法 .of (Java 9)

Java 9+ 版本,可以用List.of()取代Arrays.asList()

遍历List

方法一:for循环 + get(i)

        for (int i=0; i<list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }

不推荐这种方法,因为只对ArrayList高效,对LinkedList很低效。

方法二:迭代器Iterator (推荐!)

迭代器:

        for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
            String s = it.next();
            System.out.println(s);
        }

简化版为:

		//自动使用迭代器
        for (String s : list) {
            System.out.println(s);
        }

不同的List类型,返回的Iterator对象实现也是不同的,但总是具有最高的访问效率。

Java的for each循环会自动使用Iterator遍历。实际上,只要实现了Iterable接口的集合类都可以直接用for each循环来遍历。

List转为Array

Integer[] array = list.toArray(new Integer[3]);
Integer[] array = list.toArray(new Integer[list.size()]);
Integer[] array = list.toArray(Integer[]::new);  //函数式写法

编写equals方法

List中contains()、indexOf()等方法使用equals()方法判断两个元素是否相等。

因此,对于自定义的类型,要想正确使用List的contains()、indexOf()这些方法,放入的实例必须正确覆写equals()方法,否则,放进去的实例查找不到。

public class Person {
    public String name;
    public int age;
}

public boolean equals(Object o) {
    if (o instanceof Person) {
        Person p = (Person) o;
        //引用字段用equals,基本类型字段用==
        return this.name.equals(p.name) && this.age == p.age;
    }
    return false;
}

但上面方法中若this.namenullequals()方法就会报错。一种解决方式是手动判断是否为null,但这显然太麻烦。一般使用Objects.equals()静态方法即可。

标准覆写equals()方法样例:

public boolean equals(Object o) {
    if (o instanceof Person) {
        Person p = (Person) o;  //安全向下转型
        return Objects.equals(this.name, p.name) && this.age == p.age;
    }
    return false;
}

Map 映射表

put(),get()分别用于添加、获取映射。

        Student s = new Student("Xiao Ming", 99);
        Map<String, Student> map = new HashMap<>();  //创建HashMap
        map.put("Xiao Ming", s); // 添加元素
        Student target = map.get("Xiao Ming"); // 查找元素

遍历Map,顺序是不确定的:

        //方法一:通过key遍历,keySet()相当于python中的keys()
        for (String key : map.keySet()) {  
            Integer value = map.get(key);
            System.out.println(key + " = " + value);
        }
		//方法二:直接遍历key、value,entrySet()相当于python中的items()
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key + " = " + value);
        }

用List存储全量数据,用Map做缓存的例子:

class Students {
	List<Student> list;//定义了一个Student的List,可能十分巨大
	Map<String, Integer> cache;//定义了一个Map,缓存被查询频率很高的学生

	Students(List<Student> list) {
		this.list = list;
		cache = new HashMap<>();
	}

	int getScore(String name) {
		Integer score = this.cache.get(name);//试图从cache中查找这娃对应的成绩
		if (score == null) {
			score = findInList(name);
			if (score!= null) {
				cache.put(name, score);//加入缓存
			}
		}
		return score == null ? -1 : score.intValue();
	}

	Integer findInList(String name) {
		for (var ss : this.list) {
			if (ss.name.equals(name)) {
				return ss.score;
			}
		}
		return null;//表示查询的人不在名单内
	}
}

编写equals()和hashcode()方法

在Map的内部,对key做比较是通过equals()实现的,这一点和List查找元素需要正确覆写equals()是一样的。

所以若是自定义的对象作为key,一定要覆写equals()方法(常用的String等作为key时,已经自带覆写了equals方法)。

另外,还应覆写hashCode()方法,它返回一个int整数。

package oop_package.src.com.itranswarp.sample;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class Main {
    public static void main(String[] args) {
        Map<Person, Integer> mp = new HashMap<>();
        Person p1 = new Person("Bob", "Dylan", 23);
        Person p2 = new Person("Bob", "Dylan", 23);
        System.out.println(p1.hashCode());  //2126225543
        System.out.println(p2.hashCode());  //2126225543
        mp.put(p1, 100);
        System.out.println(mp.get(p1));  //100
        System.out.println(mp.get(p2));  //100
    }
}
class Person {
    String firstName;
    String lastName;
    int age;

    public Person(String firstName, String lastName, int age){
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName, age);
    }

    @Override
    public boolean equals(Object p) {  //equals传入的参数是Object类型,若写成Person类型则无法正确覆写
        if (p instanceof Person){
            Person person = (Person) p;
            return Objects.equals(this.lastName, person.lastName)
                    && Objects.equals(this.firstName, person.firstName)
                    && this.age == person.age;
        }
        return false;
    }
}

注意覆写equals()时,传入的参数必须是Object类型。若写成Person类型则无法正确覆写!

哈希冲突解决方案

哈希散列内部是用数组存储的,数组长度当然不能和int范围一样大。

初始化内部数组默认长度16

HashMap初始化时默认的数组大小只有16,任何key,无论它的hashCode()有多大,都可以简单地通过:

int index = key.hashCode() & 0xf; // 0xf = 15

把索引确定在0~15。

按需扩容

添加超过一定数量的key-value时,HashMap会在内部自动扩容,每次扩容一倍,即长度为16的数组扩展为长度32,相应地,需要重新确定hashCode()计算的索引位置。例如,对长度为32的数组计算hashCode()对应的索引,计算方式要改为:

int index = key.hashCode() & 0x1f; // 0x1f = 31

每次扩容都要重新计算hashcode,效率很低。所以最好能提前指定HashMap的容量:

//初始容量总是2的指数倍,所以实际大小为比10000大的 16384 = 2^14
Map<String, Integer> map = new HashMap<>(10000);  

冲突解决 类似矩阵邻接表

万一hash的结果相同,比如a和b都映射到了5的位置,就在那里建立一个List,包含两个Entry,一个是"a"的映射,一个是"b"的映射:

  ┌───┐
0 │   │
  ├───┤
1 │   │
  ├───┤
2 │   │
  ├───┤
3 │   │
  ├───┤
4 │   │
  ├───┤
5 │ ●─┼───> List<Entry<String, Person>>
  ├───┤
6 │   │
  ├───┤
7 │   │
  └───┘

冲突的概率越大,这个List就越长,Map的get()方法效率就越低。所以应尽量保证hashcode()返回值不同。

EnumMap

若key是enum类型,就能使用EnumMap。

它在内部以一个非常紧凑的数组存储value,并且根据enum类型的key直接定位到内部数组的索引,并不需要计算hashCode(),不但效率最高,而且没有额外的空间浪费。

import java.time.DayOfWeek;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
        map.put(DayOfWeek.MONDAY, "星期一");
        map.put(DayOfWeek.TUESDAY, "星期二");
        map.put(DayOfWeek.WEDNESDAY, "星期三");
        map.put(DayOfWeek.THURSDAY, "星期四");
        map.put(DayOfWeek.FRIDAY, "星期五");
        map.put(DayOfWeek.SATURDAY, "星期六");
        map.put(DayOfWeek.SUNDAY, "星期日");
        System.out.println(map);
        System.out.println(map.get(DayOfWeek.MONDAY));
    }
}

TreeMap

HashMap的Key无序,SortedMap的Key有序。SortedMap是个接口,其实现类是TreeMap。

       ┌───┐
       │Map│
       └───┘
         ▲
    ┌────┴─────┐
    │          │
┌───────┐ ┌─────────┐
│HashMap│ │SortedMap│
└───────┘ └─────────┘
               ▲
               │
          ┌─────────┐
          │ TreeMap │
          └─────────┘

TreeMap中key是按顺序排列的,相当被sort()过。例如String类型的key就按照字典序排列:

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

如果是自定义的class作为key,则需要实现Comparable接口,要不没法排序啊。例如:

        Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
            public int compare(Person p1, Person p2) {
                return p1.name.compareTo(p2.name);
            }
        });

注意:Person类并未覆写equals()和hashCode(),因为TreeMap不使用equals()和hashCode()。

有趣的例子

Comparator接口要求实现一个比较方法,它负责比较传入的两个元素a和b。

如果a<b,则返回负数,通常是-1;
如果a==b,则返回0;
如果a>b,则返回正数,通常是1;

TreeMap内部根据比较结果对Key进行排序。

TreeMap在比较两个Key是否相等时,依赖Key的compareTo()方法或者Comparator.compare()方法。若返回0就相当于判定二者equals

package oop_package.src.com.itranswarp.sample;

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<Student, Integer> map = new TreeMap<>(new Comparator<Student>() {
            public int compare(Student p1, Student p2) {
			    if (p1.score == p2.score) {
			        return 0;
			    }
			    return p1.score > p2.score ? -1 : 1;
            }
        });
        map.put(new Student("Tom", 77), 1);
        map.put(new Student("Bob", 66), 2);
        map.put(new Student("Lily", 99), 3);
        for (Student key : map.keySet()) {
            System.out.println(key);
        }
        System.out.println(map.get(new Student("Bob", 66))); // 2
    }
}

class Student {
    public String name;
    public int score;
    Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    public String toString() {
        return String.format("{%s: score=%d}", name, score);
    }
}

使用Properties HashTable的一种应用

Java专门为配置文件准备的Properties文件,默认扩展名是.properties,每行以key=value 表示,以#为注释:

Properties文件

# setting.properties

last_open_file=/data/hello.txt
auto_save_interval=60

读取Properties

String f = "setting.properties";
Properties props = new Properties();  //1. 创建Properties实例
props.load(new java.io.FileInputStream(f));  //2. 用load读取文件
String filepath = props.getProperty("last_open_file");  //3.用getProperty获取配置
String interval = props.getProperty("auto_save_interval", "120");

也可从classpath或jar包中读取:

props.load(getClass().getResourceAsStream("/common/setting.properties"));  //2. 用load从classpath或jar包中读取

也可从内存读取:

        String settings = "# test" + "\n" + "course=Java" + "\n" + "last_open_date=2019-08-07T12:35:01";
        ByteArrayInputStream input = new ByteArrayInputStream(settings.getBytes("UTF-8"));
        Properties props = new Properties();
        props.load(input);

        System.out.println("course: " + props.getProperty("course"));
        System.out.println("last_open_date: " + props.getProperty("last_open_date"));
        System.out.println("last_open_file: " + props.getProperty("last_open_file"));
        System.out.println("auto_save: " + props.getProperty("auto_save", "60"));

打印:

course: Java
last_open_date: 2019-08-07T12:35:01
last_open_file: null
auto_save: 60

从java9开始,读取中文时,能指定编码:

Properties props = new Properties();
props.load(new FileReader("settings.properties", StandardCharsets.UTF_8));

写入Properties

Properties props = new Properties();
props.setProperty("url", "http://www.liaoxuefeng.com");
props.setProperty("language", "Java");
props.store(new FileOutputStream("C:\\conf\\setting.properties"), "这是写入的properties注释");

Set 集合 只有key的Map

支持add、contains、remove操作:

        Set<String> set = new HashSet<>();
        System.out.println(set.add("abc")); // true
        System.out.println(set.add("xyz")); // true
        System.out.println(set.add("xyz")); // false,添加失败,因为元素已存在
        System.out.println(set.contains("xyz")); // true,元素存在
        System.out.println(set.contains("XYZ")); // false,元素不存在
        System.out.println(set.remove("hello")); // false,删除失败,因为元素不存在
        System.out.println(set.size()); // 2,一共两个元素

实现原理是,只存储key、不存储value的Map。

因为Map的key是不能重复的,所以能保证Set里元素不重复。

和Map一样,也需要正确实现equals()hashCode()方法,否则元素无法正确添加:

package oop_package.src.com.itranswarp.sample;

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Set<Person> set = new HashSet<>();
        Person p1 = new Person("Bob", "Dylan", 23);
        Person p2 = new Person("Bob", "Dylan", 23);
        System.out.println(set.add(p1));  //true
        System.out.println(set.add(p2));  //false
    }
}
class Person {
    String firstName;
    String lastName;
    int age;

    public Person(String firstName, String lastName, int age){
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName, age);
    }

    @Override
    public boolean equals(Object p) {
        if (p instanceof Person){
            Person person = (Person) p;
            return Objects.equals(this.lastName, person.lastName)
                    && Objects.equals(this.firstName, person.firstName)
                    && this.age == person.age;
        }
        return false;
    }
}

TreeSet

与TreeMap类似,是SortedSet接口的实现,是有序set。

定义自定义类型的TreeSet时必须传入一个Comparator对象用于指定排序规则。

package oop_package.src.com.itranswarp.sample;

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Message> received = Arrays.asList(
                new Message(1, "Hello!"),
                new Message(2, "发工资了吗?"),
                new Message(2, "发工资了吗?"),
                new Message(3, "去哪吃饭?"),
                new Message(3, "去哪吃饭?"),
                new Message(4, "Bye")
        );
        List<Message> displayMessages = process(received);
        for (Message message : displayMessages) {
            System.out.println(message.text);
        }
    }

    static List<Message> process(List<Message> received) {
        Set<Message> st = new TreeSet<>(new Comparator<Message>() {
            //<0,o1在前;
            //>0,o1在后;
            //=0,二者相等;
            @Override
            public int compare(Message o1, Message o2) {
                return o1.sequence - o2.sequence;  //排序函数编写技巧
        }});
        for (Message m : received){
            st.add(m);
        }
        return new ArrayList<>(st);
    }
}

class Message {
    public final int sequence;
    public final String text;
    public Message(int sequence, String text) {
        this.sequence = sequence;
        this.text = text;
    }
}

打印:

Hello!
发工资了吗?
去哪吃饭?
Bye

Queue 队列

Queue接口定义了两组方法:

int size():获取队列长度;
boolean add(E) / boolean offer(E):添加元素到队尾;
E remove() / E poll():获取队首元素并从队列中删除;
E element() / E peek():获取队首元素但并不从队列中删除。

前面一组会抛异常,后面一组不抛异常而是返回false或null。

使用LinkedList类即可,它既实现了List接口,又实现了Queue接口:

// 这是一个List:
List<String> list = new LinkedList<>();
// 这是一个Queue:
Queue<String> queue = new LinkedList<>();

面向抽象编程的妙处。

PriorityQueue 优先队列

PriorityQueue和Queue的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue调用remove()或poll()方法,返回的总是优先级最高的元素。

String类型的PriorityQueue,默认按字典序排序,字典序越靠前优先级越高:

        Queue<String> q = new PriorityQueue<>();
        // 添加3个元素到队列:
        q.offer("apple");
        q.offer("pear");
        q.offer("banana");
        System.out.println(q.poll()); // apple
        System.out.println(q.poll()); // banana
        System.out.println(q.poll()); // pear
        System.out.println(q.poll()); // null,因为队列为空

若是存放自定义类型,则需要实现Comparable接口:

public class Main {
    public static void main(String[] args) {
        Queue<User> q = new PriorityQueue<>(new UserComparator());
        // 添加3个元素到队列:
        q.offer(new User("Bob", "A1"));
        q.offer(new User("Alice", "A2"));
        q.offer(new User("Boss", "V1"));
        System.out.println(q.poll()); // Boss/V1
        System.out.println(q.poll()); // Bob/A1
        System.out.println(q.poll()); // Alice/A2
        System.out.println(q.poll()); // null,因为队列为空
    }
}

class UserComparator implements Comparator<User> {
    public int compare(User u1, User u2) {
        if (u1.number.charAt(0) == u2.number.charAt(0)) {
            // 如果两人的号都是A开头或者都是V开头,比较号的大小:
            return Integer.valueOf(u1.number.substring(1)) - Integer.valueOf(u2.number.substring(1));
        }
        if (u1.number.charAt(0) == 'V') {
            // u1的号码是V开头,优先级高:
            return -1;
        } else {
            return 1;
        }
    }
}

class User {
    public final String name;
    public final String number;

    public User(String name, String number) {
        this.name = name;
        this.number = number;
    }

    public String toString() {
        return name + "/" + number;
    }
}

Deque 双端队列

Deque提供了分别向队首队尾增删元素的方法:

addFirst(E e) / offerFirst(E e)
addLast(E e) / offerLast(E e)
E removeFirst() / E pollFirst()
E removeLast() / E pollLast()
E getFirst() / E peekFirst()
E getLast() / E peekLast()

它的实现类有ArrayDequeLinkedList

LinkedList yyds!

用deque时应调用xxxFirst()/xxxLast()以便与Queue的方法区分开。(Queue里的方法如poll它也都能调)

Stack 栈

用Deque实现Stack,把往双向队列的队头作为栈顶:

把元素压栈:push(E)/addFirst(E);
把栈顶的元素“弹出”:pop()/removeFirst();
取栈顶元素但不弹出:peek()/peekFirst()

把Deque作为Stack使用时,注意只调用push()/pop()/peek()方法,不要调用addFirst()/removeFirst()/peekFirst()方法,这样代码更加清晰。

栈的应用:函数调用

函数调用就是靠栈,JVM的做法是:

  1. 调用一个方法时,先将参数压栈,然后执行对应的方法;
  2. 当方法返回时,返回值压栈,调用方法通过出栈操作获得方法返回值。

栈的应用:中缀表达式

前缀表达式(例:+ 3 4 )
后缀表达式(例:3 4 + )
中缀表达式(例:3 + 4)

运算符在中间的表达式叫中缀表达式。中缀表达式不容易被电脑解析,但仍被许多程序语言使用,因为它符合人们的普遍用法。 与前缀或后缀记法不同的是,中缀记法中括号是必需的。 计算过程中必须用括号将操作符和对应的操作数括起来,用于指示运算的次序。

编译器会把中缀表达式转为后缀表达式如:1 + 2 * (9 - 5) => 1 2 9 5 - * +

计算后缀表达式用到了栈:

//首先准备一个空的栈:

│   │
│   │
│   │
│   │
│   │
│   │
│   │
│   │
└───┘
//依次扫描后缀表达式1 2 9 5 - * +,遇到数字1,就直接扔到栈里:

│   │
│   │
│   │
│   │
│   │
│   │
│   │
│ 1 │
└───┘
//紧接着,遇到数字2,9,5,也扔到栈里:

│   │
│ 5 │
│   │
│ 9 │
│   │
│ 2 │
│   │
│ 1 │
└───┘
//接下来遇到减号时,弹出栈顶的两个元素,并计算9-5=4,把结果4压栈:

│   │
│   │
│   │
│ 4 │
│   │
│ 2 │
│   │
│ 1 │
└───┘
//接下来遇到*号时,弹出栈顶的两个元素,并计算2*4=8,把结果8压栈:

│   │
│   │
│   │
│   │
│   │
│ 8 │
│   │
│ 1 │
└───┘
接下来遇到+号时,弹出栈顶的两个元素,并计算1+8=9,把结果9压栈:

│   │
│   │
│   │
│   │
│   │
│   │
│   │
│ 9 │
└───┘
//扫描结束后,没有更多的计算了,弹出栈的唯一一个元素,得到计算结果9。

例子:10进制转16进制

package oop_package.src.com.itranswarp.sample;

import java.util.*;

public class Main {
    public static void main(String[] args) {
        String hex = toHex(12500);
        if (hex.equalsIgnoreCase("30D4")) {
            System.out.println("测试通过");
        } else {
            System.out.println("测试失败");
        }
    }

    static String toHex(int n) {
        Deque<Character> st = new LinkedList<Character>();
        while (n != 0){
            st.push(int2hex(n % 16));
            n /= 16;
        }
        StringBuilder sb = new StringBuilder();
        while (!st.isEmpty()){
            sb.append(st.pop());
        }
        return sb.toString();
    }

    static char int2hex(int n){
        if (n < 10){
            return (char)(n + '0');
        }
        else{
            return (char)(n - 10 + 'A');
        }
    }
}

使用Iterator

Iterator是一种抽象的数据访问模型。使用Iterator模式进行迭代的好处有:

  • 对任何集合都采用同一种访问模型;
  • 调用者对集合内部结构一无所知;
  • 集合类返回的Iterator对象知道如何迭代。

Java提供了标准的迭代器模型,即集合类实现java.util.Iterable接口,返回java.util.Iterator实例。

自己实现倒序Iterator的例子:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        ReverseList<String> rlist = new ReverseList<>();
        rlist.add("Apple");
        rlist.add("Orange");
        rlist.add("Pear");
        for (String s : rlist) {
            System.out.println(s);
        }
    }
}

class ReverseList<T> implements Iterable<T> {

    private List<T> list = new ArrayList<>();

    public void add(T t) {
        list.add(t);
    }

    @Override
    public Iterator<T> iterator() {  //为了能用for each,集合类需实现Iterable接口,该接口要求返回一个Iterator对象;
        return new ReverseIterator(list.size());
    }

    class ReverseIterator implements Iterator<T> {  //具体Iterator对象
        int index;

        ReverseIterator(int index) {
            this.index = index;
        }

        @Override
        public boolean hasNext() {
            return index > 0;
        }

        @Override
        public T next() {
            index--;
            return ReverseList.this.list.get(index);  //ReverseList.this获得当前外部类的this引用,通过它可以访问ReverseList的所有字段和方法。直接用this,只能指向内部ReverseIterator。
        }
    }
}

Collections 工具类

位于java.util包中的工具类,提供了一系列静态方法,能更方便地操作各种集合。

创建集合

创建空ListList<T> emptyList()
创建空MapMap<K, V> emptyMap()
创建空SetSet<T> emptySet()

返回的空集合是不可变集合,无法向其中添加或删除元素。

创建一个单元素集合:

创建一个元素的ListList<T> singletonList(T o)
创建一个元素的MapMap<K, V> singletonMap(K key, V value)
创建一个元素的SetSet<T> singleton(T o)

把可变集合转为不可变集合,创建代理对象拦截修改方法实现的:

封装成不可变ListList<T> unmodifiableList(List<? extends T> list)
封装成不可变SetSet<T> unmodifiableSet(Set<? extends T> set)
封装成不可变MapMap<K, V> unmodifiableMap(Map<? extends K, ? extends V> m)

sort和shuffle

 Collections.sort(list);  //对list内部排序
 Collections.shuffle(list);  //对list内部随机打乱顺序

版权声明:本文为zhang35原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。