常用类,集合 一
1. 常用类
1.1 String
String是java.lang 包下的不需要导包
1.1.1 字符串特点
- 所有双引号字符串,都是String类的对象
- 创建后不可变
- 值虽然不可变,但是可以共享
- 字符串效果相当于字符数组(char[]),但是底层原理是字节数组{byte[]).JKD8及以前是字符数组,,9开始是字节数组
1.1.2 字符串创建

通过new 创建的字符串对象,每次new都会申请一个内存空间,虽然内容相同但是地址不同。
通过""创建的字符串,JVM只会建立一个String对象,并在字符串常量池中维护
例:

1.1.3 字符串的比较
- 使用==比较
- 基本类型:比较数值
- 引用类型:比较地址值
- 字符串比较内容使用equals()
字符串遍历用charAt(index)
1.2 StringBuilder
常规字符串拼接操作,每次拼接都会构建一个新的String对象,耗时且浪费内存空间。所以引入StringBuilder解决这个问题
String:内容不可变
StringBuilder 可看作一个容器,对象中的内容是可变的
1.2.1 StringBuilder该方法
- 构造方法
StringBuilder()
StringBuilder(String str) - 添加和反转方法
append(任意类型),返回StringBuilder对象本身,不会创建新的
reverse() 返回相反序列
扩展 链式编程, 方法返回值是对象,可以一行内继续调用该对象的方法
sb.append("hello").append("world").append("100");
1.2.2 StringBuilder和String相互转换
- StringBuilder 转换为String使用toString方法
- String 转换为StringBuilder使用StringBuilder(string)构造方法
1.3 Arrays
Arrays 工具类,包含操作数组的各种方法
工具类,构造方法用private修饰(API里没有构造方法).成员用public static修饰
- Arrays常用方法
- toString(int[] a) 数组内容转换为字符串
- sort(int[]a) 按照数字顺序排列指定的数组
1.4 包装类
封装基本数据类型,提供更多功能操作该数据,如基本数据类型和字符串之间的转换
除Integer--int; Character--char外,其他类型的包装类,都对应首字母大写即可
1.4.1 Integer 常用方法
- 构造方法
Integer(int value) 过时
Integer(String s) 过时
public static Integer valueOf(int i) 返回指定int值的Integer实例
public static Integer valueOf(String s) 返回一个保存String值的Integer对象,字符串需要是数字字符串,否则NumberFormatException
推荐使用静态方法来得到Integer对象 - int转化为String
推荐String类里的方法public static String valueOf(int i) 返回int参数对应的字符串。 - String转化为int
推荐Integer里的方法 public static int parseInt(String s) 将字符串解析为int类型。(最终转化成的是啥,就在相应的类中)
例1. 字符串和int转换
package collection;
public class IntegerDemo {
public static void main(String[] args) {
//int---String
int number = 100;
//方式1,字符串拼接
String s1 = ""+number;
System.out.println(s1);
//方式2.
//public static String valueOf(int i) 返回int参数对应的字符串
String s2 = String.valueOf(100);
System.out.println(s2);
System.out.println("=================");
//String--- int
//方式1 String---Integer--int
String s = "100";
Integer i = Integer.valueOf(s);
int i1 = i.intValue();
System.out.println(i1);
//方式2 String---int
// parseInt(String s)
int i2 = Integer.parseInt(s);
System.out.println(i2);
}
}
例2

package collection;
import java.util.Arrays;
/*
需求:
有一个字符串"91 27 46 38 50",请写程序最终输出“27 38 46 50 91”
思路:
1.定义一个字符串
2. 把字符串中的数字存储到一个int类型的数组中
split 分割成String[]数组
parseInt 转化字符串为int型数组
3. 对int型数组排序
4. 排序后数组使用StringBuilder来拼接成字符串
5. 输出结果
*/
public class IntegerTest {
public static void main(String[] args) {
String s ="91 27 46 38 50";
String[] strArray = s.split(" ");
/* for (int i = 0; i < strArray.length; i++) {
System.out.println(strArray[i]);
}*/
int[] arr = new int[strArray.length];
for (int i = 0; i < arr.length; i++) {
arr[i] = Integer.parseInt(strArray[i]);
}
/* for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}*/
Arrays.sort(arr);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
if(i == arr.length-1){
sb.append(arr[i]);
}else{
sb.append(arr[i]).append(" ");
}
}
String s1 = sb.toString();
System.out.println("result:"+ s1);
}
}
1.4.2 自动装箱和拆箱
- 装箱 基本数据--》包装类
- 拆箱 包装类--》基本数据
手动装箱 Integer i = Integer.valueOf(100)
自动装箱 Integer ii = 100;
手动拆箱 ii = ii.intValue()+200;
自动拆箱 ii+=200; ii+200 隐含拆箱调用intValue方法的动作
Integer iii = null;
if(iii!=null){
iii +=300; //NullPointException
}
在开发中使用引用变量/对象之前(含包装类对象),最好先做不为null的判断
1.5 日期类
1.5.1 Date 类
Java.util.Date表示一个特定的时间,精确到毫秒
- 构造方法
Date()
Date(long date) date表示从标准基准时间以后的指定毫秒数,即1970年1月1日00:00:00 GMT。 - 常用方法
getTime() 自1970年1月1日以来,由此Date对象表示的00:00:00 GMT的毫秒数
setTime(long time) 设置时间
d.getTime()*1.0/1000/60/60/24/365 年
扩展CST指中国标准时间
1.5.2 SimpleDateFormat类
用于格式化日期和解析日期
- 日期的格式由日期和时间模式字符串指定
- y 年
- M 月
- d 日
- H 时
- m 分
- s 秒
- 常用方法:
- 构造方法
SimpleDateFormat() 默认斜杠格式
SimpleDateFormat(String pattern) 指定格式 - 格式化 Date--> String
String format(Date date) 将日期格式成日期/时间字符串 - 解析 String --> Date
Date parse(String source) 给定字符串生成日期
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
sdf.format(d)
String ss = "2048-08-09 11:11:11";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date dd = sdf2.parse(ss);
1.5.3 Calendar类
提供某一时刻和日历字段之间转换方法
常用方法:
- 类方法getInstance用于获取Calendar对象,其日历字段已使用当前日期和时间初始化。
虽然Calendar是抽象类,但是getInstance通过其子类或者孙类创建了实例。 - get(int field)指定字段值
- void add (int field, int amount)给指定字段,添加或者减去值
- void set(int year, int month, int date)设置当前日历的年月日
Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH)+1;
int day = c.get(Calendar.DATE);
System.out.println(year + " year " + month+" month "+ day+" day");
c.add(Calendar.YEAR, 10);
c.add(Calendar.DATE, -5);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH)+1;
day = c.get(Calendar.DATE);
System.out.println(year + " year " + month+" month "+ day+" day");
c.set(2048, 11, 11);
c.add(Calendar.DATE, -5);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH)+1;
day = c.get(Calendar.DATE);
System.out.println(year + " year " + month+" month "+ day+" day");
结果:
2021 year 4 month 8 day
2031 year 4 month 3 day
2048 year 12 month 6 day
例 输出2月份有多少天(借助Calendar不用手动判断是否闰年)
/*
需求:获取任意一年的二月有多少天
思路:
3月减一天就是2月的最后一天,也就输出了二月有多少天
1.键盘录入任意月份
2. 设置日历对象的年月日
年:来自键盘录入
月:设置为2即为3月
日:设置为1
3. 往前推一天,并输出
*/
public class CalendarDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int year = sc.nextInt();
int month = 2;
int day =1;
Calendar calendar = Calendar.getInstance();
calendar.set(year,month,day);
calendar.add(Calendar.DATE,-1);
System.out.println(calendar.get(Calendar.DATE));
}
1.6 System
包含静态字段和方法,不能被实例化
常用方法
- void exit(int status) 终止当前运行的java虚拟机,非零表示异常终止
- currentTimeMills()返回当前时间距离1970年1月1日的毫秒数
1.7 Math
基本数值类型工具类
常用方法
- abs(int a)绝对值
- ceil(double a) 大于等于a的整数 double形式,如13.0
- floor(double a) 小于等于a的整数double形式
- round(float a) 四舍五入
- max(int a, int b) 取最大值
- min(int a, int b) 取最小值
- pow(double a, double b) 返回a的b次幂
- random() 返回0.0到1.0之间的随机数
例 求1到100之间的随机数 (int)(Math.random()*100+1) 0到99要加1,同时int强转为整数
2 集合
存储多个数据,且长度不固定,原有的数组不能满足需求,引入集合的概念
集合特点:可变存储空间,容量可以发生改变
2.1 集合结构
2.2 ArryList
ArrayList
- 底层是可调整大小的数组实现
泛型,表示是数据类型,在使用的地方用具体的引用数据类型替换
2.1.1 ArrayList常用方法
- ArrayList() 创建一个空的集合对象
- add(E e)追加指定的元素
- add(int index, E e) 指定位置插入指定的元素,索引超出范围( index < 0 || index > size() )报数组越界Exception
- get(int index) 返回列表中指定位置元素
- remove(int index) 删除指定位置元素;remove(Object o) 删除第一个遇到的指定元素
- set(int index, E element) 替换指定位置的元素
- size() 返回元素数
扩展: 为了提高代码的可复用性,采用方法改进。明确两点,返回值类型,及参数类型。
如果传递的参数是引用类型,那么引用类型在方法里值改变了,在外面也会改变
2.3 Collection
2.3.1 Collection集合概述
- 单列集合的顶层接口
- JDK只提供此接口的子接口如List和Set,不提供其直接实现
通过子类(多态方式)创建Collection集合的对象
Collectionc = new ArrayList ();
2.3.2 Collection集合常用方法
- boolean add(E e) 添加元素
- boolean remove(Object o) 删除
- void clear() 清空
- boolean contains(Object o) 判断集合中是否存在指定的元素
- boolean isEmpty()判断集合是否为空
- int size() 集合长度,元素个数
2.3.3 Collection集合遍历
Iterator:迭代器,集合专用遍历方式
- Iterator
iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到 - 迭代器是通过集合中iterator()返回得到的,所以说迭代器依赖于集合而存在的
Iterator中常用的方法 - E next()返回迭代中的下一个元素
- boolean hasNext():迭代还有更多元素,返回true
例
public static void main(String[] args) {
//创建集合对像
Collection<String> c = new ArrayList<String>();
//创建元素,有时元素/对象不是简单的字符串,需要显示创建步骤
String s1 = "hello";
String s2 = "world";
String s3 = "java";
//添加元素到集合
c.add(s1);
c.add(s2);
c.add(s3);
//遍历集合
//获取迭代器
Iterator <String> iterator = c.iterator();
//通过迭代器判断是否为空,空还去使用会报NoSuchElementException,所以要提前判断
while(iterator.hasNext()){
//获取下一个元素
String s = iterator.next();
System.out.println(s);
}
}
2.4 List集合
2.4.1 List集合特点:
- 有序集合(序列)--存储和取出的元素顺序一致。可以精确控制列表中的每个元素的插入位置,通过索引可以访问元素
- 可重复 -- 不同于Set集合,列表是允许元素重复的
2.4.2 List集合特有方法(collection中没有的)
在子类ArrayList学过,注意操作时要保证索引是存在的
add(int index,E Element),E remove(int index), E set(int index, E element), E get (int index)
例:遍历List集合可以用迭代器,因为其是序列还可用for循环遍历,判断条件是size的大小
for(int i =0; i<list.size();i++){
String s = list.get(i);
System.out.println(s);
}
2.4.3 List并发修改异常ConcurrentModificationException
- 使用迭代器一遍遍历,一边往集合里添加元素代码
package collections;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
* 需求:遍历集合,如果集合中有world元素,就添加一个"javaee" 元素
*/
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("happy");
Iterator<String> it = list.iterator();
boolean flag = false;
while(it.hasNext()){
String s = it.next();
if("world".equals(s)){
list.add("javaee");
}
}
System.out.println(list);
}
}
- 输出结果抛出并发修改异常
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
at java.util.ArrayList$Itr.next(ArrayList.java:791)
at collections.ListDemo.main(ListDemo.java:19)
并发修改异常ConcurrentModificationException 继承RuntimeException,是运行期异常
- 源码分析
将涉及到的接口和主要方法摘抄到analysis.txt里,逐个分析。(熟练后不用摘出来)
public interface List<E>{
Iterator<E> iterator();
boolean add(E e);
}
public abstract class AbstractList<E> {
// 4. 跟中modCount,发现在父类里定义初始值为0
protected transient int modCount = 0;
}
public class ArrayList<E> extends AbstractList<E> implements List<E>{
//3. 跟踪发现创建迭代器时,实例化了内部类Itr
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
//2. 跟踪expectedModCount在初始化Itr的时候是和modCount相等的
// modCount -- actual modified times
// expectedModCount -- expect modified times
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
// 1. 异常抛出部位,原因modCount和预期的不符
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
public boolean add(E e) {
//5. 跟踪add方法 发现每次add时modCount+1
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
}
- 解决方法,通过for循环遍历
for(int i =0; i< list.size();i++){
String s = list.get(i);
if("world".equals(s)){
list.add("javaee");
}
}
- 结论:ArrayList迭代器遍历的时候,不能同时改变元素长度,否则抛出ConcurrentModificationException异常
产生原因:迭代器遍历过程中,修改集合中元素的长度,造成迭代器获取元素中判断预期修改值和实际修改值不一致
解决方案: 用for循环遍历,然后用集合对象做对应的操作即可
2.4.4 ListIterator列表迭代器
-
List集合特有的迭代器,可以通过listIterator()方法得到
-
该迭代器允许沿任一方向遍历列表,允许在迭代期间修改列表,并获取列表中迭代器的当前位置
-
常用方法
- E next()
- hasNext()
- previous() 不常用,需要先把集合遍历到最后,再往之前遍历
- hasPrevious()
- add() 通过列表迭代器来添加,而不是集合的添加方法
-
为什么ListIterator没有并发问题
在ArrayList的内部类ListItr中add方法如下,最后会把增加的modCount赋值给expectedModCount.
二者始终相等,所以next不会报错
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
2.4.5 增强for循环
- 简化数组和Collection集合的遍历
- 实现Iterable接口的类,其对象可以使用增强for循环(Map并没有实现Iterable接口,不能直接使用增强for来遍历它)
- JDK5之后出现的,遍历集合内部原理是一个Iterator迭代器
- 增强for循环称为for-each格式:
for(元素数据类型 变量名: 数组或者Collection集合){
//此处使用变量即可,该变量就是元素
}
只需要遍历查看推荐使用增强for,如果想要使用List里的索引操作,要使用普通for
例:
package collection;
import java.util.ArrayList;
import java.util.List;
public class ForDemo {
public static void main(String[] args) {
int [] arr = {1,2,3,4,5};
for(int a: arr){
System.out.println(a);
}
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
for(String s : list){
System.out.println(s);
if(s.equals("world")){
list.add("javaee");
}
}
}
}
输出结果,抛出并发修改异常:
1
2
3
4
5
hello
world
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at collection.ForDemo.main(ForDemo.java:16)
Process finished with exit code 1
2.4.6 常见数据结构
- 栈 后进先出
- 队列 先进先出
- 数组 查询快,增删慢的模型
查询数据,索引定位,查询效率高
删除数据,原始数据删除,后面每个数据前移,删除效率低
添加数据,添加位置后每个数据后移,再添加元素,添加效率低 - 链表
![]()
- 对比数组,链表是一种增删快,查询慢的模型
增删只需要改变地址的指向,查询元素是否存在或者查询第N个元素必须从head节点开始查
2.4.7 List集合子类
- List集合常用子类:ArrayList,LinkedList
- ArrayList:底层数据结构是数组,查询快,增删慢
- LinkedList:底层是链表,查询慢,增删快
- ArrayList和LinkedList继承List,所以也有三种遍历方式,迭代器遍历,普通for,增强for遍历
例
LinkedList<String> linkedList = new LinkedList<String>();
linkedList.add("最难");
linkedList.add("不过是");
linkedList.add("坚持");
for (String s : linkedList){
System.out.println(s);
}
- LinkedList集合特有方法
- void addFirst(E e) 在该列表开头插入指定的元素
- void addLast(E e) 插入元素到末尾
- E getFirst() 返回第一个元素
- E getLast() 返回最后一个元素
- E removeFirst() 删除并返回第一个元素
- E removeLast() 删除并返回最后一个元素
2.5 Set
2.5.1 Set接口
Set是个接口,继承Collection, 特点
- 不包含重复元素
- 没有带索引的方法,所以不能使用普通for循环遍历
HashSet 底层是哈希表,对集合的迭代顺序不做任何保证
//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");
//不包含重复元素,已经有hello了,不能再次添加进去
set.add("hello");
//遍历
for(String s: set){
System.out.println(s);
}
}
}
输出:
world
java
hello
2.5.2 哈希值
哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中方法 public int hashCode() 返回对象的哈希值
默认情况下,不同对象的hashcode是不同的
hashcode()方法被重写时,可以实现不同对象的哈希值相同。
System.out.println("重地".hashCode()); //1179395
System.out.println("通话".hashCode()); //1179395
String类对hashcode方法进行了重写,所以上述两个汉字字符输出的hashCode一样的
equals比较的是hashCode 和 == 是不一样的。 ==比较的是内存地址, 而hashCode是根据实例变量计算出来的。
2.5.3 HashSet
- 特点:
- 底层数据结构是哈希表
- 对集合的迭代顺序不做任何保证,不保证存储和取出元素顺序一致
- 没有带索引的方法,不能普通for循环遍历
- Set集合,不包含重复元素的集合
- HashSet集合保证元素唯一性源码分析
Set<String> set = new HashSet<String>();
set.add("hello");
set.add("world");
set.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;
/*
存入的元素和以前的元素比较哈希值
如果哈希值不同,会继续向下比较,把元素添加到集合
如果哈希值相同,会比较key是否==,或者调用对象的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;
}
HashSet集合添加一个元素的过程

HashSet要保证元素的唯一性,需要重写hashCode()和equals()(equal保证内容)方法
2.5.4 常见数据结构之哈希表
- JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组
- JDK8之后,在长度比较长的时候,底层实现了优化
![]()
例 hashSet 添加并遍历Student对象
package collection;
import java.util.HashSet;
/*
需求:
创建一个存储对象的集合,存储3个学生对象。使用程序实现控制台遍历该集合
思路:
1.定义学生类
2.创建HashSet集合对象
3.创建学生对象
4.把学生添加到集合
5.遍历集合(增强for)
6.重写hashCode和equals方法,Idea自动生成就行
*/
public class HashSetDemo1 {
public static void main(String[] args) {
HashSet<Student> hs = new HashSet<Student>();
Student s1 = new Student("严宽","35");
Student s2 = new Student("陈晓","30");
Student s3 = new Student("何家劲","52");
Student s4 = new Student("何家劲","52");
hs.add(s1);
hs.add(s2);
hs.add(s3);
//重写hashCode和equals方法,不重写就可能添加重复对象
hs.add(s4);
for(Student s : hs){
System.out.println(s);
}
}
}
/////////////////////////////////////////////////////////
package collection;
import java.util.Objects;
public class Student {
private String name;
private String age;
public Student(String name, String age) {
this.name = name;
this.age = age;
}
public Student() {
}
public String getName() {
return name;
}
public String getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
//引用地址一样,返回true
if (this == o) return true;
//不是同一个类对象,返回false
if (!(o instanceof Student)) return false;
Student student = (Student) o;
//比较两个字段,只有两个字段都相同才返回true
return Objects.equals(getName(), student.getName()) && Objects.equals(getAge(), student.getAge());
}
//hashcode将字段传入Object的hash方法来计算
@Override
public int hashCode() {
return Objects.hash(getName(), getAge());
}
}
2.5.5 LinkedHashSet
- 特点
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 由哈希表保证元素唯一,集合中元素没有重复的
(=哈希表是数组+链表,此处又有个链表,底层结构是什么样的???以后深入学习补充。。。。=)
2.5.6 TreeSet集合
2.5.6.1 TreeSet概述
- 特点
- 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
TreeSet()默认构造器,根据元素的自然排序进行排序
TreeSet(Comparator comparator), 根据指定的比较器进行排序,插入到排序集中的所有元素必须实现Comparable接口,是可比较的 - 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以不包含重复元素的集合
例 存储整数并遍历
- 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
package collection;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
//集合存储的是引用类型,int元素必须转为使用Integer
TreeSet<Integer> treeSet = new TreeSet<Integer>();
treeSet.add(10);
treeSet.add(8);
treeSet.add(11);
treeSet.add(9);
treeSet.add(10);
for(Object i :treeSet){
System.out.println(i);
}
}
}
2.5.6.2 自然排序Comparable的使用
- 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
- 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
例
package collection;
import java.util.TreeSet;
/*
存储学生对象并遍历,创建集合使用无参构造方法
要求:按照年龄从小岛大排序,年龄相同时,按照姓名的字母顺序排序
*/
public class TreeSetDemo1 {
public static void main(String[] args) {
TreeSet<Student> students = new TreeSet<>();
Student s1 = new Student("xiaoming",22);
Student s2 = new Student("xiaohong",20);
Student s3 = new Student("lanlan",22);
Student s4 = new Student("xiaoming",20);
Student s5 = new Student("xiaoming",20);
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
students.add(s5);
for(Student s : students){
System.out.println(s);
}
}
}
//////////////////////////////
package collection;
import java.util.Objects;
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return Objects.equals(getName(), student.getName()) && Objects.equals(getAge(), student.getAge());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getAge());
}
/*
返回是0 认为是相等的不添加
返回是-1 降序,后添加的放在前面
返回是1 升序,后添加的放在后面
可以通过让返回值一直视-1/1/0 测试出来
*/
@Override
public int compareTo(Student o) {
/* 自己写的没有老师写的精妙,int直接相减就可以比较
if(this.age.compareTo(o.age)<0){
return -1;
}
if(this.age.equals(o.age)){
return (this.name.compareTo(o.age));
}
return 1;
*/
int num = this.age-o.age;
//采用三元运算符,只有在年龄相等时才继续比较名字
int num2 = num==0? this.name.compareTo(o.name):num;
return num2;
}
}
输出:
Student{name='xiaohong', age='20'}
Student{name='xiaoming', age='20'}
Student{name='lanlan', age='22'}
Student{name='xiaoming', age='22'}
2.5.6.3 比较器Comparator的使用
- TreeSet集合存储自定义对象,带参构造方法使用比较器对元素排序
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1, T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
例 使用比较器实现有序插入Student元素到TreeSet集合中
package collections;
import java.util.Comparator;
import java.util.TreeSet;
/*
* create TreeSet using constructor with parameter
*/
public class TreeSetDemo {
public static void main(String[] args) {
//在一开始创建TreeSet的时候指定了比较器
//比较器利用匿名内部类实现,直接new Comparator接口
TreeSet<Student> students = new TreeSet<Student>(new Comparator<Student>(){
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge()-s2.getAge();
int num2 = num == 0? s1.getName().compareTo(s2.getName()):num;
return num2;
}
});
Student s1 = new Student("xiaoming",22);
Student s2 = new Student("xiaohong",20);
Student s3 = new Student("lanlan",22);
Student s4 = new Student("xiaoming",20);
Student s5 = new Student("xiaoming",20);
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
students.add(s5);
for(Student s : students){
System.out.println(s);
}
}
}
///////////////////////////////
package collections;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Student() {
super();
// TODO Auto-generated constructor stub
}
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 String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
例2 成绩排序,按照总分从高到低排序
排序时注意主要条件和次要条件,当主要条件总分相同时,要比较其他科目如语文分数,当科目相同时要比较名字,目的是确保不同人都能添加到集合里
(有重名的用学号解决)
例3 存储10个0到20间不重复的随机数
分析不重复采用set集合,不需要排序,直接用hashSet
随机数用 Random nextInt(20)+1限制范围
package collections;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class TreeSetDemo1 {
public static void main(String[] args) {
Set<Integer> set = new HashSet<Integer>();
//创建随机数生成器
Random r = new Random();
while(set.size()<10){
int number = r.nextInt(20)+1;
set.add(number);
}
for(Integer i: set){
System.out.println(i);
}
}
}
注: 内容是bilibili黑马程序员笔记



浙公网安备 33010602011771号