一.Collection
Java标准库自带的java.util包提供了集合类:Collection,它是除Map外所有其他集合类的根接口。Java的java.util包主要提供了以下三种类型的集合:
List:一种有序列表的集合,例如,按索引排列的Student的List;Set:一种保证没有重复元素的集合,例如,所有无重复名称的Student的Set;Map:一种通过键值(key-value)查找的映射表集合,例如,根据Student的name查找对应Student的Map。
Java集合的设计有几个特点:一是实现了接口和实现类相分离,例如,有序表的接口是List,具体的实现类有ArrayList,LinkedList等,二是支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素,例如:
List<String> list = new ArrayList<>(); // 只能放入String类型
最后,Java访问集合总是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。
由于Java的集合设计非常久远,中间经历过大规模改进,我们要注意到有一小部分集合类是遗留类,不应该继续使用:
Hashtable:一种线程安全的Map实现;Vector:一种线程安全的List实现;Stack:基于Vector实现的LIFO的栈。
还有一小部分接口是遗留接口,也不应该继续使用:
Enumeration<E>:已被Iterator<E>取代。
Java的集合类定义在java.util包中,支持泛型,主要提供了3种集合类,包括List,Set和Map。Java集合使用统一的Iterator遍历,尽量不要使用遗留接口。
二.List

1.创建List
List<String> list = new ArrayList<>();
除了使用ArrayList和LinkedList,我们还可以通过List接口提供的of()方法,根据给定元素快速创建List:
List<Integer> list = List.of(1, 2, 5);
但是List.of()方法不接受null值,如果传入null,会抛出NullPointerException异常。
2.遍历List
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = List.of("apple", "pear", "banana");
for (String s : list) {
System.out.println(s);
}
}
}
import java.util.*;
public class Main {
public static void main(String[] args) {
// 构造从start到end的序列:
final int start = 10;
final int end = 20;
List<Integer> list = new ArrayList<>();
for (int i = start; i <= end; i++) {
list.add(i);
}
// 洗牌算法shuffle可以随机交换List中的元素位置:
Collections.shuffle(list);
// 随机删除List中的一个元素:
int removed = list.remove((int) (Math.random() * list.size()));
int found = findMissingNumber(start, end, list);
System.out.println(list.toString());
System.out.println("missing number: " + found);
System.out.println(removed == found ? "测试成功" : "测试失败");
}
static int findMissingNumber(int start, int end, List<Integer> list) {
final int start1 = 10;
final int end1 = 20;
List<Integer> list1 = new ArrayList<>();
for (int i = start1; i <= end1; i++) {
list1.add(i);
}
for(Integer s : list1){
if (!list.contains(s.intValue())) {
return s.intValue();
}
}
return 0;
}
}
三.equals
List内部并不是通过==判断两个元素是否相等,而是使用equals()方法判断两个元素是否相等。
要正确使用List的contains()、indexOf()这些方法,放入的实例必须正确覆写equals()方法,否则,放进去的实例,查找不到。我们之所以能正常放入String、Integer这些对象,是因为Java标准库定义的这些类已经正确实现了equals()方法。
public class Main {
public static void main(String[] args) {
List<Person> list = List.of(
new Person("Xiao Ming"),
new Person("Xiao Hong"),
new Person("Bob")
);
System.out.println(list.contains(new Person("Bob"))); // false
}
}
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
//虽然放入了new Person("Bob"),但是用另一个new Person("Bob")查询不到,原因就是Person类没有覆写equals()方法。
编写equals
equals()方法的正确编写方法:
- 先确定实例“相等”的逻辑,即哪些字段相等,就认为实例相等;
- 用
instanceof判断传入的待比较的Object是不是当前类型,如果是,继续比较,否则,返回false; - 对引用类型用
Objects.equals()比较,对基本类型直接用==比较。
使用Objects.equals()比较两个引用类型是否相等的目的是省去了判断null的麻烦。两个引用类型都是null时它们也是相等的。
如果不调用List的contains()、indexOf()这些方法,那么放入的元素就不需要实现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;
}
import java.util.Objects;
public class Main {
public static void main(String[] args) {
// List<Person> list = List.of(
// new Person("Xiao", "Ming", 18),
// new Person("Xiao", "Hong", 25),
// new Person("Bob", "Smith", 20)
// );
List<Person> list = new ArrayList<>();
list.add(new Person("Xiao", "Ming", 18));
list.add(new Person("Xiao", "Hong", 25));
list.add(new Person("Bob", "Smith", 20));
boolean exist = list.contains(new Person("Bob", "Smith", 20));
System.out.println(exist ? "测试成功!" : "测试失败!");
}
}
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;
}
public boolean equals(Object o) {
if (o instanceof Person) {
Person p = (Person) o;
return Objects.equals(this.firstName, p.firstName) &&Objects.equals(this.lastName, p.lastName)&& this.age == p.age;
}
return false;
}
}
在
List中查找元素时,List的实现类通过元素的equals()方法比较两个元素是否相等,因此,放入的元素必须正确覆写equals()方法,Java标准库提供的String、Integer等已经覆写了equals()方法;编写
equals()方法可借助Objects.equals()判断。如果不在
List中查找元素,就不必覆写equals()方法。
四.Map
Map<K, V>是一种键-值映射表,当我们调用put(K key, V value)方法时,就把key和value做了映射并放入Map。当我们调用V get(K key)时,就可以通过key获取到对应的value。如果key不存在,则返回null。和List类似,Map也是一个接口,最常用的实现类是HashMap。
如果只是想查询某个key是否存在,可以调用boolean containsKey(K key)方法。
始终牢记:Map中不存在重复的key,因为放入相同的key,只会把原有的key-value对应的value给替换掉。
遍历Map
对Map来说,要遍历key可以使用for each循环遍历Map实例的keySet()方法返回的Set集合,它包含不重复的key的集合:
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 123);
map.put("pear", 456);
map.put("banana", 789);
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}
}
}
class Students {
List<Student> list;//定义了一个Student的List,名为list,可能十分巨大
Map<String, Integer> cache;//定义了一个Map,key为String类型,value为Integer类型
//作为缓存来存放那些被查询频率很高的学生,比如成绩差爱惹事的学生,下次查询直接从这里面找就会很快
Students(List<Student> list) {
this.list = list;
cache = new HashMap<>();
}
int getScore(String name) {
// a.有个娃犯事了,先看看重点关注对象(cache)里有没有这娃
Integer score = this.cache.get(name);//试图从cache中查找这娃对应的成绩
if (score == null) {//b.如果成绩为null,说明没有重点关注他(cache里没有他的名字)
// TODO:
score = findInList(name);//c.我们再在学校的超级学生名册里来查找一下
if (score!= null) {//d.如果有成绩记录,score不为null,看来确实是我们学校的学生
cache.put(name, score);//e.立即把他放到重点关注对象里来,下次犯事了很快就能查到他所有信息
}
}
return score == null ? -1 : score.intValue();//f.这里不太好理解,为什么会再次判断score是否为null,我们来分析一下:
//这里有两种情况:
//1.当在执行步骤b的判断时如果score!=null,说明重点关注对象里有他,直接跳过if循环,return时score==null为假,返回他的成绩
//2.当在执行步骤b的判断时如果score==null,if循环里的程序会被执行,这里步骤c的语句 score = findInList(name)会重新对score进行赋值
//根据下面findInList(String name)方法我们知道它可能返回null,也可能返回分数,所以最后return的时候会再次判断score是否为null
//如果score为null则返回-1,表示这个学生不在超级学生名单里,不是学校里的娃
}
Integer findInList(String name) {
for (var ss : this.list) {
if (ss.name.equals(name)) {
return ss.score;
}
}
return null;//表示查询的人不在名单内
}
}
Map是一种映射表,可以通过key快速查找value。可以通过
for each遍历keySet(),也可以通过for each遍历entrySet(),直接获取key-value。最常用的一种
Map实现是HashMap。
五.equals和hashCode
在Map的内部,对key做比较是通过equals()实现的,这一点和List查找元素需要正确覆写equals()是一样的,即正确使用Map必须保证:作为key的对象必须正确覆写equals()方法。
我们经常使用String作为key,因为String已经正确覆写了equals()方法。但如果我们放入的key是一个自己写的类,就必须保证正确覆写了equals()方法。
正确使用Map必须保证:
-
作为
key的对象必须正确覆写equals()方法,相等的两个key实例调用equals()必须返回true; -
作为
key的对象还必须正确覆写hashCode()方法,且hashCode()方法要严格遵循以下规范:
- 如果两个对象相等,则两个对象的
hashCode()必须相等; - 如果两个对象不相等,则两个对象的
hashCode()尽量不要相等。
public class Person {
String firstName;
String lastName;
int age;
@Override
int hashCode() {
int h = 0;
h = 31 * h + firstName.hashCode();
h = 31 * h + lastName.hashCode();
h = 31 * h + age;
return h;
}
//编写equals()和hashCode()遵循的原则是:equals()用到的用于比较的每一个字段,都必须在hashCode()中用于计算;equals()中没有使用到的字段,绝不可放在hashCode()中计算。
int hashCode() {
return Objects.hash(firstName, lastName, age);
}
}
//引用类型使用Objects.equals()比较,基本类型使用==比较。
package test.MapDemo;
import java.util.*;
public class equalsDemo2 {
public static void main(String[] args) {
Person p1 = new Person("zhang",12);
Map<Person,Integer> map = new HashMap<>();
map.put(p1,123);
Person p2 = new Person("zhang",12);
System.out.println(p1==p2);
System.out.println(p1.equals(p2));
System.out.println(map.get(p1));
System.out.println(map.get(p2));
}
}
class Person{
public String name;
public int age;
public Person(){}
public Person(String n,int a){
name = n;
age = a;
}
@Override public boolean equals(Object obj) {
if(obj instanceof Person){
Person p = (Person) obj;
return Objects.equals(this.name,p.name) && this.age == p.age;
}
return false;
}
public int hashCode(){
return Objects.hash(name,age);
}
}
false
true
123
123
p1和p2都是Person类的对象,虽然地址值不一样,但是内容相同
所以map.get()获取的value值是相同的
要正确使用
HashMap,作为key的类必须正确覆写equals()和hashCode()方法;一个类如果覆写了
equals(),就必须覆写hashCode(),并且覆写规则是:
如果
equals()返回true,则hashCode()返回值必须相等;如果
equals()返回false,则hashCode()返回值尽量不要相等。实现
hashCode()方法可以通过Objects.hashCode()辅助方法实现
六.set
Map用于存储key-value的映射,对于充当key的对象,是不能重复的,并且,不但需要正确覆写equals()方法,还要正确覆写hashCode()方法。
如果我们只需要存储不重复的key,并不需要存储映射的value,那么就可以使用Set。
Set用于存储不重复的元素集合,它主要提供以下几个方法:
- 将元素添加进
Set<E>:boolean add(E e) - 将元素从
Set<E>删除:boolean remove(Object e) - 判断是否包含元素:
boolean contains(Object e)
Set实际上相当于只存储key、不存储value的Map。我们经常用Set用于去除重复元素。
因为放入Set的元素和Map的key类似,都要正确实现equals()和hashCode()方法,否则该元素无法正确地放入Set。
Set接口并不保证有序,而SortedSet接口则保证元素是有序的:
HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口;TreeSet是有序的,因为它实现了SortedSet接口。
使用TreeSet和使用TreeMap的要求一样,添加的元素必须正确实现Comparable接口,如果没有实现Comparable接口,那么创建TreeSet时必须传入一个Comparator对象。
在聊天软件中,发送方发送消息时,遇到网络超时后就会自动重发,因此,接收方可能会收到重复的消息,在显示给用户看的时候,需要首先去重。请练习使用Set去除重复的消息:
public class Main {
public static void main(String[] args) {
// List<Message> received = List.of(
// new Message(1, "Hello!"),
// new Message(2, "发工资了吗?"),
// new Message(2, "发工资了吗?"),
// new Message(3, "去哪吃饭?"),
// new Message(3, "去哪吃饭?"),
// new Message(4, "Bye")
// );
List<Message> received = new ArrayList<>();
received.add(new Message(1, "Hello!"));
received.add(new Message(2, "发工资了吗?"));
received.add(new Message(2, "发工资了吗?"));
received.add(new Message(3, "去哪吃饭?"));
received.add(new Message(3, "去哪吃饭?"));
received.add(new Message(4, "Bye"));
List<Message> displayMessages = process(received);
for (Message message : displayMessages) {
System.out.println(message.text);
}
}
//方法一:用到sequence来判断,而做这个判断最好的就是用Set来判断要加到List中的Message对象的sequence字段是不是已经存在了
static List<Message> process(List<Message> received) {
// TODO: 按sequence去除重复消息
Set<Integer> map = new HashSet<>();
List<Message> receiveds = new ArrayList<>();
for(Message message: received){
if(!map.contains(message.sequence)){
map.add(message.sequence);
receiveds.add(message);
}
}
return receiveds;
}
//方法二:重写message对象的equals和hashMap使message类可以满足放入set,然后用以message泛型的set就可以自动去重。
//用list和set的相互转换
static List<Message> process(List<Message> received) {
HashSet<Message> messageSet = new HashSet<>(received);
ArrayList<Message> messageList = new ArrayList<>(messageSet);
return messageList;
}
//写在message类中
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Message message = (Message) o;
return sequence == message.sequence && Objects.equals(text, message.text);
}
@Override
public int hashCode() {
return Objects.hash(sequence, text);
}
}
class Message {
public final int sequence;
public final String text;
public Message(int sequence, String text) {
this.sequence = sequence;
this.text = text;
}
}
七.Queue
Queue实际上是实现了一个先进先出(FIFO:First In First Out)的有序表.

注意:不要把null添加到队列中,否则poll()方法返回null时,很难确定是取到了null元素还是队列为空。(调用poll()方法来取出队首元素,当获取失败时,它不会抛异常,而是返回null)
队列
Queue实现了一个先进先出(FIFO)的数据结构:
- 通过
add()/offer()方法将元素添加到队尾;- 通过
remove()/poll()从队首获取元素并删除;- 通过
element()/peek()从队首获取元素但不删除。要避免把
null添加到队列。

1597

被折叠的 条评论
为什么被折叠?



