java基础(集合)

本文详细介绍了Java集合框架,包括Collection接口、迭代器、foreach循环、数据结构(栈、队列、数组、链表、红黑树、哈希表)以及List、Set集合及其常见实现类如ArrayList、LinkedList、HashSet、TreeSet的特性和使用。此外,还讲解了Map接口、HashMap、TreeMap、LinkedHashMap的映射关系和遍历方法,以及集合工具类Collections的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 集合

1.1 概述

集合是JavaAPI中提供的一种容器工具,可以用来存储多个数据。

集合和数组之间的区别有:

  • 数组的长度是固定的,集合的长度是可变的
  • 数组中存储的是同一类型的元素,集合中存储的数据可以是不同类型的
  • 数组中可以存放基本类型数据或者对象,集合中只能存放对象
  • 数组是由JVM中现有的 类型+[] 组合而成的,除了一个length属性,还有从Object中继承过来的方法之外,数组对象就调用不到其他属性和方法了
  • 集合是由JavaAPI中的java.util包里面所提供的接口和实现类组成的,这里面定义并实现了很多方法,可以使用集合对象直接调用这些方法,从而操作集合存放的数据

集合框架中主要有三个要素组成:

  1. 接口

    整个集合框架的上层结构,都是用接口进行组织的。

    接口中定义了集合中必须要有的基本方法。

    通过接口还把集合划分成了几种不同的类型,每一种集合都有自己对应的接口。

  2. 实现类
    对于上层使用接口划分好的集合种类,每种集合的接口都会有对应的实现类。

    每一种接口的实现类很可能有多个,每个的实现方式也会各有不同。

  3. 数据结构

    每个实现类都实现了接口中所定义的最基本的方法,例如对数据的存储、检索、操作等方法。

    但是不同的实现类,它们存储数据的方式不同,也就是使用的数据结构不同。

集合按照其存储结构可以分为两大类:

  • java.util.Collection
  • java.util.Map

其他的集合接口,都是由这俩个接口派生出来的:

在这里插入图片描述

注意1,图中列出的是java集合框架中的主要接口,以及它们之间的继承关系

注意2,接口中定义了该种集合具有的主要方法,以及集合的基本特点

注意3,将来真正要使用的,是这些接口的实现类,每种实现类对接口的实现方式不同,那么其特点也不同

1.2 Collection接口

Collection接口是单列集合类的父接口,这种集合可以将数据一个一个的存放到集合中。它有两个重要的子接口,分别是java.util.Listjava.util.Set

Collection是父接口,其中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合对象。

Collection类型集合必须要有的基本的方法:

//向集合中添加元素	
boolean 	add(E e)
//把一个指定集合中的所有数据,添加到当前集合中    
boolean 	addAll(Collection<? extends E> c)
//清空集合中所有的元素。    
void 		clear()
//判断当前集合中是否包含给定的对象。
boolean 	contains(Object o)
//判断当前集合中是否包含给定的集合的所有元素。    
boolean 	containsAll(Collection<?> c)
//判断当前集合是否为空。
boolean 	isEmpty()
//返回遍历这个集合的迭代器对象    
Iterator<E> iterator()
//把给定的对象,在当前集合中删除。
boolean 	remove(Object o)
//把给定的集合中的所有元素,在当前集合中删除。    
boolean 	removeAll(Collection<?> c)
//判断俩个集合中是否有相同的元素,如果有当前集合只保留相同元素,如果没有当前集合元素清空    
boolean 	retainAll(Collection<?> c)
//返回集合中元素的个数。    
int 		size()
//把集合中的元素,存储到数组中。    
Object[] 	toArray()
//把集合中的元素,存储到数组中,并指定数组的类型  
<T> T[] 	toArray(T[] a)

一些方法的使用样例:

public static void main(String[] args) {

    //ArrayList类是Collection集合的一个实现类
    Collection c1 = new ArrayList();
    c1.add("hello1");
    c1.add("hello2");
    c1.add("hello3");


    //默认调用集合的toString方法,输出集合中内容
    System.out.println("操作之前集合中元素:"+c1);
    System.out.println("集合中的元素个数:"+c1.size());

    System.out.println("集合中是否包含hello1:"+c1.contains("hello1"));

    System.out.println("删除hello1:"+c1.remove("hello1"));
    System.out.println("操作之后集合中元素:"+c1);

    Object[] objects = c1.toArray();

    for (int i = 0; i < objects.length; i++) {
        System.out.println(objects[i]);
    }

    c1.clear();

    System.out.println("集合中内容为:"+c1);

    System.out.println(c1.isEmpty());

}

1.3 迭代器

为了能够方便的遍历集合中的每一个元素,API中提供了一个迭代器接口:java.util.Iterator

该接口可以很方便的迭代出集合中的元素。

java.lang.Iterable接口中,定义了获取迭代器的方法:

public interface Iterable {
    Iterator iterator();
}

java.util.Collection接口继承了java.lang.Iterable接口

public interface Collection extends Iterable {
    //...
}

所以,Collection接口及其子接口中,都有一个获取迭代器对象的方法:Iterator iterator();

java.util.Iterator接口中,主要定义俩个方法:

public interface Iterator {
    boolean hasNext();//返回当前迭代器中是否还有下一个对象
    Object next();//获取迭代器中的下一个对象
}

例如,

public static void main(String[] args) {

    Collection c1 = new ArrayList();
    c1.add("hello1");
    c1.add("hello2");
    c1.add("hello3");

	//获取c1集合的迭代器对象
    Iterator iterator = c1.iterator();
    
    //判断迭代器中,是否还有下一个元素
    while(iterator.hasNext()){
        //如果有的话,就取出来
        Object obj = iterator.next();
        System.out.println(obj);
    }


}

注意,这种迭代器方式获取集合中的每一个元素,是Collection集合及其子类型集合通用的方式

1.4 foreach循环

除了使用迭代器遍历集合之外,使用JDK1.5及以上提供的增强for循环,也可以遍历集合。

foreach循环的格式:

for(变量类型 变量名 : 集合){
    //操作变量
}

相当于,每次循环,使用指定变量,去指向集合中的一个对象,然后在循环体中对该变量进行操作

例如,

public static void main(String[] args) {

    Collection<String> c1 = new ArrayList<>();
    c1.add("hello1");
    c1.add("hello2");
    c1.add("hello3");

	for(Object o:c1){
        System.out.println(o);
    }


}

可以看出,使用foreach循环对集合进行遍历,会更加简单一些

同时,使用foreach循环也可以遍历数组

public static void main(String[] args) {

    int[] arr = {1,3,5,7,9};
	
    //每次循环,使用变量i接收数组中的一个数据
	for(int i : arr){
        System.out.println(i);
    }

}

注意,Collection类型及其子类型的集合,还有数组,都可以使用foreach循环进行遍历其中的元素数据

1.5 数据结构

集合接口都会有不同的实现类,每种实现类的底层,采用了不同的数据结构对数据元素进行存储

数据存储的常用结构有:

  • 队列
  • 数组
  • 链表
  • 红黑树
  • 哈希表

数据结构:栈

栈(stack),又称堆栈,仅允许在栈的一端进行插入和删除操作,并且不允许在其他任何位置进行操作。

其特点是:先进后出,最先存进去的元素,最后才能取出来。

例如,薯片存在薯片桶中,我们当前只能取出最上面的一个薯片,而最早存放到薯片桶的薯片,反而是我们最后吃到的一片。

栈的入口、出口的都是栈的顶端位置:

在这里插入图片描述

注意1,入栈也称为压栈,把数据存入到栈的顶端位置

注意2,出栈也称为弹栈,把栈顶位置的数据取出

思考,JVM中的栈区中,为什么把main方法标注在最低端位置?

数据结构:队列

队列(queue),仅允许在队列的一端进行插入,而在队列的另一端进行删除。

其特点是:先进先出,最先存进去的元素,可以最先取出来。

例如,火车穿过山洞的时候,第一节车厢先进去山洞的一端,并且这节车厢优先从山洞的另一端出来,后面的车厢依次从一端进入并另一端出来。

队列的入口、出口分别在队列的俩端:

数据结构:数组

数组(array),内存中一块连续的空间,元素数据在其中按照下标索引依次存储,比较常用的数据结构。

其特点是:通过下标索引,可以快速访问指定位置的元素,但是在数组中间位置添加数据或者删除数据会比较慢,因为数组中间位置的添加和删除元素,为了元素数据能紧凑的排列在一起,那么就会引起其后面的元素移动位置。

所以,数组查询元素较快,中间位置的插入、删除元素较慢。

可以看出,数组中间添加数据后,之后的数据都要依次移动位置。同理,中间位置删除的时候也是这样

数据结构:链表

链表(linked list),是有一个一个node节点组成,每个node节点中存储了一个数据,以及一个指向下一个node节点对象的引用(单向链表),如果是双向链表的话,还会存储另一个引用,指向了上一个node节点对象。

其特点是:

  • 查找元素慢,因为需要通过连接的节点,依次向后查找指定元素(没有直接的下标索引)
  • 新增和删除元素较快,例如删除,只需要让当前node节点中的引用指向另一个节点对象即可,原来的指向的node节点就相当于删除了。

可以看出,只需要将数据2节点中的引用,指向数据4的节点对象即可

head表示链表的头部,tail表示链表的尾部

思考,是否能根据单向链表的特点,想象出双向链表的特点?

数据结构:红黑树

二叉树(Binary tree)是树形结构的一个重要类型。二叉树特点是每个结点最多只能有两棵子树,且有左右之分。

二叉树顶上的叫根结点,两边被称作“左子树”和“右子树”。

二叉树中有一种叫做红黑树(Red/Black Tree),它最早被称为平衡二叉B树(symmetric binary B-trees),后来被称为红黑树。

红黑树是一种特殊化的平衡二叉树,它可以在进行插入和删除的时候,如果左右子数的高度相差较大,那么就通过特定操作(左旋、右旋)保持二叉查找树的平衡(动态平衡),从而获得较高的查找性能。

红黑树的每一个节点的左子树的所有数据都比自己小,而右子树的所有数据都比自己大,并且左右子树的高度近似

红黑树的约束:

  1. 根节点必须是黑色
  2. 其他节点可以是红色的或者黑色
  3. 叶子节点(特指null节点)是黑色的
  4. 每个红色节点的子节点都是黑色的
  5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同

注意,红黑树的指定颜色的目的,是利用颜色值作为二叉树的平衡对称性的检查

数据结构:哈希表

java中的哈希表(hash),在JDK1.8之前是采用数组+链表进行实现,根据数据的哈希值,把数据存在数组中,但是当前哈希值冲突的时候,再使用链表进行存储,那么在数组中,同一hash值的数据都存在一个链表里。

注意,之前学习过Object中hashCode方法的作用,hash值的特点以及和对象之间的关系

例如,

例如,如果数据的哈希值相同,在数组使用使用链表存储哈希值相同的几个数据

可以看出,当链表中元素过多,即hash值相等的元素较多时,查找的效率会变低

JDK1.8中,哈希表存储采用数组+链表+红黑树进行实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样可以大大提高查找的性能。

例如,

思考,java的集合框架中,三个重要组成部分是什么?

接口、实现类、数据结构

1.6 List集合

java.util.List 接口继承了Collection 接口,是常用的一种集合类型。

List集合具有Collection集合的特点之外,还具有自己的一些特点:

  • List是一种有序的集合

    例如,向集合中存储的元素顺序是8、2、5。那么集合中就是按照这个顺序进行存储的

  • List是一种带索引的集合

    可以通过元素的下标索引,精确查找对应的元素数据

  • List是一种可以存放重复数据的集合

    可以把相同的数据,在List集合中多次保存

List接口中常用方法:

//返回集合中指定位置的元素。
E		get(int index);
//用指定元素替换集合中指定位置的元素,并返回被替代的旧元素。
E		set(int index, E element);
//将指定的元素,添加到该集合中的指定位置上。
void	add(int index, E element);
//从指定位置开始,把另一个集合的所有元素添加进来
boolean addAll(int index, Collection<? extends E> c);
//移除列表中指定位置的元素, 并返回被移除的元素。
E		remove(int index);
//查收指定元素在集合中的所有,从前往后查到的第一个元素(List集合可以重复存放数据)
int		indexOf(Object o);
//查收指定元素在集合中的所有,从后往前查到的第一个元素(List集合可以重复存放数据)
int		lastIndexOf(Object o);
//根据指定开始和结束位置,截取出集合中的一部分数据
List<E> subList(int fromIndex, int toIndex);

注意,除了这些方法之外,还有从父接口Collection中继承过来的方法

List集合的方法使用:

public static void main(String[] args) {

    // 创建List集合对象
    List list = new ArrayList();
    // 往 尾部添加 指定元素
    list.add("hello1");
    list.add("hello2");
    list.add("hello3");
    System.out.println(list);
    // add(int index,String s) 往指定位置添加
    list.add(1, "world");
    System.out.println(list);

    // 删除索引位置为2的元素
    System.out.println("删除索引位置为2的元素");
    System.out.println(list.remove(2));
    System.out.println(list);

    // 修改指定位置元素
    list.set(0, "briup");
    System.out.println(list);

    //遍历集合
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }

    System.out.println("-----------------");
    //使用foreach遍历
    for(Object obj : list){
        System.out.println(obj);
    }

    System.out.println("-----------------");
    //使用迭代器进行遍历集合
    Iterator it = list.iterator();
    while(it.hasNext()){
        Object obj = it.next();
        System.out.println(obj);
    }

}
public static void main(String[] args) {

    List list = new ArrayList();

    list.add("hello1");
    list.add("hello2");
    list.add("hello3");
    list.add("hello3");

    for(int i=0;i<list.size();i++){
        System.out.println(list.get(i));
    }


}

输出结果:
hello1
hello2
hello3
hello3
    

可以看出,List集合的特点:有序可充分,并且可以使用下标索引进行访问

1.7 List实现类

List接口继承的父接口有:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3HHqWVO5-1597646681192)(corejava-day11.assets/image-20200804151642763.png)]

可以看出,List接口继承了Collection接口,Collection接口继承了Iterable接口

List接口的实现类有:

注意,这些实现类中,都已经实现了List接口、Collection接口、Iterable接口中的方法,我们只要了解并能使用这些接口中的方法,就已经能够操作这些集合对象了(面向接口)。

额外的,我们还需要了解这些常用的接口实现类,分别都是什么特点,使用的什么数据结构,以及适合在什么样的场景下使用。

List接口实现类:ArrayList

java.util.ArrayList是最常用的一种List类型集合,ArrayList类中使用数组来实现数据的存储,所以它的特点是就是:增删慢,查找快。

在日常的开发中,查询数据也是用的最多的功能,所以ArrayList是最常用的集合。

但是,如果项目中对性能要求较高,并且在集合中大量的数据做增删操作,那么ArrayList就不太适合了。

List接口实现类:LinkedList

java.util.LinkedList存储数据采用的数据结构是链表,所以它的特点是:增删快,查找慢

它的特点刚好和ArrayList相反,所以在代码中,需要对集合中的元素做大量的增删操作的时候,可以选择使用LinkedList

注意,这里描述的快和慢,需要在大量的数据操作下,才可以提醒,如果数据量不大的话,集合每一种集合的操作几乎没有任何区别。

public static void main(String[] args) {
    //操作集合的次数
    final int NUM = 100000;

    List list;
    //list = new ArrayList();
    list = new LinkedList();

    long start1 = System.currentTimeMillis();
    for (int i = 0; i < NUM; i++) {
        list.add(0,"hello"+i);
    }
    long end1 = System.currentTimeMillis();

    System.out.println(list.getClass().getSimpleName()+"插入"+NUM+"条数据耗时"+(end1-start1)+"毫秒");


    long start2 = System.currentTimeMillis();
    for(int i=0;i<list.size();i++){
        list.get(i);
    }
    long end2 = System.currentTimeMillis();
    System.out.println(list.getClass().getSimpleName()+"检索"+NUM+"条数据耗时"+(end2-start2)+"毫秒");

}

//根据电脑的当前情况,每次运行的结果可能会有差异
//以下是我的电脑运行俩次的实验结果,第一次使用ArrayList,第二次使用LinkedList
ArrayList插入100000条数据耗时631毫秒
ArrayList检索100000条数据耗时3毫秒

LinkedList插入100000条数据耗时30毫秒
LinkedList检索100000条数据耗时24252毫秒

注意,调用的方法都一样,但是每次改变list指向的对象,分别执行ArrayList对象和LinkedList对象

注意,list.getClass().getSimpleName()方法可以获取list引用当前指向对象的实际类型的简单名字

注意,System.currentTimeMillis();可以获取当前时刻的时间戳

注意,集合中操作的数据量小的时候,使用哪种实现类,性能差别都不大


下面对LinkedList的介绍内容,可以作为额外的了解


LinkedList同时还是一个双向链表:

可以看出,双向链表,除了头节点(head)和尾节点(tail)之外,前提节点都含有俩个引用,一个引用指向它的上一个节点,另一个引用指向它的的下一个节点

LinkedList中,定义了一些操作头节点和尾节点的方法:

//将指定元素插入此列表的开头
void 		addFirst(E e)
//将指定元素添加到此列表的结尾
void 		addLast(E e)
//返回此列表的第一个元素
E 			getFirst()
//返回此列表的最后一个元素    
E 			getLast()
//从此列表所表示的堆栈处弹出一个元素    
E 			pop()
//将元素推入此列表所表示的堆栈    
void 		push(E e)
//移除并返回此列表的第一个元素    
E 			removeFirst()
//移除并返回此列表的最后一个元素
E 			removeLast()

LinkedList类不仅实现了List接口,还有Queue接口以及子接口Deque也实现了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zRKhglo2-1597646681193)(corejava-day11.assets/image-20200804164018362.png)]

Queue是队列接口,Deque是双端队列

JavaAPI中提供了java.util.Stack来实现栈结构,但官方目前已不推荐使用,而是推荐使用java.util.Deque双端队列来实现队列的各种需求

所以LinkedList同时具有队列和栈的操作方法,pop和push都是栈结构的操作方法

Queue中方法和Deque中方法对比:

QueueDeque说明
add(e)addLast(e)向队尾插入元素,失败则抛出异常
offer(e)offerLast(e)向队尾插入元素,失败则返回false
remove()removeFirst()获取并删除队首元素,失败则抛出异常
poll()pollFirst()获取并删除队首元素,失败则返回null
element()getFirst()获取但不删除队首元素,失败则抛出异常
peek()peekFirst()获取但不删除队首元素,失败则返回null

Stack中方法和Deque中方法对比:

StackDeque说明
push(e)addFirst(e)向栈顶插入元素,失败则抛出异常
offerFirst(e)向栈顶插入元素,失败则返回false
pop()removeFirst()获取并删除栈顶元素,失败则抛出异常
pollFirst()获取并删除栈顶元素,失败则返回null
peek()peekFirst()获取但不删除栈顶元素,失败则返回null

所以,LinkedList集合也可以作为栈和队列的数据结构来使用

List接口实现类:Vector

Vector内部也是采用了数组来存储数据,但是Vector中的方法大多数都是线程安全的方法,所以在多线并发访问的环境中,可以使用Vector来保证集合中元据操作的安全。

查看Vector中方法的定义,可以看到多大数方法都使用了synchronized关键字,来给当前方法加锁。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0TaGKFbD-1597646681194)(corejava-day11.assets/image-20200804171751492.png)]

关于线程的相关问题,后面再具体的学习了解

1.8 Set集合

java.util.Set 接口继承了Collection 接口,是常用的一种集合类型。

Set集合具有Collection集合的特点之外,还具有自己的一些特点:

  • Set是一种无序的集合

  • Set是一种不带下标索引的集合

  • Set是一种不能存放重复数据的集合

注意,这里可以对比上面描述的List集合的特点来理解和记忆

Set接口中的方法,都继承自它的父接口:

public static void main(String[] args) {

    Set set = new HashSet();
    set.add("hello1");
    set.add("hello2");
    set.add("hello3");
    set.add("hello4");
    set.add("hello5");
    set.add("hello5");

    for(Object obj:set){
        System.out.println(obj);
    }

    System.out.println("-----------------");

    Iterator it = set.iterator();
    while(it.hasNext()){
        Object obj = it.next();
        System.out.println(obj);
    }


}

通过以上代码和运行结果,可以看出Set类型集合的特点:无序、不可重复

1.9 Set实现类

Set接口常用的的实现类有俩个

  • HashSet
  • TreeSet

注意,TreeSet是Set接口的子接口SortedSet的实现类

Set接口实现类:HashSet

java.util.HashSet类的实现,主要依靠的是HashMap

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k8SZfMhb-1597646681195)(corejava-day11.assets/image-20200804210542210.png)]

注意,HashMap的实现主要是利用上面介绍的【哈希表】

思考,HashSet为什么是无序的,它是怎么判断元素是否是重复的?

HashSet中存储元素是无序的,主要因为它是靠对象的哈希值来确定元素在集合中的存储位置。

HashSet中元素不可重复,主要是靠对象的hashCode和equals方法来判断对象是否重复。

将自定义对象存放到HashSet中:

public class Student{
    String name;
    int age;

    public Student(){}

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public static void main(String[] args) {

    Set set = new HashSet();
    set.add(new Student("tom",20));
    set.add(new Student("tom",20));
    set.add(new Student("tom",20));
    set.add(new Student("tom",20));

    for(Object obj : set){
        System.out.println(obj);
    }

}

//运行结果为:
Student{name='tom', age=20}
Student{name='tom', age=20}
Student{name='tom', age=20}
Student{name='tom', age=20}

可以看出,HashSet并没有把name和age都相同的对象,当做相同的对象进行去重

原因是是因为,HashSet中判断对象是否重复是根据对象的hashCode值和equals的比较结果,而不是根据对象的name和age值是否相等!

如果俩个对象的hashCode值相等,那么再使用equals判断是否俩对象是否相同,

如果俩个对象的hashCode值不同等,那么就不再使用equals进行判断了,因为hashCode不同的俩个对象一定是不同的俩个对象!

所以,想要将Student对象根据我们的要求进行去重,就要重写Student中的hashCode方法和equals方法

public class Student{
    String name;
    int age;

    public Student(){}

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }
    
}

public static void main(String[] args) {

    Set set = new HashSet();
    set.add(new Student("tom",20));
    set.add(new Student("tom",20));
    set.add(new Student("tom",20));
    set.add(new Student("tom",20));

    for(Object obj : set){
        System.out.println(obj);
    }

}

//输出结果:
Student{name='tom', age=20}

java.util.Objects是JDK1.7提供的一个对象的工具类,里面定义的一些静态方法,提供了操作对象的方法
例如,equals(Object a, Object b)方法可以判断俩个对象是否相等,当然也可以直接进行equals判断
例如,hash(Object… values)方法,可以根据若干个参数计算出一个哈希值,当然也可以自己设置哈希值

根据输出结果,可以看出,重写hashCode和equals后,HashSet就可以按照我们的要求进行去重了

Set接口实现类:TreeSet

TreeSet是Set接口的子接口SortedSet的实现类

TreeSet可以将我们存进去的数据进行排序,排序的方式有俩种:

  • 自然排序
  • 比较器排序(也称客户化排序)

TreeSet:自然排序

如果一个类,实现了java.lang.Comparable接口,那么这个类的俩个对象就是可以比较大小的。

public interface Comparable<T> {
    public int compareTo(T o);
}

compareTo方法使用说明:int result = o1.compareTo(o2);

  • result的值大于0,说明o1比o2大
  • result的值小于0,说明o1比o2小
  • result的值等于0,说明o1与o2相等

例如,

public static void main(String[] args) {

    Set set = new TreeSet();
    set.add(3);
    set.add(5);
    set.add(1);
    set.add(7);

    for(Object obj : set){
        System.out.println(obj);
    }


}

//输出结果:
1
3
5
7

可以看出,存进去的数字已经安从小到大进行了排序

set.add(3);代码执行的时候,会自动进行装箱,把基本类型数据3变为Integer对象,然后再存放到集合中。

Integer类是实现了Comparable接口的,那么Integer类的俩个对象之间就可以调用compareTo方法比较大小了,当前对象比较出来大小,那么对象就可以按照从小到大的顺序进行排序。

所以,数据排序的前提,一定是可以比较出数据的大小

Integer类中的部分代码如下:

public class Integer implements Comparable<Integer>{
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }
    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
}

可以看出,Integer的俩个对象比较大小,其实就是比较Integer对象对应的int值的大小

注意,compareTo方法返回值代表的含义(三种情况:正数、负数、零)

给自定义类型进行排序:

public class Student{
    String name;
    int age;

    public Student(){}

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public static void main(String[] args) {

    Set set = new TreeSet();
    set.add(new Student("mary",23));
    set.add(new Student("jack",21));
    set.add(new Student("tom",20));
    set.add(new Student("lucy",22));

    for(Object obj : set){
        System.out.println(obj);
    }


}

//运行结果报错:
Exception in thread "main" java.lang.ClassCastException: com.briup.demo.Student cannot be cast to java.lang.Comparable


报错原因,TreeSet中会把对象强制为Comparable类型,因为转成Comparable类型就可以调用compareTo方法进行俩个对象的比较大小了。但是这里的Student没有实现该接口,所以报错了。

Student类实现Comparable接口,并在compareTo方法中,编写比较大小的规则:

public class Student implements Comparable{
    String name;
    int age;

    public Student(){}

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    
    @Override
    public int compareTo(Object o) {
        //注意,这里this代表我,other代表你,也就是当前要和我比较大小的对象
        Student other = (Student)o;

        //如果我的年龄比你大,就返回正数,说明我比你大
        if(this.age > other.age){
            return 1;
        }
        //如果我的年龄比你小,就返回负数,说明我比你小
        else if(this.age < other.age){
            return -1;
        }
        //其他情况返回0,说明俩个对象(我和你)一样大
        else {
            return 0;
        }

    }
}

public static void main(String[] args) {

    Set set = new TreeSet();
    set.add(new Student("mary",23));
    set.add(new Student("jack",21));
    set.add(new Student("tom",20));
    set.add(new Student("lucy",22));

    for(Object obj : set){
        System.out.println(obj);
    }


}

//运行结果:
Student{name='tom', age=20}
Student{name='jack', age=21}
Student{name='lucy', age=22}
Student{name='mary', age=23}

可以看出,现在学生对象在TreeSet中,就可以按照我们定义的规则进行从小到大排序了

注意,compareTo方法的放回结果,只关心正数、负数、零,不关心具体的值是多少

TreeSet:比较器排序

假设现在Student类已经写好了,但是没有实现Comparable接口,同时我们又不愿意直接修改Student类的代码,那么在这种情况下,Student对象在TreeSet中是否还能排序呢?

java.util.Comparator接口,是一个比较器接口,它的实现类可以对俩个对象作出大小的比较,即使对象没有实现Comparable接口,也可以进行比较。

public interface Comparator<T> {   
    int compare(T o1, T o2);
}

比较器的使用规则: int result = compare(o1, o2);

  • result的值大于0,说明o1比o2大
  • result的值小于0,说明o1比o2小
  • result的值等于0,说明o1与o2相等

注意,这里和自然排序的规则是一样的,只关心正数、负数、零,不关心具体的值是多少

在TreeSet类重载的构造器中,有一个构造器可以接收比较器对象:

public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

例如,

public class Student{
    String name;
    int age;

    public Student(){}

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

}

public static void main(String[] args) {

    //使用Comparator接口,创建出匿名内部类对象,这个对象就是要用的比较器对象
    Comparator c = new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            Student s1 = (Student) o1;
            Student s2 = (Student) o2;

            return s1.age > s2.age? 1 : (s1.age==s2.age? 0 : -1);
        }
    };

    //创建TreeSet对象的时候,把比较器对象传入
    Set set = new TreeSet(c);
    
    set.add(new Student("mary",23));
    set.add(new Student("jack",21));
    set.add(new Student("tom",20));
    set.add(new Student("lucy",22));

    for(Object obj : set){
        System.out.println(obj);
    }


}

//运行结果:
Student{name='tom', age=20}
Student{name='jack', age=21}
Student{name='lucy', age=22}
Student{name='mary', age=23}

1.10 Map集合

很多时候,我们会遇到成对出现的数据,例如,姓名和电话,身份证和人,IP和域名等等,这种成对出现,并且一一对应的数据关系,叫做映射。

java.util.Map<K, V>接口,就是专门处理这种映射关系数据的集合类型。

Map类型集合与Collection类型集合,存储数据的形式不同:

Collection类型集合中,每次存一个数据。

Map类型集合中,每次需要存一对数据,key-value(键值对)

  • key值必须是唯一的,value值允许重复
  • 键(key)和值(value)一一映射,一个key对应一个value
  • 在Map中,通过key值(唯一的),可以快速的找到对应的value值

Map接口中的方法:

//把key-value存到当前Map集合中
V	put(K key, V value)
//把指定map中的所有key-value,存到当前Map集合中
void 			putAll(Map<? extends K,? extends V> m)
//当前Map集合中是否包含指定的key值
boolean 		containsKey(Object key)
//当前Map集合中是否包含指定的value值
boolean 		containsValue(Object value)
//清空当前Map集合中的所有数据
void 			clear()
//在当前Map集合中,通过指定的key值,获取对应的value
V 				get(Object key)
//在当前Map集合中,移除指定key及其对应的value
V 				remove(Object key)
//返回当前Map集合中的元素个数(一对key-value,算一个元素数据)
int 			size()
//判断当前Map集合是否为空
boolean 		isEmpty()
//返回Map集合中所有的key值
Set<K> 			keySet()
//返回Map集合中所有的value值
Collection<V> 	values()
//把Map集合中的的key-value封装成Entry类型对象,再存放到set集合中,并返回
Set<Map.Entry<K,V>> 	entrySet()

Map集合的方法使用:

public static void main(String[] args) {

    Map map = new HashMap();
    map.put(1,"tom");
    map.put(2,"jack");
    map.put(3,"lucy");
    map.put(4,"mary");

    System.out.println("map是否为空:"+map.isEmpty());
    System.out.println("map中元素的个数:"+map.size());
    System.out.println("map中是否包含指定key值1:"+map.containsKey(1));
    System.out.println("map中是否包含指定value值mary:"+map.containsValue("mary"));
    System.out.println("map中获取指定key值1对应的value值:"+map.get(1));
    System.out.println("map中获取指定key值5对应的value值:"+map.get(5));
	
    /* Map集合的这三种遍历方式,在【Map的遍历】部分有详细的画图说明 */
    
    System.out.println("--------获取map中所有的kay值--------");
    Set keys = map.keySet();
    for(Object key : keys){
        System.out.println(key+" : "+map.get(key));
    }

    System.out.println("--------获取map中所有的value值--------");
    Collection values = map.values();
    for(Object value : values){
        System.out.println(value);
    }

    System.out.println("--------获取map中所有的key-value(键值对),封装成Entry类型对象--------");
    Set entrySet = map.entrySet();
    for(Object obj : entrySet){
        Map.Entry entry = (Map.Entry) obj;
        System.out.println(entry.getKey()+" : "+entry.getValue());
    }

}

1.11 Map实现类

Map接口有很多实现类,以及子接口:

代码中经常会用到的有:HashMapHashTableTreeMapLinkedHashMap

HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需
要重写键的hashCode()方法、equals()方法。(重要,最常用)

HashTable:和之前List集合中的Vector的功能类似,可以在多线程环境中,保证集合中的数据的操作安全,类中的方法大多数使用了synchronized修饰符进行加锁。(线程安全)

TreeMap:该类是Map接口的子接口SortedMap下面的实现类,和TreeSet类似,它可以对key值进行排序,同时构造器也可以接收一个比较器对象作为参数。支持key值的自然排序和比较器排序俩种方式。(支持key排序)

LinkedHashMap:该类是HashMap的子类,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;(存入顺序就是取出顺序)

TreeMap的使用(自然排序):

public static void main(String[] args) {

    Map map = new TreeMap();
    map.put(4,"mary");
    map.put(2,"jack");
    map.put(1,"tom");
    map.put(3,"lucy");


    Set keys = map.keySet();
    for(Object key : keys){
        System.out.println(key+" : "+map.get(key));
    }


}

//运行结果:
1 : tom
2 : jack
3 : lucy
4 : mary

TreeMap的使用(比较器排序):

public static void main(String[] args) {

    Comparator c = new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            Integer k1 = (Integer) o1;
            Integer k2 = (Integer) o2;
            //注意,这里在默认的比较结果基础上加了一个符号,即原来返回的正数变负数、返回的负数变正数
            return -k1.compareTo(k2);
        }
    };

    //构造器中传入比较器对象
    Map map = new TreeMap(c);

    map.put(4,"mary");
    map.put(2,"jack");
    map.put(1,"tom");
    map.put(3,"lucy");


    Set keys = map.keySet();
    for(Object key : keys){
        System.out.println(key+" : "+map.get(key));
    }


}

//运行结果:
4 : mary
3 : lucy
2 : jack
1 : tom

LinkedHashMap的使用:

public static void main(String[] args) {


    Map map = new LinkedHashMap();

    map.put(4,"mary");
    map.put(2,"jack");
    map.put(1,"tom");
    map.put(3,"lucy");


    Set keys = map.keySet();
    for(Object key : keys){
        System.out.println(key+" : "+map.get(key));
    }


}
//运行结果:
4 : mary
2 : jack
1 : tom
3 : lucy

可以看出,数据存入Map中的顺序,就是存储的顺序,也是取出的顺序!

1.12 Map的遍历

Map中的keySet方法,可以返回Map集合中所有的key值的集合:

拿到Set集合后,就可以遍历Set集合拿到Map中每个key值,通过key值可以获取到对应的value值(get方法)

Map中的values方法,可以返回Map集合中所有的value值的集合:

拿到Collection集合后,可以可以遍历Collection集合拿到Map中每一个value值,但是这时候拿不到相应的key

Map中的entrySet方法

Map接口中有定义了一个内部接口Entry:(类似于内部类)

public interface Map<K,V>{
    
    interface Entry<K,V> {
        K getKey();
        V getValue();
    }
    
}

一个Entry类型的对象,可以代表Map集合中的一组key-value(键值对),并且提供了获取key值和value值的方法。

Map接口中的entrySet()方法,就是将Map集合中的每一组key-value(键值对)都封装成一个Entry类型对象,并且把这些个Entry对象存放到Set集合中,并返回。

public static void main(String[] args) {


    Map map = new LinkedHashMap();

    map.put(4,"mary");
    map.put(2,"jack");
    map.put(1,"tom");
    map.put(3,"lucy");

    Set entrySet = map.entrySet();
    for(Object obj : entrySet){
        Map.Entry entry = (Map.Entry) obj;
        System.out.println(entry.getKey()+" : "+entry.getValue());
    }


}

//运行结果:
4 : mary
2 : jack
1 : tom
3 : lucy

1.13 集合工具类

java.util.Arrays是一个工具类,专门用来操作数组对象的,里面都是静态方法,可直接调用。

java.util.Collections也是一个工具类,专门用来操作集合对象的,里面都是静态方法,可以直接调用。

注意,Collection接口 和 Collections类 的区别!

Collections中的常用方法:

  • fill方法,使用指定元素替换指定列表中的所有元素

    List list = new ArrayList();
    list.add(1);
    list.add(2);
    list.add(3);
    Collections.fill(list, 20);
    for(Object o:list){
        System.out.println(o);
    }
    
  • max方法,根据元素的自然顺序,返回给定集合的最大元素

    List list = new ArrayList();
    list.add(1);
    list.add(9);
    list.add(3);
    System.out.println(Collections.max(list));
    
  • min方法,根据元素的自然顺序,返回给定集合的最小元素

  • reverse方法,反转集合中的元素

    List list = new ArrayList();
    list.add(1);
    list.add(9);
    list.add(3);
    Collections.reverse(list);
    for(Object o:list){
        System.out.println(o);
    }
    
  • sort方法,根据元素的自然顺序,对指定列表按升序进行排序

    List list = new ArrayList();
    list.add(1);
    list.add(9);
    list.add(3);
    Collections.sort(list);
    for(Object o:list){
        System.out.println(o);
    }
    
  • shuffle方法,使用默认随机源对指定列表进行置换

    List list = new ArrayList();
    list.add(1);
    list.add(9);
    list.add(3);
    //如果需要,也可以在第二个参数位置传一个比较器对象
    //Collections.shuffle(list,c);
    Collections.shuffle(list);
    for(Object o:list){
        System.out.println(o);
    }
    
  • addAll方法,往集合中添加一些元素

    List list = new ArrayList();
    //注意,addAll的第二个参数,是可变参数
    Collections.addAll(list,1,3,5,7);
    for(Object o:list){
        System.out.println(o);
    }
    
  • synchronizedCollection,把非线程安全的Collection类型集合,转为线程安全的集合

  • synchronizedList,把非线程安全的List类型集合,转为线程安全的集合

  • synchronizedSet,把非线程安全的Set类型集合,转为线程安全的集合

  • synchronizedMap,把非线程安全的Map类型集合,转为线程安全的集合

1.14 案例

完成斗地主游戏的生成牌、洗牌、发牌、查看三名玩家的牌及三张底牌

public class Game {

    public static void main(String[] args) {

        //准备三个List集合,分别存放花色信息、点数信息、以及组合好的54张牌
        List colors = new ArrayList();
        List numbers = new ArrayList();
        List cards = new ArrayList();

        //向colors集合中添加4种花色
        Collections.addAll(colors,"♠","♥","♣","♦");
        //向numbers集合中添加13种点数
        Collections.addAll(numbers,"A","2","3","4","5","6","7","8","9","10","J","Q","K");

        //存储每种牌的权重信息(比较大小用的,方便排序)
        Map cardWeight = new HashMap();
        cardWeight.put("3",1);
        cardWeight.put("4",2);
        cardWeight.put("5",3);
        cardWeight.put("6",4);
        cardWeight.put("7",5);
        cardWeight.put("8",6);
        cardWeight.put("9",7);
        cardWeight.put("10",8);
        cardWeight.put("J",9);
        cardWeight.put("Q",10);
        cardWeight.put("K",11);
        cardWeight.put("A",12);
        cardWeight.put("2",13);
        cardWeight.put("小王",14);
        cardWeight.put("大王",15);


        //让4种花色和13种点数 分别配对组合成52张牌
        for(int i=0;i<colors.size();i++){
            String color = (String) colors.get(i);
            for(int j=0;j<numbers.size();j++){
                String number = (String) numbers.get(j);
                //根据number从Map集合中获取这个牌的权重值
                int weight = (int) cardWeight.get(number);
                //把当前花色、点数、权重封装成Card对象,保存到cards集合中
                //Card是下面自定义的一个类型,封装了牌的信息
                cards.add(new Card(color,number,weight));
            }
        }

        //单独处理并存放,大王和小王这俩张牌
        cards.add(new Card("","小王",(int) cardWeight.get("小王")));
        cards.add(new Card("","大王",(int) cardWeight.get("大王")));


        //此时,cards集合中共有54张牌,进行随机元素调换位置(洗牌)
        Collections.shuffle(cards);

        //map集合存放3名玩家和三张底牌
        Map map = new LinkedHashMap();
        List player1 = new ArrayList();
        List player2 = new ArrayList();
        List player3 = new ArrayList();
        List threeCard = new ArrayList();

        map.put("1号玩家",player1);
        map.put("2号玩家",player2);
        map.put("3号玩家",player3);
        map.put("底牌",threeCard);

        //循环遍历cards,进行发牌
        for(int i=0;i<cards.size();i++){
            //先拿出这张牌
            Card card = (Card) cards.get(i);
            //前51张牌发给三名玩家
            if(i<51){
                //三名玩家,依次抓牌
                if(i%3==0){
                    player1.add(card);
                }
                else if(i%3==1){
                    player2.add(card);
                }
                else if(i%3==2){
                    player3.add(card);
                }
            }
            //最后三张,存到指定集合中
            else{
                threeCard.add(card);
            }

        }

        //创建比较器对象(匿名内部类),根据牌的权重比较大小进行排序
        Comparator c = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Card c1 = (Card) o1;
                Card c2 = (Card) o2;
                return c1.weight-c2.weight;
            }
        };

        //对三名玩家手里的牌和底牌进行按权重排序
        Collections.sort(player1,c);
        Collections.sort(player2,c);
        Collections.sort(player3,c);
        Collections.sort(threeCard,c);


        //最后,显示三名玩家手里的牌,以及三张底牌
        for(Object key:map.keySet()){
            System.out.println(key+" : "+map.get(key));
        }


    }

}

//封装扑克牌的信息
class Card{
    //花色
    String color;
    //点数
    String number;
    //权重(比较大小用的)
    int weight;

    public Card(String color, String number, int weight) {
        this.color = color;
        this.number = number;
        this.weight = weight;
    }

    //重写toString方法,方便输出牌的信息
    @Override
    public String toString() {
        return color+number;
    }
}


//运行结果:
1号玩家 : [3,4,5,6,6,7,8,9,9,10,10,10, ♥Q, ♦K, ♣A, ♥A,2]
2号玩家 : [3,4,5,5,7,7,8, ♣J, ♠J, ♦J, ♠Q, ♦Q, ♣Q, ♣K, ♠K,2,2]
3号玩家 : [3,3,4,4,5,6,8,8,9,9,10, ♥J, ♦A, ♠A,2, 小王, 大王]
底牌 : [6,7, ♥K]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值