如标题,你将获得Collection、List、Set接口,以及其实现类ArrayList,LinkedList,HashSet、TreeSet类的讲解。众所周知,Java的学习就是从API里有啥、是啥、怎么用开始,再进阶至源码。
注意:
- 文章较长,但非常基础和实用。
- 一句废话也没有,再说我也不喜欢搞那些花里胡哨的。
- 这是我学习笔记,其中一定有一部分是你能够拿去做成你的笔记的。
- enjoy…
文章目录
集合体系
诞生背景
为啥要诞生集合,还得从数组的缺陷说起。
数组(存储同一种数据类型的集合容器)的特点:
- 只能存储同一种数据类型的数据,例如在同一个容器中不能同时存储int型和double型。
- 一旦初始化,容量固定,容量不能扩容或缩减。
- 数组中的元素与元素之间的内存地址是连续的。
注意: Object类型的数组可以存储任意类型的数据。
Object[] arr = new Object[10];
arr[1] = "abc";
arr[2] = 'a';
arr[3] = 12;
基于数组的仅能存储同一种类型和容量不可变化的劣势下,诞生的集合!
集合的定义: 是存储任何对象数据的集合容器。在同一个容器中,可以同时存储int型和double型,甚至是自定义类型。
//试一试
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList array = new ArrayList();
array.add("我是字符串");
array.add(134);
array.add(3.14534);
for(int i=0; i<array.size(); i++) {
System.out.println(array.get(i));
}
}
}
/*
输出:
我是字符串
134
3.14534
*/
集合比数组的优势:
1. 集合可以存储任意类型的对象数据,数组只能存储同一种数据类型的数据。
2. 集合的长度是允许发生变化的,数组的长度是固定的。
集合(Collection)接口
Collection的体系结构:
方法
看别人的技术文章和看API(源码)的根本区别就是,技术文章会把最重要、最核心的方法先罗列出来,还会把方法进行分门别类,这样学习才能事半功倍!
方法:(增、删、查、判断、遍历/迭代)
增加
boolean add(E e)
添加成功返回true,添加失败返回false.boolean addAll(Collection c)
把一个集合的元素添加到另外一个集合中去。
删除
void clear()
//移除所有元素boolean remove(Object o)
//指定集合中的元素删除,删除成功返回true,删除失败返回false.boolean removeAll(Collection c)
//删除交集元素boolean retainAll(Collection c)
//保留交集元素,其他元素删除;retain[保持、记住]
查看
- int size()` //返回元素的个数
判断:Boolean类型;
boolean isEmpty()
boolean contains(Object o)
//判断集合中是否存在参数的元素;boolean containsAll(Collection c)
//判断调用集合是否包含参数集合的全部元素,即判断集合是否完全相等;
//试一试
import java.util.ArrayList;
import java.util.Collection;
public class Main {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add("por*hub"); //增
col.add("xvideo*");
col.remove("xvideo*"); //删
System.out.println("数量:"+col.size()); //查
System.out.println("含por*hub?"+col.contains("por*hub"));
System.out.println("空?"+col.isEmpty());//判断
}
}
/*
输出:
数量:1
含por*hub?true
空?false
*/
迭代(遍历方法)
Object[] toArray()
//返回一个Object的数组,把集合中的元素全部存储到一个Object的数组中返回;Iterator<E> iterator()
//迭代器遍历集合中的元素增强for循环、forEach
:始于JDK1.8(Lambda表达式)
两种迭代方法示例:
//迭代:toArray()方法
import java.util.ArrayList;
import java.util.Collection;
public class Main {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
Object[] arr = coll.toArray(); //将ArrayList集合转换为Object数组
for(Object i:arr) { //遍历Object数组
System.out.println(i);
}
}
}
//迭代:迭代器方法
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
// 获取迭代器对象
Iterator it = coll.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
//增强for
import java.util.ArrayList;
import java.util.Collection;
public class Main {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
//增强for
for(String ele:coll) {
System.out.println(ele);
}
//forEach
coll.forEach(ele -> System.out.println(ele));
}
}
迭代器(Iterator)接口
迭代器诞生的背景:
- java中提供了很多个集合,它们在存储元素时,采用的存储方式不同。
- 对于众多的集合,
取出
集合中的元素,每个集合都需要不同的操作。 - 由此就会有很多的取出集合操作的类。
- 为了
减少
操作类,Java使用一个专业术语“迭代器iterator”来统称所有集合的取出操作。
迭代器功能:
取出任意种类的集合中的元素。
方法概览:
- 判定集合中是否还有元素可以迭代:
boolean hasNext();
- 返回迭代的下一个元素:
E next();
- 移除迭代器最后一次返回的元素,即删除该指针现在指向元素:
void remove();
迭代器的应用的底层原理:
- 因为Iterator是接口,就需要找实现类。
- 在Collection接口中,有一个方法:Iterator iterator();
- 在Collection接口中的实现类中(如ArrayList、HashSet),都会重写iterator方法,返回Iterator接口的实现类对象。
- 在具体集合遍历时,就会调用相应的方法。
快速理解迭代器的使用:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
//迭代器,对集合ArrayList中的元素进行取出
//调用集合的方法iterator()获取出,Iterator接口的实现类的对象
Iterator<String> it = coll.iterator();
//接口实现类对象,调用方法hasNext()判断集合中是否有元素,集合中没元素, hasNext()返回了false
//接口的实现类对象,调用方法next()取出集合中的元素
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
}
}
List接口
实现类:
- ArrayList
- LinkedList
- Vector(已淘汰)
特点:
- 有序(即添加的顺序与出来的顺序一致);
- 索引;
- 可重复;
(除了Collection)特有方法:(增、删、获取、改、迭代)
添加
boolean add(int index, E element)
//把元素添加到指定的索引值里void add(int index, E element)
//把元素插入指定位置boolean addAll(int index, Collection<? extends E> c)
//把指定的集合添加到指定的索引里;
删除
E remove(int index)
//移除指定索引上的元素,返回被删除的元素
获取:
E get(int index)
//根据索引值,返回值int indexOf(Object o)
//找元素第一次出现的索引值int lastIndexOf(Object o)
//找元素最后一次出现的索引值E subList(int fromIndex, int toIndex)
//截取元素:从fromIndex到toIndex【注意:顾头不顾尾】;
修改:
E set(int index, E element)
//使用指定的元素替换该索引值的内容,返回修改之前的元素
总结:
- List接口中的特有方法特点:操作的方法都与索引值有关。
- 只有List接口下的集合类才具备索引值的方法,其他的都没有!
- 因为List的本质是Object数组;
List接口专属迭代器:ListIterator
特性:
- ListIterator的
超接口是Iterator
;并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。 - ListIterator
只能用来遍历List
,不能遍历其他。 - ListIterator既可以
前向
也可以后向
遍历,而Iterator只能是前向遍历。因为List接口的本质是个双向链表。 - 可以一边遍历、一边修改List元素。
方法:(ListIterator接口除了Iterator接口的额外方法有)
增加操作:void add(E e);
删除操作:void remove();
修改操作:void set(E e);
取出操作(查询):
boolean hasNext()
向前判断是否有元素E next()
先取出当前指针指向的元素,然后指针向下移动一个单位。int nextIndex()
返回调用next()的元素索引boolean hasPrevious()
判断是否存在上一个元素。E previous()
当前指针先向上移动一个单位,然后再取出当前指针指向的元素。
//实例
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
List col = new ArrayList();
col.add("thepor*tube com");
col.add("por**hub com");
col.add("javbus com");
col.add("3atv com");
ListIterator it = col.listIterator(); //获取到List的迭代器
System.out.println("向前迭代:");
while(it.hasNext()) {
System.out.println(it.next());
}
System.out.println("向后迭代:");
while(it.hasPrevious()) {
System.out.println(it.previous());
}
}
}
/*
输出:
向前迭代:
thepor*tube com
por**hub com
javbus com
3atv com
向后迭代:
3atv com
javbus com
por**hub com
thepor*tube com
*/
使用迭代器的注意事项
- 在迭代过程使用add函数,它
不会立即添加
到本个循环的指针所在位置;而是等待本个迭代器完成后再添加入; - 在迭代器迭代元素过程中,
不允许
使用集合对象的方法改变集合中元素个数; - 只有在迭代器方法使用
完毕后
,才能使用集合的操作方法
,即在使用迭代器进行迭代的时候,不能改变集合的元素个数;
在迭代器里next()和add():
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
List list = new ArrayList();
list.add("AA");
list.add("BB");
list.add("CC");
ListIterator it = list.listIterator(); //获取到迭代器
while(it.hasNext()){
System.out.print(it.next()+",");
it.add("additional"); // 把元素添加到当前指针指向位置;它不会立即添加到本个循环的指针所在位置;而是等待本个迭代器完成后再添加入;
}
System.out.print("\n集合list的内容是:"+list);
}
}
/* 输出:
AA,BB,CC,
集合list的内容是:[AA, additional, BB, additional, CC, additional]
*/
迭代时不能改变个数:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
List list = new ArrayList();
list.add("AA");
list.add("BB");
list.add("CC");
ListIterator it = list.listIterator(); // 获取到迭代器
while (it.hasNext()) {
System.out.println(it.next());
/*
如果使用集合对象的方法操作集合元素个数,则有异常抛出: ConcurrentModificationException
list.add("aa"); // add方法是把元素添加到集合的末尾处的。相当于改变集合个数
list.remove("BB"); //remove方法是删除集合中的指定元素。相当于改变集合个数
*/
}
}
}
/* 输出:
AA
BB
CC
*/
只有在迭代器使用完毕后,才能改变集合个数:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
List list = new ArrayList();
list.add("AA");
list.add("BB");
list.add("CC");
ListIterator it = list.listIterator(); //获取到迭代器
while(it.hasNext()){
System.out.print(it.next()+",");
}
list.add("aa"); //改变了集合元素个数的后面不能出现迭代器的使用,只能在迭代器使用完毕后才能使用集合对象方法来操作集合
//it.next(); 异常抛出!
System.out.print("\n集合中的元素"+list);
}
}
/* 输出:
AA,BB,CC,
集合中的元素[AA, BB, CC, aa]
*/
几种遍历方式
三种遍历集合的方式:
第一种: 使用get方法遍历。
第二种: 使用迭代器正序遍历。
第三种: 使用迭代器逆序遍历。
import java.util.ArrayList;
import java.util.Collection;
import java.util.ListIterator;
import java.util.List;
public class TestMain {
public static void main(String[] args) {
List list = new ArrayList();
list.add("AA");
list.add("BB");
list.add("CC");
//get遍历
for(int i=0;i<list.size();i++) {
System.out.println(list.get(i));
}
//迭代器正序遍历
ListIterator it = list.listIterator();
while(it.hasNext()) {
System.out.println(it.next());
}
//迭代器逆序遍历
while(it.hasPrevious()) {
System.out.println(it.previous());
}
}
}
再次提醒:只能在List接口的实例中使用ListIterator!!!
ArrayList类
学习目标:
由于该类的绝大部分方法都是其父接口中继承而来,故需要学习的是该类的实现原理与特点!
实现原理:
底层是维护一个Object数组来实现;
使用指导: (什么时候使用ArrayList?)
如果目前的数据是查询比较多,增删比较少的时候,那么就使用ArrayList存储这批数据。 比如:高校的图书馆。
特点:
查询速度快(像数组一样以连续的内存空间存储元素),增删慢(检查容量、拓展容量、把旧数组的内容拷贝到新数组中,见ArrayList源代码中的具体方法)- ArrayList
底层是维护了一个Object数组实现 的,使用无参构造函数时,Object数组默认的容量是10,当长度不够时,自动增长0.5倍。 - 为追求效率,ArrayList
没有实现同步(synchronized),如果需要多个线程并发访问,用户可以手动同步,也可使用 Vector 替代。
特有方法:
void ensureCapacity(int minCapacity)
//自己制定ArrayList数组的初始容量,亦可用构造方法来实现;trimToSize()
//为当前ArrayList实例根据已经存储的内容个数量调整容量
核心方法
增:
public boolean add(E e)
:添加元素public void add(int index, E element)
:在指定的索引处添加一个元素
删:
public boolean remove(Object o)
:删除指定的元素,返回删除是否成功public E remove(int index)
:删除指定索引处的元素,返回被删除的元素
改:
public E set(int index,E element)
:修改指定索引处的元素,返回被修改的元素
查:public E get(int index)
:返回指定索引处的元素public int size()
:返回集合中的元素的个数
增方法详解
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("kyle");
array.add("Java");
// add(int index,E element):在指定的索引处添加一个元素
array.add(1, "android");
System.out.println("ArrayList目前的状态是:" + array); //ArrayList目前的状态是:[hello, android, kyle, Java]
}
}
删方法详解
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("kyle");
array.add("Java");
array.remove("hello"); //根据元素名删除
System.out.println("删除某元素后ArrayList目前的状态是:" + array); //删除某元素后ArrayList目前的状态是:[kyle, Java]
String s = array.remove(0); //根据下标删除
System.out.println("删除第一个元素后的ArrayList目前的状态是:" + array); //删除第一个元素后的ArrayList目前的状态是:[Java]
}
}
改方法详解
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("kyle");
array.add("Java");
array.set(0, "olleh");
System.out.println("ArrayList目前的状态是:" + array); //输出:ArrayList目前的状态是:[olleh, kyle, Java]
}
}
查方法详解
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("kyle");
array.add("Java");
//public E get(int index):返回指定索引处的元素
System.out.println("获取的元素是:" + array.get(0));
//public int size():返回集合中的元素的个数
System.out.println("元素的个数:" + array.size());
}
}
遍历方式
遍历方法:
- 类似数组的遍历方法,通过size()和get()配合实现的
- 通过迭代器遍历
- 增强for循环
- forEach,源于JDK1.8的Lambda表达式
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
//索引方式
for(int i=0; i<list.size();i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
//Collection迭代器
Iterator<String> it = list.iterator();
while(it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
//List专属迭代器
ListIterator li = list.listIterator();
while(li.hasNext()) {
System.out.print(li.next() + " ");
}
System.out.println();
while(li.hasPrevious()) {
System.out.print(li.previous() + " ");
}
System.out.println();
//增强for
for(String ele:list) {
System.out.print(ele + " ");
}
System.out.println();
//forEach
list.forEach(ele -> System.out.print(ele + " "));
}
}
LinkedList类
特点:
- 链表实现;
- 增删快;
- 查找慢
实现原理:
LinkedList 同时实现了 List 接口和 Deque 接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。
底层原理:
- 由于LinkedList在内存中的地址不连续,需要让上一个元素记住下一个元素。所以每个元素中保存的有下一个元素的位置。虽然也有角标,但是查找的时候,需要从头往下找,显然是没有数组查找快的。但是,链表在插入新元素的时候,只需要让前一个元素记住新元素,让新元素记住下一个元素就可以了。所以插入很快。
- 由于链表实现, 增加时只要让前一个元素记住自己就可以,删除时让前一个元素记住后一个元素,后一个元素记住前一个元素。这样的增删效率较高。
核心方法
增:
addFirst(E e)
//把元素添加到集合的首位置上。addLast(E e)
//把元素添加到集合的末尾处。
删:
removeFirst()
//删除集合中的首位置元素,并返回removeLast()
//
查:
getFirst()
//获取集合中的首位置元素getLast()
//获取集合中末尾的元素
方法的使用方式类似于ArrayList,遍历的方式和ArrayList一样!
由于Vector已经被时间所淘汰,这里将不再详解。待后面Java集合进阶(研究源码和线程安全)时再做分析!
Set接口
Set的特点:
- 无序(存储和读取的顺序可能不一样)
- 不允许重复,仅针对于系统提供的数据类型;如果是自定义的类型/类,则允许重复
- 没有整数索引
方法
从其父接口(Collection)那里继承了绝大部分方法;
增:
- boolean add(E e);
- boolean addAll(Collection<? extends E> c);
删:
- void clear();
- boolean remove(Object obj);
- boolean reomveAll(Collection<?> c);
迭代:
- Object[] toArray();
- Iterator iterator();
查:
- int size();
- boolean isEmpty();
- boolean contains(Collection c);
特有方法(相对于List接口):
- boolean equals(Object o);
- int hashCode();
//试一试
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set ss = new HashSet();
ss.add("Aaa"); //增
ss.add("Bbb");
ss.add(1234);
ss.add(8848);
ss.add(3.1415926);
ss.remove(8848); //删
System.out.println("长度:"+ss.size()); //获取
System.out.println("空?"+ss.isEmpty());
System.out.println("含1234?"+ss.contains(1234));
//迭代
System.out.println("内容迭代:");
Iterator it = ss.iterator();
while(it.hasNext()) {
System.out.print(it.next() + " ");
}
}
}
/*
输出:
长度:4
空?false
含1234?true
内容迭代:
Aaa 1234 Bbb 3.1415926
*/
遍历迭代
Set接口的三种遍历方式:
- 转换为Object;
- 迭代器
- 增强for循环
遍历Set
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Main {
public static void main(String[] args) {
//创建集合对象
//HashSet<String> hs = new HashSet<String>();
Set<String> set = new HashSet<String>(); //父接口引用指向子类对象,无法使用子类对象中特有的成员
//添加元素
set.add("hello"); //实现了Collection接口的add方法
set.add("world");
set.add("java");
//遍历集合的三种方式
//第一种:转Object数组
Object[] objs = set.toArray();
for(int i=0;i<objs.length;i++) {
System.out.println(objs[i]);
}
//第二种:迭代器
Iterator<String> it = set.iterator();
while(it.hasNext()) {
String s = it.next();
System.out.println(s);
}
//第三种:增强for
for(String s:set) {
System.out.println(s);
}
}
}
HashSet类
HashSet的核心功能就是自动去重,保证整个容器中的每个元素都是唯一的。
特性:
- 底层的数据结构是哈希表(hash table);
- 存储,取出的速度都比较快;
- 线程不安全(也叫实现不同步);
//第一个HashSet程序
import java.util.Set;
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("前田可奈子");
set.add("桃乃木香奈");
set.add("深田咏美");
System.out.println(set); //输出:[桃乃木香奈, 深田咏美, 前田可奈子]
}
}
Set去重复的原理?
- 对于基本数据类型,直接判断值大小去重复,并不需要开发者过多干预;
- 对于引用类型,通过比较哈希值(hashCode)进行比较(equals),所以需要重写hashCode()和equals方法;
HashSet如何检查重复(add方法的执行流程):
- HashSet的add()方法,首先会使用当前集合中的每一个元素和新添加的元素进行hash值比较,
- 如果hash值不一样,则直接添加新的元素
- 如果hash值一样,比较地址值或者使用equals方法进行比较
- 比较结果一样,则认为是重复不添加
- 所有的比较结果都不一样则添加
为什么要调用equals方法?
- hashCode()与equals()的相关规定:
a) 如果两个对象相等,则hashcode一定也是相同的
b) 两个对象相等,对两个equals方法返回true
c) 两个对象有相同的hashcode值,它们也不一定是相等的 - 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖。hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
//HashSet保证元素唯一,自动去重
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set ss = new HashSet();
ss.add("cc");
ss.add("oo");
ss.add("bd");
ss.add("ba");
ss.add("ba");
ss.add("oo");
ss.add("cc");
Iterator it = ss.iterator();
while(it.hasNext()) {
System.out.print(it.next() + " ");
}
}
}
//输出:cc oo bd ba
无序的原理
Set容器的元素无序的根本原因是底层采用了哈希表存储元素。
- JDK 1.8之前:哈希表 = 数组 + 链表 + (哈希算法)
- JDK 1.8及之后:哈希表 = 数组 + 链表 + 红黑树(据哈希值排序) + (哈希算法)
自定义对象如何实现自动去重、保证唯一?
HashSet集合添加的元素是不重复的,是如何去重复的?
- 对于基本数据类型,直接判断值大小去重复,并不需要开发者过多干预;
- 对于引用类型,通过比较哈希值(hashCode)进行比较(equals),所以需要重写hashCode()和equals方法;
//自定义类在Set中去重
import java.util.Set;
import java.util.HashSet;
import java.util.Objects;
public class Main {
public static void main(String[] args) {
Set<Apple> apples = new HashSet<>();
Apple a1 = new Apple("红富士",59.9 ,"红色");
Apple a2 = new Apple("阿克苏",39.9 ,"青红色");
Apple a3 = new Apple("阿克苏",39.9 ,"青红色");
apples.add(a1);
apples.add(a2);
apples.add(a3);
System.out.println(apples);
}
}
class Apple {
private String name;
private double price;
private String color;
public Apple() {
}
public Apple(String name, double price, String color) {
this.name = name;
this.price = price;
this.color = color;
}
//IDEA自动重写出的equals、hashCode方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Apple apple = (Apple) o;
return Double.compare(apple.price, price) == 0 && Objects.equals(name, apple.name) && Objects.equals(color, apple.color);
}
@Override
public int hashCode() {
return Objects.hash(name, price, color);
}
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", price=" + price +
", color='" + color + '\'' +
'}';
}
}
LinkedHashSet类
LinkedHashSet架构:
特性:
-
基于链表的哈希表实现;
-
继承自HashSet;
-
维护着一个双重链表;
-
具有顺序,存储和取出的顺序相同
-
不允许有重复的元素
-
线程不安全,运行速度快
TreeSet类
特性:
- 不重复,无索引,按照大小默认升序排序
排序特性:
- 元素是基本数据类型:直接按大小,进行升序排序;进行排序的底层实现是:红-黑树;
- 元素是字符串类型:按照字典顺序(即ASCII码表)逐个排序;进行排序的底层实现是:红-黑树;
- 元素是自定义类:TreeSet无法进行默认排序,需要定义排序规则;
TreeSet类的架构:
//第一个TreeSet程序
import java.util.Set;
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
Set<Double> set01 = new TreeSet<Double>();
set01.add(3.14159);
set01.add(0.618);
set01.add(8488.888);
System.out.println(set01); //输出:[0.618, 3.14159, 8488.888]
Set<String> set = new TreeSet<>();
set.add("China");
set.add("china");
set.add("USA");
System.out.println(set); //输出:[China, USA, china]
}
}
自动排序的核心原理
自定义类设置自动排序规则:
- 方案一:直接在类上实现Comparable接口,重写比较方法;优先级较高。
- 方案二:写一个专门的比较器类(实现Comparator接口的类,并重写比较方法),需要比较时将比较器传入;
//Comparable实例
import java.util.Set;
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
Set<Employee> set01 = new TreeSet<>();
set01.add(new Employee("前田可奈子",22,8000.00D));
set01.add(new Employee("神田咏梅",28,9000.3D));
set01.add(new Employee("桃乃木香奈", 25,9999.3D));
System.out.println(set01);
}
}
//输出:[Employee{name='前田可奈子', age=22, salary=8000.0}, Employee{name='桃乃木香奈', age=25, salary=9999.3}, Employee{name='神田咏梅', age=28, salary=9000.3}]
class Employee implements Comparable<Employee> {
private String name;
private Integer age;
private Double salary;
public Employee(String name, Integer age, Double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public int compareTo(Employee o) {
return this.age - o.age; //升序
//return 0.age - this.age; //降序
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
//Comparator实例
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
EmployeeComparator employeeComparator = new EmployeeComparator();
Set<Employee> set01 = new TreeSet<>(employeeComparator); //传入比较器
set01.add(new Employee("前田可奈子",22,8000.00D));
set01.add(new Employee("神田咏梅",28,9000.3D));
set01.add(new Employee("桃乃木香奈", 25,9999.3D));
System.out.println(set01);
}
}
//输出:[Employee{name='前田可奈子', age=22, salary=8000.0}, Employee{name='神田咏梅', age=28, salary=9000.3}, Employee{name='桃乃木香奈', age=25, salary=9999.3}]
class Employee {
private String name;
private Integer age;
private Double salary;
public Employee(String name, Integer age, Double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Double getSalary() {
return salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
class EmployeeComparator implements Comparator<Employee> {
@Override
public int compare(Employee obj01, Employee obj02) {
//return (int)(obj01.getSalary() - obj02.getSalary());当两数相减值小于1,则始终返回0
return Double.compare(obj01.getSalary(), obj02.getSalary());
}
}