
Collection集合
Collection接口
集合与数组
使用数组的局限性
如果要存放多个对象,可以使用数组,但是数组有局限性,比如 声明长度是10的数组,不用的数组就浪费了超过10的个数,又放不下。
ArrayList存放对象
为了解决数组的局限性,引入容器类的概念。 最常见的容器类就是ArrayList
-
容器的容量"capacity"会随着对象的增加,自动增长
-
只需要不断往容器里增加元素即可,不用担心会出现数组的边界问题。
集合框架
List
特点:有索引、可以存储重复元素,存储有序。
Vector
集合实现类。被ArrayList取代ArrayList
集合实现类:底层时数组实现的,查询快、增删慢LinkedList
集合实现类:底层时链表实现的,查询慢、增删快
Set
特点:无索引、不可存储重复元素、存储无序
HashSet
集合实现类:底层是哈希表+(红黑树)实现的,无索引、不可存储重复元素、存储无序LinkedHashSet
集合实现类:底层是哈希表+链表实现的,无索引、不可存储重复元素、存取无序TreeSet
集合实现类:底层是二叉树实现的,一般用于排序
常用方法
boolean add(E e);
:向集合中添加元素boolean remove(E e);
:删除集合中的某个元素void clear();
:清空集合中所有元素boolean contains(E e);
:判断集合中是否包含某个元素boolean isEmpty();
:判断集合中是否为空int size();
:获取集合长度Object[] toArray();
:将集合转成一个数组
Iterator迭代器
Iterator接口
在程序开发过程中,经常需要遍历集合中的所有元素,
迭代:即Collection集合元素的通用获取方式,在去元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续判断,如果还有就再取出来。一直把集合中的多有元素全部取出,这种方式就叫迭代
常用方法:
- public boolean hasNext(); 如果集合中下一个仍有元素,返回True
- public E next(); 返回迭代的下一个元素
使用步骤
-
使用集合中的方法iterator()获取迭代器的实现类,使用Iterator接口接收(多态)
-
使用Iterator接口中的方法hasnext判断还有没有下一个元素
-
使用Iterator接口中的方法next取出集合中的下一个元素
示例
public class Demo01Iterator {
public static void main(String[] args) {
// 创建一个集合对象
Collection<String> coll = new ArrayList<>();
// 往集合中添加元素
coll.add("张三");
coll.add("李四");
coll.add("王五");
coll.add("赵六");
/*
* 1 使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
* 注意:
* Iterator<E> 接口也是有泛型的,迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型
* */
// 多态方式创建一个集合的迭代器实现类对象it
Iterator<String> it = coll.iterator();
// 2 使用Iterator接口中的hasNext判断还有没有下一个元素
boolean b = it.hasNext();
System.out.println(b); // true
// 3 使用Iterator接口中的方法next取出集合中的下一个元素
String s = it.next();
System.out.println(s);
System.out.println("=========循环输出剩余元素:========");
while(it.hasNext()){
s = it.next();
System.out.println(s);
}
}
}
增强for循环
jdk1.5开始有的高级for循环,专门来遍历数组和集合的,他的内部原理就是个Iterator迭代器,所以遍历过程中,不能对集合中的元素进行增删操作。
格式:
for(元素的数据类型 变量 : Collection集合or数组){
// 操作规则
}
练习1 遍历数组
public class ForeachDemo1 {
public static void main(String[] args) {
int[] arr = {3,2,4,5,1};
// 使用增强for循环遍历数组
for (int a : arr) {
System.out.println(a);
}
}
}
练习2 遍历集合
public class ForeachDemo2 {
public static void main(String[] args) {
// 创建一个集合对象
Collection<String> coll = new ArrayList<>();
// 往集合中添加元素
coll.add("张三");
coll.add("李四");
coll.add("王五");
coll.add("赵六");
// 使用增强for循环遍历数组
for (String e : coll) {
System.out.println(e);
}
}
}
泛型
可以看作一种未知的类型,当我们不知道使用什么类型的时候,可以使用泛型
不指定泛型容易报错
泛型的定义和使用
定义和使用含有泛型的类
/*
定义一个含有泛型的类
*/
public class GenericClass<E> {
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
private E name;
}
在main中调用创建和使用
public class Demo01Main {
public static void main(String[] args) {
// 不写泛型默认是Object类型
GenericClass gc = new GenericClass();
gc.setName("啊哈");
Object obj = gc.getName();
// 创建GenericClass对象,泛型使用String类型
GenericClass<String> gc2 = new GenericClass<>();
gc2.setName("啊哈");
String name = gc2.getName();
System.out.println(name);
}
}
含有泛型的方法
定义格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
示例:
/*
* 定义含有泛型的方法:泛型定义在方法的修饰符和返回值类型之间
* 格式:
* 修饰符 <泛型> 返回值类型 方法名(参数){ }
* 含有泛型的方法,在调用方法的时候确定泛型的数据类型
* 传递什么类型的参数,泛型就是什么类型
* */
public class GenericMethod {
//定义一个含有泛型的普通方法
public <M> void method01(M m){
System.out.println(m);
}
//定义一个含有泛型的静态方法
public static <S> void method02(S s){
System.out.println(s);
}
}
public class GenericMethodMain {
public static void main(String[] args) {
// 创建GenericMethod的对象
GenericMethod gm = new GenericMethod();
// 调用含有泛型方法的method01
gm.method01("asd");
gm.method01(2);
gm.method01(true);
gm.method02("静态方法不建议创建对象使用");
// 静态方法,通过 【类名.静态方法名(参数)】 可直接使用
GenericMethod.method02("通过类名调用静态方法");
GenericMethod.method02(3);
GenericMethod.method02(false);
}
}
含有泛型的接口
主要有两种
-
首先定义一个含有泛型的接口
// 定义一个含有泛型的接口 public interface GenericInterface<I> { public abstract void method(I i); // 一个抽象方法 }
-
定义接口的实现类,有两种方式:
第一种:定义接口实现类接口的实现类,在实现类中指定接口的泛型,即在实现类中先确定好接口的泛型,如字符串类型
// GenericInterface接口的实现类,重写接口中方法,泛型在实现类中指定 public class GenericInterfaceImpl_1 implements GenericInterface<String> { @Override public void method(String s) { System.out.println(s); } }
第二种:定义接口实现类接口的实现类,在实现类中也先不确定接口的泛型,在创建实现类对象的时候再指定。灵活性更好
// GenericInterface接口的实现类,重写接口中方法 public class GenericInterfaceImpl_2<I> implements GenericInterface<I> { @Override public void method(I i) { System.out.println(i); } }
-
main中使用和调用
public class GenericInterfaceMain { public static void main(String[] args) { // 创建第一种GenericInterfaceImpl_1对象 GenericInterfaceImpl_1 gi1 = new GenericInterfaceImpl(); gi1.method("字符串"); // 因为实现类中指定了为String类型,所以此次参数必须是String类型的 // 第二种 GenericInterfaceImpl_2<Integer> gi2_1 = new GenericInterfaceImpl<>(); // 指定泛型为Integer gi2_1.method("haha"); GenericInterfaceImpl_2<String> gi2_2 = new GenericInterfaceImpl<>(); // 指定泛型为String gi2_2.method(23); GenericInterfaceImpl_2<Boolean> gi2_3 = new GenericInterfaceImpl<>(); // 指定泛型为Boolean gi2_3.method(true); } }
泛型通配符
当使用泛型胡接口时,传递的数据中,泛型类型不确定,可以通过通配符 <?>
表示,但是一旦以用泛型的通配符后,只能使用Object类中共性方法,集合中的元素自身方法无法使用。
通配符的基本使用
泛型通配符:不知道使用什么类型来接收的时候,此时可用 ? , ? 表示未知通配符。
此时只能接受数据,不能往该集合中存储数据
示例:
public static void main(String[] args){
Collection<Integer> list1 = new ArrayList<Integer>();
getElement(list1);
Collection<String> list2 = new ArrayList<String>();
getElement(list2);
}
public static void getElement(Collection<?> coll){ } // ? 代表可以接受任意类型
通配符的高级使用----受限泛型
- 泛型的上线限定:? extends E 代表使用泛型只能是E类型及其子类
- 泛型的下线限定:? super E 代表使用泛型只能是E类型及其父类
public static void getElement1(Collection<? extends Number> coll){ } // 泛型的上线限定:此时的泛型?,必须是Number类型或是Number类型的子类
public static void getElement2(Collection<? super Number> coll){ } // 泛型的上线限定:此时的泛型?,必须是Number类型或是Number类型的父类
集合案例的综合案例-斗地主
案例介绍
按照斗地主的规则,完成洗牌发牌的动作。
具体规则:使用54张牌,打乱顺序,三人参与游戏,三人交替摸牌,没人17张牌,最后三张作为底牌
案例分析
-
准备牌:
派可以设计为 ArrayList , 每个字符串为一张牌。
每张牌有花色数字两部分组成,我们可以使用花色集合与数字集合嵌套迭代完成每张牌的组装。
牌有Collections类的shuffle方法进行随机打乱排序。
-
发牌
将每个人以及底牌设计为ArrayList ,将最后三张牌直接存放于底牌,剩余牌通过对3取模依次发牌。
-
看牌
直接打印每个集合
代码示例
import java.util.ArrayList;
import java.util.Collections;
public class DouDiZhu {
public static void main(String[] args) {
// 1 准备牌
// 定义一个存储54张牌的ArrayList集合,泛型使用String
ArrayList<String> poker = new ArrayList<>();
String[] colors = {"♥","♣","♦","♠"};
String[] numbers = {"2","A","K","Q","J","10","9","8","7","6","5","4","3"};
// 先把大王小王存储到poker中
poker.add("大王");
poker.add("小王");
// 循环嵌套遍历两个数组,组装52张牌
for (String number : numbers) {
for (String color : colors) {
poker.add(color + number);
}
}
// 2 洗牌
// 使用集合工具类 Collections中的方法
// static void shuffle(List<?> list) 使用默认随机源对指定列表进行置换
Collections.shuffle(poker);
// 3 洗牌
// 定义4个集合,存储玩家的牌和底牌
ArrayList<String> player1 = new ArrayList<>();
ArrayList<String> player2 = new ArrayList<>();
ArrayList<String> player3 = new ArrayList<>();
ArrayList<String> dipai = new ArrayList<>();
/*
* 遍历poker集合,获取每张牌
* 使用poker集合的索引%3给3个玩家轮流发牌
* 剩余3张给底牌
* 注意:
* 先判断底牌(i>=52),否则牌就没了
* */
for (int i = 0; i < poker.size(); i++) {
String p = poker.get(i);
if(i>=51){
dipai.add(p);
}else if(i%3==0){
player1.add(p);
}else if(i%3==1){
player2.add(p);
}else if(i%3==2){
player3.add(p);
}
}
// 4 看牌
System.out.println("玩家1的牌:" + player1);
System.out.println("玩家2的牌:" + player2);
System.out.println("玩家3的牌:" + player3);
System.out.println("底牌:" + dipai);
}
}
常见的Collection集合
数据存储的常用结构有:栈、队列、数组、链表(单链表、双链表、环形链表)、树(二叉树、B+树、红黑树…)、堆、散列表、图
List接口
java.util.List接口
List接口的特点:
- 有序的集合 ,存储元素和取出元素的顺序是一致的
- 有索引,包含了一些带索引的方法
- 允许存储重复的元素
List接口常用方法
- public void add(int index, E element):将指定元素element插入指定index位置。
- public E get(int index):返回集合中指定位置上的元素
- public E remove(int index):移除列表指定位置上的元素,并将被移除的元素返回
- public E set(int index, E element):修改集合指定位置index上的值为element。返回更新前的元素
代码示例
public class ListDemo01 {
public static void main(String[] args) {
// 多态的方式创建一个List集合对象
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
// 在 张三 和 李四 之间加一个
list.add(1,"新添加");
String g = list.get(2);
String rm = list.remove(2); // 删除索引为2的元素,并将这个元素保存至rm变量中
String a = list.set(3,"小二"); // 将索引为3个元素修改为“小二”,原元素被返回给a
// List集合遍历的3中方式
// 1 索引遍历
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
}
// 2 使用迭代器访问
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
}
// 3 foreach 遍历
for (String s : list) {
System.out.println(s);
}
}
}
List接口的子类
ArrayList集合
java.util.ArrayList
集合数据存储的结构是数组结构,元素增删慢,查找快,
LinkedList集合
java.util.LinkedList
集合数据存储的结构是链表结构,元素查询慢、增删快。是一个双向链表
注意:使用LinkedList集合特有的方法,不能使用多态。
常用方法
- public void
addFirst(E e)
:将指定元素添加在此列表的开头 - public void
addLast(E e)
:将指定元素添加在此列表的结尾。 - public E
getFirst()
:返回列表的第一个元素。// 如果列表为空时调用此方法会报错 - public E
getLast()
:返回列表的最后一个元素。// 如果列表为空时调用此方法会报错 - public E
removeFirst()
:移除并返回列表的第一个元素。 - public E
removeLast()
:移除并返回列表的最后一个元素 - public E
pop()
:从此列表所表示的堆栈处弹出一个元素。 // 等价于removeFirst(E e) 方法 - public void
push(E e)
:将元素推入此列表所表示的堆栈。 - public boolean
isEmpty()
:判断列表是否为空。
public static void main(String[] args) {
LinkedList<String> linked = new LinkedList<>();
linked.add("a");
linked.add("b");
linked.add("c");
linked.addFirst("dd"); // 等效于push(E e) 方法,在开头处添加元素
linked.addLast("ee"); // 等效于add(E e) 方法,在末尾处添加元素
System.out.println(linked); // [dd, a, b, c, ee]
if(!linked.isEmpty()){
// 如果列表为空时,调用下面几个方法都会报错
String first = linked.getFirst();
String last = linked.getLast();
String rm_first = linked.removeFirst(); // 等价于 pop(E e) 方法
String rm_last = linked.removeLast();
}
linked.clear(); // 清空列表
}
Set接口
java.util.Set
一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2)
的元素对 e1
和 e2
,并且最多包含一个 null 元素。
HashSet集合
java.util.HashSet集合特点:
- 不允许存储重复的元素
- 没有索引,没有带索引的方法,也不能使用普通的for循环遍历,可以使用迭代器或增强for遍历
- 是一个无序的集合,存储数据和取出数据的顺序可能不一致
- 底层是一个哈希表结构(查询数据非常快)底层是一个哈希表(数组+链表/红黑树)
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
// 使用迭代器遍历
Iterator<Integer> it = set.iterator();
while(it.hasNext()){
Integer n = it.next();
System.out.println(n);
}
// 使用增强for循环遍历集合
for (Integer i : set) {
System.out.println(i);
}
}
HashSet集合存储数据的结构(哈希表)
先了解一个名词:哈希值
哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到地址值,不是数据实际存储的物理地址)
在Object类中有一个方法,可以获取对象的哈希值:int hashCode()
返回该对象的哈希值
HashSe存储元素不重复的原理
Set集合在调用add()方法的时候,add方法会调用元素的hashCode方法和equals方法,判断元素是否重复。
举例说明:
-
Set.add(s1):(假设s1=“abc”)
- add方法会先调用hashCode方法,计算字符串 “abc” 的哈希值,如为:96354,
- 在集合中找有没有96354这个哈希值,发现没有,
- 就把 s1 存储到集合中。
-
Set.add(s2):(假设s2=“abc”)
- add方法会先调用hashCode方法,计算字符串 “abc” 的哈希值,如为:96354,
- 在集合中找有没有96354这个哈希值,发现有(哈希冲突)
- s2 会再调用equals方法和哈希值相同的元素进行比较,s2.equals(s1),返回true
- 两个元组的哈希值相同,equals方法返回true,认定两个元素相同。
- 就不会把s2存储到集合中。
HashSet存储自定义类型元素
例如自定义Person类,对于同名同年龄的人,视为同一个人,只能存储一次。在Person类中对hashCode和equals方法
@Override
public boolean equals(Object o){
if(this==o) return true;
if(o==null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public boolean hashCode(Object o){
return Objects.hash(name, age);
}
LinkedHashSet集合
java.util.LinkedHashSet
public class LinkedHashSet<E> extends HashSet<E>implements Set<E>, Cloneable, Serializable
继承了HashSet
LinkedHashSet的特点
- 底层是一个哈希表(数组+链表/红黑树)+ 链表。多的这个链表用于记录元素的存储顺序,保证元素有序(什么顺序存储,就是什么顺序取出)
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("aaa");
set.add("sss");
set.add("ddd");
set.add("fff");
System.out.println(set); // 结果可能是:[aaa, ddd, sss, fff]
LinkedHashSet<String> linked = new LinkedHashSet<>();
linked.add("aaa");
linked.add("sss");
linked.add("ddd");
linked.add("fff");
System.out.println(linked); // 结果是:[aaa, sss, ddd, fff]
}
Collections
java.util.Collections是集合工具类,用于对集合进行操作,部分方法如下:
- public static boolean
addAll(Collection<T> c, T... elements)
:往集合中添加一些元素 - public static void
shuffle(List<?> list)
:打乱孙旭,打乱集合顺序。 - public static void
sort(List<T> list)
:将集合中的元素按照默认规则排序。 - public static void
sort(List<T> list, Comparator<? super T> )
:将集合中的元素按照指定规则排序。
代码示例
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
/*
// 逐个添加
list.add(3);
list.add(2);
list.add(5);
list.add(1);
*/
// 一次性添加
Collections.addAll(list, 3, 2, 5, 1);
System.out.println(list); // [3, 2, 5, 1]
// 将list集合打乱
Collections.shuffle(list);
System.out.println(list); // 可能是:[3, 5, 1, 2]
// 对集合排序
Collections.sort(list);
System.out.println(list); // [1, 2, 3, 5]
/***************************************************
如何对自定义类进行排序?
法1:让自定义类实现Comparable接口,并重写compareTo方法
法2:Comparator:调这个排序方法:public static <T> void sort(List<T> list, Comparator<? super T> )
用Comparable和用Comparator的区别
用Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo方法。
用Comparator:相当于找个第三方的裁判,比较两个
*/
ArrayList<Person> list2 = new ArrayList<>();
list2.add(new Person("张三",21));
list2.add(new Person("李四",25));
list2.add(new Person("王五",18));
System.out.println(list2); // [Person{name='张三', age=21}, Person{name='李四', age=25}, Person{name='王五', age=18}]
// ************
// 法1:让自定义类实现Comparable接口,并重写compareTo方法
// 按照Person类中重写的compareTo方法规则进行排序
// Collections.sort(list2);
// System.out.println(list2); // [Person{name='李四', age=25}, Person{name='张三', age=21},Person{name='王五', age=18}]
// *************
// 法2:用Comparator:调这个排序方法:public static <T> void sort(List<T> list, Comparator<? super T> )
// Comparator的排序规则:
// o1 - o2 : 升序,反之降序
// 【先将Person类中实现Comparable接口及重写的compareTo方法注销掉】
Collections.sort(list2, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
int result = o1.getAge() - o2.getAge(); // 年龄升序,反之降序
// 如果两人年龄相同,再使用姓名的第一个字比较
if(result==0){
result = o1.getName().charAt(0) - o2.getName().charAt(0);
}
return result;
}
});
System.out.println(list2); // [Person{name='王五', age=18}, Person{name='张三', age=21}, Person{name='李四', age=25}]
}
注意在Person类中要
- 让Person实现Comparable接口:
public class Person implements Comparable<Person> { }
- Person类要重写compareTo方法。
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 重写排序规则,比如按照年龄排序
@Override
public int compareTo(Person o) {
// return 0; // 认为元素是相同的
// 自定义比较的规则,比较两个人的年龄(this 和 参数Person)
// return this.age - o.age; // 按年龄升序
return o.age - this.age; // 按年龄降序
}
}
可变参数
需求:方法需要接收的参数不知道具体是多少,比如计算参数列表的和,有时候传过来的是两个数,有时候传过来的是5个数。
格式:修饰符 返回值类型 方法名(数据类型...变量名){}
public static void main(String[] args) {
int s1 = mySum(3,4,6); // 13
int s2 = mySum(5,6); // 11
int s3 = mySum(1,2,3,4,5,6); // 21
}
public static int mySum(int... arr){
int sum = 0;
for (int i : arr) {
sum += i;
}
return sum;
}
注意事项:
-
一个方法的参数列表,只能有一个可变参数。
-
如果方法的参数有多个,那么可变参数必须卸载参数列表的末尾。
如
public static int mySum(String arg1, int... arr){}
Map集合
java.util.Map。public interface Map<K,V>
。将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。
Map集合的特点
- Map集合是一个双列集合,一个集合包含两个值(一个key,一个value)
- Map集合中的元素,key和value的数据类型可以相同也可以不同
- Map集合中的元素,key是不允许重复的,value是可以重复的
- Map集合中的元素,key和value是一一对应的。
Map常用子类(HashMap、LinkedHashMap)
通过Map接口描述,看到Map有多个子类,这里我们主要讲解常用的HashMap集合、LinkedHashMap集合。
HashMap<K, V>
:存储数据采用的是哈希表结构,查询速度特别快,元素存取顺序不能保证一致,由于要保证键的唯一,不重复,需要重写键的hashCode()方法。- LinkedHashMap<K, V> :HashMap下有个子类LinkedHashMap,存储数据采用哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希结构可以保证键的唯一、不重复;需要重写键的hashCode()方法、equals()方法。
Map中常用的方法
-
public V
get(K key)
:获取map中key键所对应的value值。(有就返回value,没有就返回null) -
public V
put(K key, V value)
:把指定的键和值添加到Map集合中,并将K对应的旧值返回,若map原本中无K,则返回null(添加键值对的时候,集合中若不存在key,返回值V是null;存在key时,会使用新的value替换map中已有key的value,返回被替换的value值,即旧值。) -
public V
remove(Object key)
:把指定的键 所对应的键值对元素 在map中删除,返回被删除元素的值。(key若存在,返回被删除的值;key若不存在,返回null) -
boolean
containsKey(Object key)
:判断集合中是否包含指定键key。 -
int
size()
:返回此映射中的键-值映射关系数。 -
boolean
isEmpty()
:判断map是否为空。 -
Set
keySet()
返回map中所有键,存储在一个set集合中。 -
Set<Map.Entry<K, V>>
entrySet()
:返回此Map中包含的映射关系的Set视图
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
String v1 = map.put("a", "A");
System.out.println("v1:" + v1);
String v2 = map.put("a", "AA");
System.out.println("v2:" + v2);
map.put("b", "B");
map.put("c", "C");
System.out.println(map);
// 删除:remove(K);
String v3 = map.remove("a");
System.out.println("v3:" + v3); // v3:AA
// 遍历map
// 1 使用Map集合中的keySet()方法,把Map集合中所有的key取出来,存储到一个Set集合中。
Set<String> set = map.keySet();
// 2 使用迭代器遍历Set,从而获取map集合中的每个key,进而获取每个value
Iterator<String> it = set.iterator();
while(it.hasNext()){
String k = it.next();
// 3 通过Map集合中的法get(key),通过key得到value
String value = map.get(k);
System.out.println(k + "=" + value);
}
// 使用增强for循环
for (String key : map.keySet()) {
System.out.println(key + "=" + map.get(key));
}
}
Map集合遍历的第二种方法,使用Entry对象遍历
-
说明,在Map中,每个键值对组成了一个Map.Entry<key, value> 对象,有多少个键值对,就有多少个Entry对象,Entry对象有getKey()和getValue()两个方法。
-
示例:
public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("a", 1); map.put("b", 2); map.put("c", 3); System.out.println(map); // 1 使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出,存入一个Set集合中。 Set<Map.Entry<String, Integer>> set = map.entrySet(); // 2 遍历set集合,获取每个Entry对象 // 使用迭代器遍历Set集合 Iterator<Map.Entry<String, Integer>> it = set.iterator(); while(it.hasNext()){ Map.Entry<String, Integer> entry = it.next(); String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + "=" + value); } // 使用增强for循环 for (Map.Entry<String, Integer> entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + "=" + value); } }
HashMap存储自定义类型的键值
Map集合保证key是唯一的:
所以,对于自定义类型,要保证key是唯一的,必须重写hashCode()和equals()方法
示例:
public static void main(String[] args) {
show01();
// 对Person类重写hashCode和equals()方法。
show02();
}
private static void show01(){
/*
key:String类型
String类已经重写过hashCode和equals方法,可以保证key唯一
value:Person类型
value可以重复,Person类可以不重写hashCode和equals方法。
*/
HashMap<String, Person> map = new HashMap<>();
map.put("工号001", new Person("张三", 21));
map.put("工号002", new Person("李四", 18));
map.put("工号001", new Person("王五", 24)); // 会把原来工号为001的张三覆盖掉
Set<Map.Entry<String, Person>> set = map.entrySet();
// 遍历set集合,获取每个Entry对象
// 使用迭代器遍历Set集合
Iterator<Map.Entry<String, Person>> it = set.iterator();
while(it.hasNext()){
Map.Entry<String, Person> entry = it.next();
String key = entry.getKey();
Person value = entry.getValue();
System.out.println(key + "=" + value);
}
}
private static void show02(){
/*
key:Person类型
Person类必须重写hashCode和equals方法,以可以保证key唯一
value:String类型
可以重复
*/
HashMap<Person, String> map = new HashMap<>();
map.put(new Person("张三", 24), "米国");
map.put(new Person("吉田", 18), "日本");
map.put(new Person("张三", 24), "中国"); // 会把原来是米国的Mark覆盖掉
// 使用增强for循环
for (Map.Entry<Person, String> entry : map.entrySet()) {
Person key_person = entry.getKey();
String value_country = entry.getValue();
System.out.println(key_person + "=" + value_country);
}
}
public class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) &&
Objects.equals(age, person.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
小案例:计算一个字符串中每个字符出现的次数
/*
* 练习:
* 计算要给字符串中每个字符出现的次数
* 分析:
* 1 使用Scanner获取用户输入的字符串
* 2 创建Map集合,key是字符串中的字符,value是字符的个数
* 3 遍历字符串,获取每个字符,
* 4 使用获取到的字符,去map集合中判断key是否存在
* */
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str = sc.next();
HashMap<Character, Integer> map = new HashMap<>();
for (char c : str.toCharArray()) {
if(map.containsKey(c)){
Integer value = map.get(c);
map.put(c, value+1);
}else{
map.put(c, 1);
}
}
for (Character key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + ":" + value);
}
}
JDK9对集合添加操作的优化(of方法)
在jdk9之前,对List、Set集合的添加是add()
方法,对Map集合的添加是put()
方法。每次只能添加一个元素。
JDK9的新特性:
- List接口、Set接口、Map接口:增加了一个静态方法
of()
,可以一次性添加多个元素。 - static List of(E… elements)。使用前提:当集合中存储的元素的个数已经确定了,不改变时使用
- 注意:
of()
方法只适用于List、Set、Map三个接口,不适用于接口实现类。of()
方法的返回值是一个不能改变的集合,该集合不能再使用add()
,put()
方法添加元素。- Set接口和Map接口再调用
of()
方法的时候,不能有重复元素,否则异常。