0.写在前面
- 本笔记用作复习查看用,基础完整总结部分,基础不牢,地动山摇!
🔥Java帝国之行🔥 | 地址 |
---|---|
Java核心编程总结(一、继承) 🔥 | https://blog.csdn.net/Augenstern_QXL/article/details/116209463 |
Java核心编程总结(二、抽象类与接口)🔥 | https://blog.csdn.net/Augenstern_QXL/article/details/116209487 |
Java核心编程总结(三、多态与内部类)🔥 | https://blog.csdn.net/Augenstern_QXL/article/details/116209507 |
Java核心编程总结(四、异常与线程) 🔥 | https://blog.csdn.net/Augenstern_QXL/article/details/116209529 |
Java核心编程总结(五、线程池与死锁)🔥 | https://blog.csdn.net/Augenstern_QXL/article/details/116209580 |
Java核心编程总结(六、常用API与集合)🔥 | https://blog.csdn.net/Augenstern_QXL/article/details/116209607 |
Java核心编程总结(七、Stream流)🔥 | https://blog.csdn.net/Augenstern_QXL/article/details/116209624 |
Java核心编程总结(八、IO输入输出流)🔥 | https://blog.csdn.net/Augenstern_QXL/article/details/116209648 |
Java核心编程总结(九、File文件类)🔥 | https://blog.csdn.net/Augenstern_QXL/article/details/116209674 |
Java核心编程总结(十、反射) 🔥 | https://blog.csdn.net/Augenstern_QXL/article/details/117744497 |
1.常用API学习
1. Object 类
- Object 类是 Java 中的祖宗类
- 一个类要么默认继承了 Object 类,要么间接继承了 Object 类
- Object 类的方法是一切子类都可以直接使用的,所以我们要学习Object类的方法
Object 类常用方法
-
public String toString()
- 默认是返回当前对象在堆内存中的地址信息
- 直接输出对象名称,默认会调用 toString()方法,所以直接输出对象可以省略 toString()
- 所以 toString() 存在的意义是为了被子类重写,以便能够返回对象的数据内容输出
-
public boolean equals(Object o)
- 默认是比较两个对象的地址是否相同,相同返回true
- 直接比较两个对象的地址是否完全相同,可以用"=="替代equals
- 所以 equals 存在的意义是为了被子类重写
-
重写 Object 类的 toString() 方法以便返回对象的内容数据
-
右键 - generate - toString - OK
2.Objects 类
- Objects类与Object还是继承关系
- Objects类是 jdk 1.7 开始之后才有的
Objects 的方法
public static boolean equals(Object a, Object b)
- 比较两个对象
- 底层进行非空判断,从而可以避免空指针异常,更安全
public static boolean isNull(Object obj)
- 判断变量是否为 null , 为 null 返回 true
public class ObjectsDemo {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
System.out.println(s1.equals(s2));
System.out.println(Objects.equals(s1,s2));
// false
// false
//询问s1是否为null,为null返回true
System.out.println(Objects.isNull(s1));
}
}
3.Date日期类
-
包:
java.util.Date
-
构造器
public Date()
创建当前系统的此刻日期时间对象public Date(long time)
把时间毫秒值转换成日期对象
-
方法
public long getTime()
返回自1970年1月1日00:00:00 以来走过的毫秒值
public class DateDemo01 {
public static void main(String[] args) {
// a.创建一个日期对象代表了系统此刻日期时间对象
Date d = new Date();
System.out.println(d);
// Sun Jun 06 11:04:24 CST 2021
// b.拿当前日期对象的时间毫秒值
long time = d.getTime();
System.out.println(time);
// 1622948664752
// c.把时间毫秒值转换成日期对象
Date d1 = new Date(time);
System.out.println(d1);
// Sun Jun 06 11:04:24 CST 2021
}
}
4.DateFormat时间格式化类
DateFormat 作用
- 可以把 “日期对象” 或者 " 时间毫秒值" 格式化成我们喜欢的时间格式(格式化时间)
- 可以把字符串的时间形式解析成日期对象(解析字符串时间)
– DateFormat 是一个抽象类,不能直接使用,我们找它的子类简单日期格式化类: SimpleDateFormat
SimpleDateFormat
- 包:
java.text.SimpleDateFormat
- 构造器
public SimpleDateFormat(String pattern)
- 指定时间的格式创建简单日期格式化对象
- 成员方法
public String format(Date date)
:可以把日期对象格式化成我们喜欢的时间形式,返回的是字符串!public String format(Object time)
: 可以把时间毫秒值格式化成我们喜欢的时间形式,返回的是字符串!public Date parse(String date) throws ParseException
:把字符串的时间解析成日期对象
public class SimpleDateFormatDemo01 {
public static void main(String[] args) {
// 需求:把此刻日期对象格式化成我们喜欢的形式
// 1.得到此刻日期对象
Date d = new Date();
System.out.println(d);
//Wed Jan 27 17:35:55 CST 2021
// 2.创建一个简单日期格式化对象负责格式化日期对象
// 注意: 参数是时间的格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");
// 3.开始调用方法格式化时间得到格式化字符串形式
String rs = sdf.format(d);
System.out.println(rs);
// 2021年01月27日 17:35:55 周三 下午
}
}
面试: 请问"2019-11-01 09:30:30" 往后1天15小时30分29s 后的时间是多少
public class SimpleDateFormatDemo03 {
public static void main(String[] args) throws ParseException {
// 面试题: 请问 "2019-11-01 09:30:30 "往后 1天15小时,30分29s 后的时间是多少
// a.定义一个字符串时间
String date = "2019-11-01 09:30:30";
// b.把字符串的时间解析成Date日期对象
// 1.创建一个简单日期格式化对象负责解析字符串的时间成为日期对象
// 注意: 参数必须与被解析的时间的格式完全一致,否则执行报错
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 2.开始解析成日期对象
Date newDate = sdf.parse(date);
// c.得到日期对象的时间毫秒值 + 往后走1天15小时,30分29s
long time = newDate.getTime() + (24L*60*60 + 15*60*60 +30*60 +29) * 1000;
// d.把时间毫秒值格式化成喜欢的字符串的时间形式
System.out.println(sdf.format(time));
}
}
5.Calendar日历类
- Calendar 代表了系统此刻日期对应的日历对象
- Calendar 是一个抽象类,不能直接创建对象
- 方法
public static Calendar getInstance()
:返回一个日历类的对象public int get(int field)
: 取日期中的某个字段信息public void set(int field,int value)
:修改日历的某个字段信息public void add(int field,int value)
:添加日历的某个字段信息public final Date getTime()
:拿到此刻日期对象public long getTimeInMillis()
: 拿到此刻时间毫秒值
public class CalendarDemo01 {
public static void main(String[] args) {
// 1 通过调用日历类的静态方法getInstance得到一个当前此刻日期对象对应的日历对象
Calendar rightNow = Calendar.getInstance();
System.out.println(rightNow);
// 2.获取年
//public int get(int field)
int year = rightNow.get(Calendar.YEAR);
System.out.println(year);
// 获取月 月要+1 因为月从 0 开始
int mm = rightNow.get(Calendar.MONTH) + 1;
System.out.println(mm);
// 一年中的第几天
int days = rightNow.get(Calendar.DAY_OF_YEAR);
System.out.println(days);
// 4.修改日历的信息
// rightNow.set(Calendar.YEAR,2099);
// System.out.println(rightNow.get(Calendar.YEAR));
// 5.日历可以得到此刻日期对象
Date d = rightNow.getTime();
System.out.println(d);
// 6.此刻时间毫秒值
long time = rightNow.getTimeInMillis();
// 7.请问701天 15 小时后是哪个日期
// 让日历中的一年中的第几天往后走701天
rightNow.add(Calendar.DAY_OF_YEAR,701);
rightNow.add(Calendar.HOUR,15);
long time1 = rightNow.getTimeInMillis();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");
System.out.println(sdf.format(time1));
}
}
6.Math数学类
Math类中的方法全是静态方法,直接用类名调用即可
方法:
方法名 | 说明 |
---|---|
public static int abs(int a) | 获取参数a的绝对值 |
public static double ceil(double a) | 向上取整 |
public static double floor(double a) | 向下取整 |
public static double pow(double a,double b) | 获取a的b次幂 |
public static long round(double a) | 四舍五入取整 |
public class MathDemo01 {
public static void main(String[] args) {
// 1.取绝对值,返回正数
System.out.println(Math.abs(10)); // 10
System.out.println(Math.abs(-10.3)); // 10.3
// 向上取整
System.out.println(Math.ceil(4.000000001)); // 5.0
// 向下取整
System.out.println(Math.floor(4.999999)); // 4.0
// 求指数次方
System.out.println(Math.pow(2,3)); // 8.0
// 四舍五入
System.out.println(Math.round(4.4999)); // 4
}
}
7. System系统类
– 静态方法
public static void exit(int status)
: 终止JVM虚拟机,非 0 是异常终止public static long currentTimeMillis()
:获取当前系统此刻时间毫秒值- 可以做数组的拷贝
arraycopy (Object var0, int var1 , Object var2,int var3,int var4 )
- 参数一: 原数组
- 参数二: 从原数组的哪个索引位置开始复制
- 参数三:目标数组
- 参数四:赋值到目标数组的哪个位置
- 参数五:赋值几个
public class SystemDemo {
public static void main(String[] args) {
System.out.println("程序开始。。。。");
// 1.终止当前虚拟机
// System.exit(0); //0代表正常终止
// 2.得到系统当前时间毫秒值
long time = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");
System.out.println(sdf.format(time));
// 3.可以做数组的拷贝(了解)
int[] arrs1 = new int[]{10,20,30,40,50,60,70};
int[] arrs2 = new int[6]; // [0,0,0,0,0,0]
// arrs2 = [0,30,40,50,0,0]
System.arraycopy(arrs1,2,arrs2,1,3);
System.out.println(Arrays.toString(arrs2));
}
}
8.BigDecimal大数据类
– 浮点型运算的时候直接 + - * / 可能会出现数据失真(精度问题)
BigDecimal 可以解决浮点型运算数据失真的问题
-
包:
java.math.BigDecimal
-
创建对象的方式
public static BigDecimal valueOf(double val)
: 包装浮点数成为大数据对象
-
方法
public BigDecimal add(BigDecimal value)
: 加法运算public BigDecimal subtract(BigDecimal value)
:减法运算public BigDecimal multiply(BigDecimal value)
:乘法运算public BigDecimal divide(BigDecimal value)
:除法运算public double doubleValue()
: 把BigDecimal 转换成 double 类型
public class BigDecimalDemo {
public static void main(String[] args) {
// 浮点型运算的时候直接+ * / 可能会出现数据失真(精度问题)。
System.out.println(0.1 + 0.2);
System.out.println(0.09 + 0.01);
System.out.println(1.0 - 0.32);
System.out.println(1.015 * 100);
System.out.println(1.301 / 100);
System.out.println("-------------------------");
double a = 0.1 ;
double b = 0.2 ;
double c = a + b ;
System.out.println(c);
// 1.把浮点数转换成大数据对象运算
BigDecimal a1 = BigDecimal.valueOf(a);
BigDecimal b1 = BigDecimal.valueOf(b);
//BigDecimal c1 = a1.add(b1); // 加法
BigDecimal c1 = a1.divide(b1); // 除法
System.out.println(c1);
// 结果可能需要继续使用!!!
// BigDecimal只是解决精度问题的手段,double数据才是我们的目的!!
double rs = c1.doubleValue();
System.out.println(rs);
}
}
9. 包装类
什么是包装类?
- Java 认为一切皆对象,引入数据类型就是对象了
- 但是在Java 中 8 种 基本数据类型不是对象,只是表示一种数据的类型形式
- Java 为了一切皆对象的思想统一,把8种基本类型转换成对应的类,这个类称为基本数据类型的包装类
基本数据类型 | 包装类(引用数据类型) |
---|---|
byte | Byte |
short | Short |
int | Integer(特殊) |
long | Long |
float | Float |
double | Double |
char | Character(特殊) |
- 自动装箱: 可以直接把基本数据类型的值或变量赋值给包装类
- 自动拆箱: 可以把包装类的变量直接赋值给基本数据类型
public class PackageClass {
public static void main(String[] args) {
int a = 12;
Integer a1 = 12; // 自动装箱
Integer a2 = a; // 自动装箱
double b = 99.9; // 自动装箱
Double b1 = 99.9; // 自动装箱
Integer c= 100;
int c1 = c; // 自动拆箱
// int d = null; 报错
Integer d1 = null; //引用数据类型的默认值可以为null
Integer d2 = 0;
}
}
包装类的特殊功能
– Java 为包装类做了一些特殊功能
– 包装类作为类首先拥有 Object 类的方法
– 包装类作为引用类型的变量可以存储 null 值
- 可以把基本数据类型的值转换成字符串类型的值(没啥用)
- 调用
toString()
方法 - 调用
Integer.toString(基本数据类型的值)
得到字符串 - 直接把基本数据类型 + 空字符串 就得到了字符串
- 调用
- 把字符串类型的数值转换成对应的基本数据类型的值(真的很有用)
Xxx.parseXxx("字符串类型的数值")
Xxx.valueOf("字符串类型的数值")
:推荐使用!
public class PackageClass02 {
public static void main(String[] args) {
// 1.把基本数据类型的值转成字符串
Integer it = 100 ;
// a.调用toString()方法。
String itStr = it.toString();
System.out.println(itStr+1);
// 1001
// b.调用Integer.toString(基本数据类型的值)得到字符串。
String itStr1 = Integer.toString(it);
System.out.println(itStr1+1);
//1001
// c.直接把基本数据类型+空字符串就得到了字符串。
String itStr2 = it+"";
System.out.println(itStr2+1);
//1001
// 2.把字符串类型的数值转换成对应的基本数据类型的值。(真的很有用)
String numStr = "23";
//int numInt = Integer.parseInt(numStr);
int numInt = Integer.valueOf(numStr);
// Integer.valueOf 自动转int
System.out.println(numInt+1);
// 24
String doubleStr = "99.9";
//double doubleDb = Double.parseDouble(doubleStr);
double doubleDb = Double.valueOf(doubleStr);
// Double.valueOf 自动转double
System.out.println(doubleDb+0.1);
}
}
3.泛型
什么是泛型?
- 泛型就是一个标签: <数据类型>
- 泛型可以在编译阶段约束只能操作某种数据类型
- JDK 1.7开始之后,泛型后面的申明可以省略不写
- 泛型和集合都只能支持引用数据类型,不支持基本数据类型
ArrayList<String> lists = new ArrayList<String>();
ArrayList<String> lists = new ArrayList<>();
// JDK 1.7开始之后,泛型后面的申明可以省略不写!!
3.1 泛型的好处
- 泛型在编译阶段约束了操作的数据类型,从而不会出现类型转换异常
ArrayList<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("张无忌");
// lists.add(false); 报错
// lists.add(99.9); 报错
3.2 自定义泛型类
-
我们之前用的泛型都是别人写好的,接下来我们来自定义泛型类的使用
-
泛型类的概念:使用了泛型定义的类就是泛型类
-
格式:泛型变量建议使用 E,T,K,V
-
修饰符 class 类名<泛型变量>{}
-
-
泛型类的核心思想: 是把出现泛型变量的地方全部替换成传输的真实数据类型
– 需求:模拟ArrayList
集合自定义一个集合MyArrayList
集合
public static void main(String[] args) {
MyArrayList<String> lists = new MyArrayList<String>();
MyArrayList<String> lists1 = new MyArrayList<>();
lists1.add("java");
lists1.add("mysql");
// list1.add(12.3); 报错,把出现泛型变量的地方全部替换成传输的String类型
lists1.remove("java");
System.out.println(lists1);
}
}
class MyArrayList<E>{
private ArrayList lists = new ArrayList();
public void add(E e){
lists.add(e);
}
public void remove(E e){
lists.remove(e);
}
@Override
public String toString() {
return lists.toString();
}
}
3.3 自定义泛型方法
什么是泛型方法?
- 定义了泛型的方法就是泛型方法
泛型方法的定义格式
- 修饰符 <泛型变量> 返回值类型 方法名称(形参列表){}
- 注意:方法定义了什么是泛型变量,后面就只能用什么泛型变量
– 需求:给你任何一个类型的数组,都能返回它的内容
public class GenericDemo {
public static void main(String[] args) {
Integer[] nums = {10 , 20 , 30 , 40 , 50};
String rs1 = arrToString(nums);
System.out.println(rs1);
String[] names = {"贾乃亮","王宝绿","陈羽凡"};
String rs2 = arrToString(names);
System.out.println(rs2);
}
public static <T> String arrToString(T[] nums){
StringBuilder sb = new StringBuilder();
sb.append("[");
if(nums!=null && nums.length > 0){
for(int i = 0 ; i < nums.length ; i++ ){
T ele = nums[i];
sb.append(i == nums.length-1 ? ele : ele+", ");
}
}
sb.append("]");
return sb.toString();
}
}
3.4 自定义泛型接口
什么是泛型接口?
- 使用了泛型定义的接口就是泛型接口
泛型接口的格式
- 修饰符 interface 接口名称<泛型变量> {}
3.5 泛型的通配符
通配符:?
?
可以用在使用泛型的时候代表一切类型
E
T
K
V
是在定义泛型的时候使用代表一切类型
泛型的上下限:
<? extends Car>
: 那么?必须是Car或者其子类(泛型的上限)
<?super Car>
: 那么?必须是Car或者其父类(泛型的下限,不是很常见)
集合重点
4.Collection集合
什么是集合?
- 集合是一个大小可变的容器
- 容器中的每个数据称为一个元素。 数据 == 元素
集合与数组的区别
- 集合中类型可以不确定,大小可以不固定
- 数组中类型和长度一旦定义出来就都固定了
集合的作用
- 在开发中,很多时候元素的个数是不确定的
- 而且要经常进行元素的增删改查操作,集合都是非常合适的
Collection
集合 是Java中集合的祖宗类,Collection
集合的功能是一切集合都可以直接使用的- 包:
import java.util.Collection
4.1 Coleection集合API
public boolean add(E e)
: 把给定的元素添加到当前集合中public void clear()
: 清空集合中所有的元素public boolean remove(E e)
: 把给定的对象在当前集合中删除public boolean contains(Object obj)
: 判断当前集合中是否包含给定的对象public boolean isEmpty()
: 判断当前集合是否为空public int size()
: 返回集合中元素的个数public Object[] toArray()
: 把集合中的元素,储存到数组中
public class CollectionDemo {
public static void main(String[] args) {
// HashSet:添加的元素是无序,不重复,无索引。
Collection<String> sets = new HashSet<>();
// 1.添加元素,添加成功返回true.
System.out.println(sets.add("贾乃亮")); // true
System.out.println(sets.add("贾乃亮")); // false
System.out.println(sets.add("王宝强")); // true
sets.add("陈羽凡");
System.out.println(sets); // 集合重写了toString()方法,默认打印出内容信息
// [贾乃亮, 王宝强, 陈羽凡]
// 2.清空集合的元素。
//sets.clear();
//System.out.println(sets);
// 3.判断集合是否为空 是空返回true 反之
System.out.println(sets.isEmpty()); // false
// 4.获取集合的大小
System.out.println(sets.size()); // 3
// 5.判断集合中是否包含某个元素 。
System.out.println(sets.contains("贾乃亮"));
// 6.删除某个元素:如果有多个重复元素默认删除前面的第一个!
sets.remove("陈羽凡");
System.out.println(sets);
// 7.把集合转换成数组
Object[] arrs = sets.toArray();
System.out.println("数组:"+ Arrays.toString(arrs));
String[] arrs1 = sets.toArray(String[]::new); // 以后再了解,指定转换的数组类型!
System.out.println("数组:"+ Arrays.toString(arrs1));
System.out.println("---------------------拓展---------------------------");
Collection<String> c1 = new ArrayList<>();
c1.add("李小璐");
c1.add("马蓉");
Collection<String> c2 = new ArrayList<>();
c2.add("白百合");
c1.addAll(c2); // 把c2集合的元素全部倒入到c1
System.out.println(c1);
}
}
4.2 Collection集合遍历
– Collection 集合的遍历方式有三种:
- 迭代器
- foreach(增强for循环)
- JDK 1.8 开始的新技术 Lambda 表达式(了解)
迭代器遍历集合
-
方法
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素的E next()
: 获取下一个元素值boolean hasNext()
: 判断是否有下一个元素,有返回true,反之
-
流程
-
先获取当前集合的迭代器
Iterator<String> it = lists.iterator()
-
定义一个 while 循环, 问一次取一次
- 通过
it.hasNext()
询问是否有下一个元素,有就通过 it.next()
取出下一个元素
- 通过
-
public class CollectionDemo{
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("小昭");
lists.add("殷素素");
lists.add("周芷若");
System.out.println(lists);
// lists = [赵敏, 小昭, 殷素素, 周芷若]
// 1.得到集合的迭代器对象。
Iterator<String> it = lists.iterator();
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next()); // 出现异常NoSuchElementException,出现没有此元素异常!
// 2.使用while循环遍历。
while(it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
}
}
foreach
遍历
-
foreach 是一种遍历形式,可以遍历集合或者数组
-
foreach 遍历集合实际上是迭代器遍历的简化写法
-
foreach 遍历的关键是记住格式
-
for(被遍历集合或者数组中元素的类型 变量名称:被遍历集合或者数组){ }
public class CollectionDemo{
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("小昭");
lists.add("殷素素");
lists.add("周芷若");
System.out.println(lists);
// lists = [赵敏, 小昭, 殷素素, 周芷若]
for (String ele : lists) {
System.out.println(ele);
}
int[] ages = new int[]{17 , 18 , 38 , 21};
for (int age : ages) {
System.out.println(age);
}
}
}
– 缺点:foreach 遍历无法知道遍历到了哪个元素,因为没有索引
Lambda(暂时了解)
- jdk 1.8 开始之后的新技术
public class CollectionDemo03 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("小昭");
lists.add("殷素素");
lists.add("周芷若");
System.out.println(lists);
// [赵敏, 小昭, 殷素素, 周芷若]
// s
lists.forEach(s -> {
System.out.println(s);
});
// lists.forEach(s -> System.out.println(s));
// lists.forEach(System.out::println);
}
}
4.3 Set集合
– Set系列集合:添加的元素是无序,不重复,无索引
HashSet
:添加的元素是无序,不重复,无索引的LinkedHashSet
: 添加的元素是有序,不重复,无索引的TreeSet
: 不重复,无索引,按照大小默认升序排列
public class CollectionDemo01 {
public static void main(String[] args) {
// HashSet:添加的元素是无序,不重复,无索引的。
// 多态写法
Collection<String> sets = new HashSet<>();
sets.add("MyBatis");
sets.add("Java");
sets.add("Java");
sets.add("Spring");
sets.add("MySQL");
sets.add("MySQL");
// [Java, MySQL, MyBatis, Spring]
System.out.println(sets);
}
}
4.3.1 HashSet
HashSet
:添加的元素是无序,不重复,无索引的
public class CollectionDemo01 {
public static void main(String[] args) {
// 无序,不重复,无索引的。
Set<String> sets = new HashSet<>(); // 一行经典代码!!
sets.add("Mybatis");
sets.add("Java");
sets.add("Java");
sets.add("MySQL");
sets.add("MySQL");
sets.add("Spring");
// [Java, MySQL, Spring, Mybatis]
System.out.println(sets);
}
}
面试热点:Set 集合添加的元素是不重复的,是如何去重复的
- 对于有值特性的,
Set
集合可以直接判断进行去重复 - 对于引用数据类型的类对象,Set 集合是按照如下流程进行是否重复的判断
- 引用数据类型:自己定义的类型,比如自己定义的苹果apple
Set
集合会让两对象先调用自己的hashCode()
方法得到彼此的哈希值(所谓的内存地址)- 然后比较两个对象的哈希值是否相同,如果不相同则直接认为两个对象不重复
- 如果哈希值相同,会继续让两个对象进行
equals
比较内容是否相同,如果相同认为重复,如果不相同认为不重复
面试热点:Set系列集合元素无序的根本原因(面试必考)
- Set 系列集合添加元素无序的根本原因是因为底层采用了哈希表存储元素
- JDK1.8 之前:哈希表 = 数组 + 链表 + (哈希算法)
- JDK1.8 之后:哈希表 = 数组 + 链表 + 红黑树 + (哈希算法)
- 当链表长度超过 8 时,将链表转换为红黑树,这样大大减少了查找时间
Set 系列集合是基于哈希表存储数据的,它的增删改查的性能都很好,但是它是无序不重复的。
4.4.2 LinkedHashSet
- 是
HashSet
的子类,元素是 有序,不重复,无索引 LinkedHashSet
底层依然是使用哈希表存储元素的,但是每个元素都额外带一个链来维护添加顺序- 不光增删查快,还有序
- 缺点是多了一个存储顺序的链会占内存空间,而且不允许重复,无索引
public static void main(String[] args) {
// 有序不重复无索引
Set<String> sets = new LinkedHashSet<>();
sets.add("Mybatis");
sets.add("Java");
sets.add("Java");
sets.add("MySQL");
sets.add("MySQL");
sets.add("Spring");
// [Java, MySQL, Spring, Mybatis]
System.out.println(sets);
}
}
4.4.3 TreeSet
– TreeSet
: 不重复,无索引,按照大小默认升序排序
– TreeSet 集合称为排序不重复集合,可以对元素进行默认的升序排序
TreeSet 集合自排序的方式
- 有值特性的元素直接可以升序排序(整型,浮点型)
- 字符串类型的元素会按照首字符的编号排序
- 对于自定义的引用数据类型,TreeSet 默认无法排序,执行的时候直接报错,因为人家不知道排序规则
自定义的引用数据类型的排序实现
– 对于自定义的引用数据类型,TreeSet 默认无法排序
– 所以我们需要定制排序的大小规则,方案有两种
-
直接为对象的类实现比较器规则接口
Comparable
,重写比较方法 -
直接为集合设置比较器
Comparator
对象,重写比较方法 -
如果类和集合都存在大小规则,默认使用集合自带的规则进行大小排序!
4.4 List集合
– List系列集合:添加的元素是有序,可重复,有索引
ArrayList
: 添加的元素是有序,可重复,有索引LinkedList
: 添加的元素是有序,可重复,有索引Vector
是线程安全的,速度慢,开发中很少使用
- List 集合继承了 Collection 集合的全部功能,同时因为 List 系列集合有索引,所以多了很多按照索引操作元素的功能
- 开发中
ArrayList
集合用的最多,查询快,增删慢,是基于数组储存数据的
4.4.1 ArrayList 集合
开发中ArrayList集合用的最多!
方法:
public void add(int index,E element)
: 将指定的元素,添加到该集合中的指定位置上public E get(int index)
: 返回集合中指定位置的元素public E remove(int index)
: 移除列表中指定位置的元素,返回的是被移除的元素public E set(int index,E element)
: 用指定元素替换集合中指定位置的元素,返回更新前的元素值
public class ListDemo01 {
public static void main(String[] args) {
// 1.创建一个ArrayList集合对象:这是一行经典代码!
// List:有序,可重复,有索引的。
// 多态写法: 将子类对象给父类类型的接口
List<String> lists = new ArrayList<>();
lists.add("java1");
lists.add("java1");
lists.add("java2");
lists.add("java2");
System.out.println(lists);
// [java1, java1, java2, java2]
// 2.在某个索引位置插入元素。
lists.add(2,"MySQL");
System.out.println(lists);
// [java1, java1, MySQL, java2, java2]
// 3.根据索引删除元素,返回被删除元素
System.out.println(lists.remove(2));
// MySQL
System.out.println(lists);
// [java1, java1, java2, java2]
// 4.根据索引获取元素
System.out.println(lists.get(2));
// java2
// 5.修改索引位置处的元素
lists.set(3,"Mybatis");
System.out.println(lists);
// [java1, java1, java2, Mybatis]
}
}
4.4.2 List集合的遍历方式
-
4种
-
List 系列集合多了索引,所以多了一种按照索引遍历集合的 for 循环
- for循环
- 迭代器
- foreach
- jdk 1.8 之后的 Lambda 表达式
public class ListDemo02 {
public static void main(String[] args) {
// 1.这是一行经典代码
// 将子类对象给父类接口,多态写法
List<String> lists = new ArrayList<>();
lists.add("java1");
lists.add("java2");
lists.add("java3");
/** (1)for循环。 */
for(int i = 0 ; i < lists.size() ; i++ ) {
String ele = lists.get(i);
System.out.println(ele);
}
System.out.println("-----------------------");
/** (2)迭代器。 */
Iterator<String> it = lists.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("-----------------------");
/** (3)foreach。 */
for(String ele : lists){
System.out.println(ele);
}
System.out.println("-----------------------");
/** (4)JDK 1.8开始之后的Lambda表达式*/
lists.forEach(s -> {
System.out.println(s);
});
}
}
4.4.3 LinkedList 集合
LinkedList
也是List 的实现类,增删快,查询慢,底层是基于链表的LinkedList
是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的- 所以
LinkedList
除了拥有 List 集合的全部功能还多了很多操作首尾元素的特殊功能
方法:
public void addFirst(E,e)
: 将指定元素插入此列表的开头public void addLast(E,e)
: 将指定元素添加到此列表的结尾public E getFirst()
: 返回此列表的第一个元素public E getLast()
:返回此列表的最后一个元素public E removeFirst
:移除并返回此列表的第一个元素public E removeLast
:移除并返回此列表的最后一个元素public E pop()
:从此列表所表示的堆栈处弹出一个元素public void push(E e)
:将元素推入此列表所表示的堆栈
public class ListDemo03 {
public static void main(String[] args) {
// 1.用LinkedList做一个队列:先进先出,后进后出。
LinkedList<String> queue = new LinkedList<>();
// 入队
queue.addLast("1号");
queue.addLast("2号");
queue.addLast("3号");
queue.addLast("4号");
System.out.println(queue); // [1号, 2号, 3号, 4号]
// 出队
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue);
// 做一个栈
LinkedList<String> stack = new LinkedList<>();
// 压栈
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
System.out.println(stack); // [第4颗子弹, 第3颗子弹, 第2颗子弹, 第1颗子弹]
// 弹栈
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack);
}
}
4.4.4 总结
- 查询多而增删少 用
ArrayListt
集合 - 查询少而增删首尾多 用
LinkedList
集合
4.5 总结
- 如果希望元素可以重复,又有索引,查询要快,用
ArrayList
集合(用的最多) - 如果希望元素可以重复,又有索引,增删要快,用
LinkedList
集合(适合查询元素比较少,经常要首尾操作元素的情况) - 如果希望增删改查都很快,但是元素不重复以及无序无索引,那么用
HashSet
集合 - 如果希望增删改查都很快且有序,但是元素不重复无索引,那么用
LinkedHashSet
集合
4.6 Collections工具类
- 包:
java.util.Collections
- Collections 并不属于集合,而是用来操作集合的工具类
- 常用API:
addAll(被添加元素的集合,可变参数)
给集合对象批量添加元素public static void shuffle(<?> list)
:打乱集合顺序(只能打乱有序的List集合)public static <T> void sort(List<T> list)
:将集合中元素按照默认规则排序public static <T> void sort(List<T> list,comparator(<? super T>))
: 将集合中元素按照指定规则排序
public class CollectionsDemo01 {
public static void main(String[] args) {
// 1.给集合批量添加元素
Collection<String> names = new ArrayList<>();
/**
* 参数一:被添加元素的集合
* 参数二:可变参数,一批元素
*/
Collections.addAll(names,"曹操","贾乃亮","王宝强","陈羽凡");
System.out.println(names);
//[曹操, 贾乃亮, 王宝强, 陈羽凡]
// 2.打乱集合的顺序:public static void shuffle(List<?> list)
// 注意:只能打乱有序的List集合。
List<String> newnames = new ArrayList<>();
Collections.addAll(newnames,"曹操","贾乃亮","王宝强","陈羽凡");
Collections.shuffle(newnames); // 打乱顺序
System.out.println(newnames);
//[陈羽凡, 王宝强, 曹操, 贾乃亮]
// 3.public static <T> void sort(List<T> list):给List集合升序排序。
List<Double> scores = new ArrayList<>();
Collections.addAll(scores, 98.5, 66.5 , 59.5 , 66.5 , 99.5 );
Collections.sort(scores); // 默认升序排序!
System.out.println(scores);
//[59.5, 66.5, 66.5, 98.5, 99.5]
}
}
4.7 可变参数
-
可变参数用在形参中可以接收多个数据
-
可变参数的格式:
数据类型....参数名称
可变参数的作用
- 传输参数非常灵活,方便
- 可以不传输参数
- 可以传输多个参数
- 可以传输一个数组
– 可变参数在方法内部本质上就是一个数组
public static void main(String[] args) {
sum(); // 可以不传输参数。
sum(10); // 可以传输一个参数。
sum(10,20,30); // 可以传输多个参数。
sum(new int[]{10,30,50,70,90}); // 可以传输一个数组。
}
public static void sum(int... nums){
// nums 就是可变参数
// 可变参数在方法内部本质上就是一个数组。
System.out.println("元素个数:"+nums.length);
System.out.println("元素内容:"+ Arrays.toString(nums));
System.out.println("--------------------------");
}
}
注意:
- 一个形参列表中可变参数只能有一个
- 可变参数必须放在形参列表的最后面
5.Map集合
- Map集合是一种双列集合,每个元素包含两个值
- Map集合是另一个集合体系,Collection 是单值集合体系
- Map集合的每个元素的格式:
key = value(键值对元素)
- Map集合也被称为“键值对集合”
Map集合的作用
-
Map 集合存储的信息更加的具体丰富
-
Map集合的完整格式:{key1 = value1,key2 = value2,key3 = value3,…}
Collection:[“张三”,“中国”,“男”,“法外狂徒”,“23”]
Map:{name = “张三”,jiaxiang = “中国”,sex = “男”,age = “23”}
Map集合的特点:
- Map 集合的特点都是由键决定的
- Map 集合的键是无序,不重复,无索引的
- Map 集合后面重复的键对应的元素会覆盖前面的整个元素
- Map 集合的值无要求
- Map 集合的键值对都可以为null
HashMap
: 元素按照键是无序,不重复,无索引,值不做要求LinkedHashMap
: 元素按照键是有序,不重复,无索引,值不做要求
5.1 Map 集合API(重)
重点中的重点
public V put(K key, V value)
: 把指定的键与指定的值添加到Map集合中public V remove(Object key)
: 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值public V get(Object key)
根据指定的键,在Map集合中获取对应的值。public Set<K> keySet()
: 获取Map集合中所有的键,存储到Set集合中。public Set<Map.Entry<K,V>> entrySet()
: 获取到Map集合中所有的键值对对象的集合(Set集合)public boolean containKey(Object key)
:判断该集合中是否有此键。
public class MapDemo{
public static void main(String[] args) {
Map<String , Integer> maps = new HashMap<>();
// 1.添加元素: 无序,不重复,无索引。
maps.put("iphoneX",10);
maps.put("娃娃",30);
maps.put("iphoneX",100);// Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
// {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
System.out.println(maps);
// 2.清空集合
//maps.clear();
//System.out.println(maps);
// 3.判断集合是否为空,为空返回true ,反之!
System.out.println(maps.isEmpty());
// 4.根据键获取对应值。
//Integer value = maps.get("娃娃");
//System.out.println(value);
System.out.println(maps.get("娃娃"));
// 5.根据键删除整个元素。(删除键会返回键的值)
maps.remove("iphoneX");
System.out.println(maps);
// 6.判断是否包含某个键 ,包含返回true ,反之
System.out.println(maps.containsKey("手表")); // true
System.out.println(maps.containsKey(10)); // false
// 7.判断是否包含某个值。
System.out.println(maps.containsValue(1000)); // true
System.out.println(maps.containsValue(10)); // true
System.out.println(maps.containsValue("30")); // false 包含的是整数30不是字符串。
// 8.获取全部键的集合:public Set<K> keySet()
// Map集合的键是无序不重复的,所以返回的是一个Set集合。
Set<String> keys = maps.keySet();
for (String key : keys) {
System.out.println(key);
}
// 9.获取全部值的集合:Collection<V> values();
// Map集合的值是不做要求的,可能重复,所以值要用Collection集合接收!
Collection<Integer> values = maps.values();
for (Integer value : values) {
System.out.println(value);
}
// 10.集合的大小
System.out.println(maps.size());
// 11.合并其他Map集合。(拓展)
Map<String,Integer> maps2 = new HashMap<>();
maps2.put("xiaoMi" , 1);
maps2.put("🔨手机" , 10);
maps2.put("手表" , 10000);
maps.putAll(maps2); // 把Map集合maps2的数据全部倒入到maps集合中去
System.out.println(maps);
}
}
5.2 Map 集合遍历
– Map 集合遍历有三种方式:
- "键找值"的方式遍历:先获取Map集合全部的键,再根据遍历键找值
- "键值对"的方式遍历: 难度较大
- JDK1.8开始之后的新技术:Lambda 表达式
"键找值"的方式遍历
- 先获取Map集合的全部键的Set集合
- 遍历键的Set集合,然后通过键找值
public class MapDemo{
public static void main(String[] args) {
Map<String , Integer> maps = new HashMap<>();
// 1.添加元素: 无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);// Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
// maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
// a.键找值方式遍历
// 获取当前Map集合的全部键的集合 。
Set<String> keys = maps.keySet();
System.out.println(keys);
// [huawei, 手表, 生活用品, iphoneX, 娃娃]
// key
// b.通过遍历键然后通过键取对应的值
for (String key : keys) {
// 通过键取对应的值
Integer value = maps.get(key);
System.out.println(key + "=" + value);
}
}
}
“键值对” 的方式遍历
- 把Map集合转换成一个Set集合
- 此时键值对元素的类型就确定了,类型是键值对实体类型
- 接下来就可以用foreach遍历这个Set集合
public class MapDemo02 {
public static void main(String[] args) {
Map<String , Integer> maps = new HashMap<>();
// 1.添加元素: 无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);// Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
// maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
//
/**
“键值对”的方式遍历:更加面向对象的方式,代码复杂。
“键值对”想把键值对当成一个整体遍历,也就是直接使用foreach遍历:
for(被遍历集合的元素类型 变量:集合名称){
}
但是发现Map集合的键值对数据直接是没有元素类型的,foreach无法直接遍历Map集合。
👇
把Map集合通过代码Set<Map.Entry<K, V>> entrySet()转换成Set集合。
👇
Set<Map.Entry<String,Integer>> entries = maps.entrySet();
👇
entries = [(huawei=1000), (手表=10), (生活用品=10), (iphoneX=100), (娃娃=30) ]
// entry
此时键值对元素才能作为一个整体就有了类型。类型是 Map.Entry<String,Integer>实体类型
*/
Set<Map.Entry<String,Integer>> entries = maps.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + "=>" + value);
}
}
}
Lambda表达式
public class MapDemo03 {
public static void main(String[] args) {
Map<String , Integer> maps = new HashMap<>();
// 1.添加元素: 无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);// Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
// maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
maps.forEach((k , v) -> {
System.out.println(k+"==>"+v);
});
}
}
5.3Map集合存储自定义类型
-
Map集合的键和值都可以存储自定义类型
-
如果希望Map集合认为自定义类型的键对象重复了,必须重写对象的
hashCode()
和equals()
方法
5.4LinkedHashMap
-
LinkedHashMap
是HashMap
的子类,添加的元素按照键是有序,不重复的 -
HashSet
集合相当于是HashMap
集合的键都不带值 -
LinkedHashSet
集合相当于是LinkedHashMap
集合的键都不带值 -
底层原理都是基于哈希表按照键存储结构的
-
只是
HashMap
或者LinkedHashMap
的键都多一个附属值
public class LinkedHashMapDemo {
public static void main(String[] args) {
Map<String , Integer> maps = new LinkedHashMap<>();
maps.put("iphoneX",10);
maps.put("娃娃",30);
maps.put("iphoneX",100); // 依然是保留前面的位置,只是替换其值!
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
// {iphoneX=100, 娃娃=30, huawei=1000, 生活用品=10, 手表=10}
}
}
HashMap
集合是无序不重复的键值对集合LinkedHashMap
集合是有序不重复的键值对集合- 它们都是基于哈希表存储数据,增删改查都很好
5.5TreeMap
-
TreeMap
集合按照键是可排序不重复的键值对集合(默认升序)。 -
TreeMap
集合按照键排序的特点与TreeSet
是完全一样的。 -
TreeMap
集合和TreeSet
集合都是排序不重复集合 -
TreeSet
集合的底层是基于TreeMap
,只是键没有附属值而已。 -
所以TreeMap集合指定大小规则有2种方式:
- a.直接为对象的类实现比较器规则接口Comparable,重写比较方法(拓展方式)
- b.直接为集合设置比较器Comparator对象,重写比较方法