本节课主要学习了异常、随机数、包装类、日期类。其中异常和日期类的内容相对较多,但是日期类内容较简单好吸收,而异常的内容则比较抽象,需要多花一些时间来消化。
1、异常
1.1 概述
正如“人无完人”一样,程序也不是完美的,它总会存在这样那样的问题,而有些问题并不是能够通过程序员开发更好的代码来解决的,如果我们忽视它,可能就会造成**程序的终止**,甚至是**系统的崩溃**。因此,我们需要想办法来合理的解决它,这就是Java中异常的由来。
异常是指程序在执行过程中,出现的**非正常**情况。比如:空的引用、数组下标越界、内存溢出错误、读取文件不存在、网络断开等。
一般来说有两种方法来处理异常:
1. 遇到错误就终止程序运行;
2. 开发人员在编写程序的时候考虑到可能出现的非正常情况,提前做好处理(这个是最好的,不过也是不容易的)。
1.2 Java中的异常
Java是面向对象的语言,异常被当成了一个对象来处理。其根类是java.lang.Throwable类,Throwable类又分成了两个子类,即Error和Exception。这是异常的两个大类,他们分别代表了两种情况:
1. Error:不能处理的错误,这是系统内部的错误,运行时报错,属于系统问题。一般发生这种异常,JVM会选择终止程序,开发人员需要提前避免。
2. Exception:可以处理的异常,这是比较常见的异常,开发人员可以根据Java提供的类和问题对这类异常进行处理。
1.3 Error
对于严重错误Error,没有办法处理,只能做到提前避免。常见的Error有:StackOverflowError(栈溢出错误)和 OutOfMemoryError (内存溢出错误)。
Java中堆是用来存储对象实例的,因此如果我们不断地创建对象, 并且对象没有被垃圾回收, 那么当创建的对象过多时, 会导致内存不足, 进而引发 OutOfMemoryError 异常。
1.4 Exception
一般来说,我们所说的异常就是指Exception,因为这类异常一旦出现,我们就要对代码进行更正,修复程序进行处理。
根据编译时期还是运行时期去检查异常,可以将Exception分为:
1. 编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败(开发工具会提示),如文件找不到异常等。所以编译时期异常我们必须处理,否则编译不通过。
2. 运行时期异常:runtime异常。在运行时期,检查异常。在编译时期,运行异常不会被编译器检测到(开发工具不会提示,只有代码运行的时候才会报异常),如空指针异常,类型转换异常,数字操作异常等。
public static void main(String[] args) {
// 编辑器会直接报错,这里就是编译时期异常
// Unhandled exception: java.io.FileNotFoundException,即未处理异常。
FileInputStream inputStream = new FileInputStream("java笔记.txt");
String s1 = null;
System.out.println(s1.length()); // 空指针异常NullPointerException
Object object = new Object();
String s2 = (String) object; // 类型转换异常ClassCastException
int i = 10 / 0; // 数字操作异常ArithmeticException
}
1.5 异常的处理
程序异常时非常常见的,我们可以怎么处理呢?
处理异常有三种方式:throw(抛出)、throws(声明,往上抛)、try-catch(捕获)代码块
1. throw
Java程序出现异常时会生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。
- 自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出,自动抛出。
- 手动创建:Exception exception = new Exception();创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样,但是一旦throw抛出,就会对程序运行产生影响了,程序就会中断执行。
自动生成:
// 自动生成异常
public static void main(String[] args) {
int arr[] = { 10, 20, 30, 40 };
int index = 4;
// 运行之后控制台会报异常,下标越界了。
// 我们写相应的异常处理程序代码,由虚拟机自动检测创建异常对象自动抛出。
System.out.println(getElement(arr, index));
}
// 函数:获取数组对应下标的元素值
public static int getElement(int arr[], int index) {
return arr[index];
}
手动创建:
// 手动生成异常
public static void main(String[] args) {
int arr[] = { 10, 20, 30, 40 };
int index = 4;
System.out.println(getElement(arr, index));
}
// 函数:获取数组对应下标的元素值
// 考虑到数组可能出现的异常情况,我们手动做了处理
public static int getElement(int arr[], int index) {
if(arr == null) {
throw new NullPointerException("数组为空!");
}
if(index < 0 || index > arr.length - 1) {
throw new ArrayIndexOutOfBoundsException("下标越界!");
}
return arr[index];
}
2. throws
Java程序出现异常时将问题标识出来,报告给调用者,让调用者去处理。关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(如果调用者不处理,则接着抛出异常,甩锅)。
public static void main(String[] args) {
// 调用test方法编辑器会提示报错,因为test方法有异常但是不处理,抛了出来
test();
}
public static void test() throws FileNotFoundException {
// 这里会出现异常,但是我们不处理,把异常直接throws
FileInputStream fileInputStream = new FileInputStream("1.txt");
}
3. try...catch
try-catch就是捕获异常,可以对出现的异常进行指定方式的处理。(主动处理,不甩锅)
语法:
try {
// 可能出现异常的代码
} catch(异常类型1 e) {
// 当try里面发生异常类型1对应的异常时触发
// 这里是触发异常之后要执行的代码
} catch(异常类型2 e) {
// 当try里面发生异常类型2对应的异常时触发
// 这里是触发异常之后要执行的代码
}
...
示例:
public static void main(String[] args) {
try {
int i = 10 / 0;
// String str = null;
// str.length();
} catch (ArithmeticException e) {
// 出现数学异常时,执行这里的代码
System.out.println("数学异常");
} catch (NullPointerException e) {
// 出现空指针异常时,执行这里的代码
System.out.println("空指针异常");
}
// 这里的代码还是会执行,使用try...catch指定处理异常,程序不会中断
System.out.println("1");
}
4. finally代码块
保证出现异常之后也会执行的代码。当在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后关闭打开的资源。
语法:
try{
} catch(...) {
} finally {
}
示例:
public static void main(String[] args) {
try {
System.out.println("读取文件");
System.out.println("操作文件");
// 关闭文件的代码不适合写这里,因为读取文件之后,在操作文件的过程中可能出现异常,导致关闭文件的代码不执行。
// System.out.println("关闭文件");
} catch (Exception e) {
System.out.println("出现异常");
} finally {
// 把关闭文件的代码放在这里,不管try里面有没有出现异常,都会执行这里的代码。
System.out.println("关闭文件");
}
}
1.6 自定义异常
异常的情况有非常多,因此Java内部并不能把这些都包含进去,这就需要开发人员根据自己的实际业务情况进行自定义异常。比如年龄负数问题,考试成绩负数问题等等。
接下来介绍如何自定义异常:
自定义类继承RuntimeException。
public class AgeLessThan0Exception extends RuntimeException{
public AgeLessThan0Exception() {
super("年龄是负数!");
}
}
public static void main(String[] args) {
int age = -10;
if(age < 0) {
// 控制台会打印【AgeLessThan0Exception: 年龄是负数!】
throw new AgeLessThan0Exception();
}
}
2、随机数
随机数在常用业务中也很常用,比如抽奖、随机点名等。
//随机的数
Random random = new Random();
random.nextInt(); // int范围内产生一个随机数
random.nextInt(10); // 返回0~9之间的随机数
// 固定的随机数列
Random random = new Random(10);
for(int i = 0; i < 10; i++) {
System.out.println(random.nextInt());
}
3、日期类
在日常的开发工作当中,我们经常需要用到日期相关的类用来操作时间相关的数据。
3.1 Date
最基础的一个日期类。
Date date = new Date();
// Date重写了toString方法,打印出来的是一个外国人习惯的日期格式字符串。
System.out.println(date);
// 时间戳:获取1970.01.01到现在过去的毫秒数
//getTime()的作用是可以表示时间的唯一性
System.out.println(date.getTime());
// 有很多已经被弃用了,如果要获取年月日时分秒可以使用日历类Calendar
System.out.println(date.getYear());
System.out.println(date.getMonth());
3.2 Calendar
日历类。
// 获取日历对象
Calendar c = Calendar.getInstance();
System.out.println("获取年份:"+c.get(Calendar.YEAR));
System.out.println("获取月份:"+(c.get(Calendar.MONTH)+1)); //月份0~11
System.out.println("获取日期(今天是该月的第几天):"+c.get(Calendar.DAY_OF_MONTH));
System.out.println("获取小时数(12小时制):"+c.get(Calendar.HOUR));
System.out.println("获取小时数(24小时制):"+c.get(Calendar.HOUR_OF_DAY));
System.out.println("获取分钟数:"+c.get(Calendar.MINUTE));
System.out.println("获取秒钟数:"+c.get(Calendar.SECOND));
System.out.println("获取毫秒数:"+c.get(Calendar.MILLISECOND));
System.out.println("获取当前是该年中的第几天:"+c.get(Calendar.DAY_OF_YEAR));
System.out.println("获取今天是周几:"+c.get(Calendar.DAY_OF_WEEK));
//将月份设置到6月份
c.set(Calendar.MONTH, 5);
// 将年份+1
c.add(Calendar.YEAR, 1);
// 返回Date数据格式的时间
c.getTime();
3.3 SimpleDateFormat
格式化日期类。
// SimpleDateFormat(格式可以查看文档)
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 将当前日期转化成yyyy-MM-dd HH:mm:ss格式的字符串
String date = simpleDateFormat.format(new Date());
System.out.println(date);
// 将日期格式的字符串转化成Date对象
String dateStr = "2000-01-01 12:00:00";
try {
Date date2 = simpleDateFormat.parse(dateStr);
System.out.println(date2);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
4、包装类
Java里面8个基本数据类型都有相应的类,这些类叫做包装类。
Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。
把基本数据类型转换成类,包装类提供了很多的方法给我们使用(查看JDK文档)。
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
// 这里的i是一个对象,具有自己的属性和方法
Integer i = new Integer(10);
System.out.println(i); // 10
4.1 自动拆箱
**包装类转换成基本数据类型**的过程就是拆包装,英文对应于unboxing,中文翻译为拆箱。
自动拆箱:就是将包装类自动转换成对应的基本数据类型。
// 包装类转基本数据类型的方法
Integer i1 = new Integer(100);
// 第一种
int i2 = i1.intValue();
// 第二种:自动拆箱,自动调用intValue()方法
int i3 = i1;
4.2 自动装箱
**包装类是对基本类型的包装**,所以,把基本数据类型转换成包装类的过程就是打包装,英文对应于boxing,中文翻译为装箱。
自动装箱: 就是将基本数据类型自动转换成对应的包装类。
// 基本数据类型转包装类的方法
int i4 = 100;
// 第一种
Integer i5 = Integer.valueOf(10);
// 第二种: 自动装箱,自动调用了Integer.valueOf()方法
Integer i5 = i4;
4.3 思考
int i6 = 100;
Integer i7 = new Integer(100);
Integer i8 = new Integer(100);
Integer i9 = 200;
Integer i10 = 200;
Integer i11 = 100;
Integer i12 = 100;
System.out.println(i6 == i7);
System.out.println(i7 == i8);
System.out.println(i9 == i10);
System.out.println(i9.intValue() == i10.intValue());
System.out.println(i11 == i12);
System.out.println(i6 == i7); // true 包装类与基本类型比较的时候会将包装类拆箱成基本类型进行比较。
System.out.println(i7 == i8); // false new出两个新的地址
System.out.println(i9 == i10); // false 自动装箱,调用了valueOf() 把int转化为对象 -128~127
System.out.println(i9.intValue() == i10.intValue()); // true
System.out.println(i11 == i12); // true 常量池会初始化-128到127的值,只会保留一个地址的值