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

本文详细介绍了Java集合框架中的List、Map、Set和Queue,包括ArrayList、LinkedList、Map的实现原理、哈希冲突解决、TreeMap、PriorityQueue等,以及如何遍历和操作这些集合。此外,还讲解了 equals() 和 hashcode() 方法的重要性,以及使用Iterator和Collections工具类的方法。
部署运行你感兴趣的模型镜像

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内部随机打乱顺序

您可能感兴趣的与本文相关的镜像

LobeChat

LobeChat

AI应用

LobeChat 是一个开源、高性能的聊天机器人框架。支持语音合成、多模态和可扩展插件系统。支持一键式免费部署私人ChatGPT/LLM 网络应用程序。

廖雪峰 Java 教程 Java教程 Java快速入门 Java简介 安装JDK 第一个Java程序 Java代码助手 使用IDE 使用IDE练习插件 Java程序基础 Java程序基本结构 变量和数据类型 整数运算 浮点数运算 布尔运算 字符和字符串 数组类型 流程控制 输入和输出 if判断 switch多重选择 while循环 do while循环 for循环 break和continue 数组操作 遍历数组 数组排序 多维数组 命令行参数 面向对象编程 面向对象基础 方法 构造方法 方法重载 继承 多态 抽象类 接口 静态字段和静态方法 包 作用域 classpath和jar 模块 Java核心类 字符串和编码 StringBuilder StringJoiner 包装类型 JavaBean 枚举类 BigInteger BigDecimal 常用工具类 异常处理 Java的异常 捕获异常 抛出异常 自定义异常 使用断言 使用JDK Logging 使用Commons Logging 使用Log4j 使用SLF4J和Logback 反射 Class类 访问字段 调用方法 调用构造方法 获取继承关系 动态代理 注解 使用注解 定义注解 处理注解 泛型 什么是泛型 使用泛型 编写泛型 擦拭法 extends通配符 super通配符 泛型和反射 集合 Java集合简介 使用List 编写equals方法 使用Map 编写equals和hashCode 使用EnumMap 使用TreeMap 使用Properties 使用Set 使用Queue 使用PriorityQueue 使用Deque 使用Stack 使用Iterator 使用Collections IO File对象 InputStream OutputStream Filter模式 操作Zip 读取classpath资源 序列化 Reader Writer PrintStream和PrintWriter 日期与时间 基本概念 Date和Calendar LocalDateTime ZonedDateTime DateTimeFormatter Instant 最佳实践 单元测试 编写JUnit测试 使用Fixture 异常测试 条件测试 参数化测试
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值