集合
1.List
java.util.List接口 extends Collection接口
1.List接口的特点:
- 有序的集合,存储元素和取出元素的顺序是一致的(存储123 取出123)
- 有索引,包含了一些带索引的方法
- 允许存储重复的元素
2.List接口中带索引的方法(特有):
public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
public E get(int index):返回集合中指定位置的元素。
public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
注意:
- 操作索引的时候,一定要防止索引越界异常
- IndexOutOfBoundsException:索引越界异常,集合会报
- ArrayIndexOutOfBoundsException:数组索引越界异常
- StringIndexOutOfBoundsException:字符串索引越界异常
public class Demo01List {
public static void main(String[] args) {
//创建一个List集合对象,多态
List<String> list = new ArrayList<>();
//使用add方法往集合中添加元素
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");
//打印集合
System.out.println(list);//[a, b, c, d, a] 不是地址重写了toString
//public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
//在c和d之间添加一个itheima
list.add(3,"it");//[a, b, c, it, d, a]
System.out.println(list);
//public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
//移除元素
String removeE = list.remove(2);
System.out.println("被移除的元素:"+removeE);//被移除的元素:c
System.out.println(list);//[a, b, it, d, a]
//public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
//把最后一个a,替换为A
String setE = list.set(4, "A");
System.out.println("被替换的元素:"+setE);//被替换的元素:a
System.out.println(list);//[a, b, it, d, A]
3.List集合遍历有3种方式(接上面代码)
//使用普通的for循环
for(int i=0; i<list.size(); i++){
//public E get(int index):返回集合中指定位置的元素。
String s = list.get(i);
System.out.println(s);
}
System.out.println("-----------------");
//使用迭代器
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("-----------------");
//使用增强for
for (String s : list) {
System.out.println(s);
}
String r = list.get(5);//IndexOutOfBoundsException: Index 5 out-of-bounds for length 5
System.out.println(r);
}
}
4.并发修改异常
出现的原因
- 迭代器遍历的过程中,通过集合对象修改了集合中的元素,造成了迭代器获取元素中判断预期修改值和实际修改值不一致,则会出现:ConcurrentModificationException
通俗解释:通过迭代器来遍历的时候,在迭代器中调用next()方法的时候,会进行一个实际集合内元素大小和预期集合元素大小的比较,如果两个集合中的元素相等,才可以正常执行程序,如果你通过判断来增加集合的元素,就会造成集合内元素和预期的集合元素大小不等,就会导致并发修改异常。
解决的方案
- 用for循环遍历,然后用集合对象做对应的操作即可
示例代码:
public class ListDemo {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//遍历集合,得到每一个元素,看有没有"world"这个元素,如果有,我就添加一个"javaee"元素,请写代码实现
// Iterator<String> it = list.iterator();
// while (it.hasNext()) {
// String s = it.next();
// if(s.equals("world")) {
// 此处就是异常出现的原因
// list.add("javaee");
// }
// }
for(int i=0; i<list.size(); i++) {
String s = list.get(i);
if(s.equals("world")) {
list.add("javaee");
}
}
//输出集合对象
System.out.println(list);
}
}
5.ListIterator(util包 )
ListIterator:列表迭代器
- 通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器
- 用于允许程序员沿任一方向遍历列表的列表的迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置(通俗解释:相对于iterator迭代器来说,iterator只能从头往后遍历,而ListIterator既可以从头往后遍历,也可以从后往前遍历,而且ListIterator迭代器可以在迭代期间增删列表中的元素。)
ListIterator中的常用方法
E next():返回迭代中的下一个元素
boolean hasNext():如果迭代具有更多元素,则返回 true
E previous():返回列表中的上一个元素
boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回 true
void add(E e):将指定的元素插入列表
示例代码:
public class ListIteratorDemo {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//通过List集合的listIterator()方法得到
// ListIterator<String> lit = list.listIterator();
// while (lit.hasNext()) {
// String s = lit.next();
// System.out.println(s);
// }
// System.out.println("--------");
//
// while (lit.hasPrevious()) {
// String s = lit.previous();
// System.out.println(s);
// }
//获取列表迭代器
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
String s = lit.next();
if(s.equals("world")) {
lit.add("javaee");
}
}
System.out.println(list);
}
}
问题:为什么ListIterator可以在迭代期间遍历?
因为ListIterator底层有一个赋值操作的过程,它将modCount赋值给了expectedModCount,就是将实际修改的值赋值给了预期的值,所以当再次判断集合内元素和预期集合内元素的时候,就不会不相等了,所以就不会出现并发修改异常。
6.增强for循环
增强for:
- 简化数组和Collection集合的遍历
- 实现Iterable接口的类允许其对象成为增强型 for语句的目标
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
格式:
for(元素数据类型 变量名 : 数组或者Collection集合) {
//在此处使用变量即可,该变量就是元素
}
示例代码:
public class ForDemo {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
for(int i : arr) {
System.out.println(i);
}
System.out.println("--------");
String[] strArray = {"hello","world","java"};
for(String s : strArray) {
System.out.println(s);
}
System.out.println("--------");
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
for(String s : list) {
System.out.println(s);
}
System.out.println("--------");
//增强for循环内部原理是一个Iterator迭代器
/*
for(String s : list) {
if(s.equals("world")) {
list.add("javaee"); //ConcurrentModificationException
}
}
*/
}
}
增强for循环内部原理是一个Iterator迭代器
- 解释:iterator迭代器迭代期间无法进行对集合增删元素,会造成并发修改异常,
而增强for循环因为内部原理是一个iterator迭代器,所以也无法进行集合元素的增删。
7.List集合常用子类:ArrayList,LinkedList(util包 )
public class ArrayList
extends AbstractList
implements List, RandomAccess, Cloneable, Serializable
- ArrayList:底层数据结构是数组,查询快,增删慢,是不同步的(多线程),所以效率高,速度快。
- ArrayList实际上是一个动态数组,数组的初始化长度为10,而这个10是指的逻辑上的长度,就是当一个元素添加进去的时候,剩下的9个长度不做计算。
ArrayList集合中元素的增删是通过创建数组、复制数组元素来实现的,所以增删慢。
ArrayList集合中查询集合元素是通过索引来实现,所以查询速度很快。
- LinkedList:底层数据结构是双向链表,查询慢,增删快,是不同步的(也是多线程的实现类)
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, Serializable
LinkedList因为有大量首尾元素操作的方法,所以增删快,而查询只能从头部尾部比较着查询,所以查询很慢。
注意:LinkedList不能使用多态,因为LinkedList有特有的方法,而多态不能使用自身特有的方法。
ArrayList集合练习:
需求:创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合
思路:
-
1:定义学生类
2:创建ArrayList集合对象
3:创建学生对象
4:把学生添加到集合
5:遍历集合 -
迭代器:集合特有的遍历方式
普通for:带有索引的遍历方式
增强for:最方便的遍历方式
Student类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
ArrayListDemo测试类
public class ArrayListDemo {
public static void main(String[] args) {
//创建ArrayList集合对象
ArrayList<Student> array = new ArrayList<Student>();
//创建学生对象
Student s1 = new Student("林青霞", 30);
Student s2 = new Student("张曼玉", 35);
Student s3 = new Student("王祖贤", 33);
//把学生添加到集合
array.add(s1);
array.add(s2);
array.add(s3);
//迭代器:集合特有的遍历方式
Iterator<Student> it = array.iterator();
while (it.hasNext()) {
Student s = it.next();
System.out.println(s.getName() + "," + s.getAge());
}
System.out.println("--------");
//普通for:带有索引的遍历方式
for(int i=0; i<array.size(); i++) {
Student s = array.get(i);
System.out.println(s.getName() + "," + s.getAge());
}
System.out.println("--------");
//增强for:最方便的遍历方式
for(Student s : array) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
LinkedList集合练习
LinkedList集合的特有功能:
public void addFirst(E e):在该列表开头插入指定的元素
public void addLast(E e):将指定的元素追加到此列表的末尾
public E getFirst():返回此列表中的第一个元素
public E getLast():返回此列表中的最后一个元素
public E removeFirst():从此列表中删除并返回第一个元素
public E removeLast():从此列表中删除并返回最后一个元素
示例代码:
public class LinkedListDemo {
public static void main(String[] args) {
//创建集合对象
LinkedList<String> linkedList = new LinkedList<String>();
linkedList.add("hello");
linkedList.add("world");
linkedList.add("java");
public void addFirst(E e):在该列表开头插入指定的元素
public void addLast(E e):将指定的元素追加到此列表的末尾
linkedList.addFirst("javase");
linkedList.addLast("javaee");
public E getFirst():返回此列表中的第一个元素
public E getLast():返回此列表中的最后一个元素
System.out.println(linkedList.getFirst());
System.out.println(linkedList.getLast());
public E removeFirst():从此列表中删除并返回第一个元素
public E removeLast():从此列表中删除并返回最后一个元素
System.out.println(linkedList.removeFirst());
System.out.println(linkedList.removeLast());
System.out.println(linkedList);
}
}
8.Vector集合(了解,已过时)
Vector集合可以实现可增长的对象数组,Vector集合和ArrayList类似,底层都是数组
但是Vector集合是同步的(单线程),速度慢,所以1.2版本后,被ArrayList替代了。
2.Set
1. Set集合特点
不包含重复元素的集合
没有带索引的方法,所以不能使用普通for循环遍历
HashSet:对集合的迭代顺序不作任何保证
示例代码:
public class SetDemo {
public static void main(String[] args) {
//创建集合对象
Set<String> set = new HashSet<String>();
//添加元素
set.add("hello");
set.add("world");
set.add("java");
//不包含重复元素的集合
set.add("world");
//遍历
for(String s : set) {
System.out.println(s);
}
}
}
2.哈希值
哈希值:
是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
通俗解释:哈希值是一个十进制的的整数,由系统随机给出(是一个对象的地址,一个逻辑地址,一个模拟出来的地址,不是数据实际存储的物理地址)
Object类中有一个方法可以获取对象的哈希值
public int hashCode():返回对象的哈希码值
对象的哈希值的特点:
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
- 默认情况下,不同对象的哈希值是不相同的通过方法重写,可以实现不同对象的哈希值是相同的。
示例代码:(两个类,一个Student类,一个测试类)
Student类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
return 0;
}
}
测试类
public class HashDemo {
public static void main(String[] args) {
//创建学生对象
Student s1 = new Student("林冲",25);
//同一个对象多次调用hashCode()方法返回的哈希值是相同的
System.out.println(s1.hashCode()); //1060830840
System.out.println(s1.hashCode()); //1060830840
System.out.println("--------");
Student s2 = new Student("林青霞",30);
//默认情况下,不同对象的哈希值是不相同的
//通过方法重写,可以实现不同对象的哈希值是相同的
System.out.println(s2.hashCode()); //2137211482
System.out.println("--------");
System.out.println("hello".hashCode()); //99162322
System.out.println("world".hashCode()); //113318802
System.out.println("java".hashCode()); //3254818
System.out.println("world".hashCode()); //113318802
System.out.println("--------");
System.out.println("重地".hashCode()); //1179395
System.out.println("通话".hashCode()); //1179395
}
}
3.HashSet集合( util包 )
HashSet集合特点
实现不同步。(多线程),线程不安全。
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以是不包含重复元素的集合
示例代码:
public class HashSetDemo01 {
public static void main(String[] args) {
//创建集合对象
HashSet<String> hs = new HashSet<String>();
//添加元素
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("world");
//遍历
for(String s : hs) {
System.out.println(s);
}
}
}
4.HashSet集合保证元素唯一性的源码分析
源码解析:
1.创建了一个HashSet对象
2.通过对象调用add方法往集合中添加了三个元素
3.首先调用add()方法传参,参数列表中的e就是字符串参数,add()方法中又调用了一个put()方法。
4.put()方法中参数的key就是字符串参数,put()方法中又调用了一个putVal()方法,参数列表中,调用了一个hash()方法,hash()方法中传入了key,也就是字符串参数。
5.hash()方法中传入参数key,然后将key也就是字符串参数与null作比较,判断是不是为空,如果为空,返回0,另外通过字符串参数调用hashCode()方法获得字符串的哈希值并赋值给变量h,并返回。
6.所以在put()方法中的putVal()方法中,参数列表的一个值是字符串的哈希值,第二个参数为add方法中传入的字符串。
7.接着看putVal()方法,putVal()方法中的参数列表,第一个值就是传入的哈希值,第二个就是传入的字符串元素。Node<K,V>[] tab; 看到这个内容,其实哈希表就是一个数组结构,Node是一个节点,所以这是一个元素类节点的数组。
8.接着看putVal()方法里的语句,
第一个if判断,判断的是tab这个哈希表是否为null,或者长度是否为0,如果为0,说明哈希表没
有进行初始化,所以要进行初始化。
第二个if判断,是根据对象的哈希值,计算对象的存储位置,并且还判断是否等于null,如果等于null,代表该位置没有元素,就存储元素。如果该位置有元素,就用现在要存入的元素和以前存入的元素比较哈希值,
如果哈希值不同,会继续向下执行,把元素添加到集合中
如果哈希值相同,会调用对象的equals()方法比较。 (对应代码中的 key.equals(k))
如果返回false,会继续向下执行,把元素添加到集合
如果返回true,说明元素重复,不存储。
//创建集合对象
HashSet<String> hs = new HashSet<String>();
//添加元素
hs.add("hello");
hs.add("world");
hs.add("java");
----------------------------------------------
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//hash值和元素的hashCode()方法相关
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果哈希表未初始化,就对其进行初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//根据对象的哈希值计算对象的存储位置,如果该位置没有元素,就存储元素
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
/*
存入的元素和以前的元素比较哈希值
如果哈希值不同,会继续向下执行,把元素添加到集合
如果哈希值相同,会调用对象的equals()方法比较
如果返回false,会继续向下执行,把元素添加到集合
如果返回true,说明元素重复,不存储
*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
图解:
哈希表
哈希表是一个数组+链表/红黑树的结合体,默认初始容量是16,索引就是0-15.
将字符串元素添加到哈希表中
- 首先计算字符串元素的哈希值。
- 通过哈希值对16取余(%),通过得出的数字,将元素存储到数字所对应的索引上。
- 如果三个元素的哈希值%16后得出的数字相同,哪个元素方法先执行,就先把哪个元素添加进去,然后第二个元素发现索引上有元素了,那么这个元素就要与已经存储的元素进行比较。
- 首先比较的是哈希值,如果哈希值不相同,就将元素存储到相同索引下,接下来第三个元素发现索引下依然还是有元素,那么第三个元素就要与第一个元素进行比较,然后在同第二个元素进行比较。
- (1)还是相同步骤,先比较哈希值,如果哈希值不相同,就将元素存储到相同索引的下方,
(2)如果哈希值相同,就继续判断元素的内容是否相同,如果还是相同,就不会将该元素存储到索引下。
(3)如果哈希值相同,而元素的内容不同,就将该元素存储到索引的下方。
总结:
1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠。
2.hashCode()相等的两个对象他们的equal()不一定相等,就是hashCode()不是绝对可靠。
hashCode是对象在内存地址通过hash算法得到的哈希码;
比较两个对象是否相等:
1.首先比较hashcode ,如果hashcode相等则进一步比较equals,不相等则两个对象肯定不相等;
图解:
5.HashSet集合案例
需求:
创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象
思路:
- 1:定义学生类
2:创建HashSet集合对象
3:创建学生对象
4:把学生添加到集合
5:遍历集合(增强for)
public class HashSetDemo02 {
public static void main(String[] args) {
//创建HashSet集合对象
HashSet<Student> hs = new HashSet<Student>();
//创建学生对象
Student s1 = new Student("张飞", 30);
Student s2 = new Student("关羽", 35);
Student s3 = new Student("刘备", 33);
Student s4 = new Student("赵子龙", 33);
//把学生添加到集合
hs.add(s1);
hs.add(s2);
hs.add(s3);
hs.add(s4);
//遍历集合(增强for)
for (Student s : hs) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
6.LinkedHashSet集合 (util包)
不同步(多线程),线程不安全
public class LinkedHashSet
extends HashSet
implements Set, Cloneable, Serializable
LinkedHashSet集合特点
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 由哈希表保证元素唯一,也就是说没有重复的元素
LinkedHashSet集合底层是由哈希表和双向链表组成, 具有可预测的迭代次序,
即存储的元素和取出的元素是一致的。(存取元素一致是由链表来保证的。)
LinkedHashSet集合元素不能重复是由哈希表来保证的。
7.TreeSet集合概述和特点(util包)
TreeSet底层是由红黑树实现的
TreeSet集合不同步(多线程),线程不安全
public class TreeSet
extends AbstractSet
implements NavigableSet, Cloneable, Serializable
TreeSet集合特点
-
元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法:
TreeSet():根据其元素的自然排序进行排序(根据整数的123,字母的abc等排序)
TreeSet(Comparator comparator) :根据指定的比较器进行排序 -
没有带索引的方法,所以不能使用普通for循环遍历
-
由于是Set集合,所以不包含重复元素的集合
-
可以通过字符串直接调用compareTo方法,因为String类实现了Compareable接口
代码举例:
存储整数,遍历(自然排序)
public class TreeSetDemo01 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Integer> ts = new TreeSet<Integer>();
//添加元素
ts.add(10);
ts.add(40);
ts.add(30);
ts.add(50);
ts.add(20);
ts.add(30);
//遍历集合
for(Integer i : ts) {
System.out.println(i);
}
}
}
运行结果:
10
20
30
40
50
8.自然排序Comparable的使用
存储学生对象并遍历,创建集合使用无参构造方法
要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
要对一个类进行自定义的自然排序
- 实现Comparable<>接口,并重写compareTo方法
- compareTo方法中,
如果return 0,默认认为是重复元素,不添加元素。
如果return 1,默认认为不是重复元素,并按照升序存储元素。
如果return -1,默认认为不是重复元素,并按照降序存储元素。
如果想要升序排列,this.属性 - 传入的对象.属性值 (this在前)
如果想要降序排列,传入的对象.属性 - this.属性值 (this在后) - 多条件排序的时候:
适用场景:比如当age相同,名字不同的时候
int num = 传入的对象.age - this.age ; (当num为0时,表示是重复元素。)
这就需要使用 int num2 num== 0?this.name.compareTo(传入的对象.name):num
因为String类实现了Comparable< String > 接口,所以可以直接调用compareTo方法比较。
代码实现:
Student类
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student s) {
// return 0;
// return 1;
// return -1;
//按照年龄从小到大排序
int num = this.age - s.age;
// int num = s.age - this.age;
//年龄相同时,按照姓名的字母顺序排序
int num2 = num==0?this.name.compareTo(s.name):num;
return num2;
}
}
测试类
public class TreeSetDemo02 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>();
//创建学生对象
Student s1 = new Student("xishi", 29);
Student s2 = new Student("wangzhaojun", 28);
Student s3 = new Student("diaochan", 30);
Student s4 = new Student("yangyuhuan", 33);
Student s5 = new Student("linqingxia",33);
Student s6 = new Student("linqingxia",33);
//把学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
9.比较器排序
结论
- 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
/*
存储学生对象并遍历,创建TreeSet集合使用带参构造方法
要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
*/
public class TreeSetDemo {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//this.age - s.age
//s1,s2
int num = s1.getAge() - s2.getAge();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num2;
}
});
//创建学生对象
Student s1 = new Student("xishi", 29);
Student s2 = new Student("wangzhaojun", 28);
Student s3 = new Student("diaochan", 30);
Student s4 = new Student("yangyuhuan", 33);
Student s5 = new Student("linqingxia",33);
Student s6 = new Student("linqingxia",33);
//把学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
3.成绩排序
需求:用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成绩),并遍历该集合
要求:按照总分从高到低出现
思路:
- 定义学生类
- 创建TreeSet集合对象,通过比较器排序进行排序
- 创建学生对象
- 把学生对象添加到集合
- 遍历集合
示例代码:
Student类
public class Student {
private String name;
private int Chinese;
private int math;
public Student() {
}
public Student(String name, int Chinese, int math) {
this.name = name;
this.Chinese = Chinese;
this.math = math;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChinese() {
return Chinese;
}
public void setYuwen(int yuwen) {
this.Chinese = yuwen;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getSum() {
return this.Chinese+this.math;
}
}
测试类
public class Test {
public static void main(String[] args) {
TreeSet<Student> list = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//int num = (s2.getMath()+s2.getChinese()) - (s1.getMath()+s1.getChinese());
int num = s2.getSum() - s1.getSum();
int num2 = num == 0 ? s1.getChinese() - s2.getChinese():num;
int num3 = num2 == 0 ? s1.getName().compareTo(s2.getName()):num2;
return num3;
}
});
Student s1 = new Student("林冲",60,70);
Student s2 = new Student("张飞",80,60);
Student s3 = new Student("赵云",90,90);
Student s4 = new Student("刘备",89,91);
Student s5 = new Student("关羽",89,91);
list.add(s1);
list.add(s2);
list.add(s3);
list.add(s4);
list.add(s5);
for (Student student : list) {
System.out.println("姓名:"+student.getName() + "语文:" + student.getChinese()+"数学:"+student.getMath());
}
}
}
4.不重复的随机数
需求:编写一个程序,获取10个1-20之间的随机数,要求随机数不能重复,并在控制台输出
示例代码:
public class Test2 {
public static void main(String[] args) {
// 创建Set集合对象
//Set<Integer> set = new HashSet<>();
//使用TreeSet集合
Set<Integer> set = new TreeSet<Integer>();
// 创建随机数对象
Random r = new Random();
// 判断集合的长度是否小于10
while (set.size()<10){
int i = r.nextInt(20) + 1;
set.add(i);
}
//遍历集合
for (Integer count : set) {
System.out.println(count);
}
}
}