一个程序开发出来之后,无论是用户还是程序员,都希望它稳定地运行,然而程序毕竟是人写的,人无完人哪能不犯点错误呢?就算事先考虑得天衣无缝,揣着一笔巨款跑去岛国买了栋抗震性能良好的海边别墅,谁料人算不如天算,碰到猴年马月遇上了一场大海啸,整个别墅被冲到山上去了。计算机程序也是如此,不管是人为的错误,还是意外的风险,都会导致程序在运行时异常退出。引起程序异常的原因多种多样,就已经介绍过的知识点而言,主要有这么几种可能发生异常的情况:数学运算异常、数组越界异常、字符串与日期格式异常、空指针异常、类型转换异常等等,接下来分别进行详细说明。

1、数学运算异常
最常见的算术异常当为除数为零,众所周知,在除法运算中除数是不能为零的,纵使数学家规定一除以零的结果等于无穷大,可是计算机该如何表达无穷大呢?要知道个人电脑的内存总共才几个G。既然有限的内存容纳不了无限的大小,想让程序计算一除以零就是不可能的事情了。接下来不妨通过一个除数为零的Java程序验证看看,测试用的方法代码示例如下:

	// 测试算术异常:除数为0
private static void testDivideByZero() {
int one = 1;
int zero = 0;
int result = one / zero;
System.out.println("divide result="+result);
}

运行以上的测试代码,果不其然观察到了异常日志“java.lang.ArithmeticException: / by zero”,可见除数为零是不正确的写法。
另一种算术异常也跟无限有关,像一除以三的结果为三分之一,使用小数表达的话便是0.33333333……这样的无限循环小数。当然由于浮点类型和双精度类型有精度限制,因此使用浮点数抑或双精度数存放三分之一,都只会精确到小数点后若干位,并不存在无限循环的问题。麻烦出在大小数BigDecimal上面,因为大小数默认是绝对精确的,只要开发者不指定大小数的精度位数,则系统会竭尽所能把大小数的精确值原原本本地表达出来。那么问题就来了,三分之一的数值乃无限循环小数,小数点后面的3有无限多个,似此无限的位数,依旧让有限的内存徒呼奈何。下面便是通过大小数计算一除以三的代码例子:

	// 测试算术异常:商是无限循环小数
private static void testDivideByDecimal() {
BigDecimal one = BigDecimal.valueOf(1);
BigDecimal three = BigDecimal.valueOf(3);
BigDecimal result = one.divide(three);
System.out.println("sqrt result="+result);
}

运行上面的除法代码,可见程序仍然打印了异常日志“java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.”,意思是无限小数没法用精确的十进制数来表达。

2、数组越界异常
假设某个数组只有三个元素,正常情况能够访问第一个、第二个和第三个元素,要是程序强行访问第四个元素,系统该怎么办?总不能无中生有变戏法变出一个吧,计算机程序可不是魔术师,它找不到第四个元素就崩溃退出了。比如以下的数组访问代码就重现了这个问题:

	// 测试越界异常:下标超出数组范围
private static void testArrayByIndex() {
int[] array = {1, 2, 3};
int item = array[3];
System.out.println("array item="+item);
}

运行以上的测试代码,程序果然输出了异常信息“java.lang.ArrayIndexOutOfBoundsException: 3”,此处的下标3代表数组的第四个元素,而该数组总共只有三个元素。
不光是数组存在越界异常,容器里的清单List也存在同样的问题,因为清单的索引类似数组的下标,一旦寻求访问的元素索引超出了清单大小,程序运行时也会扔出数组越界异常。用于演示通过索引访问清单元素的代码示例如下:

	// 测试越界异常:索引超出清单范围
private static void testListByIndex() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Integer item = list.get(5);
System.out.println("list item="+item);
}

运行上述的清单访问代码,依然可见程序扔出的异常描述“java.lang.ArrayIndexOutOfBoundsException: 5”,表示索引为5的位置已经超出了当前数组(其实是清单)的边界。

3、字符串与日期格式异常
调用String类的format方法进行字符串格式化之时,每种格式定义与数据类型是一一对应的,例如%d对应整型数,%s对应字符串,%b对应布尔值等等。所以格式化的参数值必须和它的格式要求相符,倘若二者匹配不了,这可如何是好?譬如原先定义的参数格式为%d,表示此处期望格式化一个整型数,结果后面的参数列表却传入某个字符串,难道字符串要格成整数?恐怕只能让程序嗝屁了。不信请看下面的字符串格式化代码:

	// 测试格式异常:字符串格式非法
private static void testStringByFormat() {
String str = String.format("%d", "Hello");
System.out.println("str="+str);
}

运行上面的格式化代码,毫无疑问程序无法正常运行,只能无奈地打印异常日志“java.util.IllegalFormatConversionException: d != java.lang.String”。
不单单字符串有格式要求,日期时间也有格式要求,如果需要把日期数据转换成字符串类型,就得在构造SimpleDateFormat实例时书写正确的时间格式,一个字不多一个字不少,倘若把分钟格式mm误写为mi,试试看程序会怎么运行以下的时间转换代码?

	// 测试格式异常:日期格式非法
private static void testDateByFormat() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mi:ss");
String strDate = sdf.format(new Date());
System.out.println("strDate="+strDate);
}

由于时间格式指定的分钟代号mi有误(正确的应为mm),因此运行以上的测试代码,程序也只能乖乖地打印出错信息“java.lang.IllegalArgumentException: Illegal pattern character 'I'”,表示mi里面的字母I是非法的格式字符。

4、空指针异常
面向对象的前提是有这个对象,好比这个春节你妈喊你带上对象回家过年,可要是对象连影都见不着,你妈给你对象准备的嘘寒问暖就都泡汤了。在Java代码里面,除了少数几个基本类型,其余绝大多数类型必须先给对象创建实例,然后才能访问该对象的各项成员属性和成员方法。假如不给对象分配实例,就想牵起对象的小手,系统会果断地告诉你:门都没有!譬如常用的字符串类型,不管是new出一个字符串实例,还是硬塞给它一个双引号括起来的具体串,都算作分配了对象实例。如果声明字符串对象时啥都不干,或者随便填了个null,那真是对不起了,程序认为该对象没有初始化,就不会给它分配存储空间。后面的代码再想操作这个对象的时候,找不到对象地址只能报空指针异常了,有关的异常重现代码如下所示:

	// 测试空指针异常:对象不存在
private static void testStringByNull() {
String str = null;
int length = str.length();
System.out.println("str length="+length);
}

运行如上的测试代码,观察到打印的异常信息为“java.lang.NullPointerException”,显然被系统揪到了偷懒的小辫子。

5、类型转换异常
在运用多态技术的时候,常常将某个父类实例转换成子类的类型,以便调用子类自身的方法。但这得确保原来的父类实例来自于该子类才行,倘使父类实例来自另一个子类B,代码却想把它强行转换为子类A,也就是俗称的张冠李戴,系统自然不允许这种胡搅蛮缠的情况。尽管开发者一般不会糊涂,可是难保偶尔脑袋抽筋,比如数组工具Arrays的asList方法返回一个清单对象,乍看过去与列表类型ArrayList是一个家伙,谁知真要转换类型的话,程序居然会不认账!这里转换清单类型的代码示例如下:

	// 测试类型转换异常:原始数据与目标类型不匹配
private static void testConvertLyList() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
ArrayList<Integer> arrays = (ArrayList<Integer>) list;
System.out.println("arrays size="+arrays.size());
}

运行上述的类型转换代码,结果输出异常日志“java.lang.ClassCastException: java.util.Arrays$ArrayList cannot be cast to java.util.ArrayList”,没想到此列表非彼列表,当真是大意不得。

更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(七十三)常见的程序异常的更多相关文章

  1. Java开发笔记(十三)利用关系运算符比较大小

    前面在<Java开发笔记(九)赋值运算符及其演化>中提到,Java编程中的等号“=”表示赋值操作,并非数学上的等式涵义.Java通过等式符号“==”表示左右两边相等,对应数学的等号“=”: ...

  2. Java开发笔记(序)章节目录

    现将本博客的Java学习文章整理成以下笔记目录,方便查阅. 第一章 初识JavaJava开发笔记(一)第一个Java程序Java开发笔记(二)Java工程的帝国区划Java开发笔记(三)Java帝国的 ...

  3. Java开发笔记(八十三)利用注解技术检查空指针

    注解属于比较高级的Java开发技术,前面介绍的内置注解专用于编译器检查代码,另外一些注解则由各大框架定义与调用,像Web开发常见的Spring框架.Mybatis框架,Android开发常见的Butt ...

  4. Java开发笔记(四十二)日历工具的常见应用

    前面介绍了日历工具Calendar的基本用法,乍看起来Calendar与Date两个半斤八两,似乎没有多大区别,那又何苦庸人自扰鼓捣一个新玩意呢?显然这样小瞧了Calendar,其实它的作用大着呢,接 ...

  5. Java开发笔记(七十六)如何预防异常的产生

    每个程序员都希望自己的程序稳定运行,不要隔三岔五出什么差错,可是程序运行时冒出来的各种异常着实烦人,令人不胜其扰.虽然可以在代码中补上try/catch语句捕捉异常,但毕竟属于事后的补救措施.与其后知 ...

  6. Java开发笔记(一百四十三)FXML布局的基本格式

    前面介绍了JavaFX的常见控件用法,虽然JavaFX控件比起AWT与Swing要好用些,但是一样通过代码编写控件界面,并没有提高什么开发效率.要想浏览界面的展示效果,都必须运行测试程序才能观看,即使 ...

  7. Java开发笔记(一百零七)URL地址的组成格式

    URL的全称是Uniform Resource Locator,意思是统一资源定位符,俗称网络地址或网址.网络上的每个文件及接口,都有对应的URL网址,它规定了其他设备如何通过一系列的路径找到自己,犹 ...

  8. Java开发笔记(二十三)数组工具Arrays

    数组作为一种组合形式的数据类型,必然要求提供一些处理数组的简便办法,包括数组比较.数组复制.数组排序等等.为此Java专门设计了Arrays工具,该工具包含了几个常用方法,方便程序员对数组进行加工操作 ...

  9. Java开发笔记(四十三)更好用的本地日期时间

    话说Java一连设计了两套时间工具,分别是日期类型Date,以及日历类型Calendar,按理说用在编码开发中绰绰有余了.然而随着Java的日益广泛使用,人们还是发现了它们的种种弊端.且不说先天不良的 ...

随机推荐

  1. [Lyft Level 5 Challenge 2018 - Elimination Round][Codeforces 1033D. Divisors]

    题目链接:1033D - Divisors 题目大意:给定\(n\)个数\(a_i\),每个数的约数个数为3到5个,求\(\prod_{i=1}^{n}a_i\)的约数个数.其中\(1 \leq n ...

  2. 创建Jdbc封装工具类

    jdbc.propertie url=jdbc:mysql:///empye user=root password=root driver=com.mysql.jdbc.Driver 读取资源文件  ...

  3. c++ 读取不了hdf5文件中的字符串

    问题描述: 在拿到一个hdf5文件,想用c++去读取文件中的字符串,但是会报错:read failed ps: c++读取hdf5的字符串方法见:https://support.hdfgroup.or ...

  4. koa2学习(一)

    前期准备: node环境 npm包管理工具 安装Koa npm install --save koa 第一个程序 创建index.js const Koa = require('koa'); cons ...

  5. 《JavaScript DOM编程艺术》学习笔记(二)

    终于开始接着写我的读书笔记了. 17.DOM有insertBefore方法,但并没有提供insertAfter()方法.不过可利用已有的DOM方法和属性编写此函数: function insertAf ...

  6. For each...in / For...in / For...of 的解释与例子

    1.For each...in for each...in 语句在对象属性的所有值上迭代指定的变量.对于每个不同的属性,执行一个指定的语句. 语法: for each (variable in obj ...

  7. SimpleRpc-客户端与服务端工作模型探讨

    前言 本篇文章讲述客户端与服务端的具体设计细节.有细心的小伙伴发现,客户端和服务端的工作方式不一样:服务端是多线程计算模型,利用工作线程完成数据的读取,而客户端是单线程(利用Reactor线程完成数据 ...

  8. [Swift]LeetCode682. 棒球比赛 | Baseball Game

    You're now a baseball game point recorder. Given a list of strings, each string can be one of the 4 ...

  9. 如何将项目上传到GitHub?

    如何将项目上传到GitHub? 1.注册GitHub账户 浏览器输入GitHub官网地址:https://github.com/ 进入后点击Sign In 然后点击Create an account ...

  10. 前端基本知识(三):JS的闭包理解(第一个思考题有错误,已修改)

    JS闭包的理解 一.变量的作用域 二.如何从外部读取局部变量 三.什么是闭包 四.深入理解闭包 五.闭包的用途 六.使用闭包注意情况 七.JavaScript的垃圾回收机制 八.一些思考题 一.变量作 ...