第四章
重点章节!!!
- 主要集合概述
- Collection 和 Iterator
- List
- Set
- Map
- Collections工具类
- Comparable与Comparator
集合概述
总结重点:
- 把继承结构图背会
- 把Collection接口常用方法测试几遍
- 把迭代弄明白
- Collection接口中的contains方法和remove方法底层都调用了equals方法,这个弄明白。
-
集合不能直接存储基本数据类型,另外集合也不能直接存储Java对象,集合当中存储的都是Java对象的地址(或者说集合当中存储的是引用)。如:list.add(100);//自动装箱Integer。
-
在Java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到不同的数据结构当中。数组、二叉树、链表、哈希表等,以上这些都是常见的数据结构。要掌握在不同情况下使用哪一种合适的集合去使用即可。
new ArrayList();创建一个集合,底层是数组。
new LinkedList();创建一个集合,底层是链表。
new TreeSet();创建一个集合,底层是二叉树。
-
集合在java JDK那个包下?
java.util.*
所有的集合类和集合接口都在java.util包下。
-
集合继承图!!!(必须掌握)
Collection集合继承图:
注意:HashSet底层是一个HashMap,TreeSet底层是一个TreeMap。
Map集合继承图:
总结(所有的实现类):
ArrayList:底层是数组,初始化容量是10,扩容因子是1.5。
LinkedList:底层是双链表。
Vector:底层是数组,线程安全的,效率较低,使用较少。
HashSet:底层是HashMap,放到HashSet中的元素等同于放到HashMap集合key部分。
TreeSet:底层是TreeMap,放到TreeSet的元素等同于放到TreeMap集合的key部分。
HashMap:底层是哈希表,初始化容量16,加载因子0.75,初始化容量必须是2的倍数,k,v允许为null。
Hashtable:底层也是哈希表,只不过线程安全的,效率较低,使用较少,初始化容量是11,扩容是 原容量乘以2加1,k和v都不能为null。
Properties:是线程安全的,并且key和value只能存储字符串String类型。
TreeMap:底层是二叉树,TreeMap集合的key可以自动按大小顺序排序。
List集合存储元素的特点:有序可重复。
Set(Map)集合存储元素的特点:无序不可重复。
SortedSet(SortedMap)存储元素的特点:首先是无序不可重复的,但是SortedSet集合中的元素是可排序的(可以按照大小顺序排列)。
记住:Map集合的key就是一个Set集合!!!往Set集合中放数据,实际上放到了Map集合的key部分。
Collection 和 Iterator
Collection接口常用方法:
/**
* 关于java.util.Collection接口中常用的方法。
* 1、Collection中能存放什么元素?
* 没有使用“泛型”之前,Collection中可以存储Object的所有子类型。
* 使用了“泛型”之后,Collection中只能存储某个具体的类型。
* 集合中不能直接存储基本数据类型,也不能存java对象,只是存储java对象的内存地址。。
* 2、Collection中的常用方法
* boolean add(Object e); 向集合中添加元素
* int size() 获取集合中元素的个数
* void clear() 清空集合
* boolean contains(Object o) 判断当前集合是否包含元素o
* boolean remove(Object o) 删除集合中某个元素
* boolean isEmpty() 判断集合中是否为空
* Object[] toArray(); 将集合转化为数组。
*/
public class CollectionTest01 {
public static void main(String[] args) {
//Collection c = new Collection();//接口1完全抽象的,无法new对象。
//多态
Collection c = new ArrayList();
//测试Collection接口中的常用方法。
c.add(1200);//自动装箱(java5的新特性),实际上是放进去了一个对象的内存地址。Integer x = new Integer(1200);
c.add(true);
c.add(3.14);
c.add(new Object());
c.add(new Student());
//获取集合元素的个数
System.out.println("集合元素的个数:"+c.size());//5
//清空集合
c.clear();
System.out.println("集合元素的个数:"+c.size());//0
//再向集合中添加元素
c.add("hello");//"hello"对象的内存地址放到了集合当中。
c.add("world");
c.add("浩克");
c.add("绿巨人");
c.add(1);
//判断集合中是否包含绿巨人
boolean flat = c.contains("绿巨人");
System.out.println(flat);//true
boolean flat2 = c.contains("绿巨人2");
System.out.println(flat2);//false
System.out.println(c.contains(1));//true
System.out.println("集合元素的个数:"+c.size());//5
//删除集合中某个元素
c.remove(1);
System.out.println("集合元素的个数:"+c.size());//4
c.clear();
System.out.println(c.isEmpty());//true
c.add("abc");
c.add("def");
c.add(100);
c.add("hello world");
//转换成数组 使用不多,了解即可。
Object[] objects = c.toArray();
for (int i=0;i<objects.length;i++){
System.out.println(objects[i]);
}
}
}
class Student{
}
Iterator迭代器:
/**
* 关于集合遍历/迭代专题。(重点:五颗星*****)
*/
public class CollectionTest02 {
public static void main(String[] args) {
//注意:以下讲解的遍历方式,是所有Collection通用的一种方式。
//在Map集合中不能用。在所有的Collection以及子类中使用。
//创建集合对象
Collection c = new HashSet();
c.add("abc");
c.add("def");
c.add(true);
c.add(new Object());
//对集合Collection进行遍历/迭代
//第一步:获取集合对象的迭代器对象Iterator
Iterator it = c.iterator();
//第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
/*
以下两个方法是迭代器Iterator中的方法:
boolean hasNext() 如果仍有元素可以迭代,则返回true。
Object next() 返回迭代的下一个元素。
*/
while (it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
}
/**
* 关于集合的迭代/遍历
*/
public class CollectionTest03 {
public static void main(String[] args) {
//创建集合对象
Collection c = new ArrayList();//ArrayList集合有序可重复。
//添加元素
c.add(1);
c.add(2);
c.add(3);
c.add(4);
c.add(1);
//迭代集合
Iterator it = c.iterator();
while (it.hasNext()){
//存进去什么类型,取出来还是什么类型。
Object obj =it.next();
/*if (obj instanceof Integer){
System.out.println("Integer类型");
}*/
//只不过在输出的时候会转换为字符串,因为println会调用toString()方法。
System.out.println(obj);
}
//HashSet集合:无序不可重复。
Collection c2 = new HashSet();
c2.add(100);
c2.add(200);
c2.add(300);
c2.add(100);
c2.add(50);
c2.add(20);
c2.add(30);
c2.add(100);
System.out.println("HashSet元素个数:"+c2.size());//3
Iterator it2 = c2.iterator();
while (it2.hasNext()){
Object obj = it2.next();
System.out.println(obj);
}
}
}
/*
运行结果:
1
2
3
4
1
HashSet元素个数:6
50
100
20
200
300
30
*/
深入Collection的contains以及remove方法
/**
* 深入Collection的contains方法。
* boolean contains(Object o)
* 判断集合中是否包含某个对象o
* 如果包含返回true,如果不包含返回false
*
* contains方法是用来判断集合中是否包含某个元素的方法,
* 那么它在底层到底怎样判断集合中是否包含某个元素的呢?
* 调用了equals方法进行对比。
* equals方法返回true就表示包含这个元素。
*/
public class CollectionTest04 {
public static void main(String[] args) {
//创建集合对象
Collection c =new ArrayList();
//向集合中存储元素
String s1 = new String("abc");
c.add(s1);
String s2 = new String("def");
c.add(s2);
//集合遍历
Iterator it = c.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
//集合元素个数
System.out.println("集合元素个数:"+c.size());
//新建对象String
String x = new String("abc");
//c集合中是否包含x? 不包含(见上面内存图) 那为什么contains以及equals比较出来都是true呢?
System.out.println(c.contains(x));//true 因为contains方法重写了equals方法,比较的是内容是否相同!!!
System.out.println(x.equals(s1));//true String类重写equals方法
}
}
测试contains方法和remove方法:
/**
* 测试contains方法和remove方法
*/
public class CollectionTest05 {
public static void main(String[] args) {
Collection c = new ArrayList();
User u1 = new User("jack");
User u2 = new User("jack");
c.add(u1);
//判断集合中是否包含u2
System.out.println(c.contains(u2));//false
//contains底层调用的equals方法,但是User类没有重写equals方法,的u1和u2比较用的是==,
//==比较的是u1和u2保存的堆区对象地址,new了两次,地址显然不同,所以为false。
//重写equals方法后就比较的是内容。结果为true!!!
Integer i1 = new Integer("10000");
c.add(i1);
Integer i2 = new Integer("10000");
System.out.println(c.contains(i2));//true 因为Integer类重写了equals方法!!
//remove 方法
Collection cc = new ArrayList();
String s1 = new String("hello");
cc.add(s1);
System.out.println("集合元素个数:"+cc.size());//1
String s2 = new String("hello");
cc.remove(s2);//s1.equals(s2) java认为s1和s2是一样的,删除s2就是删除s1。
System.out.println("集合元素个数:"+cc.size());//0 remove成功
}
}
class User{
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
}
重写equals方法后就比较的是内容。结果为true!!!
放在集合里面的元素要重写equals方法!!!这样比较的就是内容是否相等
关于集合中元素的删除:
/**
* 关于集合元素的remove
* 重点:当集合结构发生了改变,迭代迭必须重新获取,如果还是用以前老的迭代器,会出现
* 异常:java.util.ConcurrentModificationException
*
* 重点:在迭代集合元素过程中,不能调用集合对象的remove方法,删除元素
* c.remove(obj);迭代过程中不能这样。
* 会出现:java.util.ConcurrentModificationException
*
* 在集合迭代过程中使用迭代器删除还是可以的!!!
*
* 重点:在迭代元素的过程当中,一定要使用迭代器Iterator的remove方法删除元素,
* 不要使用集合自带的remove方法删除元素。
*/
public class CollectionTest06 {
public static void main(String[] args) {
//创建集合
Collection c = new ArrayList();
//注意:此时获取的迭代器,指向的是那个集合中没有状态的迭代器。
//一定要注意:集合结构只要发生改变,迭代器必须重新获取。
//当集合结构发生了改变,迭代没有重新获取时,调用next()方法时会出现java.util.ConcurrentModificationException异常
//Iterator it =c.iterator();
//添加元素
c.add(1);//Integer类型
c.add(2);
c.add(3);
//获取迭代器
Iterator it = c.iterator();
while (it.hasNext()){
//编写next()方法返回值类型必须是Object
Object obj = it.next();
System.out.println(obj);
}
Collection c2 = new ArrayList();
c2.add("abc");
c2.add("def");
c2.add("xyz");
Iterator it2 = c2.iterator();
while (it2.hasNext()){
Object obj = it2.next();
//删除元素
//删除元素之后,集合的结构发生了变化,应该重新去获取迭代器,
//但是,循环下一次的时候并没有重新获取迭代器,所以会出现异常。java.util.ConcurrentModificationException
//出异常的根本原因是:集合中元素删除了,但是没有更新迭代器。
//c2.remove(obj);
//使用迭代器来删除可以吗? 可以
//迭代器删除时会自动更新迭代器,并更新集合(删除集合中的元素)。
it2.remove();//使用迭代器删除的一定是迭代器指向的当前元素。
System.out.println(obj);
}
System.out.println("集合中的元素个数:"+c2.size());
}
}
List集合常用方法
/**
* 测试LiST接口中常用方法
* 1、List集合存储特点:有序可重复
* 2、List仍然是Collection的子接口,那么肯定List接口有自己"特有"的方法:
* 以下只列出List接口特有的常用的方法:
* void add(int index,E element)
* Object get(int index)
* int indexOf(Object o)
* int lastIndexOf(Object o)
* Object remove(int index)
* Object set(int index, E element)
*
*
*/
public class ListTest01 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("A");//默认是想列表的尾部插入元素。
list.add("B");
list.add("C");
list.add("D");
//将KING插入到集合下标为1的位置。
//这个方法使用不多,因为ArrayList底层是数组,效率低,需要移动大量元素。
list.add(1,"KING");
//迭代
Iterator it = list.iterator();
while (it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
System.out.println("============================================");
//根据下标获取元素
Object o = list.get(2);//B
System.out.println(o);
System.out.println("============================================");
//因为有下标,所以List集合有自己比较特殊的遍历方式
//通过下标遍历
for(int i = 0;i<list.size();i++){
Object object = list.get(i);
System.out.println(object);
}
System.out.println("============================================");
//获取指定对象第一次出现的索引
System.out.println(list.indexOf("KING"));//1
//获取指定对象最后一次出现的索引
System.out.println(list.indexOf("C"));//3
//删除List指定下标的元素
list.remove(3);//删除了C
System.out.println(list.size());//4
System.out.println("================================");
//修改指定位置的元素的值
list.set(2,"Soft");//将B修改为了Soft
//遍历
for (int i = 0;i<list.size();i++){
Object object = list.get(i);
System.out.println(object);
}
}
}
ArrayList集合
/**
* ArrayList集合:
* 1、默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为10)
* 2、集合底层是一个Object数组。
* 3、构造方法:
* new ArrayList();
* new ArrayList(20);
* 4、ArrayList集合的扩容:
* 原容量的1.5倍。
* ArrayList集合底层是数组,怎么优化?
* 尽可能少的扩容。因为数组扩容的效率比较低,建议使用ArrayList集合的
* 时候预估以下元素的个数,给的一个初始化容量。
* 5、数组优点:
* 检索效率比较高。(每个元素占用的空间大小相同,内存地址连续,
* 知道元素内存地址以及下标就可以计算出元素的内存地址,所以检索效率最高)
* 6、数组缺点:
* 随机增删元素效率比较低。
* 数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间)
* 7、向数组末尾添加元素,效率很高,不受影响。
* 8、面试题:这么多集合中,哪个用的最多?
* 答:ArrayList集合。
* 因为在数组末尾添加元素,效率不受影响。
* 另外,我们检索/查询某个元素比较多。
* 9、ArrayList集合是非线程安全的。
*/
public class ArrayListTest01 {
public static void main(String[] args) {
//默认初始化容量是10
List list1 = new ArrayList();
//集合size()方法是获取当前集合中元素的个数,不是获取集合的容量。
System.out.println(list1.size());//0
//指定初始化容量
List list2 = new ArrayList(20);
System.out.println(list2.size());//0
}
}
ArrayList集合的构造方法:
/**
* 集合ArrayList的构造方法
*/
public class ArrayListTest02 {
public static void main(String[] args) {
//默认初始化容量10
List myList1 = new ArrayList();
//指定初始化容量100
List myList2 = new ArrayList(100);
//创建一个HashSet集合
Collection c = new HashSet();
//添加元素到Set集合
c.add(100);
c.add(200);
c.add(500);
c.add(900);
//通过这个构造方法可以将HashSet集合转换成List集合
List myList3 = new ArrayList(c);//
for (int i =0;i<myList3.size();i++){
Object obj = myList3.get(i);
System.out.println(obj);
}
}
}
单链表数据结构
/**
* 单链表中的节点。
* 节点是单向链表中基本的单元。
* 每个Node都有两个属性
* 一个属性:是存储的数据
* 另一个属性:存储的是下一个节点的内存地址。
*/
public class Node {
//存储的数据
Object element;
//下一个节点的内存地址
Node next;
public Node() {
}
public Node(Object element, Node next) {
this.element = element;
this.next = next;
}
}
public class Link {
//头结点
Node header = null;
//向链表中添加元素
public void add(Object data){//尾插法
Node t = header;
if (header==null){
header = new Node(data,null);
}else {
while (t.next!=null){//获取末尾节点
t=t.next;
}
t.next=new Node(data,null);//将新节点尾插法插入到链表末尾。
}
}
//删除元素
public void remove(Object data){
/*Node t = header;
if (header.element==data){
header=header.next;
}else {
while (t.next!=null){
if (t.next.element==data){
t.next = t.next.next;
}
t = t.next;
}
}*/
}
//修改某个元素
public void modify(Object data){
}
//查找某个元素
public int find(Object data){
/*Node t = header;
int cnt = 0;
while (t!=null){
if (t.element==data){
return cnt;//返回下标
}
t=t.next;
cnt++;
}*/
return -1;
}
public void printAll(){
Node t = header;//第一个元素地址
while (t!=null){
System.out.println(t.element);
t=t.next;
}
}
}
public class Test01 {
public static void main(String[] args) {
Link link = new Link();
link.add(100);//添加元素
link.add(200);
link.add(300);
link.add(400);
link.add(500);
link.printAll();
System.out.println("======================================");
/*link.remove(300);//删除元素
link.printAll();
System.out.println("======================================");
int index = link.find(200);
System.out.println("index:="+index);*/
}
}
链表的优缺点:
LinkedList集合
Vector
/**
* Vector:
* 1、底层也是一个数组。
* 2初始化容量:10
* 3、怎么扩容的?
* 扩容之后是原容量的2倍。
* 10-->20-->40-->80
* 4、ArrayList集合扩容特点:
* 扩容之后是原容量1.5倍
* 10-->15->15*1.5
* 5、Vector中所有的方法都是线程同步的,都带有synchronized关键字,
* 是线程安全的。
* 6、怎么将一个线程不安全的ArrayList集合转换为线程安全的呢?
* 使用集合工具类:
* java.util.Collection;
*
* java.util.Collection 是集合接口
* java.util.Collections 是集合工具类
*
*
*/
public class VectorTest {
public static void main(String[] args) {
//创建一个Vector集合
List v = new Vector();//使用多态
//Vector v = new Vector();
//添加元素
//默认容量是10个
v.add(1);
v.add(2);
v.add(3);
v.add(4);
v.add(5);
v.add(6);
v.add(7);
v.add(8);
v.add(9);
v.add(10);
//满了之后扩容,扩容之后的容量是20.
v.add(11);
Iterator it = v.iterator();
while (it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
//这个可能以后要使用
List myList = new ArrayList();//非线程安全的。
//变成线程安全的
Collections.synchronizedList(myList);
//这样myList集合就是线程安全的。
myList.add("111");
myList.add("222");
myList.add("333");
}
}
泛型机制
/**
*1、 JDK5.0之后推出的新特性:泛型
*2、泛型这种语法机制,只在程序编译阶段起作用,值是给编译器参考的。(运行阶段泛型没用!)
*3、使用泛型有什么好处?
* 第一:集合中存储的元素类型统一了。
* 第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的强制类型转换了。
*4、泛型的缺点是什么?
* 导致集合中存储的元素缺乏多样性。
* 大多数业务中,集合的类型还是统一的,所这种泛型特性被大家所认可。
*
*/
public class GenericTest01 {
public static void main(String[] args) {
/*
//不使用泛型机制,分析程序存在的缺点。
List myList = new ArrayList();
//准备对象
Cat c = new Cat();
Bird b = new Bird();
//将对象添加到集合当中
myList.add(c);
myList.add(b);
//遍历集合,取出猫让它抓老鼠,取出鸟让它飞
Iterator it = myList.iterator();
while (it.hasNext()){
Object obj =it.next();
if (obj instanceof Cat){//向下转型
((Cat)obj).catchMouse();
}
if (obj instanceof Bird){//向下转型
((Bird)obj).fly();
}
}
*/
//使用JDK5之后的泛型机制
//使用泛型List<Animal>之后,表示List集合只允许存储Animal类型的数据。
List<Animal> myList = new ArrayList<Animal>();
myList.add(new Cat());
myList.add(new Bird());
//获取迭代器
//这个表示迭代器迭代的是Animal类型。
Iterator<Animal> it = myList.iterator();
while (it.hasNext()){
//使用泛型之后,每次迭代返回的数据都是Animal类型。
Animal a = it.next();
//这里不需要进行强制类型转换。直接调用。
a.move();
//调用子类特有方法还是需要向下转型的!!!
}
}
}
class Animal{
//父类自带方法
public void move(){
System.out.println("动物在移动!");
}
}
class Cat extends Animal{
//特有方法
public void catchMouse(){
System.out.println("猫抓老鼠!");
}
}
class Bird extends Animal{
//特有方法
public void fly(){
System.out.println("鸟儿在飞翔!");
}
}
/**
*JDK8之后引入了:自动类型推断机制。(又称为钻石表达式<>)
*/
public class GenericTest02 {
public static void main(String[] args) {
//ArrayList<这里的类型会自推断>(),前提是JDK8之后才允许。
List<Animal> myList = new ArrayList<>();
myList.add(new Animal());
myList.add(new Bird());
myList.add(new Cat());
//遍历
Iterator<Animal> it = myList.iterator();
while (it.hasNext()){
Animal a = it.next();
a.move();
}
List<String> myList2 = new ArrayList<>();
//类型不匹配。
//myList2.add(new Cat());
myList2.add("http://www.126.com");
myList2.add("http://www.baidu.com");
myList2.add("http://www.qq.com");
//类型不匹配。
//myList2.add(123);
System.out.println(myList2.size());
//遍历
Iterator<String> it2 = myList2.iterator();
while (it2.hasNext()){
String str = it2.next();
System.out.println(str.substring(7));
}
}
}
foreach
/**
* JDK5.0之后推出了一个新特性:foreach
*/
public class ForEachTest01 {
public static void main(String[] args) {
int[] arr = {1,200,8,500,36,99,124};
//
for (int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
System.out.println("===============================");
//foreach方式
for(int data:arr){
System.out.println(data);
}
}
}
/**
* 集合使用foreach
*/
public class forEachTest02 {
public static void main(String[] args) {
//创建List集合
List<String > list = new ArrayList<>();
//添加元素
list.add("hello");
list.add("ketty");
list.add("world");
//遍历,使用迭代器方式
Iterator<String> it =list.iterator();
while (it.hasNext()){
String str =it.next();
System.out.println(str);
}
System.out.println("===============================");
//遍历,使用下标的方式
for (int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
System.out.println("===============================");
//遍历,使用foreach方式(推荐使用!!!)
for (String data:list){
System.out.println(data);
}
System.out.println("===============================");
List<Integer> list1 = new ArrayList<>();
list1.add(100);
list1.add(200);
list1.add(300);
for (Integer data:list1){//data代表集合中的元素
System.out.println(data);
}
}
}
HashSet集合
/**
* HashSet集合:
* 无序不可重复
*/
public class HashSetTest01 {
public static void main(String[] args) {
//演示一下HashSet集合特点
Set<String> strs = new HashSet<>();
//添加元素
strs.add("hello3");
strs.add("hello3");
strs.add("hello1");
strs.add("hello2");
strs.add("hello3");
System.out.println(strs.size());
//遍历
/*
3
hello1
hello2
hello3
1、存储时顺序和取出的顺序不同。
2、不可重复。
3、放到HashSet集合的元素实际上是放到了HashMa集合的key部分。
*/
for (String data:strs){
System.out.println(data);
}
}
}
TreeSet集合
/**
*TreeSet集合存储元素特点:
* 1、无序不可重复,但是存储的元素可以自动按照大小顺序排序,又称为可排序集合。
*/
public class TreeSetTest01 {
public static void main(String[] args) {
Set<String> set = new TreeSet();
set.add("222");
set.add("222");
set.add("222");
set.add("111");
set.add("333");
set.add("111");
set.add("111");
set.add("111");
//遍历
for (String data:set){
System.out.println(data);
}
}
}
/*
结果:
111
222
333
*/
/**
* 1、TreeSet集合底层实际上是一个TreeMap
* 2、TreeMap集合底层是一个二叉树。
* 3、放到TreeSet集合的元素,等同于放到TreeMap集合的key部分
* 4、TreeSet集合中的元素:无序不可重复,但是按元素的大小顺序自动排序。
* 称为:可排序集合
*/
public class TreeSetTest02 {
public static void main(String[] args) {
//创建一个TreeSet集合
Set<String> set = new TreeSet<>();
//添加元素
set.add("1999");
set.add("1996");
set.add("2000");
set.add("1997");
//遍历
for (String data:set){//有序的
System.out.println(data);
}
}
}
/**
* 对于自定义类型TreeSet可以自动排序吗?
* 以下程序对于Person类型来说,无法排序。因为没有指定Person对象的比较规则
* 谁大谁小并没有说明啊。
*
* 以下程序运行时会出现这个异常:
* java.lang.ClassCastException
* com.chinasoft.javase.set.Person
* cannot be cast to java.lang.Comparable
* 出现这个异常的原因是:
* Person类没有实现java.util.Comparable接口
*/
public class TreeSetTest03 {
public static void main(String[] args) {
TreeSet<Person> ts = new TreeSet<>();
Person p1 = new Person(32);
Person p2 = new Person(28);
Person p3 = new Person(48);
Person p4 = new Person(18);
ts.add(p1);
ts.add(p2);
ts.add(p3);
ts.add(p4);
for (Person data:ts){
System.out.println(data);
}
}
}
class Person{
int age;
public Person(int age) {
this.age = age;
}
}
Map集合常用方法
/**
/**
* java.util.Map接口中的常用方法:
* 1、Map和Collection没有继承关系。
* 2、Map集合以key和value的方式存储数据:键值对。
* key和value都是引用数据类型。
* key和value都是存储对象的内存地址。
* key起到主导的地位,value是key的一个附属品。
* 3、Map接口中常用的方法:
* V put(K key,V value); 向集合中添加元素
* V get(Object key); 通过key获取value
* void clear(); 清空Map集合
* boolean containsKey(Object key); 判断Map集合是否包含某个key
* boolean containsValue(Object value); 判断Map集合是否包含某个value
* boolean isEmpty(); 判断Map集合中元素个数是否为0
* Set<K> keySet(); 获取Map集合所有的key(所有的键是一个Set集合)
*
* V remove(K key,V value); 通过key删除元素
* int size();
* Collection<V> values(); 获取Map集合中所有的value,返回一个Collection
*
* Set<Map.Entry<K,V>> entrySet();
* 将Map结合转换为Set集合
* 假设现在有一个Map集合,如下所示:
* map1集合
* key value
* ----------------------
* 1 zhangsan
* 2 lisi
* 3 wangwu
* 4 zhaoliu
* Set set = map1.entrySet();
* set集合对象:
* 1=zhangsan 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是Map.Entry<K,V>】
* 2=lisi 【Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类】 * 3=wangwu
* 4=zhaoliu --->这个东西是个什么? Map.Entry
*
*/
public class MapTest01 {
public static void main(String[] args) {
//创建Map对象
Map<Integer,String> map = new HashMap<>();
//向Map中添加键值对
map.put(1, "aaa");//key在这里自动装箱
map.put(2, "bbb");
map.put(3, "ddd");
map.put(4, "ccc");
//通过key获取value
String value = map.get(3);
System.out.println(value);
//获取键值对数量
System.out.println("键值对的数量:"+map.size());//4
//contains方法底层调用的都是equals方法,所以自定义的类型需要重新equals方法。
//通过key删除键值对
map.remove(4);
System.out.println("键值对的数量:"+map.size());//3
//判断是否包含某个key
System.out.println(map.containsKey(2));//true
//判断是否包含某个value
System.out.println(map.containsValue("aaa"));//true
//获取所有的value
Collection<String> c = map.values();
for (String data:c){
System.out.println(data);
}
//清空集合
map.clear();
System.out.println("键值对的数量:"+map.size());//0
}
}
/*
结果:
ddd
键值对的数量:4
键值对的数量:3
true
true
aaa
bbb
ddd
键值对的数量:0
*/
静态内部类:
/**
* 静态内部类
*/
public class MyClass {
//声明一个静态内部类
private static class InnerClass{
//静态方法
public static void m1(){
System.out.println("静态内部类的m1方法执行");
}
//实例方法
public void m2(){
System.out.println("静态内部类的实例方法m2执行!");
}
}
public static void main(String[] args) {
//类名叫做:MyClass.InnerClass
MyClass.InnerClass.m1();//调用静态内部类的静态方法
MyClass.InnerClass mi = new MyClass.InnerClass();
mi.m2();//调用静态内部类的实例方法
//给一个set集合
//该set集合中存储的对象是:MyClass.InnerClass类型
Set<MyClass.InnerClass> set = new HashSet<>();
set.add(new MyClass.InnerClass());
set.add(new MyClass.InnerClass());
//set.add("12300");//类型不匹配
System.out.println(set.size());
for (MyClass.InnerClass data:set){
System.out.println(data);
}
System.out.println("======================================");
//<>里面是类型,这里是 一个静态内部类类型加上了泛型
Set<MyClass.MyEntry<String,Integer>> set1 = new HashSet<>();
set1.add(new MyClass.MyEntry<String, Integer>());
set1.add(new MyClass.MyEntry<String, Integer>());
//set1.add("aaa");//类型不匹配
System.out.println(set1.size());
for (MyClass.MyEntry data:set1){
System.out.println(data);
}
}
public static class MyEntry<K,V>{
}
}
/*
结果:
静态内部类的m1方法执行
静态内部类的实例方法m2执行!
2
com.chinasoft.javase.map.MyClass$InnerClass@4554617c
com.chinasoft.javase.map.MyClass$InnerClass@1b6d3586
======================================
2
com.chinasoft.javase.map.MyClass$MyEntry@74a14482
com.chinasoft.javase.map.MyClass$MyEntry@1540e19d
*/
Map集合的遍历
必须掌握!!!
方式二内存图(将map元素放到set集合):
/**
* Map集合的遍历。
* 第一种方式:获取所有的key,通过遍历key,来遍历value(适合小数据量)
* 第二种方式:Set<Map.Entry<K,V>> entrySet();(适合大数据量,效率高,相比于第一种,不需要先查keys集合)
* 第二种方式效率高!!!
*
*/
public class MapTest02 {
public static void main(String[] args) {
//第一种方式:获取所有的key,通过遍历key,来遍历value
Map<Integer,String> map = new HashMap<>();
map.put(1,"hello");
map.put(2, "ketty");
map.put(6, "lisi");
map.put(3, "王五");
Set<Integer> keys = map.keySet();
//foreach方式遍历
for (Integer key:keys){
System.out.println("key:"+key+" value:"+map.get(key));
}
System.out.println("==============================");
//迭代器方式遍历
Iterator<Integer> it = keys.iterator();
while (it.hasNext()){
Integer key =it.next();
System.out.println("key:"+key+" value:"+map.get(key));
}
System.out.println("=====================================");
//第二种方式:Set<Map.Entry<K,V>> entrySet();
//该方法是把Map集合直接转换成Set集合
//Set集合中的元素是Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet();
//foreach遍历方式
for (Map.Entry<Integer,String> data:set){
System.out.println("key:"+data.getKey()+" value:"+data.getValue());
}
System.out.println("==========================");
//迭代器遍历方式
//效率高,适合大数据量
Iterator<Map.Entry<Integer,String>> it2 =set.iterator();
while (it2.hasNext()){
Map.Entry<Integer,String> me = it2.next();
System.out.println(me);
}
}
}
/*
结果:
key:1 value:hello
key:2 value:ketty
key:3 value:王五
key:6 value:lisi
==============================
key:1 value:hello
key:2 value:ketty
key:3 value:王五
key:6 value:lisi
=====================================
key:1 value:hello
key:2 value:ketty
key:3 value:王五
key:6 value:lisi
==========================
1=hello
2=ketty
3=王五
6=lisi
*/
哈希表
/**
* HashMap集合:
* 1、HashMap集合底层是哈希表/散列的数据结构。
* 2、哈希表是一个怎样的数据结构?
* 哈希表是一个数组和单链表的结合体
* 数组:在查询方面效率较高,随机增删方面效率很低。
* 单向链表:在随机增删方面效率较高,在查询方面效率较低。
* 哈希表将以上两种数据结构融合在一起,充分发挥了各自的优点。
* 3、HashMap集合底层的源代码:
* public class HashMap{
* //HashMap底层实际上就是一个数组(一维数组)
* Node<K,V>[] table;
*
* //静态内部类HashMap.Node
* static class Node<K,V> implements Map.Entry<K,V>{
* final int hash;//哈希值
* final K key;
* V value;
* Node<K,V> next;//Node指针,指向下一个元素。
* }
* 哈希表/散列表:一维数组,这个数组中每一个元素是一个单项链表。(数组和链表的结合体)
* 4、最主要掌握的是:
* map.put(k,v)
* map.get(k)
* 以上两个方法的实现原理是必须掌握的。
* 5、HashMap集合key部分特点:
* 无序不可重复。
* 为什么无序?因为不一定挂到哪个单向链表上。
* 不可重复是怎么保证的?equals方法来保证HashMap集合的key不可重复。
* 如果key重复了,value会覆盖掉。
*
* 放在HashMap集合key部分的元素其实就是放到HashSet集合中了。
* 所以HashSet集合中的元素也需要重写hashCode()和equals()方法。
* 6、假设将哈希表的hashCode()方法返回值固定为某个值,那么就与纯单链表没有区别.
* 这种情况散列不均匀,无法发挥散列表的优点。
* 什么是分布均匀?
* 假设100个元素,10个单向联邦,那么每个联邦有10个元素,这就是最好的情况
* ,散列分布均匀。
* 假设将每个元素的hashCode()值都返回不一样的,那么就与纯数组没有区别,散列分布不均匀,
* 无法发挥散列表的优点。
*
* 散列分布均匀需要你在重新hashCode()方法的时候有一定的技巧。
* 7、重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,
* 需要同时重写hashCode()方法和equals()方法!!
*
* 8、HashMap集合的初始化容量是16,默认加载因子是0.75
* 这个加载因子是当HashMap集合底层数组容量达到75%的时候,数组开始扩容。
*
* 9、HashMap集合初始化的时候,初始化容量必须是2的倍数,这也是官方推荐的,
* 专硕因为达到散列均匀,为了提高HashMap集合的存储效率,所必须的。
*/
public class HashSetTest02 {
public static void main(String[] args) {
//测试HashMap集合key部分的元素特点
//Integer是key,它的hashCode()方法和equals()方法都重写了。
Map<Integer,String> map = new HashMap<>();
map.put(1,"aaa");
map.put(2, "bbb");
map.put(3, "ccc");
map.put(2, "wanglei");//key值一样,更新value即可。
System.out.println(map.size());
//遍历
Set<Map.Entry<Integer,String>> set = map.entrySet();
for (Map.Entry<Integer,String> data:set){
//验证结果:HashMap集合key部分无序不可重复。
System.out.println("key:"+data.getKey()+" value:"+data.getValue());
}
}
}
/**
*1、向Map集合中存,以及向Map集合中取,都是先调用key的hashCode方法,然后
* 再调用equals方法。
* equals方法可能调用,也有可能不调用。
* 拿put(k,v)举例,什么时候equals不会调用?
* k.hashCode()方法返回哈希值
* 哈希值讲经过哈希算法转换成数组下标
* 数组下标位置上是null,equals不需要执行。
* 拿get(k) 举例,什么时候equals方法不会调用?
* 如果数组下标位置上是null。不需要调用equals方法。
*
* 2、如果一个类的equals方法重写了,那么hashCode()方法必须重写。
*
*/
public class HashMapTest02 {
public static void main(String[] args) {
Student s1 = new Student("张三");
Student s2 = new Student("张三");
System.out.println(s1.equals(s2));
//false 没有重写equals方法
//重写equals之后是true
//不重写hashCode()方法,s1和s2的hashCode肯定不一样!!!
//重写了hashCode()方法,就一样了
System.out.println("s1的hashCode="+s1.hashCode());//460141958
System.out.println("s2的hashCode="+s2.hashCode());//1163157884
//s1.equals(s2)结果已经是true了,表示s1和s2是一样的,相同的,那么
//往HashMap集合中放的话,只能放进去个。(HashSet集合特点:无序不可重复)
Set<Student> set = new HashSet<>();
set.add(s1);
set.add(s2);
//如果不重写hashCode()方法,那么就是2个元素了!!就不符合hashSet集合的存储特点了。
System.out.println(set.size());//1
}
}
/**
* HashMap集合key部分允许为null吗? 允许
*/
public class HashMapTest03 {
public static void main(String[] args) {
Map map = new HashMap();
//HashMap集合允许key为空
map.put(null, null);
System.out.println(map.size());//1
//
map.put(null, "123");
System.out.println(map.size());//1
System.out.println(map.get(null));//123
}
}
Properties
/**
* 1、目前只需要掌握Properties属性类对象的相关方法即可。
* 2、Properties是一个Map集合,继承Hashtable,Properties
* 的key和value都是String类型。
* 3、Properties被称为属性类对象。
* 4、Properties是线程安全的。
*/
public class PropertiesTest01 {
public static void main(String[] args) {
//创建一个Properties对象
Properties p = new Properties();
//需要掌握Properties的两个方法,一个存,一个取。
p.setProperty("name", "王磊");
p.setProperty("url","jdbc:mysql://localhost:3306/chinasoft");
System.out.println(p.getProperty("name"));
System.out.println(p.getProperty("url"));
}
}
自定义类型实现Comparable接口
TreeSet和TreeMap是自平衡二叉树:
方式一:通过实现Comparable接口,并重写compareTo()方法
public class TreeSetTest04 {
public static void main(String[] args) {
TreeSet<Customer> ts = new TreeSet<>();
Customer c1 = new Customer(32);
Customer c2 = new Customer(28);
Customer c3 = new Customer(48);
Customer c4 = new Customer(18);
ts.add(c1);
ts.add(c2);
ts.add(c3);
ts.add(c4);
for (Customer data:ts){
System.out.println(data.age);
}
}
}
//放在TreeSet集合中的元素需要实现java.lang.Comparable接口。
//并且实现compareTo方法。equals可以不写。
class Customer implements Comparable<Customer>{
int age;
public Customer(int age) {
this.age = age;
}
@Override
public int compareTo(Customer c) {
return this.age-c.age;//
}
}
方式二:通过自定义比较器并实现Comparator接口
/**
* TreesSet集合元素可排序的第二种方式:使用比较器的方式。
*
* Comparable和Comparator怎么选择呢?
* 当比较规则不会发生改变的时候,或者说当比较规则只有一个的时候,建议使用Comparable接口。
* 如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。
*
*/
public class TreeSetTest06 {
public static void main(String[] args) {
//Set<WuGui> set = new TreeSet<>();//这样不行,没有通过构造方法传递一个比较器。
//Set<WuGui> set = new TreeSet<>(new WuGuiComparator());//通过构造方法传比较器
//这里也可以使用匿名内部类的方式new比较器。
Set<WuGui> set = new TreeSet<>(new Comparator<WuGui>() {
@Override
public int compare(WuGui o1, WuGui o2) {
return o1.age-o2.age;//升序
}
});
set.add(new WuGui(24));
set.add(new WuGui(18));
set.add(new WuGui(36));
set.add(new WuGui(16));
for (WuGui data:set){
System.out.println(data.age);
}
}
}
//乌龟
class WuGui{
int age;
public WuGui(int age) {
this.age = age;
}
@Override
public String toString() {
return "WuGui{" +···
"age=" + age +
'}';
}
}
//单独在这里编写一个比较器
//比较器java.util.Comparator接口。(Comparable是java.lang包下的《Comparator是java.util下的)。
class WuGuiComparator implements Comparator<WuGui> {
@Override
public int compare(WuGui o1, WuGui o2) {
//指定比较规则
//按照年龄排序
return o1.age-o2.age;//升序
}
}
Collections工具类
/**
* java.util.Collection 集合接口
* java.util.Collections 集合工具类,方便集合的操作。
*/
public class CollectionsTest01 {
public static void main(String[] args) {
//ArrayList不是线程安全的
List<String> list = new ArrayList<>();
//1、将ArrayList变成线程安全的。
Collections.synchronizedList(list);
//2、给List集合排序
//注意:如果是自定义类型,需要实现Comparable接口或者编写Comparator比较器。
list.add("abz");
list.add("abe");
list.add("abf");
list.add("abx");
Collections.sort(list);//这个sort方法要求元素类型必须实现Comparable接口,否则会报错。
for (String data:list){
System.out.println(data);
}
//对Set集合怎么排序?
Set<String> set = new HashSet<>();
set.add("king");
set.add("kingsoft");
set.add("king2");
set.add("king1");
//Collections.sort(set);//报错,只能传List
//将Set集合转化为List集合
List<String> myList = new ArrayList<>(set);
Collections.sort(myList);
for (String data:myList){
System.out.println(data);
}
}
}