文章目录
- 一、异常
- 1. 防御性编程
- 2. throw关键字
- 3. throws关键字
- 4. 捕获
- 5. finally关键字
- 二、自定义异常类
- 三、常用工具类
- 1. Date以及相关的类
- 1. 创建时间(基本弃用)
- 2. 捕获系统时间
- 3. 获取当前年月日时分秒
- 4. 日期加减
- 5. 根据字符串创建日期
- 6. 根据当前时间获取指定时间
- 四、RuntimeException及其子类异常拓展
- 1. ArrayStoreException——数组“错放东西”的异常
- 2. ClassCastException——类的对象身份识别错误的异常
- 3. DateTimeException——日期/时间不合法时异常
- 4. IllegalArgumentException——参数质检员
- 5. IndexOutOfBoundsException——边界型异常
- 五、LocalDate类及其拓展
- 1. 日期检查
- 2. 计算月份/年份长度
- 3. 日期转字符串
- 4. 创建副本实现修改
- 1. 获取指定日期
- 2. 直接修改月份
- 3. 自适应修改月份
- 4. 按月份精确修改日期
- 5. 按年份修改日期
- 6. 自适应修改年份
- 六、BigDecimal类
- 1. 加减乘方法
- 2. 除方法
- 1. 模式一:四舍五入——用于财务审计
- 2. 模式二:银行家舍入——用于金融系统
- 3. 模式三:向零舍入——用于发票
- 4. 模式四:远离零舍入——用于估计值
- 5. 模式五:向正无穷舍入——用于计算资源上限
- 6. 模式六:向负无穷舍入——用于计算库存
- 7.模式七:五舍六入——极少使用
- 8. 模式八:禁止舍入——用于密码学
- 七、想说的话
一、异常
1. 防御性编程
什么是防御性编程,有事先做检查即运行前检查(LBYL)和有事后检查即运行后抛出异常时再检查(EAFP),我们一般选择后者
2. throw关键字
在Java中我们知道一切都是对象,因此抛出异常的时候就要先new一个异常的类
比如常见的空指针异常(NullPointerException)
而且你抛出必须在方法体的内部进行抛出,不能在外边
对于RuntimeException(运行时异常)异常类以及其子类JVM虚拟机可以直接处理
如果是受查异常(编译时异常)则需要我们自己处理
3. throws关键字
这个是抛出异常类型的声明,写在方法声明结尾,列举出所有可能抛出的异常
同样不建议写error类以及其子类,如果你有异常不处理,编译器会报错
当然如果你抛出的异常类之间存在父子关系可以直接写父类,但是还是建议把异常类写明确
如果你不处理异常,则你要继续throws下去,直到子类处理为止
4. 捕获
捕获异常我们会用到try-catch
代码块,try{}
中写可能抛出异常的代码,catch{}
块中就写处理异常的代码,比如经典的除0问题
public class Test {public void func() {try {int a = 0;int ret = 6 / 0;} catch (ArithmeticException e) {System.out.println("捕获到了除0异常");}}public static void main(String[] args) {Test test = new Test();test.func();}
}
此时在try块中就抛出了异常之后被catch块捕获,可以看到我new了一个ArithmeticException
类的对象,这个对象可以针对这个异常类调用其中的方法,其中之一就是显示异常详细信息,比如行数内容等等
但是,如果你try块中有多个异常,那在这个异常之后的代码就不会再执行了
public void func() {try {int a = 0;int ret = 6 / 0;int [] arrays = new int[2];arrays [5] = 0;}catch (ArrayIndexOutOfBoundsException e){System.out.println("捕获到了数组越界异常");}catch (ArithmeticException e) {System.out.println("捕获到了除0异常");}}
可以看到我try块中存在两个异常,但是数组越界的代码并没有执行
而且捕获数组异常的catch也没有执行,说明它并没有捕获数组越界的异常
还记得我刚刚说的异常类创建的对象e吗,可以通过printStackTrace
让我们知道抛出异常的类在第几行,但是当然在调用printStackTrace
方法时其内部也在调用其他方法,会存在延时
catch (ArithmeticException e) {e.printStackTrace();System.out.println("捕获到了除0异常");}
对于重大问题我们有权利让程序终止运行,否则就想办法处理,及时记录日志,并尝试恢复
如果你嫌麻烦还可以把多个异常类写在一个catch块中,用“|”分割,前提先保证都有一致的处理逻辑,catch (ArrayIndexOutOfBoundsException | ArithmeticException e)
如果你多个异常类之间有父子关系,则一定要先写子类再写父类,你想如果父类都catch完了你还要子类干啥
因此聪明的你一定想到我直接catch异常的父类Exception
不就好了吗
但是我们处理异常讲究的就是细分,你这样太宽泛没有意义
5. finally关键字
它是放在catch块最后的,一般用于释放资源
比如你new了个Scanner
资源,使用后总要关闭吧,这个finally代码块不论你是否抛出了异常,都会执行
public void ea(){Scanner sc = new Scanner(System.in);int simple = sc.nextInt();try {int a = 5;int ret = 10 / simple;}catch (ArithmeticException e){e.printStackTrace();System.out.println("捕获到了除0异常");}finally {System.out.println("finally代码块执行");sc.close();//关闭Scanner资源}}
可以看到这里并不会抛出异常,但是finally代码块还是执行了,而且我们还关闭了Scanner资源,释放了内存
当然我们也可以不在finally中关闭资源,假设你的try块中有return的代码,没有抛出异常,那try-catch-finally后的代码会执行
但是如果你抛出了异常,是不是会直接返回值,而try-catch-finally后的代码会执行吗,不会,因此为了规范化编程以及提高代码的可读性,还是建议在finally中关闭资源
如果你finally和try中都有return,会执行finally的return而非try中的,因此我建议你finally中就老老实实关闭资源,不要写其他东西
处理流程:在JVM虚拟机栈中,存着各个类之间调用关系,总的来说就是不断向上抛出异常,直到被处理完毕
如果最后main方法也没处理,则会丢给JVM虚拟机,此时就会崩溃,除非是运行时异常类(RunTimeException),其他直接报错
二、自定义异常类
我们先创建一个Person类,写上方法,再定义两个自定义的异常类AgeException
和NameException
并继承Exception
类,两个异常类中不写任何方法和变量
在Person类中方法再声明异常类,并在内部抛出异常,我们再在main方法中去处理,整体如下
public class Person{String name = "zhangsan";int age = 10;void func(String names,int ages) throws NameException ,AgeException{if(!names.equals(name)){throw new NameException();}if(age != ages){throw new AgeException();}}public static void main(String[] args) {Person person = new Person();try {person.func("sss", 8);}catch (NameException | AgeException e){System.out.println("身份核对失败");}}
}
我们可以在自定义异常类中重写异常类的构造方法,然后在new的时候就可以传参了,然后在异常报错信息中就可以看到自己写的说明信息
不建议再在catch块中再去try-catch,除非有毛病
在受查异常中,如果你自定义异常类之间存在父子关系,那么你子类的异常声明范围不可以大于父类
三、常用工具类
注意在Java中类命名中“AofB”就是说明B的A这种从属关系
如何记忆:
看到 Formatter/Parser → 必然是字符串和对象的转换器
看到 Builder → 肯定能 .append().append() 链式调用
看到 Utils → 直接找静态方法(如 Arrays.sort())
1. Date以及相关的类
1. 创建时间(基本弃用)
Date date = new Date(100,1,1);System.out.println(date);
打印结果:Tue Feb 01 00:00:00 CST 2000
第一个参数对应的是1900年加上多少年,第二个参数是二月(0->1,1->2…),第三个参数是天数,打印结果中CST是中国时区
2. 捕获系统时间
LocalDateTime
类对于时间的精确度要高于LocalDate
对于LocalDate
一般用于记录信用卡有效期或者是用户注册信息只需要精确到日的
而对于LocalDateTime
一般用于比如游戏击杀瞬间或者是系统操作日志这种需要精确到时分秒纳秒
LocalDateTime localDateTime = LocalDateTime.now();System.out.println(localDateTime);
打印结果:2025-07-12T16:05:03.759227300
我们还可以创建一个指定时间
LocalDateTime localDateTime1 = LocalDateTime.of(2000,1,1,10,10,10);System.out.println(localDateTime1);
打印结果就是:2000-01-01T10:10:10(对应年月日时分秒)
3. 获取当前年月日时分秒
比如获取当前系统日期的年/月等等
public static void main(String[] args) {LocalDateTime localDateTime = LocalDateTime.now();int year = localDateTime.getYear();int month = localDateTime.getMonth().getValue();//这里原本是Month类型,转换成数字类型了,或者写成getMonthValue()System.out.println(year+" "+month);}
4. 日期加减
public static void main(String[] args) {LocalDateTime localDateTime = LocalDateTime.now();LocalDateTime ret = localDateTime.plusDays(1);LocalDateTime rets = localDateTime.minusDays(1);System.out.println(ret+" "+rets);}
不只是可以加减天数,周数、月数、年数都可以
5. 根据字符串创建日期
- 普通玩法
public static void main(String[] args) {String date = "2025-07-13 10:10:10";//月份必须写成07形式而非7//而且时间一定要写,因为这是LocalDateTime类转化,精确到秒DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");LocalDateTime localDateTime = LocalDateTime.parse(date,dateTimeFormatter);System.out.println(localDateTime);}
- 进阶玩法
public static void main(String[] args) {LocalDateTime localDateTime = LocalDateTime.now();String date = localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd EEEE a"));System.out.println(date);}
这样就打印出了上午和下午以及星期几:2025-07-13 星期日 上午
6. 根据当前时间获取指定时间
我们就以星期举例
public static void main(String[] args) {LocalDateTime nowDate = LocalDateTime.now();LocalDateTime firstDayOfWeek = nowDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));//当前日期或之前的指定星期几LocalDateTime thirdDayOfWeek = nowDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.FRIDAY));LocalDateTime firstDyaOfWeekPlus = nowDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));//当前日期或之后的指定星期几LocalDateTime thirdDyaOfWeekPlus = nowDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.WEDNESDAY));System.out.println(firstDayOfWeek+" "+thirdDayOfWeek);System.out.println("===============================分割线");System.out.println(firstDyaOfWeekPlus+" "+thirdDyaOfWeekPlus);}
打印结果:2025-07-07T10:13:38.284732400 2025-07-11T10:13:38.284732400
-------------------------------------------------------------------------------------------------------------
2025-07-14T10:13:38.284732400 2025-07-16T10:13:38.284732400
因此我们不难知道previousOrSame
是获取当前时期或之前的
而nextOrSame
是获取当前日期或之后的
或者是根据当前日期获取本月第一天或者是最后一天
public static void main(String[] args) {LocalDateTime nowDate = LocalDateTime.now();LocalDateTime firstDayOfMonth = nowDate.with(TemporalAdjusters.firstDayOfMonth());LocalDateTime lastDayOfMonth = nowDate.with(TemporalAdjusters.lastDayOfMonth());System.out.println(firstDayOfMonth+" "+lastDayOfMonth);}
打印结果:2025-07-01T10:23:11.338781300 2025-07-31T10:23:11.338781300
四、RuntimeException及其子类异常拓展
1. ArrayStoreException——数组“错放东西”的异常
public static void main(String[] args) {try {Object[] student = new Person[2];//此时student对象指向的是Person类student[0] = new Person();//此时第一个元素存放Person类的对象可以student[1] = new Teacher();//但是存储其他类对象就会抛出异常}catch (ArrayStoreException e){e.printStackTrace();System.out.println("抛出了数组引用异常");}}
注意我这里的Person类和Teacher类是两个无关系的类,如果互相是父子类关系则不会抛出异常了
2. ClassCastException——类的对象身份识别错误的异常
public static void main(String[] args) {try{Person person = new Teacher();//此时的person指的是老师Student student = (Student) person;//此时你又把person原本指的是老师强转成学生}catch(ClassCastException e){e.printStackTrace();System.out.println("抛出了对象指向错误异常");}}
在上述代码中,你的person对象原本指向的是老师,但是通过你的强转又把person对象指向了学生,然后你非说person就是指向学生,这是不被允许的
3. DateTimeException——日期/时间不合法时异常
这个是专门检查日期时间的异常,包括创建非法日期,设置非法时间,输入日期非法
//设置非法年月日
try{LocalDate localDate = LocalDate.of(2023,13,1);
}catch (DateTimeException e){e.printStackTrace();System.out.println("时间非法");
}
//设置非法分和秒
try {LocalTime localTime = LocalTime.of(25,25);
}catch (DateTimeException e){e.printStackTrace();System.out.println("时间非法");
}
//非法时间捕获
try {String time = "2025-1-32";DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME;LocalDate.parse(time, dateTimeFormatter);
}catch (DateTimeException e){System.out.println("时间非法");
}
4. IllegalArgumentException——参数质检员
这个说白了就是检查参数是否符合要求,就以整型举例
public static void main(String[] args) {try {int ret = -10;if (ret <0){throw new IllegalArgumentException();}}catch (IllegalArgumentException e){System.out.println("数值有问题");}try {String name = null;if(name == null){throw new IllegalArgumentException();}}catch (IllegalArgumentException e){System.out.println("名字异常,不接受null");}}
5. IndexOutOfBoundsException——边界型异常
比如说数组越界和字符串指定下标字符获取
try {int[] a = new int[]{0, 1};a [3] = 10;}catch (IndexOutOfBoundsException e){System.out.println("数组越界异常");}try{String str = "abcd";//0~3下标char ret = str.charAt(5);}catch (IndexOutOfBoundsException e){System.out.println("字符获取失败");}
五、LocalDate类及其拓展
LocalDateTime类与之类似,只不过多了时分秒纳秒的操作,这里就不再细讲了
1. 日期检查
比如我们可以检查某个日期是否在指定日期之前或者是之后,是不是相等,年份是不是闰年
public static void main(String[] args) {LocalDate date1 = LocalDate.of(2012, 9, 30);LocalDate date2 = LocalDate.of(2025, 7, 13);LocalDate date3 = LocalDate.of(2025,7,13);boolean ret = date1.isAfter(date2);System.out.println("判断date1是否在date2之后:"+ret);boolean ret2 = date1.isBefore(date2);System.out.println("判断date1是否在date2之前:"+ret2);boolean ret3 = date2.isEqual(date3);System.out.println("判断date2是否与date3同一天:"+ret3);boolean ret4 = date1.isLeapYear();System.out.println(date1+"是否是闰年:"+ret4);}
打印结果
判断date1是否在date2之后:false
判断date1是否在date2之前:true
判断date2是否与date3同一天:true
2012-09-30是否是闰年:true
2. 计算月份/年份长度
public static void main(String[] args) {LocalDate date1 = LocalDate.of(2025,7,13);int ret1 = date1.lengthOfMonth();int ret2 = date1.lengthOfYear();System.out.println(date1+"月份长度和年份长度是"+ret1+" "+ret2);}
打印结果是:2025-07-13月份长度和年份长度是31 365
3. 日期转字符串
public static void main(String[] args) {LocalDate date1 = LocalDate.of(2025,7,13);String str = date1.toString();System.out.println(str);//结果:2025-07-13}
4. 创建副本实现修改
1. 获取指定日期
之前已经有演示过LocalDateTime类的获取方式,这里与之类似
public static void main(String[] args) {LocalDate localDate = LocalDate.now();//2025-7-13//获取下周五LocalDate nextFriday = localDate.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));//获取下周一LocalDate nextMonday = localDate.with(TemporalAdjusters.next(DayOfWeek.MONDAY));System.out.println(nextFriday+" "+nextMonday);}
2. 直接修改月份
public static void main(String[] args) {LocalDate localDate = LocalDate.of(2025,7,13).with(ChronoField.MONTH_OF_YEAR,12);System.out.println(localDate);//2025-12-13}
3. 自适应修改月份
会根据你的日期判断,比如你输入的是7月的31日,有31号
如果你修改月份为9月,没有31号,其会自己调整成30号
public static void main(String[] args) {LocalDate localDate = LocalDate.parse("2025-07-31").withMonth(9);System.out.println(localDate);//2025-09-30}
4. 按月份精确修改日期
修改的时候会判断本月有多少天
public static void main(String[] args) {LocalDate localDate = LocalDate.parse("2025-07-13").withDayOfMonth(15);System.out.println(localDate);//2025-07-15}
5. 按年份修改日期
同理修改的时候会判断本年是不是闰年,参数是本年中第多少天
public static void main(String[] args) {LocalDate localDate = LocalDate.of(2025,1,1).withDayOfYear(150);System.out.println(localDate);//2025-05-30}
6. 自适应修改年份
跟刚刚的自适应修改月份一样的,这里就直接给代码了
public static void main(String[] args) {LocalDate localDate = LocalDate.parse("2024-02-29").withYear(2023);System.out.println(localDate);//2023-02-28}
六、BigDecimal类
这个类专门用于高精度计算,对于超过16位有效数字的也能做到精确计算
它对于处理浮点数可以是非常大的也可以是非常小的
其构造方法我们可以传整型、double型、String型、Long型,但是double类型如果使用势必会带来精度丢失问题,一般都是使用String类型,比如
public static void main(String[] args) {BigDecimal bigDecimal = new BigDecimal(3.14);BigDecimal bigDecimal1 = new BigDecimal("3.14");System.out.println(bigDecimal+"\n"+bigDecimal1);}
打印结果:3.140000000000000124344978758017532527446746826171875 3.14
可以很明显看到传浮点型打印出的数字很长,虽然精确位数很高,但是我们用不到那么高,3.14就是3.14嘛,所以这就是为什么使用String类的原因
并且BigDecimal
这个类可以表示比Long类型更大的数,比如:
public static void main(String[] args) {Long max = Long.MAX_VALUE;BigDecimal bigDecimal = new BigDecimal(max);BigDecimal bigDecimal1 = new BigDecimal("9223372036854775808");System.out.println("最大长整型"+bigDecimal+"\n最大长整型数字加一"+bigDecimal1);}
打印结果:最大长整型9223372036854775807 最大长整型数字加一9223372036854775808
1. 加减乘方法
加——add,减——subtract,乘——multiply
public static void main(String[] args) {BigDecimal bigDecimal1 = new BigDecimal("12");BigDecimal bigDecimal2 = new BigDecimal("40");BigDecimal bigDecimal3 = new BigDecimal("10.5");BigDecimal ret = bigDecimal1.add(bigDecimal2);BigDecimal ret2 = bigDecimal1.subtract(bigDecimal2);BigDecimal ret3 = bigDecimal1.multiply(bigDecimal2);BigDecimal ret4 = bigDecimal1.add(bigDecimal3);BigDecimal ret5 = bigDecimal1.subtract(bigDecimal3);BigDecimal ret6 = bigDecimal1.multiply(bigDecimal3);System.out.println("加:"+ret);System.out.println("减:"+ret2);System.out.println("乘:"+ret3);System.out.println("不同类型加:"+ret4);System.out.println("不同类型减:"+ret5);System.out.println("不同类型乘:"+ret6);System.out.println("====请类比一般的浮点计算====");double num3 = 10.25;double num4 = 10.55;double ret11 = num3+num4;System.out.println(+ret11);}
打印结果如下:
加:52
减:-28
乘:480
不同类型加:22.5
不同类型减:1.5
不同类型乘:126.0
–请类比一般的浮点计算–
20.8
我们可以很清楚看到,用BigDecimal
类去计算浮点,精度更高,因此这就是为什么高精度使用BigDecimal
类
2. 除方法
public static void main(String[] args) {BigDecimal bigDecimal = new BigDecimal("3.5");BigDecimal bigDecimal1 = new BigDecimal("0.5");BigDecimal bigDecimal2 = new BigDecimal("1.1");BigDecimal ret1 = bigDecimal.divide(bigDecimal1);BigDecimal ret2 = bigDecimal.divide(bigDecimal2);System.out.println("正常除法:"+ret1);System.out.println("无限循环小数除法:"+ret2);}
根据图片我们可以很清楚看到,抛出了算数异常,这是要安慰ret2结果是无限循环小数BigDecimal
会抛出异常,怎么办呢,我们可以限定保留几位小数
1. 模式一:四舍五入——用于财务审计
BigDecimal result = bigDecimal.divide(bigDecimal2,2,RoundingMode.HALF_UP);
3.185 → 3.19(保留2位小数) 3.184 → 3.18
2. 模式二:银行家舍入——用于金融系统
定义:四舍六入五成双(当恰好0.5时,向最近的偶数舍入)
BigDecimal result2 = bigDecimal.divide(bigDecimal2,2,RoundingMode.HALF_EVEN);
3.185 → 3.18(5前面是8偶数,舍去) 3.175 → 3.18(5前面是7奇数,进位)
3. 模式三:向零舍入——用于发票
定义:直接砍掉多余的小数位
BigDecimal result3 = bigDecimal.divide(bigDecimal2,2,RoundingMode.DOWN);
3.189 → 3.18 -3.189 → -3.18
4. 模式四:远离零舍入——用于估计值
定义:总是想远离零方向进位
BigDecimal result4 = bigDecimal.divide(bigDecimal2,2,RoundingMode.UP);
3.181 → 3.19 -3.181 → -3.19
5. 模式五:向正无穷舍入——用于计算资源上限
定义:向较大数值方向舍入
BigDecimal result5 = bigDecimal.divide(bigDecimal2,2,RoundingMode.CEILING);
3.181 → 3.19 -3.189 → -3.18
6. 模式六:向负无穷舍入——用于计算库存
定义:向较小数值方向舍入
BigDecimal result6 = bigDecimal.divide(bigDecimal2,2,RoundingMode.FLOOR);
3.189 → 3.18 -3.181 → -3.19
7.模式七:五舍六入——极少使用
定义:严格版的四舍五入,只有>0.5才进位
BigDecimal result7 = bigDecimal.divide(bigDecimal2,2,RoundingMode.DOWN);
3.185 → 3.18 3.186 → 3.19
8. 模式八:禁止舍入——用于密码学
如果强行舍入,会抛出异常
BigDecimal result8 = bigDecimal.divide(bigDecimal2,2,RoundingMode.UNNECESSARY);//禁止舍入
System.out.println(result8);
此时抛出了异常
总结就是这些方法,我们可以整合到一起看看效果
System.out.println("==================");BigDecimal result = bigDecimal.divide(bigDecimal2,2,RoundingMode.HALF_UP);//四舍五入法BigDecimal result2 = bigDecimal.divide(bigDecimal2,2,RoundingMode.HALF_EVEN);//银行家舍入BigDecimal result3 = bigDecimal.divide(bigDecimal2,2,RoundingMode.DOWN);//向0舍入BigDecimal result4 = bigDecimal.divide(bigDecimal2,2,RoundingMode.UP);//远离0舍入BigDecimal result5 = bigDecimal.divide(bigDecimal2,2,RoundingMode.CEILING);//向正无穷舍入BigDecimal result6 = bigDecimal.divide(bigDecimal2,2,RoundingMode.FLOOR);//向负无穷舍入BigDecimal result7 = bigDecimal.divide(bigDecimal2,2,RoundingMode.DOWN);//五舍六入,少用//BigDecimal result8 = bigDecimal.divide(bigDecimal2,2,RoundingMode.UNNECESSARY);//禁止舍入System.out.println(result);System.out.println(result2);System.out.println(result3);System.out.println(result4);System.out.println(result5);System.out.println(result6);System.out.println(result7);
打印结果:3.18 3.18 3.18 3.19 3.19 3.18 3.18
七、想说的话
目前JavaSE不知不觉语法大致上都学完了,时间过得很快啊,从第一次接触Java开始
我就发现了Java语言的奇妙,让我醍醐灌顶,在未来的路上,我也会脚踏实地,继续努力的学习,祝你和我早日拿到理想offer