集合进阶
集合体系结构
List -> ArrayList , LinkedList,
Collection <
Set -> HashSet(-> LinkedHashSet ) , TreeSet,
List系列集合 : 添加的元素是有序, 可重复, 有索引.
Set系列集合 : 添加的元素是无序, 不重复, 无索引.
单列集合 Collection
Collection是单列集合的顶级父接口, 它的功能是全部单列集合都可以继承使用的.
常用方法:
boolean add(E e) 把指定对象添加到集合
void clear() 清空集合中所有元素
boolead remove(E e) 删除指定对象
boolean contains(Object obj) 判断当前集合中是否包含指定对象
boolean isEmpty() 判断当前集合是否为空
int size() 返回集合中元素的个数 / 集合长度
注意点:
Collection是一个接口, 我们不能直接创建他的对象. 只能创建他的实现类对象ArrayList
/*boolean add(E e) 把指定对象添加到集合
void clear() 清空集合中所有元素
boolead remove(E e) 删除指定对象
boolean contains(Object obj) 判断当前集合中是否包含指定对象
boolean isEmpty() 判断当前集合是否为空
int size() 返回集合中元素的个数 / 集合长度*/
public class CollectionMethod {
public static void main(String[] args) {
//多态的形式创建对象是为了学习Collection中的方法
Collection<String> coll = new ArrayList<>();
//1.添加方法
//返回值的细节: 往List系列集合中添加数据方法永远返回true,因为List系列集合允许元素重复.
// 往Set集合中添加数据,如果当前添加的元素不存在,返回true,表示添加成功.
// 如果要添加的元素已经存在,返回false,添加失败.
// 因为Set系列的集合不允许元素重复.
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
System.out.println(coll);
//2.清空方法
// coll.clear();
// System.out.println(coll);
//3.删除方法
//注意: 因为Collection里是集合中共性的方法,所以不能通过索引删除,只能通过元素的对象进行删除.
//细节: 要删除的元素存在就删除成功,返回true. 否则就返回false.
coll.remove("aaa");
System.out.println(coll);
//3.是否包含方法
//细节:在底层依赖equals方法进行判断.
//所以集合中是自定义对象,要使用contains方法判断是否存在,就要在Javabean类中重写equals方法.
boolean bbb = coll.contains("bbb");
System.out.println(bbb);
//4.判断集合是否为空
boolean empty = coll.isEmpty();
System.out.println(empty);
//获取集合长度
System.out.println(coll.size());
}
}
使用contains方法,重写equals方法
public static void main(String[] args) {
//创建学生对象
student s1 = new student("xiaoming", 8);
student s2 = new student("dazhuang", 9);
//s3和s2一样的属性
student s3 = new student("dazhuang", 9);
//创建集合
ArrayList<student> list = new ArrayList<>();
//添加对象
list.add(s1);
list.add(s2);
//用contains方法判断
//如果想要判断属性是否相同,就要重写javabean里的equals方法.否则Object类中的equals方法比较的是地址值.
System.out.println(list.contains(s3));//不重写false, 重写后true
}
//在Javabean类中重写equals方法
@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);
}
Collection 遍历
迭代器遍历
迭代器在Java中的类是Iterator, 迭代器是集合专用的遍历方式, 不依赖索引.
获取迭代器:
Iterator
方法:
boolean hasNext() 判断当前位置是否有元素, 有元素返回true, 无元素返回false
E next() 获取当前位置的元素, 并将迭代器对象移动到下一个位置
Collection<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//迭代器遍历集合
//获取迭代器对象,迭代器就好比一个箭头,默认指向集合的0索引处
Iterator<String> it = list.iterator();
//利用循环不断的获取集合中的每一个元素
while (it.hasNext()) {
//next方法做的两件事,1.获取元素 2.往下移动指针
String next = it.next();
System.out.print(next + " ");
}
迭代器的注意点:
-
报错NoSuchElementException : 循环遍历后,迭代器的指针已经指向了最后没有元素的位置,再用next方法就会报错.
-
迭代器遍历完毕后,指针不会复位 : 如果我们要继续第二次遍历集合,只能再次获取一个新的迭代器对象.
-
循环中只能用一次next方法 : 循环中hasNext和next方法要配套使用.
-
迭代器遍历时, 不能用集合的方法进行增加或者删除 : 暂时不能添加元素, 只能用迭代器里的remove方法删除.
增强for遍历
- 增强for底层就是迭代器, 为了简化迭代器代码书写的.
- 它是JKD5以后出现的, 其内部原理就是Iterator迭代器.
- 所有单列简化和数组才能用增强for遍历.
格式: for(元素的数据类型 变量名 : 数组或集合){ }
这里的变量名是一个第三方变量, 在循环过程中依次表示集合中的每一个数据.
修改增强for中的变量, 不会修改集合中原本的数据.
//enhanced'for'遍历
for (String s : coll) {
System.out.println(s);
}
Lambda表达式遍历
得益于JKD8开始的新技术Lambda表达式, 提供了一种更简单,更直接的遍历集合方法.
default void forEach(Consumer<>); 结合Lambda遍历集合
Collection<String> coll = new ArrayList<>();
//创建集合
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
//Lambda遍历
/* coll.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
*/
//Lambda表达式简化写法
coll.forEach(s -> System.out.println(s));
总结
- Collection是单列集合的顶层接口,所有方法被List和Set系列集合共享
- 常见成员方法:
add、 clear、 remove、 contains、 isEmpty、 size - 三种通用的遍历方式:
- 迭代器:在遍历的过程中需要删除元素,请使用迭代器。
- 增强for、Lambda:
仅仅想遍历,那么使用增强for或Lambda表达式。
List 集合常用方法和5种遍历方式
List集合的特有方法
-
Collection的方法List都继承了
-
List集合因为有索引,所以多了很多索引操作的方法。
方法名称 说明
void add(int index^E element) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素
E set(int index, E element) 修改指定索弓1处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素
//List集合中删除方法(remove)的细节
//创建集合
List<Integer> list = new ArrayList<>();
//添加元素
list.add(1);
list.add(2);
list.add(3);
//删除元素
//请问,此时删除的是1元素还是1索引上的元素
//在调用方法时,如果出现方法重载,优先调用实参和形参一致的那个方法
//1是int类型数据,就默认为索引
Integer remove = list.remove(1);
System.out.println(remove);
//如果要删除1元素,就要手动装箱
Integer i = Integer.valueOf(1);
System.out.println(list.remove(i));
List集合5种遍历方法
- 迭代器遍历
- 列表迭代器遍历------ 它是迭代器的子接口, 它添加了一个方法: 在遍历过程中可以添加元素.
- 增强for遍历
- Lambda表达式遍历
- 普通for循环遍历(list集合存在索引)
List<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
//1. 迭代器遍历
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
//2. 增强for遍历
for (String s : list) {
System.out.println(s);
}
//3. Lambda表达式遍历
list.forEach(s -> System.out.println(s));
//4. 普通for循环遍历(list集合存在索引)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("列表迭代器遍历");
//5. 列表迭代器遍历
//创建列表迭代器对象
ListIterator<String> li = list.listIterator();
//遍历list集合并添加元素
while (li.hasNext()){
String next = li.next();
if ("bb".equals(next)){
li.add("**");
}
}
System.out.println(list);
总结
迭代器遍历 --- 在遍历过程中要删除元素时使用
列表迭代器(List集合) --- 在遍历过程中要添加元素时使用
增强for / Lambda表达式 --- 仅仅想遍历时使用
普通for --- 如果遍历时想操作索引时使用
常见的数据结构
- 栈 : 后进先出,先进后出.
- 队列 : 先进先出, 后进后出.
- 数组 : 内存中的连续区域, 查询快, 增删慢.
- 链表 : 元素是游离的, 查询慢, 首位操作极快.
ArrayList 源码分析
大纲视图 : 快捷键 Alt + 7
成员搜索栏 : Ctrl + F12
LinkedList 源码分析
泛型深入
泛型类 : 当一个类中, 某个变量的数据类型不确定时,就可以定义带有泛型的类.
格式 : 修饰符 类名
public class ArrayList<E>{ }//创建对象时,E就确定类型
此处的E可以理解为变量, 但是不是用来记录数据的, 而是记录数据类型, 可以写成 T , E , K , V等.
练习 : 自定义一个myArrayList泛型类, 存储各种类型的元素.
public class myArrayList<E> {
Object[] obj = new Object[10];
int size = 0;
//E : 表示不确定的类型, 在该类的类名后已经定义过了
//e : 形参的名字, 变量名
//add
public boolean add(E e) {
obj[size] = e;
size++;
return true;
}
//get
public E get(int index) {
//把object类型的元素转换成E类型
return (E) obj[index];
}
//toString
public String toString() {
return Arrays.toString(obj);
}
}
//测试类
public static void main(String[] args) {
myArrayList<String> list = new myArrayList<>();
list.add("aaa");
list.add("bbb");
System.out.println(list.get(1));
System.out.println(list.toString());
myArrayList <Integer> list1 = new myArrayList<>();
list1.add(123);
list1.add(456);
System.out.println(list1.get(0));
System.out.println(list1.toString());
}
泛型方法
方法形参类型不确定时, 有两种方案 :
方案1 : 使用类名后面定义的泛型. --- 所有的方法都能使用.
方案2 : 在方法声明上定义自己的泛型 --- 只能本方法使用.
格式 : 修饰符 <类型> 返回值类型 方法名(类型 变量名){ }
public <T> void show(T t){ }
此处的T可以理解为变量, 但是不是用来记录数据的, 而是记录数据类型, 可以写成 T , E , K , V等.
练习 : 定义一个工具类 ListUtil, 类中定义一个静态方法addAll, 用来添加多个元素.
//工具类
public class ListUtil {
//私有化构造方法
private ListUtil() {
}
//addAll方法 泛型要写在修饰符后面
public static <E> void addAll(ArrayList<E> list, E e1, E e2, E e3) {
list.add(e1);
list.add(e2);
list.add(e3);
}
//可变参数 E ... e 底层就是数组
public static <E> void addAll(ArrayList<E> list, E... e) {
for (E elemet : e) {
list.add(elemet);
}
}
}
//测试类
public class test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
ListUtil.addAll(list, "1", "2", "a", "b", "c");
System.out.println(list);
}
} //[1, 2, a, b, c]
泛型接口
格式 : 修饰符 interface 接口名 <类型> { }
public interface List<E>{ }
重点 : 如何使用泛型接口.
方式1 : 实现类给出具体类型
public class myArrayList implements List<String>{}//一旦给出类型后,就确定了
方式2 : 实现类延续泛型, 创建对象时再确定.
public class myArrayList<E> implements List<E>{}//用于实现类还没有确定类型,在创建对象时再确定
泛型的继承和通配符
- 泛型不具备继承性, 但是数据具备继承性.
- 泛型写的什么类型, 就只能传递什么类型的数据.
利用泛型方法有一个小弊端, 就是它可以接受任意类型的数据, 有时候我们要对数据进行限制.
此时我们就可以使用泛型通配符.
*** ? 表示不确定的类型***
? extends E : 表示可以传递E和E的子类类型
*** ? super E : 表示可以传递E和E的父类类型***
**应用场景 : **
- 如果我们在定义类, 方法, 接口时, 如果类型不确定, 就可以定义泛型类, 泛型方法, 泛型接口.
- 如果类型不确定, 但是知道以后只能传递某一个继承体系, 就可以用泛型通配符.
- 泛型通配符就是限定类型的范围.
总结
- 什么是泛型?
JDK5引入的特性,可以在编译阶段约束操作的数据类型,并进行检查
- 泛型的好处?
-
统一数据类型
-
把运行时期的问题提前到了编译期间,避免了强制
类型转换可能出现的异常,因为在编译阶段类型就能确定下来。
- 泛型的细节?
-
泛型中不能写基本数据类型
-
指定泛型的具体类型后,传递数据时,可以传入该类型和他的子类类型
-
如果不写泛型,类型默认是Object
- 哪里定义泛型?
- 泛型类:在类名后面定义泛型,创建该类对象的时候,确定类型
- 泛型方法:在修饰符后面定义方法,调用该方法的时候,确定类型
- 泛型接口:在接口名后面定义泛型,实现类确定类型,实现类延续泛型
- 泛型的继承和通配符
- 泛型不具备继承性,但是数据具备继承性
- 泛型的通配符:?
? extend E
? super E
- 使用场景
- 定义类、方法、接口的时候,如果类型不确定,就可以定义泛型
- 如果类型不确定,但是能知道是哪个继承体系中的,可以使用泛型的通配符
数据结构 (二叉树, 二叉查找数, 平衡二叉树)
二叉树
node - 节点的内部结构 :
父节点地址, 值, 左子节点地址, 右子节点地址 .
度 : 每一个节点的子节点数量.
二叉树中, 任意节点的度都<=2.
高 : 就是数的总层数.
根节点 : 最顶层的节点.
左子节点 : 左下方的节点.
右子节点 : 右下方的节点.
根节点的左/右的部分 : 叫根节点的左/右子树.
普通二叉树的弊端 :
普通二叉树查找非常麻烦, 必须要遍历, 二叉查找树查找的效率很高.
二叉查找树
特点 :
每一个节点上最多有两个子节点
任意节点左子树上的值都小于当前节点
任意节点右子树上的值都大于当前节点
添加节点的规则 :
小的存左边
大的存右边
一样的不存
二叉数的遍历方式 :
- 前序遍历 : 当前节点, 左子节点, 右子节点.
- 中序遍历 : 左子节点, 当前节点 , 右子节点. (最常用)
- 后序遍历 : 左子节点, 右子节点, 当前节点.
- 层序遍历 : 一层一层去遍历.
平衡二叉树
规则 : 任意节点左右子数高度差不超过1.
数据结构 (平衡二叉树的旋转机制)
规则 : 左旋和右旋
触发时机 : 当添加一个节点后, 该数不再是一个平衡二叉树时.
确定支点 : 从添加的节点开始, 不断的往父节点找不平衡的节点.
需要旋转的四种情况
- 左左 : 一次右旋
- 左右 : 先局部左旋, 再整体右旋
- 右右 : 一次左旋
- 右左 : 先局部右旋, 再整体左旋
数据结构(红黑树)
HashSet, LinkedHashSet详解
区别 :
List系列集合 : 添加的元素是有序, 可重复, 有索引.
Set系列集合 : 添加的元素是无序, 不重复, 无索引.
Set系列集合
- 无序 : 存取顺序不一致
- 不重复 : 可以去除重复
- 无索引 : 没有带索引的方法, 所以不能使用普通for循环遍历, 也不能通过使用获取元素.
Set集合的实现类
- HashSet : 无序, 不重复, 无索引.
- LinkedHashSet : 有序, 不重复, 无索引.
- TreeSet : 可排序, 不重复, 无索引.
Set接口中的方法基本上与Collection的API一致.
练习 : 存储字符串并遍历
利用Set系列集合, 添加字符串, 并使用多种方法遍历.
- 迭代器
- 增强fof
- Lambda表达式
public class SetDemo_1 {
public static void main(String[] args) {
/*练习 : 存储字符串并遍历
利用Set系列集合, 添加字符串, 并使用多种方法遍历.
1. 迭代器
2. 增强for
3. Lambda表达式*/
Set<String> set = new HashSet<>();
//添加元素
//如果当元素第一次添加,那么可以添加成功,返回true
//如果当元素第二次添加,那么就添加失败,返回false
boolean r1 = set.add("aaa");//true
boolean r2 = set.add("aaa");//false
set.add("bbb");
set.add("ccc");
System.out.println(r1);
System.out.println(r2);
//1. 迭代器
Iterator<String> it = set.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("-----");
//2. 增强for
for (String s : set) {
System.out.print(s + " ");
}
System.out.println();
System.out.println("-----");
//3. Lambda表达式
set.forEach(s -> System.out.println(s));
}
}
hashSet底层原理
- hashSet集合底层采取哈希表存储数据.
- 哈希表是一种对于增删改查性能都较好的结构.
**哈希表的组成 **
- JDK8之前 : 数组 + 链表
- 从JDK8开始 : 数组 + 链表 + 红黑树
哈希值 : 对象的整数表现形式.
哈希值
-
根据hashCode方法算出来的int类型的整数
-
该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
-
一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈布值
对象的哈希值特点
-
如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
-
如果已经重写hashcode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
-
在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
student1 s1 = new student1("abc", 10);
student1 s2 = new student1("abc", 10);
//重写hashCode方法后,不同的对象只要属性值相同,计算出的哈希值就是一样的
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
//在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样.(哈希碰撞)
System.out.println("abc".hashCode());//96354
System.out.println("acD".hashCode());//96354
HashSet底层原理
① 创建一个默认长度16,默认加载因子0.75的数组,数组名table
② 根据元素的哈希值跟数组的长度计算出应存入的位置
③ 判断当前位置是否为null,如果是null直接存入
④ 如果位置不为null, 表示有元素,则调用equals方法比较属性值
⑤ 一样:不存 不一样:存入数组,形成链表
JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8以后:新元素直接挂在老元素下面
JDK8以后, 当链表长度超过8, 并且数组长度大于等于64时, 自动转换为红黑树.
如果集合中存储的自定义类型对象 , 必须要重写hashCode方法和equals方法.
HashSet三个问题
问题1: 为什么存和取的顺序不一样
链表的存和取的顺序就有可能不一样.
问题2: hashSet为什么没有索引
链表,红黑树都没有索引.
问题3: hashSet是利用什么机制保证数据去重的.
利用hashCode和equals方法.
练习 利用HashSet集合去除重复元素
需求:创建一个存储学生对象的集合,存储多个学生对象。
使用程序实现在控制台遍历该集合。
要求:学生对象的成员变量值相同,我们就认为是同一个对象
npc n1 = new npc("a",1);
npc n2 = new npc("b",2);
npc n3 = new npc("c",3);
//已经在javabean中重写了hashCode和equals方法
//对象中属性相同的元素就是重复的元素,不会再次添加.
npc n4 = new npc("a",1);
//创建集合,要去重就用HashSet,不去重就用ArrayList.
HashSet<npc> hs = new HashSet<>();
//添加元素,添加失败返回false,
System.out.println(hs.add(n1));//true
System.out.println(hs.add(n2));//true
System.out.println(hs.add(n3));//true
System.out.println(hs.add(n4));//false
//打印集合
System.out.println(hs);
LinkedHashSet
- LinkedHashSet集合的特点和原理是怎么样的?
- 有序、不重复、无索引
- 底层基于哈希表,使用双链表记录添加顺序
- 在以后如果要数据去重,我们使用哪个?
默认使用HashSet
如果要求去重且存取有序,才使用LinkedHashSet
TreeSet
TreeSet的特点
-
不重复, 无索引, 可排序.
-
可排序 : 按照元素默认规则(从小到大)排序.
-
TreeSet底层基于红黑树数据结构实现排序, 增删改查性能都较好.
练习:
用TreeSet集合存储整数,并进行排序.
/*用TreeSet集合存储整数,并进行排序.*/
TreeSet<Integer> ts = new TreeSet<>();
ts.add(4);
ts.add(3);
ts.add(1);
ts.add(2);
ts.add(5);
//遍历集合
System.out.println(ts);
//迭代器
Iterator<Integer> it = ts.iterator();
while (it.hasNext()) {
Integer i = it.next();
System.out.print(i + " ");
}
//增强for
for (Integer t : ts) {
System.out.print(t + " ");
}
//Lambda表达式
ts.forEach(i -> System.out.print(i + " "));
TreeSet集合默认规则
- 对于数值类型 : Integer , Double, 默认从小到大排序
- 对于字符/字符串类型 : 按照字符在ASCII码表中的数字升序排序.
- 比较字符串 : 从第一个字符开始比较,第一个字符一样再比较第二个,以此类推.
TreeSet的两种比较方法
方式1 : 默认排序 / 自然排序 - JavaBean类实现Comparable接口指定比较规则,重写里面的抽象方法compareTo().
compareTo方法中的this表示本次添加的元素, o表示已经在红黑树存在的元素.
return this.getAge() - o.getAge();
返回值如果是负数表示要添加的元素是小的, 存左边.
返回值如果是正数表示要添加的元素是大的, 存右边.
返回值如果是0表示要添加的元素已经存在, 舍弃.
hashCode 和equals方法跟"哈希表"有关.
TreeSet的底层是红黑树, 所以使用TreeSet集合比较元素时不需要重写这两个方法. 但是我们要指定他的排序规则.
练习 : 默认排序,实现Comparable接口,重写里面的抽象方法compareTo().
创建TreeSet集合,并添加3个学生对象
属性 : name age
按年龄排序, 年龄一样按姓名字母排序.年龄姓名一样就是同一个人.
public static void main(String[] args) {
//创建对象
person p1 = new person("zhangsan", 23);
person p2 = new person("lisi", 24);
person p3 = new person("wangwu", 25);
//创建TreeSet集合
TreeSet<person> ts = new TreeSet<>();
//添加元素
ts.add(p1);
ts.add(p2);
ts.add(p3);
//打印集合
System.out.println(ts);
}
//----------------------------
//JavaBean类
//需要实现Comparable接口,我们比较的是person类型对象,
//这里泛型就用<person>,最后重写CompareTo方法来指定排序规则.
class person implements Comparable<person> {
private String name;
private int age;
......
//重写compareTo方法来指定排序规则
@Override
public int compareTo(person o) {
//只比较年龄,从小到大升序
System.out.println("---------");
//显示调用方法进行比较的过程
System.out.println("this:" + this);
System.out.println("o:" + o);
//指定排序规则
return this.getAge() - o.getAge();
}
方式2 : 比较器排序 -- 创建TreeSet对象时, 传递比较器Comparator指定规则
使用原则 : 默认使用第一种方式, 如果第一种方式不能满足要求, 再用第二种方式.
练习 : 上面的练习,使用第二种方法比较器排序.
//默认排序
//创建集合
TreeSet <String> ts = new TreeSet<>();
//添加元素
ts.add("df");
ts.add("ab");
ts.add("qwer");
ts.add("c");
//打印集合
System.out.println(ts);
//[ab, c, df, qwer],默认比较结果,按字母顺序.
//比较器排序---------------------------
//创建集合 参数传递比较器Complarator接口的实现类对象
TreeSet <String> ts = new TreeSet<>(new Comparator<String>() {
@Override //它是函数式接口,可以改成Lambda表达式
public int compare(String o1, String o2) {
//o1 : 当前要添加的元素
//o2 : 已经在红黑树存在的元素
//返回值 : 和上面的方法一样
//先按长度排序
int i = o1.length() - o2.length();
//长度一样再按字母顺序排序,如果i等于0,
//说明长度一样, 就调用compareTo默认方法比较.
i = i == 0 ? o1.compareTo(o2) : i;
return i;
}//比较器比较的结果 [c, ab, df, qwer]
});
//添加元素
ts.add("df");
ts.add("ab");
ts.add("qwer");
ts.add("c");
//打印集合
System.out.println(ts);
//[ab, c, df, qwer],默认比较结果,按字母顺序.
练习 : TreeSet对象排序练习
需求 :
- 创建5个学生对象
- 属性 : 姓名,年龄,语文成绩, 数学成绩, 英语成绩
- 按总分从高到底输出
- 如果总分一样, 按语文成绩排序
- 如果语文一样, 按数学成绩排序
- 如果数学一样, 按英语成绩排序
- 如果英语一样, 按年龄排序
- 如果年龄一样, 按姓名字母顺序
- 如果都一样, 认为是一个人, 不存.
public class TreeSetDemo01 {
public static void main(String[] args) {
//创建对象
stu s1 = new stu("a", 10, 90, 90, 90);
stu s2 = new stu("b", 11, 90, 90, 90);
stu s3 = new stu("c", 12, 90, 90, 90);
stu s4 = new stu("d", 13, 100, 80, 90);
stu s5 = new stu("e", 14, 70, 100, 100);
//创建集合
TreeSet<stu> ts = new TreeSet<>();
//添加对象
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
//遍历集合
for (stu t : ts) {
System.out.println(t);
}
}
}
//比较对象属性时,默认使用第一种方式, 如果第一种方式不能满足要求, 再用第二种方式.
//这里就用第一种方式: 默认排序 / 自然排序 - JavaBean类实现Comparable接口指定比较规则,
class stu implements Comparable<stu> {
private String name;
private int age;
private int chinese;
private int math;
private int english;
public stu() {
}
//... ...
//加入总分的显示
public String toString() {
int score = this.getEnglish() + this.getMath() + this.getChinese();
return "stu{name = " + name + ", age = " + age + ", chinese = " + chinese + ", " +
"math = " + math + ", english = " + english + ", TScore = " + score + "}";
}
//排序规则
@Override
public int compareTo(stu o) {
//按总分从高到底输出
//4. 如果总分一样, 按语文成绩排序
int s1 = this.getChinese() + this.getMath() + this.getEnglish();
int s2 = o.getChinese() + o.getMath() + o.getEnglish();
int i = s1 - s2;
i = i == 0 ? this.getChinese() - o.getChinese() : i;
//5. 如果语文一样, 按数学成绩排序
i = i == 0 ? this.getMath() - o.getMath() : i;
//6. 如果数学一样, 按英语成绩排序(不用写)
//7. 如果英语一样, 按年龄排序
i = i == 0 ? this.getAge() - o.getAge() : i;
//8. 如果年龄一样, 按姓名字母顺序
i = i == 0 ? this.getName().compareTo(o.getName()) : i;
//9. 如果都一样, 认为是一个人, 不存.
return i;
}
}
//----------------------
stu{name = e, age = 14, chinese = 70, math = 100, english = 100, TScore = 270}
stu{name = a, age = 10, chinese = 90, math = 90, english = 90, TScore = 270}
stu{name = b, age = 11, chinese = 90, math = 90, english = 90, TScore = 270}
stu{name = c, age = 12, chinese = 90, math = 90, english = 90, TScore = 270}
stu{name = d, age = 13, chinese = 100, math = 80, english = 90, TScore = 270}
总结
- TreeSet集合的特点是怎么样的?
- 可排序、不重复、无索引
- 底层基于红黑树实现排序,增删改查性能较好
- TreeSet集合自定义排序规则有几种方式
- 方式一:javabean类实现Comparable接口,指定比较规则 (Java规定的方式)
- 方式二:创建集合时,自定义Comparator比较器对象,指定比较规则(自定义方式)
选择哪一种方法?
-
d默认使用第一种方式, 如果第一种方式不能满足要求, 再用第二种方式. 比如说:字符串的排序规则Java已经规定好了,是按照字母顺序排. 但我想要按照字母数量排序.或者整数排序默认是升序,但是我要降序排.以上的需求只能用第二种方式排序了.
-
当程序中方式一和方式二都存在时,是以方式二为准的.
- 方法返回值的特点
- 负数:表示当前要添加的元素是小的,存左边
- 正数:表示当前要添加的元素是大的,存右边
- 0:表示当前要添加的元素已经存在,舍弃
单列集合Collection 总结
使用场景
- 如果想要集合中的元素可重复
- 用ArrayList集合,基于数组的。(用的最多)
- 如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
- 用LinkedList集合,基于链表的。
- 如果想对集合中的元素去重
- 用HashSet集合,基于哈希表的。(用的最多)
- 如果想对集合中的元素去重,而且保证存取顺序
- 用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet
- 如果想对集合中的元素进行排序
- 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。