第12章_集合框架 本章专题与脉络
1. 集合框架概述 1.1 生活中的容器
1.2 数组的特点与弊端
一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。
另一方面,使用数组存储对象方面具有一些弊端
,而Java 集合就像一种容器,可以动态地
把多个对象的引用放入容器中。
数组在内存存储方面的特点
:
数组初始化以后,长度就确定了。
数组中的添加的元素是依次紧密排列的,有序的,可以重复的。
数组声明的类型,就决定了进行元素初始化时的类型。不是此类型的变量,就不能添加。
可以存储基本数据类型值,也可以存储引用数据类型的变量
数组在存储数据方面的弊端
:
数组初始化以后,长度就不可变了,不便于扩展
数组中提供的属性和方法少,不便于进行添加、删除、插入、获取元素个数等操作,且效率不高。
数组存储数据的特点单一,只能存储有序的、可以重复的数据
Java 集合框架中的类可以用于存储多个对象
,还可用于保存具有映射关系
的关联数组。
1.3 Java集合框架体系 Java 集合可分为 Collection 和 Map 两大体系:
Collection接口:用于存储一个一个的数据,也称单列数据集合
。
List子接口:用来存储有序的、可以重复的数据(主要用来替换数组,”动态”数组)
实现类:ArrayList(主要实现类)、LinkedList、Vector
Set子接口:用来存储无序的、不可重复的数据(类似于高中讲的”集合”)
实现类:HashSet(主要实现类)、LinkedHashSet、TreeSet
Map接口:用于存储具有映射关系“key-value对”的集合,即一对一对的数据,也称双列数据集合
。(类似于高中的函数、映射。(x1,y1),(x2,y2) —> y = f(x) )
HashMap(主要实现类)、LinkedHashMap、TreeMap、Hashtable、Properties
JDK提供的集合API位于java.util包内
图示:集合框架全图
1.4 集合的使用场景
2. Collection接口及方法
JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)去实现。
Collection 接口是 List和Set接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 集合。方法如下:
2.1 添加 (1)add(E obj):添加元素对象到当前集合中 (2)addAll(Collection other):添加other集合中的所有元素对象到当前集合中,即this = this ∪ other
注意:add和addAll的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void test () { Collection coll=new ArrayList<>(); coll.add("AA" ); coll.add(123 ); coll.add("桑鬼谷" ); coll.add("尚硅谷" ); coll.add(new Object()); System.out.println(coll); Collection coll1=new ArrayList(); coll1.add("BB" ); coll1.add(456 ); coll.addAll(coll1); System.out.println(coll); }
注意:coll.addAll(other);与coll.add(other);
2.2 判断 (3)int size():获取当前集合中实际存储的元素个数 (4)boolean isEmpty():判断当前集合是否为空集合 (5)boolean contains(Object obj):判断当前集合中是否存在一个与obj对象equals返回true的元素 (6)boolean containsAll(Collection coll):判断coll集合中的元素是否在当前集合中都存在。即coll集合是否是当前集合的“子集” (7)boolean equals(Object obj):判断当前集合与obj是否相等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Test public void test1 () { Collection coll=new ArrayList<>(); coll.add("AA" ); coll.add(123 ); coll.add("桑鬼谷" ); coll.add("尚硅谷" ); coll.add(new Object()); System.out.println(coll); Collection coll1=new ArrayList(); coll1.add("AA" ); coll1.add(123 ); System.out.println(coll1); System.out.println(coll.size()); System.out.println(coll.isEmpty()); System.out.println(coll.contains(coll1)); System.out.println(coll.contains("AA" )); System.out.println(coll.containsAll(coll1)); System.out.println(coll.equals(coll1)); }
2.3 删除 (8)void clear():清空集合元素 (9) boolean remove(Object obj) :从当前集合中删除第一个找到的与obj对象equals返回true的元素。 (10)boolean removeAll(Collection coll):从当前集合中删除所有与coll集合中相同的元素。即this = this - this ∩ coll (11)boolean retainAll(Collection coll):从当前集合中删除两个集合中不同的元素,使得当前集合仅保留与coll集合中的元素相同的元素,即当前集合中仅保留两个集合的交集,即this = this ∩ coll;
注意几种删除方法的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Test public void test2 () { Collection coll=new ArrayList<>(); coll.add("AA" ); coll.add(123 ); coll.add("桑鬼谷" ); coll.add("尚硅谷" ); coll.add(new Object()); System.out.println(coll); Collection coll1=new ArrayList(); coll1.add("AA" ); coll1.add(123 ); coll1.add("尚硅谷" ); System.out.println(coll1); System.out.println("-----------------------------------------" ); System.out.println(coll.remove("AAA" )); System.out.println(coll.remove("AA" )); System.out.println(coll); System.out.println("-----------------------------------------" ); System.out.println(coll.removeAll(coll1)); System.out.println(coll); System.out.println("-----------------------------------------" ); System.out.println(coll.retainAll(coll1)); System.out.println(coll); coll1.clear(); System.out.println(coll1.size()); System.out.println("-----------------------------------------" ); }
2.4 其它 (12)Object[] toArray():返回包含当前集合中所有元素的数组 (13)hashCode():获取集合对象的哈希值 (14)iterator():返回迭代器对象,用于集合遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Test public void test3 () { Collection coll=new ArrayList<>(); coll.add("AA" ); coll.add(123 ); coll.add("桑鬼谷" ); coll.add("尚硅谷" ); coll.add(new Object()); System.out.println(coll); Collection coll1=new ArrayList(); coll1.add("AA" ); coll1.add(123 ); System.out.println(coll1); System.out.println("-----------------------------------------" ); System.out.println(Arrays.toString(coll.toArray())); System.out.println(coll.hashCode()); System.out.println(coll.iterator()); }
2.5 数组和集合的转换 (15)toArray():集合 —> 数组 (16)Arrays.asList(Object …objs):数组 —> 集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public void test4(){ Collection coll=new ArrayList<>(); coll.add("AA"); coll.add(123); //自动装箱为包装类 coll.add("桑鬼谷"); coll.add("尚硅谷"); coll.add(new Object()); System.out.println(coll); //[AA, 123, 桑鬼谷, 尚硅谷, java.lang.Object@77f03bb1] //集合 ---> 数组 Object[] arr1=coll.toArray(); //1.Arrays.toString()输出 System.out.println(Arrays.toString(arr1)); //[AA, 123, 桑鬼谷, 尚硅谷, java.lang.Object@77f03bb1] //2.for循环遍历输出 for (int i = 0; i < arr1.length; i++) { System.out.print(arr1[i]+" "); //AA 123 桑鬼谷 尚硅谷 java.lang.Object@77f03bb1 } System.out.println(); //数组 ---> 集合 Integer[] arr = new Integer[]{1,2,3}; List list= Arrays.asList(arr); System.out.println(list); //[1, 2, 3] }
3. Iterator(迭代器)接口 3.1 Iterator接口
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator
。Iterator
接口也是Java集合中的一员,但它与Collection
、Map
接口有所不同。
Collection接口与Map接口主要用于存储
元素
Iterator
,被称为迭代器接口,本身并不提供存储对象的能力,主要用于遍历
Collection中的元素
Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素的。
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
Iterator接口的常用方法如下:
public E next()
:返回迭代的下一个元素。
public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。
注意:在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常
。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import org.junit.Test;import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;public class TestIterator { @Test public void test01 () { Collection coll = new ArrayList(); coll.add("小李广" ); coll.add("扫地僧" ); coll.add("石破天" ); Iterator iterator = coll.iterator(); System.out.println(iterator.next()); System.out.println(iterator.next()); System.out.println(iterator.next()); System.out.println(iterator.next()); } @Test public void test02 () { Collection coll = new ArrayList(); coll.add("小李广" ); coll.add("扫地僧" ); coll.add("石破天" ); Iterator iterator = coll.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
3.2 迭代器的执行原理 Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,接下来通过一个图例来演示Iterator对象迭代元素的过程:
使用Iterator迭代器删除元素:java.util.Iterator迭代器中有一个方法:void remove() ;
1 2 3 4 5 6 7 Iterator iter = coll.iterator(); while (iter.hasNext()){ Object obj = iter.next(); if (obj.equals("Tom" )){ iter.remove(); } }
注意:
Iterator可以删除集合的元素,但是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。
如果还未调用next()或在上一次调用 next() 方法之后已经调用了 remove() 方法,再调用remove()都会报IllegalStateException。
Collection已经有remove(xx)方法了,为什么Iterator迭代器还要提供删除方法呢?因为迭代器的remove()可以按指定的条件进行删除。
例如:要删除以下集合元素中的偶数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import org.junit.Test;import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;public class TestIteratorRemove { @Test public void test01 () { Collection coll = new ArrayList(); coll.add(1 ); coll.add(2 ); coll.add(3 ); coll.add(4 ); coll.add(5 ); coll.add(6 ); Iterator iterator = coll.iterator(); while (iterator.hasNext()){ Integer element = (Integer) iterator.next(); if (element % 2 == 0 ){ iterator.remove(); } } System.out.println(coll); } }
在JDK8.0时,Collection接口有了removeIf 方法,即可以根据条件删除。(第18章中再讲)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.atguigu.collection;import org.junit.Test;import java.util.ArrayList;import java.util.Collection;import java.util.function.Predicate;public class TestCollectionRemoveIf { @Test public void test01 () { Collection coll = new ArrayList(); coll.add("小李广" ); coll.add("扫地僧" ); coll.add("石破天" ); coll.add("佛地魔" ); System.out.println("coll = " + coll); coll.removeIf(new Predicate() { @Override public boolean test (Object o) { String str = (String) o; return str.contains("地" ); } }); System.out.println("删除包含\"地\"字的元素之后coll = " + coll); } }
3.3 foreach循环
foreach循环(也称增强for循环)是 JDK5.0 中定义的一个高级for循环,专门用来遍历数组和集合
的。
1 2 3 4 for (元素的数据类型 局部变量 : Collection集合或数组){ }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import org.junit.Test;import java.util.ArrayList;import java.util.Collection;public class TestForeach { @Test public void test01 () { Collection coll = new ArrayList(); coll.add("小李广" ); coll.add("扫地僧" ); coll.add("石破天" ); for (Object o : coll) { System.out.println(o); } } @Test public void test02 () { int [] nums = {1 ,2 ,3 ,4 ,5 }; for (int num : nums) { System.out.println(num); } System.out.println("-----------------" ); String[] names = {"张三" ,"李四" ,"王五" }; for (String name : names) { System.out.println(name); } } }
对于集合的遍历,增强for的内部原理其实是个Iterator迭代器。如下图。
它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class ForTest { public static void main (String[] args) { String[] str = new String[5 ]; for (String myStr : str) { myStr = "atguigu" ; System.out.println(myStr); } for (int i = 0 ; i < str.length; i++) { System.out.println(str[i]); } } } 最终输出: atguigu atguigu atguigu atguigu atguigu null null null null null
4. Collection子接口1:List 4.1 List接口特点
JDK API中List接口的实现类常用的有:ArrayList
、LinkedList
和Vector
。
4.2 List接口方法 List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引
来操作集合元素的方法。
插入元素
void add(int index, Object ele)
:在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
获取元素
Object get(int index)
:获取指定index位置的元素
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
获取元素索引
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
删除和替换元素
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import java.util.ArrayList;import java.util.List;public class TestListMethod { public static void main (String[] args) { List<String> list = new ArrayList<String>(); list.add("图图" ); list.add("小美" ); list.add("不高兴" ); System.out.println(list); list.add(1 ,"没头脑" ); System.out.println(list); System.out.println("删除索引位置为2的元素" ); System.out.println(list.remove(2 )); System.out.println(list); list.set(0 , "三毛" ); System.out.println(list); for (int i = 0 ;i<list.size();i++){ System.out.println(list.get(i)); } for (String string : list) { System.out.println(string); } } }
注意:在JavaSE中List名称的类型有两个,一个是java.util.List集合接口,一个是java.awt.List图形界面的组件,别导错包了。
4.3 List接口主要实现类:ArrayList
ArrayList 是 List 接口的主要实现类
本质上,ArrayList是对象引用的一个”变长”数组
Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是 Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合
4.4 List的实现类之二:LinkedList
对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高。这是由底层采用链表(双向链表)结构存储数据决定的。
特有方法:
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()
4.5 List的实现类之三:Vector
Vector 是一个古老
的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全
的。
在各种List中,最好把ArrayList作为默认选择
。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。
特有方法:
void addElement(Object obj)
void insertElementAt(Object obj,int index)
void setElementAt(Object obj,int index)
void removeElement(Object obj)
void removeAllElements()
4.6 练习 面试题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import org.junit.Test;import java.util.ArrayList;import java.util.List;public class Test1 { @Test public void testListRemove () { List list = new ArrayList(); list.add(1 ); list.add(2 ); list.add(3 ); System.out.println(list); updateList(list); System.out.println(list); } private static void updateList (List list) { list.remove(2 ); } }
练习1:
定义学生类,属性为姓名、年龄,提供必要的getter、setter方法,构造器,toString(),equals()方法。
使用ArrayList集合,保存录入的多个学生对象。
循环录入的方式,1:继续录入,0:结束录入。
录入结束后,用foreach遍历集合。
代码实现,效果如图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import java.util.ArrayList;import java.util.Iterator;public class StudentTest { public static void main (String[] args) { ArrayList list=new ArrayList(); list.add(new Student("宋亚翔" ,12 )); list.add(new Student("宋亚翔2" ,121 )); list.add(new Student("宋亚翔3" ,11222 )); list.add(new Student("宋亚翔4" ,13122 )); list.add(new Student("宋亚翔5" ,112 )); list.add(new Student("宋亚翔6" ,1122 )); for (Object stu:list){ System.out.println(stu); } System.out.println(); Iterator iterator=list.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import java.util.Objects; public class Student { private String name; private int 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; } public Student(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Student student)) return false; return getAge() == student.getAge() && Objects.equals(getName(), student.getName()); } @Override public String toString() { return "Student{" +"name='" + name + '\'' +", age=" + age +'}'; } }
练习2:
1、请定义方法public static int listTest(Collection list,String s)统计集合中指定元素出现的次数
2、创建集合,集合存放随机生成的30个小写字母
3、用listTest统计,a、b、c、x元素的出现次数
4、效果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import java.util.ArrayList;import java.util.Collection;import java.util.Random;public class Test2 { public static void main (String[] args) { ArrayList list=new ArrayList(); Random random=new Random(); for (int i = 0 ; i < 30 ; i++) { int temp= random.nextInt(26 ); char tempchar= (char ) ('a' +temp); list.add(tempchar); } System.out.println("随机小写字母为:" ); System.out.println(list); System.out.println("a" +":" +listTest(list,"a" )); System.out.println("b" +":" +listTest(list,"b" )); System.out.println("c" +":" +listTest(list,"c" )); System.out.println("x" +":" +listTest(list,"x" )); } public static int listTest (Collection list,String s) { char [] arr=s.toCharArray(); int sum=0 ; for (Object temp:list){ if (arr[0 ]==(char )temp){ sum++; } } return sum; } }
练习3:KTV点歌系统
描述
分别使用ArrayList和LinkedList集合,编写一个KTV点歌系统
的程序。在程序中:
指令1代表添加歌曲
指令2代表将所选歌曲置顶
指令3代表将所选歌曲提前一位
指令4代表退出该系统
要求根据用户输入的指令和歌曲名展现歌曲列表。例如输入指令1,输入歌曲名”爱你一万年”,则输出“当前歌曲列表:[爱你一万年]”。
提示
为了指引用户操作,首先要将各个指令所表示的含义打印到控制台
1 2 3 4 5 System.out.println("-------------欢迎来到点歌系统------------" ); System.out.println("1.添加歌曲至列表" ); System.out.println("2.将歌曲置顶" ); System.out.println("3.将歌曲前移一位" ); System.out.println("4.退出" );
程序中需要创建一个集合作为歌曲列表,并向其添加一部分歌曲
通过ArrayList或LinkedList集合定义的方法操作歌曲列表
代码
5. Collection子接口2:Set 5.1 Set接口概述
Set接口是Collection的子接口,Set接口相较于Collection接口没有提供额外的方法 【list接口有自己的增删查改】
Set 集合存储不相同的元素 ,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败。
Set集合支持的遍历方式和Collection集合一样:foreach和Iterator。
Set的常用实现类有:HashSet、TreeSet、LinkedHashSet。
5.2 Set主要实现类:HashSet 5.2.1 HashSet概述
HashSet 是 Set 接口的主要实现类,大多数时候使用 Set 集合时都使用这个实现类。
HashSet 按 Hash 算法 来存储集合中的元素,因此具有很好的存储、查找、删除性能。
HashSet 具有以下特点
:
不能保证元素的排列顺序【假设用取余法,那本身存放位置相同(都是放在下标为1),如果冲突的话可能存放在其他位置,】
HashSet 不是线程安全的
集合元素可以是 null
HashSet 集合判断两个元素相等的标准
:两个对象通过 hashCode()
方法得到的哈希值相等,并且两个对象的 equals()
方法返回值为true。
对于存放在Set容器中的对象,对应的类一定要重写hashCode()和equals(Object obj)方法 ,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
HashSet集合中元素的无序性,不等同于随机性。这里的无序性与元素的添加位置有关。具体来说:我们在添加每一个元素到数组中时,具体的存储位置是由元素的hashCode()调用后返回的hash值决定的。导致在数组中每个元素不是依次紧密存放的,表现出一定的无序性。
5.2.2 HashSet中添加元素的过程:
第1步:当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法得到该对象的 hashCode值,然后根据 hashCode值,通过某个散列函数决定该对象在 HashSet 底层数组中的存储位置。
第2步:如果要在数组中存储的位置上没有元素,则直接添加成功。
第3步:如果要在数组中存储的位置上有元素,则继续比较:
如果两个元素的hashCode值不相等,则添加成功;
如果两个元素的hashCode()值相等,则会继续调用equals()方法:
如果equals()方法结果为false,则添加成功。
如果equals()方法结果为true,则添加失败。
第2步添加成功,元素会保存在底层数组中。
第3步两种添加成功的操作,由于该底层数组的位置已经有元素了,则会通过链表
的方式继续链接,存储。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import java.util.Objects;public class MyDate { private int year; private int month; private int day; public MyDate (int year, int month, int day) { this .year = year; this .month = month; this .day = day; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; MyDate myDate = (MyDate) o; return year == myDate.year && month == myDate.month && day == myDate.day; } @Override public int hashCode () { return Objects.hash(year, month, day); } @Override public String toString () { return "MyDate{" + "year=" + year + ", month=" + month + ", day=" + day + '}' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import org.junit.Test;import java.util.HashSet;public class TestHashSet { @Test public void test01 () { HashSet set = new HashSet(); set.add("张三" ); set.add("张三" ); set.add("李四" ); set.add("王五" ); set.add("王五" ); set.add("赵六" ); System.out.println("set = " + set); } @Test public void test02 () { HashSet set = new HashSet(); set.add(new MyDate(2021 ,1 ,1 )); set.add(new MyDate(2021 ,1 ,1 )); set.add(new MyDate(2022 ,2 ,4 )); set.add(new MyDate(2022 ,2 ,4 )); System.out.println("set = " + set); } }
5.2.3 重写 hashCode() 方法的基本原则
在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。
对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
注意:如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
5.2.4 重写equals()方法的基本原则
5.2.5 练习 练习1: 在List内去除重复数字值,要求尽量简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import java.util.ArrayList;import java.util.HashSet;import java.util.List;public class Test1 { public static void main (String[] args) { List list=new ArrayList(); list.add(123 ); list.add(123 ); list.add(123 ); list.add(123 ); list.add(123 ); list.add(123 ); list.add(1234234 ); list=delete(list); System.out.println(list); } public static List delete (List list) { ArrayList arrayList=new ArrayList(); HashSet set=new HashSet(); for (Object temp:list){ set.add(temp); } return new ArrayList(set); } }
练习2: 获取随机数
编写一个程序,获取10个1至20的随机数,要求随机数不能重复。并把最终的随机数输出到控制台。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.util.HashSet;import java.util.Random;public class Test2 { public static void main (String[] args) { HashSet set=new HashSet(); Random random=new Random(); while (set.size()<10 ){ int temp=random.nextInt(20 ); set.add(temp); } System.out.println(set); } }
练习3: 去重
使用Scanner从键盘读取一行输入,去掉其中重复字符,打印出不同的那些字符。比如:aaaabbbcccddd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.util.HashSet;import java.util.Scanner;public class Test3 { public static void main (String[] args) { Scanner input=new Scanner(System.in); String str=input.next(); char [] arr=str.toCharArray(); HashSet set=new HashSet(); for (Object temp:arr){ set.add(temp); } System.out.println(set); } }
练习4: 面试题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import java.util.HashSet;public class Test4 { public static void main (String[] args) { HashSet set = new HashSet(); Person p1 = new Person("AA" , 1001 ); Person p2 = new Person("BB" , 1002 ); set.add(p1); set.add(p2); System.out.println("set里的元素:" +set); System.out.println("---------------------------------------------------------------------------------------------" ); p1.name = "CC" ; System.out.println("set里的元素更改信息之后:" +set); System.out.println("---------------------------------------------------------------------------------------------" ); set.remove(p1); System.out.println("set移除完元素如下:" +set); System.out.println("---------------------------------------------------------------------------------------------" ); set.add(new Person("CC" , 1001 )); System.out.println("set里添加元素:" +set); System.out.println("---------------------------------------------------------------------------------------------" ); set.add(new Person("AA" , 1001 )); System.out.println("set里添加元素:" +set); System.out.println("---------------------------------------------------------------------------------------------" ); } } 最终输出: set里的元素:[Person{name='BB' , age=1002 }, Person{name='AA' , age=1001 }] --------------------------------------------------------------------------------------------- set里的元素更改信息之后:[Person{name='BB' , age=1002 }, Person{name='CC' , age=1001 }] --------------------------------------------------------------------------------------------- set移除完元素如下:[Person{name='BB' , age=1002 }, Person{name='CC' , age=1001 }] --------------------------------------------------------------------------------------------- set里添加元素:[Person{name='BB' , age=1002 }, Person{name='CC' , age=1001 }, Person{name='CC' , age=1001 }] --------------------------------------------------------------------------------------------- Person equals () ... set里添加元素:[Person {name='BB' , age=1002 }, Person{name='CC' , age=1001 }, Person{name='CC' , age=1001 }, Person{name='AA' , age=1001 }]---------------------------------------------------------------------------------------------
5.3 Set实现类之二:LinkedHashSet
LinkedHashSet 是 HashSet 的子类,不允许集合元素重复。
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表
维护元素的次序,这使得元素看起来是以添加顺序
保存的。
LinkedHashSet插入性能略低
于 HashSet,但在迭代访问
Set 里的全部元素时有很好的性能。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import org.junit.Test;import java.util.LinkedHashSet;public class TestLinkedHashSet { @Test public void test01 () { LinkedHashSet set = new LinkedHashSet(); set.add("张三" ); set.add("张三" ); set.add("李四" ); set.add("王五" ); set.add("王五" ); set.add("赵六" ); System.out.println("set = " + set); } }
5.4 Set实现类之三:TreeSet 5.4.1 TreeSet概述
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以按照添加的元素的指定的属性的大小顺序进行遍历 。
TreeSet底层使用红黑树
结构存储数据
新增的方法如下: (了解)
Comparator comparator()
Object first()
Object last()
Object lower(Object e)
Object higher(Object e)
SortedSet subSet(fromElement, toElement)
SortedSet headSet(toElement)
SortedSet tailSet(fromElement)
TreeSet特点:不允许重复、实现排序(自然排序或定制排序)
TreeSet 两种排序方法:自然排序
和定制排序
。默认情况下,TreeSet 采用自然排序。
自然排序
:TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。
实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。
定制排序
:如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。
利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象
。
对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准
是:两个对象通过 compareTo(Object obj) 或compare(Object o1,Object o2)
方法比较返回值。返回值为0,则认为两个对象相等。
5.4.2 举例 举例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import org.junit.Test;import java.util.Iterator;import java.util.TreeSet;public class TreeSetTest { @Test public void test1 () { TreeSet set = new TreeSet(); set.add("MM" ); set.add("CC" ); set.add("AA" ); set.add("DD" ); set.add("ZZ" ); Iterator iterator = set.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } } @Test public void test2 () { TreeSet set = new TreeSet(); set.add(new User("Tom" ,12 )); set.add(new User("Rose" ,23 )); set.add(new User("Jerry" ,2 )); set.add(new User("Eric" ,18 )); set.add(new User("Tommy" ,44 )); set.add(new User("Jim" ,23 )); set.add(new User("Maria" ,18 )); Iterator iterator = set.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } System.out.println(set.contains(new User("Jack" , 23 ))); } }
其中,User类定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class User implements Comparable { String name; int age; public User () { } public User (String name, int age) { this .name = name; this .age = age; } @Override public String toString () { return "User{" + "name='" + name + '\'' + ", age=" + age + '}' ; } public int compareTo (Object o) { if (this == o){ return 0 ; } if (o instanceof User){ User user = (User)o; int value = this .age - user.age; if (value != 0 ){ return value; } return -this .name.compareTo(user.name); } throw new RuntimeException("输入的类型不匹配" ); } }
举例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Test public void test3 () { Comparator comparator = new Comparator() { @Override public int compare (Object o1, Object o2) { if (o1 instanceof User && o2 instanceof User){ User u1 = (User)o1; User u2 = (User)o2; return u1.name.compareTo(u2.name); } throw new RuntimeException("输入的类型不匹配" ); } }; TreeSet set = new TreeSet(comparator); set.add(new User("Tom" ,12 )); set.add(new User("Rose" ,23 )); set.add(new User("Jerry" ,2 )); set.add(new User("Eric" ,18 )); set.add(new User("Tommy" ,44 )); set.add(new User("Jim" ,23 )); set.add(new User("Maria" ,18 )); Iterator iterator = set.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } }
5.4.3 练习 练习1: 在一个List集合中存储了多个无大小顺序并且有重复的字符串,定义一个方法,让其有序(从小到大排序),并且不能去除重复元素。
提示:考查ArrayList、TreeSet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import java.util.ArrayList;import java.util.Comparator;import java.util.TreeSet;public class Test11 { public static void main (String[] args) { ArrayList list = new ArrayList(); list.add("ccc" ); list.add("ccc" ); list.add("aaa" ); list.add("aaa" ); list.add("bbb" ); list.add("ddd" ); list.add("ddd" ); System.out.println("排序前:" +list); sort(list); System.out.println("排序后:" +list); } public static void sort (ArrayList list) { TreeSet set=new TreeSet(new Comparator() { @Override public int compare (Object o1, Object o2) { String s1 = (String)o1; String s2 = (String)o2; int num = s1.compareTo(s2); return num == 0 ? 1 : num; } }); set.addAll(list); list.clear(); list.addAll(set); } } 最终输出: 排序前:[ccc, ccc, aaa, aaa, bbb, ddd, ddd] 排序后:[aaa, aaa, bbb, ccc, ccc, ddd, ddd]
练习2: TreeSet的自然排序和定制排序
定义一个Employee类。 该类包含:private成员变量name,age,birthday,其中 birthday 为 MyDate 类的对象; 并为每一个属性定义 getter, setter 方法; 并重写 toString 方法输出 name, age, birthday
MyDate类包含: private成员变量year,month,day;并为每一个属性定义 getter, setter 方法;
创建该类的 5 个对象,并把这些对象放入 TreeSet 集合中(下一章:TreeSet 需使用泛型来定义)
分别按以下两种方式对集合中的元素进行排序,并遍历输出:
1). 使Employee 实现 Comparable 接口,并按 name 排序[Employee类就要实现Comparable接口] 2). 创建 TreeSet 时传入 Comparator对象,按生日日期的先后排序。[MyDate类就要实现Comparable接口]
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class Employee implements Comparable { private String name; private int age; private MyDate birthday; public Employee () { } public Employee (String name, int age, MyDate birthday) { this .name = name; this .age = age; this .birthday = birthday; } @Override public String toString () { return "Employee{" +"name='" + name + '\'' +", age=" + age +", birthday=" + birthday +'}' ; } 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; } public MyDate getBirthday () { return birthday; } public void setBirthday (MyDate birthday) { this .birthday = birthday; } @Override public int compareTo (Object o) { if (o==this ){ return 0 ; } if (o instanceof Employee){ Employee temp=(Employee) o; return this .getName().compareTo(temp.getName()); } throw new RuntimeException("传入的类型不匹配" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public class MyDate implements Comparable { private int year; private int month; private int day; public MyDate () { } public MyDate (int year, int month, int day) { this .year = year; this .month = month; this .day = day; } public int getYear () { return year; } public void setYear (int year) { this .year = year; } public int getMonth () { return month; } public void setMonth (int month) { this .month = month; } public int getDay () { return day; } public void setDay (int day) { this .day = day; } @Override public String toString () { return "MyDate{" +"year=" + year +", month=" + month +", day=" + day +'}' ; } @Override public int compareTo (Object o) { if (this == o){ return 0 ; } if (o instanceof MyDate){ MyDate myDate = (MyDate) o; int yearDistance = this .getYear() - myDate.getYear(); if (yearDistance != 0 ){ return yearDistance; } int monthDistance = this .getMonth() - myDate.getMonth(); if (monthDistance != 0 ){ return monthDistance; } return this .getDay() - myDate.getDay(); } throw new RuntimeException("输入的类型不匹配" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 import org.junit.Test;import java.util.Comparator;import java.util.Iterator;import java.util.TreeSet;public class EmployeeTest { @Test public void test1 () { TreeSet set = new TreeSet(); Employee e1 = new Employee("Tom" , 23 , new MyDate(1999 , 7 , 9 )); Employee e2 = new Employee("Rose" , 43 , new MyDate(1999 , 7 , 19 )); Employee e3 = new Employee("Jack" , 54 , new MyDate(1998 , 12 , 21 )); Employee e4 = new Employee("Jerry" , 12 , new MyDate(2002 , 4 , 21 )); Employee e5 = new Employee("Tony" , 22 , new MyDate(2001 , 9 , 12 )); set.add(e1); set.add(e2); set.add(e3); set.add(e4); set.add(e5); Iterator iterator = set.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } @Test public void test2 () { TreeSet set = new TreeSet(new Comparator() { @Override public int compare (Object o1, Object o2) { if (o1 instanceof Employee && o2 instanceof Employee){ Employee e1 = (Employee) o1; Employee e2 = (Employee) o2; MyDate birth1 = e1.getBirthday(); MyDate birth2 = e2.getBirthday(); return birth1.compareTo(birth2); } throw new RuntimeException("输入的类型不匹配" ); } }); Employee e1 = new Employee("Tom" , 23 , new MyDate(1999 , 7 , 9 )); Employee e2 = new Employee("Rose" , 43 , new MyDate(1999 , 7 , 19 )); Employee e3 = new Employee("Jack" , 54 , new MyDate(1998 , 12 , 21 )); Employee e4 = new Employee("Jerry" , 12 , new MyDate(2002 , 4 , 21 )); Employee e5 = new Employee("Tony" , 22 , new MyDate(2001 , 9 , 12 )); set.add(e1); set.add(e2); set.add(e3); set.add(e4); set.add(e5); Iterator iterator = set.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
6. Map接口 现实生活与开发中,我们常会看到这样的一类集合:用户ID与账户信息、学生姓名与考试成绩、IP地址与主机名等,这种一一对应的关系,就称作映射 Java提供了专门的集合框架用来存储这种映射关系的对象,即java.util.Map接口
6.1 Map接口概述
Map与Collection并列存在。用于保存具有映射关系
的数据:key-value
Collection
集合称为单列集合,元素是孤立存在的(理解为单身 )
Map
集合称为双列集合,元素是成对存在的(理解为夫妻 )
Map 中的 key 和 value 可以是任何引用类型的数据。但常用String类作为Map的“键”。
Map接口的常用实现类:HashMap
、LinkedHashMap
、TreeMap
和``Properties。其中,HashMap是 Map 接口使用
频率最高`的实现类。
6.2 Map中key-value特点 这里主要以HashMap为例说明。HashMap中存储的key、value的特点如下:
Map 中的 key用Set来存放
,不允许重复
,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法
key 和 value 之间存在单向一对一关系,通过指定 key 总能找到唯一的、确定的 value【根据key存放value 】,不同key对应的value可以重复
。value所在的类要重写equals()方法。
key和value构成一个entry。所有的entry彼此之间是无序的
、不可重复的
。
总结 :
6.2 Map接口的常用方法
添加、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
删除操作:
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import java.util.HashMap;public class TestMapMethod { public static void main (String[] args) { HashMap map = new HashMap(); map.put("黄晓明" , "杨颖" ); map.put("李晨" , "李小璐" ); map.put("李晨" , "范冰冰" ); map.put("邓超" , "孙俪" ); System.out.println(map); System.out.println(map.remove("黄晓明" )); System.out.println(map); System.out.println(map.get("邓超" )); System.out.println(map.get("黄晓明" )); } }
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public static void main (String[] args) { HashMap map = new HashMap(); map.put("许仙" , "白娘子" ); map.put("董永" , "七仙女" ); map.put("牛郎" , "织女" ); map.put("许仙" , "小青" ); System.out.println("所有的key:" ); Set keySet = map.keySet(); for (Object key : keySet) { System.out.println(key); } System.out.println("所有的value:" ); Collection values = map.values(); for (Object value : values) { System.out.println(value); } System.out.println("所有的映射关系:" ); Set entrySet = map.entrySet(); for (Object mapping : entrySet) { Map.Entry entry = (Map.Entry) mapping; System.out.println(entry.getKey() + "->" + entry.getValue()); } }
6.3 Map的主要实现类:HashMap 6.3.1 HashMap概述
HashMap是 Map 接口使用频率最高
的实现类。
HashMap是线程不安全的。允许添加 null 键和 null 值。
存储数据采用的哈希表结构,底层使用一维数组
+单向链表
+红黑树
进行key-value数据的存储。与HashSet一样,元素的存取顺序不能保证一致。
HashMap 判断两个key相等的标准
是:两个 key 的hashCode值相等,通过 equals() 方法返回 true。
HashMap 判断两个value相等的标准
是:两个 value 通过 equals() 方法返回 true。
6.3.2 练习 练习1: 添加你喜欢的歌手以及你喜欢他唱过的歌曲
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class SingerTest1 { public static void main (String[] args) { HashMap singers = new HashMap(); String singer1 = "周杰伦" ; ArrayList songs1 = new ArrayList(); songs1.add("双节棍" ); songs1.add("本草纲目" ); songs1.add("夜曲" ); songs1.add("稻香" ); singers.put(singer1,songs1); String singer2 = "陈奕迅" ; List songs2 = Arrays.asList("浮夸" , "十年" , "红玫瑰" , "好久不见" , "孤勇者" ); singers.put(singer2,songs2); Set entrySet = singers.entrySet(); for (Object obj : entrySet){ Map.Entry entry = (Map.Entry)obj; String singer = (String) entry.getKey(); List songs = (List) entry.getValue(); System.out.println("歌手:" + singer); System.out.println("歌曲有:" + songs); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 public class SingerTest2 { @Test public void test1 () { Singer singer1 = new Singer("周杰伦" ); Singer singer2 = new Singer("陈奕迅" ); Song song1 = new Song("双节棍" ); Song song2 = new Song("本草纲目" ); Song song3 = new Song("夜曲" ); Song song4 = new Song("浮夸" ); Song song5 = new Song("十年" ); Song song6 = new Song("孤勇者" ); HashSet h1 = new HashSet(); h1.add(song1); h1.add(song2); h1.add(song3); HashSet h2 = new HashSet(); h2.add(song4); h2.add(song5); h2.add(song6); HashMap hashMap = new HashMap(); hashMap.put(singer1, h1); hashMap.put(singer2, h2); for (Object obj : hashMap.keySet()) { System.out.println(obj + "=" + hashMap.get(obj)); } } } public class Song implements Comparable { private String songName; public Song () { super (); } public Song (String songName) { super (); this .songName = songName; } public String getSongName () { return songName; } public void setSongName (String songName) { this .songName = songName; } @Override public String toString () { return "《" + songName + "》" ; } @Override public int compareTo (Object o) { if (o == this ){ return 0 ; } if (o instanceof Song){ Song song = (Song)o; return songName.compareTo(song.getSongName()); } return 0 ; } } public class Singer implements Comparable { private String name; private Song song; public Singer () { super (); } public Singer (String name) { super (); this .name = name; } public String getName () { return name; } public void setName (String name) { this .name = name; } public Song getSong () { return song; } public void setSong (Song song) { this .song = song; } @Override public String toString () { return name; } @Override public int compareTo (Object o) { if (o == this ){ return 0 ; } if (o instanceof Singer){ Singer singer = (Singer)o; return name.compareTo(singer.getName()); } return 0 ; } }
练习2 :二级联动
将省份和城市的名称保存在集合中,当用户选择省份以后,二级联动,显示对应省份的地级市供用户选择。
效果演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class CityMap { public static Map model = new HashMap(); static { model.put("北京" , new String[] {"北京" }); model.put("上海" , new String[] {"上海" }); model.put("天津" , new String[] {"天津" }); model.put("重庆" , new String[] {"重庆" }); model.put("黑龙江" , new String[] {"哈尔滨" ,"齐齐哈尔" ,"牡丹江" ,"大庆" ,"伊春" ,"双鸭山" ,"绥化" }); model.put("吉林" , new String[] {"长春" ,"延边" ,"吉林" ,"白山" ,"白城" ,"四平" ,"松原" }); model.put("河北" , new String[] {"石家庄" ,"张家口" ,"邯郸" ,"邢台" ,"唐山" ,"保定" ,"秦皇岛" }); } } public class ProvinceTest { public static void main (String[] args) { Set keySet = CityMap.model.keySet(); for (Object s : keySet) { System.out.print(s + "\t" ); } System.out.println(); System.out.println("请选择你所在的省份:" ); Scanner scan = new Scanner(System.in); String province = scan.next(); String[] citys = (String[])CityMap.model.get(province); for (String city : citys) { System.out.print(city + "\t" ); } System.out.println(); System.out.println("请选择你所在的城市:" ); String city = scan.next(); System.out.println("信息登记完毕" ); } }
练习3 :WordCount统计
需求:统计字符串中每个字符出现的次数
String str = “aaaabbbcccccccccc”;
提示:
char[] arr = str.toCharArray(); //将字符串转换成字符数组
HashMap hm = new HashMap(); //创建双列集合存储键和值,键放字符,值放次数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import java.util.HashMap;public class WordCount { public static void main (String[] args) { String str="aaaabbbcccccccccc" ; HashMap map=new HashMap(); char [] arr=str.toCharArray(); for (Object temp:arr){ if (!map.containsKey(temp)){ map.put(temp,1 ); }else { int oldvalue= (int ) map.put(temp,1 ); map.put(temp,oldvalue+1 ); } } System.out.println(map); } }
6.4 Map实现类之二:LinkedHashMap
LinkedHashMap 是 HashMap 的子类
存储数据采用的哈希表结构+链表结构,在HashMap存储结构的基础上,使用了一对双向链表
来记录添加元素的先后顺序
,可以保证遍历元素时,与添加的顺序一致。
通过哈希表结构可以保证键的唯一、不重复,需要键所在类重写hashCode()方法、equals()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class TestLinkedHashMap { public static void main (String[] args) { LinkedHashMap map = new LinkedHashMap(); map.put("王五" , 13000.0 ); map.put("张三" , 10000.0 ); map.put("张三" , 12000.0 ); map.put("李四" , 14000.0 ); String name = null ; Double salary = null ; map.put(name, salary); Set entrySet = map.entrySet(); for (Object obj : entrySet) { Map.Entry entry = (Map.Entry)obj; System.out.println(entry); } } }
6.5 Map实现类之三:TreeMap
TreeMap存储 key-value 对时,需要根据 key-value 对进行排序。TreeMap 可以保证所有的 key-value 对处于有序状态
。
TreeSet底层使用红黑树
结构存储数据
TreeMap 的 Key 的排序:
自然排序
:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
定制排序
:创建 TreeMap 时,构造器传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
TreeMap判断两个key相等的标准
:两个key通过compareTo()方法或者compare()方法返回0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 public class TestTreeMap { @Test public void test1 () { TreeMap map = new TreeMap(); map.put("CC" ,45 ); map.put("MM" ,78 ); map.put("DD" ,56 ); map.put("GG" ,89 ); map.put("JJ" ,99 ); Set entrySet = map.entrySet(); for (Object entry : entrySet){ System.out.println(entry); } } @Test public void test2 () { TreeMap map = new TreeMap(new Comparator() { @Override public int compare (Object o1, Object o2) { if (o1 instanceof User && o2 instanceof User){ User u1 = (User)o1; User u2 = (User)o2; return u1.name.compareTo(u2.name); } throw new RuntimeException("输入的类型不匹配" ); } }); map.put(new User("Tom" ,12 ),67 ); map.put(new User("Rose" ,23 ),"87" ); map.put(new User("Jerry" ,2 ),88 ); map.put(new User("Eric" ,18 ),45 ); map.put(new User("Tommy" ,44 ),77 ); map.put(new User("Jim" ,23 ),88 ); map.put(new User("Maria" ,18 ),34 ); Set entrySet = map.entrySet(); for (Object entry : entrySet){ System.out.println(entry); } } } class User implements Comparable { String name; int age; public User (String name, int age) { this .name = name; this .age = age; } public User () { } @Override public String toString () { return "User{" + "name='" + name + '\'' + ", age=" + age + '}' ; } @Override public int compareTo (Object o) { if (this == o){ return 0 ; } if (o instanceof User){ User user = (User)o; int value = this .age - user.age; if (value != 0 ){ return value; } return -this .name.compareTo(user.name); } throw new RuntimeException("输入的类型不匹配" ); } }
6.6 Map实现类之四:Hashtable
Hashtable是Map接口的古老实现类
,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构(数组+单向链表),查询速度快。
与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。
与HashMap不同,Hashtable 不允许使用 null 作为 key 或 value。
面试题:Hashtable和HashMap的区别
1 2 3 4 5 6 7 8 9 10 HashMap:底层是一个哈希表(jdk7:数组+链表;jdk8:数组+链表+红黑树),是一个线程不安全的集合,执行效率高 Hashtable:底层也是一个哈希表(数组+链表),是一个线程安全的集合,执行效率低 HashMap集合:可以存储null的键、null的值 Hashtable集合,不能存储null的键、null的值 Hashtable和Vector集合一样,在jdk1.2版本之后被更先进的集合(HashMap,ArrayList)取代了。所以HashMap是Map的主要实现类,Hashtable是Map的古老实现类。 Hashtable的子类Properties(配置文件)依然活跃在历史舞台 Properties集合是一个唯一和IO流相结合的集合
6.7 Map实现类之五:Properties
Properties 类是 Hashtable 的子类,该对象用于处理属性文件
由于属性文件里的 key、value 都是字符串类型,所以 Properties 中要求 key 和 value 都是字符串类型
存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Test public void test01 () { Properties properties = System.getProperties(); String fileEncoding = properties.getProperty("file.encoding" ); System.out.println("fileEncoding = " + fileEncoding); } @Test public void test02 () { Properties properties = new Properties(); properties.setProperty("user" ,"songhk" ); properties.setProperty("password" ,"123456" ); System.out.println(properties); } @Test public void test03 () throws IOException { Properties pros = new Properties(); pros.load(new FileInputStream("jdbc.properties" )); String user = pros.getProperty("user" ); System.out.println(user); }
7. Collections工具类 Arrays是一个操作数组 的工具类
Collections 是一个操作 Set、List 和 Map 等集合 的工具类。
7.1 常用方法(static方法 ) Collections 中提供了一系列静态方法 对集合元素进行排序 、查询 和修改 等操作,还提供了对集合对象设置不可变 、对集合对象实现同步控制 等方法(均为static方法):
- 排序
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Test public void test1(){ ArrayList list=new ArrayList(); list.add(123); list.add(12); list.add(2); list.add(4414); System.out.println("初始的list:"+list); //初始的list:[123, 12, 2, 4414] Collections.reverse(list); System.out.println("reverse之后:"+list); // 反转顺序 ---- reverse之后:[4414, 2, 12, 123] Collections.shuffle(list); System.out.println("shuffle之后:"+list); //很随机 ---- shuffle之后:[2, 4414, 12, 123] Collections.sort(list); System.out.println("sort之后:"+list); // 自然顺序(升序) ---- sort之后:[2, 12, 123, 4414] Collections.sort(list, new Comparator() { @Override public int compare(Object o1, Object o2) { int temp1=(int)o1; int temp2=(int)o2; return -(temp1-temp2); //实现倒序 } }); System.out.println("定制排序之后:"+list); //定制排序之后:[2, 12, 123, 4414] Collections.swap(list,0,2); System.out.println("将0和2位置的元素互换位置:"+list); // 将0和2位置的元素互换位置: ---- swap之后:[12, 123, 4414, 2] }
- 查找
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素
Object min(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最小元素
int binarySearch(List list,T key)在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合也事先必须是有序的,否则结果不确定。
int binarySearch(List list,T key,Comparator c)在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且集合也事先必须是按照c比较器规则进行排序过的,否则结果不确定。
int frequency(Collection c,Object o):返回指定集合中指定元素的出现次数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @Test public void test2(){ ArrayList list=new ArrayList(); list.add(123); list.add(12); list.add(2); list.add(4414); System.out.println("初始的list:"+list); //初始的list:[123, 12, 2, 4414] //寻找最大值 System.out.println("max(list)得出的最大值:"+Collections.max(list)); //max(list)得出的最大值:4414 System.out.println("max(list,Comparator)得出的最大值:"+Collections.max(list, new Comparator() { //max(list,Comparator)得出的最大值:4414 @Override public int compare(Object o1, Object o2) { int temp1=(int)o1; int temp2=(int)o2; return (temp1-temp2); //实现倒序 } })); //寻找最小值 System.out.println("min(list)得出的最小值:"+Collections.min(list)); //min(list)得出的最小值:2 System.out.println("min(list,Comparator)得出的最小值:"+Collections.min(list, new Comparator() { //min(list,Comparator)得出的最小值:2 @Override public int compare(Object o1, Object o2) { int temp1=(int)o1; int temp2=(int)o2; return (temp1-temp2); //实现倒序 } })); //查找某个元素的下标 //1.自然排序 Collections.sort(list); System.out.println("排序之后的list:"+list); // 排序之后的list:[2, 12, 123, 4414] //查找某个元素的下标 System.out.println(Collections.binarySearch(list,12)); //下标为1 //2.定制排序 System.out.println(Collections.binarySearch(list,12, new Comparator() { //下标为1 @Override public int compare(Object o1, Object o2) { int temp1=(int)o1; int temp2=(int)o2; return -(temp1-temp2); //实现倒序 } })); //查找指定元素的出现次数 System.out.println(Collections.frequency(list,412)); //412元素出现的次数为0 }
- 复制、替换
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
提供了多个unmodifiableXxx()方法,该方法返回指定 Xxx的不可修改的视图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Test public void test3(){ ArrayList list=new ArrayList(); list.add(123); list.add(12); list.add(2); list.add(4414); System.out.println("初始的list:"+list); //初始的list:[123, 12, 2, 4414] //复制 ArrayList list1=new ArrayList(); list1.add("AA"); list1.add("BA"); list1.add("CCC"); Collections.copy(list,list1); System.out.println("复制之后的list:"+list); //复制之后的list:[AA, BA, CCC, 4414] } ------------------------------------------------------------------------------------------------------------ @Test public void test4(){ ArrayList list=new ArrayList(); list.add(123); list.add(123); list.add(2); list.add(4414); System.out.println("初始的list:"+list); //初始的list:[123, 123, 2, 4414] //替换 Collections.replaceAll(list,123,"CC"); //替换之后的list:[CC, CC, 2, 4414] System.out.println("替换之后的list:"+list); }
- 添加
boolean addAll(Collection c,T… elements)将所有指定元素添加到指定 collection 中。
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test5(){ ArrayList list=new ArrayList(); list.add(123); list.add(12); list.add(2); list.add(4414); System.out.println("初始的list:"+list); //初始的list:[123, 12, 2, 4414] //添加 Collections.addAll(list,"CC","asd","ewr"); System.out.println("添加之后的list:"+list); //添加之后的list:[123, 12, 2, 4414, CC, asd, ewr] }
- 同步
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题:
7.2 练习 练习1:
请从键盘随机输入10个整数保存到List中,并按倒序、从大到小的顺序显示出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; public class CollectionTest1 { public static void main(String[] args) { ArrayList list=new ArrayList(); list.add(12); list.add(345); list.add(323); list.add(1233); list.add(3); list.add(123); list.add(232); list.add(122); list.add(167); list.add(789); System.out.println("初始的list:"+list); // 初始的list:[12, 345, 323, 1233, 3, 123, 232, 122, 167, 789] //排序的话 -- 默认排序(升序不可以) 那就只能实现Comparator接口进行定制排序 Collections.sort(list, new Comparator<Object>() { @Override public int compare(Object o1, Object o2) { int temp1=(int)o1; int temp2=(int)o2; return -(temp1-temp2); //倒序 } }); System.out.println("倒序之后的list:"+list); // 倒序之后的list:[1233, 789, 345, 323, 232, 167, 123, 122, 12, 3] } }
练习2: 模拟斗地主洗牌和发牌,牌没有排序
效果演示:
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package MapTest;import java.util.ArrayList;import java.util.Collections;public class CollectionTest2 { public static void main (String[] args) { String[] num = {"A" ,"2" ,"3" ,"4" ,"5" ,"6" ,"7" ,"8" ,"9" ,"10" ,"J" ,"Q" ,"K" }; String[] color = {"方片" ,"梅花" ,"红桃" ,"黑桃" }; ArrayList<String> poker = new ArrayList<>(); for (String s1 : color) { for (String s2 : num) { poker.add(s1.concat(" " + s2)); } } poker.add("小王" ); poker.add("大王" ); Collections.shuffle(poker); ArrayList tomCards = new ArrayList(); ArrayList jerryCards = new ArrayList(); ArrayList meCards = new ArrayList(); ArrayList lastCards = new ArrayList(); for (int i=0 ;i<17 ;i++){ tomCards.add(poker.get(i)); } for (int i=18 ;i<34 ;i++){ jerryCards.add(poker.get(i)); } for (int i=35 ;i<51 ;i++){ meCards.add(poker.get(i)); } for (int i=51 ;i<54 ;i++){ lastCards.add(poker.get(i)); } System.out.println("Tom:\n" + tomCards); System.out.println("Jerry:\n" + jerryCards); System.out.println("me:\n" + meCards); System.out.println("底牌:\n" + lastCards); } }
练习3: 模拟斗地主洗牌和发牌并对牌进行排序的代码实现。
提示:考查HashMap、TreeSet、ArrayList、Collections
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class PokerTest1 { public static void main (String[] args) { String[] num = {"3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" , "J" , "Q" , "K" , "A" , "2" }; String[] color = {"方片" , "梅花" , "红桃" , "黑桃" }; HashMap map = new HashMap(); ArrayList list = new ArrayList(); int index = 0 ; for (String s1 : num) { for (String s2 : color) { map.put(index, s2.concat(s1)); list.add(index); index++; } } map.put(index, "小王" ); list.add(index); index++; map.put(index, "大王" ); list.add(index); Collections.shuffle(list); TreeSet Tom = new TreeSet(); TreeSet Jerry = new TreeSet(); TreeSet me = new TreeSet(); TreeSet lastCards = new TreeSet(); for (int i = 0 ; i < list.size(); i++) { if (i >= list.size() - 3 ) { lastCards.add(list.get(i)); } else if (i % 3 == 0 ) { Tom.add(list.get(i)); } else if (i % 3 == 1 ) { Jerry.add(list.get(i)); } else { me.add(list.get(i)); } } lookPoker("Tom" , Tom, map); lookPoker("Jerry" , Jerry, map); lookPoker("康师傅" , me, map); lookPoker("底牌" , lastCards, map); } public static void lookPoker (String name, TreeSet ts, HashMap map) { System.out.println(name + "的牌是:" ); for (Object index : ts) { System.out.print(map.get(index) + " " ); } System.out.println(); } }