在前面的java字符串小节,我们就已经接触了string类,但并未提及string类相关的操作,现在有了面向对象相关前置知识,我们知道了类下面可以有相关的操作,作为java语言的内置类,string类也为我们预先定义了很多好用的方法,本小节我们将介绍string类的常用方法,并结合示例辅助理解。
1. string 对象的创建string对象的创建有两种方式。
第1 种方式就是我们最常见的创建字符串的方式:
string str1 = "hello, 夜猫编程";代码块1
第 2 种方式是对象实例化的方式,使用new关键字,并将要创建的字符串作为构造参数:
string str2 = new string("hello, java");代码块1
如果调用 string 类的无参构造方法,则会创建一个空字符串:
string str3 = new string();代码块1
此处的str3就是一个空字符串。但注意,这种方式很少使用。
2. 获取字符串长度可以使用length()方法来获取字符串的长度。例如:
实例演示
public class stringmethod1 {public static void main(string[] args) {// 创建string对象strstring str = "hello world!";// 调用对象下length()方法,并使用int类型变量接收返回结果int length = str.length();system.out.println("str的长度为:" length);}}123456789
可查看在线运行效果
运行结果:
str1的长度为:12代码块1
注意,hello world!中的空格也算一个字符。
3. 字符串查找3.1 获取指定位置字符可以使用char charat(int index)方法获取字符串指定位置的字符。它接收一个整型的index参数,指的是索引位置,那什么是索引位置呢?例如,有一字符串i love java,其每个字符的索引如下图所示:
可以从图示中看出,索引下标从0开始。假如我们要获取字符j,则为方法传入参数7即可:
实例演示
public class stringmethod2 {public static void main(string[] args) {string str = "i love java";char c = str.charat(7);system.out.println("索引位置为7的字符为:" c);}}1234567
可查看在线运行效果
运行结果:
3.2 查找字符串位置索引位置为7的字符为:j代码块1
这里介绍查找字符串位置的两个方法:
indexof() 获取字符或子串在字符串中第一次出现的位置。
lasindexof() 获取字符或子串在字符串中最后一次出现的位置。
这里的子串指的就是字符串中的连续字符组成的子序列。例如,字符串hello就是字符串hello java的子串。
indexof()有多个重载方法,这里我们只演示其中最常用的两个。
获取字符在字符串中第一次出现的位置:
实例演示
public class stringmethod2 {public static void main(string[] args) {string str = "i love java, i love imooc!";int i = str.indexof('a');system.out.println("字符a在字符串str第一次出现的位置为:" i);}}1234567
可查看在线运行效果
运行结果:
字符a在字符串str第一次出现的位置为:8代码块1
获取子串在字符串中第一次出现的位置:
实例演示
public class stringdemo2 {public static void main(string[] args) {string str = "i love java, i love imooc!";int i = str.indexof("love");system.out.println("子串love在字符串str第一次出现的位置为:" i);}}1234567
可查看在线运行效果
运行结果:
子串love在字符串str第一次出现的位置为:2代码块1
关于lastindexof(),我们也只演示最常用的两个重载方法。
获取字符在字符串中最后一次出现的位置:
实例演示
public class stringmethod2 {public static void main(string[] args) {string str = "i love java, i love imooc!";int i = str.lastindexof('e');system.out.println("字符e在字符串str最后一次出现的位置为:" i);}}1234567
可查看在线运行效果
运行结果:
字符e在字符串str最后一次出现的位置为:18代码块1
获取子串在字符串中最后一次出现的位置:
实例演示
public class stringmethod2 {public static void main(string[] args) {string str = "i love java, i love imooc!";int i = str.lastindexof("i love");system.out.println("字串i love在字符串str最后一次出现的位置为:" i);}}1234567
可查看在线运行效果
运行结果:
字串i love在字符串str最后一次出现的位置为:13代码块1
需要特别注意的是,以上方法的参数都是区分大小写的。这也就意味着,你永远无法在i love java中查找到字符e。如果没有查找,上述方法都会返回一个整型值:-1。我们来看以下示例:
实例演示
public class stringmethod2 {public static void main(string[] args) {string str = "i love java";int i = str.indexof('e');system.out.println(i);}}1234567
可查看在线运行效果
运行结果:
4. 字符串截取-1代码块1
字符串的截取也称为获取子串,在实际开发中经常用到,可以使用substring()方法来获取子串,string类中有两个重载的实例方法:
string substring(int beginindex) 获取从beginindex位置开始到结束的子串。
string substring(int beginindex, int endindex) 获取从beginindex位置开始到endindex位置的子串(不包含endindex位置字符)。
关于这两个方法的使用,我们来看一个实例:
实例演示
public class stringmethod3 {public static void main(string[] args) {string str = "i love java";string substring = str.substring(2);string substring1 = str.substring(2, 6);system.out.println("从索引位置2到结束的子串为:" substring);system.out.println("从索引位置2到索引位置6的子串为:" substring1);}}123456789
可查看在线运行效果
运行结果:
从索引位置2到结束的子串为:love java从索引位置2到索引位置6的子串为:love代码块12
要特别注意,方法签名上有两个参数的substring(int beginindex, int endindex)方法,截取的子串不包含endindex位置的字符。
5. 字符串切割5.1 切割为字串数组string[] split(string regex)方法可将字符串切割为子串,其参数regex是一个正则表达式分隔符,返回字符串数组。例如,我们使用空格作为分隔符来切割i love java字符串,结果将返回含有3个元素的字符串数组:
实例演示
public class stringmethod4 {public static void main(string[] args) {string str1 = "i love java";// 将字符串str1以空格分隔,并将分割结果赋值给strarr数组string[] strarr = str1.split(" ");// 遍历数组,打印每一个元素for (string str: strarr) {system.out.print(str '\t');}}}12345678910111213
可查看在线运行效果
运行结果:
i love java代码块1
注意,有几种特殊的分隔符:* ^ : | . \,要使用转义字符转义。例如:
// 以*切割string str2 = "i*love*java";string[] strarr2 = str2.split("\\*");// 以\切割string str3 = "i\\love\\java";string[] strarr4 = str3.split("\\\\");// 以|切割string str4 = "i|love|java";string[] strarr4 = str4.split("\\|");代码块1234567891011
另外,还有一个重载方法string[] split(string regex, int limit),其第二个参数limit用以控制正则匹配被应用的次数,因此会影响结果的长度,此处不再一一举例介绍。
5.2 切割为 byte 数组在实际工作中,网络上的数据传输就是使用二进制字节数据。因此字符串和字节数组之间的相互转换也很常用。
我们可以使用getbytes()方法将字符串转换为byte数组。例如:
实例演示
public class stringmethod4 {public static void main(string[] args) {string str2 = "我喜欢java";system.out.println("将字符串转换为byte数组:");// 将字符串转换为字节数组byte[] ascii = str2.getbytes();// 遍历字节数组,打印每个元素for (byte abyte : ascii) {system.out.print(abyte "\t");}}}123456789101112
可查看在线运行效果
运行结果:
将字符串转换为byte数组:-26 -120 -111 -27 -106 -100 -26 -84 -94 74 97 118 97代码块12
将字节数组转换为字符串的方法很简单,直接实例化一个字符串对象,将字节数组作为构造方法的参数即可:
6. 字符串大小写转换// 此处的ascii为上面通过字符串转换的字节数组string s = new string(ascii);代码块12
字符串的大小写转换有两个方法:
tolowercase() 将字符串转换为小写
touppercase() 将字符串转换为大写
我们来看一个实例:
实例演示
public class stringmethod5 {public static void main(string[] args) {string str = "hello world";string s = str.tolowercase();system.out.println("字符串str为转换为小写后为:" s);string s1 = s.touppercase();system.out.println("字符串s为转换为大写后为:" s1);}}123456789
可查看在线运行效果
运行结果:
字符串str为转换为小写后为:hello world字符串s为转换为大写后为:hello world代码块12
试想,如果想把字符串hello world中的大小写字母互换,该如何实现呢?
这里可以结合字符串切割方法以及字符串连接来实现:
实例演示
public class stringmethod5 {public static void main(string[] args) {string str = "hello world";// 先切割为数组string[] strarr = str.split(" ");// 将数组中元素转换大小写并连接为一个新的字符串string result = strarr[0].tolowercase() " " strarr[1].touppercase();system.out.println("字符串str的大小写互换后为:" result);}}12345678910
可查看在线运行效果
运行结果:
字符串str的大小写互换后为:hello world代码块1
当然,实现方式不止一种,你可以结合所学写出更多的方式。
7. 字符串比较string类提供了boolean equals(object object)方法来比较字符串内容是否相同,返回一个布尔类型的结果。
需要特别注意的是,在比较字符串内容是否相同时,必须使用equals()方法而不能使用==运算符。我们来看一个示例:
实例演示
public class stringmethod6 {public static void main(string[] args) {// 用两种方法创建三个内容相同的字符串string str1 = "hello";string str2 = "hello";string str3 = new string("hello");system.out.println("使用equals()方法比较str1和str2的结果为:" str1.equals(str2));system.out.println("使用==运算符比较str1和str2的结果为:" (str1 == str2));system.out.println("使用==运算符比较str1和str2的结果为:" (str1 == str2));system.out.println("使用==运算符比较str1和str3的结果为:" (str1 == str3));}}123456789101112
可查看在线运行效果
运行结果:
使用equals()方法比较str1和str2的结果为:true使用==运算符比较str1和str2的结果为:true使用equals()方法比较str1和str3的结果为:true使用==运算符比较str1和str3的结果为:false代码块1234
代码中三个字符串str1,str2和str3的内容都是hello,因此使用equals()方法对它们进行比较,其结果总是为true。
注意观察执行结果,其中使用==运算符比较str1和str2的结果为true,但使用==运算符比较的str1和str3的结果为false。这是因为==运算符比较的是两个变量的地址而不是内容。
要探究其原因,就要理解上述创建字符串的代码在计算机内存中是如何执行的。下面我们通过图解的形式来描述这三个变量是如何在内存中创建的。
当执行string str1 = "hello;"语句时,会在内存的栈空间中创建一个str1,在常量池中创建一个"hello",并将str1指向hello。
当执行string str2 = "hello";语句时,栈空间中会创建一个str2,由于其内容与str1相同,会指向常量池中的同一个对象。所以str1与str2指向的地址是相同的,这就是==运算符比较str1和str2的结果为true的原因。
当执行string str3 = new string("hello");语句时,使用了new关键字创建字符串对象,由于对象的实例化操作是在内存的堆空间进行的,此时会在栈空间创建一个str3,在堆空间实例化一个内容为hello的字符串对象,并将str3地址指向堆空间中的hello,这就是==运算符比较str1和str3的结果为false的原因。
stringbuilder上一节,我们学习了 java 的 string 类,并介绍了其常用方法。本小节我们来介绍字符串的另外一个类:stringbuilder,我们将会了解到 stringbuilder 与 string 的差异,stringbuilder 的使用场景,也会介绍与 stringbuilder 类对应的 stringbuffer 类,stringbuilder 的使用方法以及其常用方法是本小节的重点学习内容。
1. stringbuilder 概述1.1 什么是 stringbuilder与 string 相似,stringbuilder 也是一个与字符串相关的类,java 官方文档给 stringbuilder 的定义是:可变的字符序列。
1.2 为什么需要 stringbuilder在 java 字符串的学习中,我们知道了字符串具有不可变性,当频繁操作字符串时候,会在常量池中产生很多无用的数据(回忆图示)。
而 stringbuilder 与 string 不同,它具有可变性。相较 string 类不会产生大量无用数据,性能上会大大提高。
因此对于需要频繁操作字符串的场景,建议使用 stringbuilder 类来代替 string 类。
2. stringbuffer 概述2.1 定义了解了 stringbuilder 类 ,stringbuffer 也是不得不提的一个类,java 官方文档给出的定义是:线程安全的可变字符序列。
2.2 与前者的区别stringbuffer 是 stringbuilder 的前身,在早期的 java 版本中应用非常广泛,它是 stringbuilder 的线程安全版本(线程我们将在后面的小节中介绍),但实现线程安全的代价是执行效率的下降。
你可以对比 stringbuilder 和 stringbuffer 的接口文档,它们的接口基本上完全一致。为了提升我们代码的执行效率,在如今的实际开发中 stringbuffer 并不常用。因此本小节的重点在 stringbuilder 的学习。
3. stringbuilder 的常用方法3.1 构造方法stringbuilder 类提供了如下 4 个构造方法:
stringbuilder() 构造一个空字符串生成器,初始容量为 16 个字符;
stringbuilder(int catpacity) 构造一个空字符串生成器,初始容量由参数 capacity 指定;
stringbuilder(charsequence seq) 构造一个字符串生成器,该生成器包含与指定的 charsequence 相同的字符。;
stringbuilder(string str) 构造初始化为指定字符串内容的字符串生成器。
其中第 4 个构造方法最为常用,我们可以使用 stringbuilder 这样初始化一个内容为 hello 的字符串:
3.2 成员方法stringbuilder str = new stringbuilder("hello");代码块1
stringbuilder 类下面也提供了很多与 string 类相似的成员方法,以方便我们对字符串进行操作。下面我们将举例介绍一些常用的成员方法。
3.2.1 字符串连接可以使用 stringbuilder 的 stringbuilder append(string str) 方法来实现字符串的连接操作。
我们知道,string 的连接操作是通过 操作符完成连接的:
string str1 = "hello";string str2 = "world";string str3 = str1 " " str2;代码块123
如下是通过 stringbuilder 实现的字符串连接示例:
实例演示
public class connectstring1 {public static void main(string[] args) {// 初始化一个内容为 hello 的字符串生成器stringbuilder str = new stringbuilder("hello");// 调用append()方法进行字符串的连接str.append(" ");str.append("world");system.out.println(str);}}12345678910
可查看在线运行效果
运行结果:
hello world代码块1
由于 append() 方法返回的是一个 stringbuilder 类型,我们可以实现链式调用。例如,上述连续两个 append() 方法的调用语句,可以简化为一行语句:
str.append(" ").append("world");代码块1
如果你使用 ide 编写如上连接字符串的代码,可能会有下面这样的提示(intellij idea 的代码截图):
提示内容说可以将 stringbuilder 类型可以替换为 string 类型,也就是说可以将上边地代码改为:
string str = "hello" " " "world";代码块1
这样写并不会导致执行效率的下降,这是因为 java 编译器在编译和运行期间会自动将字符串连接操作转换为 stringbuilder 操作或者数组复制,间接地优化了由于 string 的不可变性引发的性能问题。
值得注意的是,append() 的重载方法有很多,可以实现各种类型的连接操作。例如我们可以连接 char 类型以及 float 类型,实例如下:
实例演示
public class connectstring2 {public static void main(string[] args) {stringbuilder str = new stringbuilder("小明的身高为");str.append(':').append(172.5f);system.out.println(str);}}1234567
可查看在线运行效果
运行结果:
小明的身高为:172.5代码块1
上面代码里连续的两个 append() 方法分别调用的是重载方法 stringbuilder append(char c) 和 stringbuilder append(float f)。
3.2.2 获取容量可以使用 int capacity() 方法来获取当前容量,容量指定是可以存储的字符数(包含已写入字符),超过此数将进行自动分配。注意,容量与长度(length)不同,长度指的是已经写入字符的长度。
例如,构造方法 stringbuilder() 构造一个空字符串生成器,初始容量为 16 个字符。我们可以获取并打印它的容量,实例如下:
实例演示
public class getcapacity {public static void main(string[] args) {// 调用stringbuilder的无参构造方法,生成一个str对象stringbuilder str = new stringbuilder();system.out.println("str的初始容量为:" str.capacity());// 循环执行连接操作for (int i = 0; i 16; i ) {str.append(i);}system.out.println("连接操作后,str的容量为" str.capacity());}}可查看在线运行效果
运行结果:
3.2.3 字符串替换str的初始容量为:16连接操作后,str的容量为34代码块12
可以使用 stringbuilder replace(int start, int end, string str) 方法,来用指定字符串替换从索引位置 start 开始到 end 索引位置结束(不包含 end)的子串。实例如下:
实例演示
public class stringreplace {public static void main(string[] args) {// 初始化一个内容为 hello 的字符串生成器stringbuilder str = new stringbuilder("hello world!");// 调用字符串替换方法,将 world 替换为 javastr.replace(6, 11, "java");// 打印替换后的字符串system.out.println(str);}}12345678910
可查看在线运行效果
运行结果:
hello java!代码块1
也可使用 stringbuilder delete(int start, int end) 方法,先来删除索引位置 start 开始到 end 索引位置(不包含 end)的子串,再使用 stringbuilder insert(int offset, string str) 方法,将字符串插入到序列的 offset 索引位置。同样可以实现字符串的替换,例如:
3.2.4 字符串截取stringbuilder str = new stringbuilder("hello world!");str.delete(6, 11);str.insert(6, "java");代码块123
可以使用 stringbuilder substring(int start) 方法来进行字符串截取,例如,我们想截取字符串的后三个字符,实例如下:
实例演示
public class stringsub {public static void main(string[] args) {stringbuilder str = new stringbuilder("你好,欢迎来到夜猫编程");string substring = str.substring(7);system.out.println("str截取后子串为:" substring);}}1234567
可查看在线运行效果
运行结果:
str截取后子串为:夜猫编程代码块1
如果我们想截取示例中的” 欢迎 “二字,可以使用重载方法 stringbuilder substring(int start, int end) 进行截取:
3.2.5 字符串反转string substring = str.substring(3, 5);代码块1
可以使用 stringbuildr reverse() 方法,对字符串进行反转操作,例如:
实例演示
public class stringreverse {public static void main(string[] args) {stringbuilder str = new stringbuilder("hello java");system.out.println("str经过反转操作后为:" str.reverse());}}123456
可查看在线运行效果
运行结果:
java scanner 类str经过反转操作后为:avaj olleh代码块1
一直以来,我们都使用system.out.println()方法向屏幕打印内容,那么如何接收输入的内容呢?本小节所学习的scanner类就可以实现对输入内容的接收。在本小节,我们将学习scanner类的定义,如何使用scanner类以及其常用方法,在学完这些基础知识后,我们会在最后学习一个比较有趣的实例程序。
1. 定义scanner是一个简单的文本扫描器,可以解析基础数据类型和字符串。
它位于java.util包下,因此如果要使用此类,必须使用import语句导入:
2. scanner 对象创建import java.util.scanner;代码块1
想要使用scanner类就要了解如何创建对象,我们可以使用如下代码创建一个扫描器对象:
scanner scanner = new scanner(system.in);代码块1
构造方法的参数system.in表示允许用户从系统中读取内容。本小节,我们的示例代码中都将使用这个构造方法。
tips:system.in是一个inputstream类型,scanner类还有很多接收其他类型的构造方法。这里不详细介绍。
3. 常用方法3.1 next()及其同伴方法想要获取用户的输入,只有对象是不行的,还要配合它的实例方法。此时配合scanner类中的next()方法及其同伴方法可以获取指定类型的输入。
3.1.1 next() 方法next()方法的返回值是字符串类型,可以使用此方法,将用户输入的内容扫描为字符串。我们来看一个示例,获取并打印用户输入的内容:
import java.util.scanner;public class scannerdemo1 {public static void main(string[] args) {// 创建扫描器对象scanner scanner = new scanner(system.in);system.out.println("请输入一段内容,输入回车结束:");// 可以将用户输入的内容扫描为字符串string str = scanner.next();// 打印输出system.out.println("您输入的内容为:" str);// 关闭扫描器scanner.close();}}代码块123456789101112131415
在代码中我们注意到,在代码块的最后调用了close()方法,这个方法用于关闭当前扫描器,就和电脑的开关机一样,使用电脑前要开机,而当用不到的时候最好关掉。
编译执行代码,屏幕将会提示:
请输入一段内容,输入回车结束:代码块1
接下来我们按照提示输入内容,然后输入回车结束输入:
3.1.2 同伴方法那什么是同伴方法呢?这里的同伴方法指的是scanner类中以next单词开头的方法。我们举例来看几个同伴方法及其作用:
nextline() :返回输入回车之前的所有字符;
nextint() :将输入内容扫描为int类型;
nextfloat() :将输入内容扫描为float类型。
这里的nextline() 方法也可以获取字符串。我们来看一下它和next()方法的差异:
next()方法只有扫描到有效字符后才会结束输入;而nextline()方法可以直接使用回车结束输入。
另外,next()方法会自动去掉空白(例如回车、空格等),也不能得到带有空格的字符串;nextline()方法可以得到空白和带有空格的字符串。
我们再来看一个示例,获取用户输入的姓名、年龄和身高,并且打印输出:
import java.util.scanner;public class scannerdemo2 {public static void main(string[] args) {// 创建扫描器对象scanner scanner = new scanner(system.in);system.out.println("请输入您的姓名:");// 将第一行输入扫描为字符串string name = scanner.nextline();system.out.println("请输入您的年龄:");// 将第二行输入扫描为int类型int age = scanner.nextint();system.out.println("请输入您的身高:");// 将第三行输入扫描为float类型float height = scanner.nextfloat();// 打印扫描器所扫描的值system.out.println("您的姓名为:" name);system.out.println("您的年龄为:" age);system.out.println("您的身高为:" height);// 关闭扫描器scanner.close();}}代码块1234567891011121314151617181920212223242526
编译执行代码,按照提示输入对应内容,直到程序完整运行:
请输入您的姓名:三井 寿请输入您的年龄:19请输入您的身高:183您的姓名为:三井 寿您的年龄为:19您的身高为:183代码块123456789
tips:上面代码中,如果使用next()方法代替nextline()方法来获取姓名字符串,是无法得到我们输入的“三井 寿”这个字符串的,这是因为next()方法不能获取带有空格的字符串。
要特别注意的是:scanner 类读到的内容,只与输入顺序有关,和终端上显示的顺序无关,因此类似于下面的这种输入,是读不到空格的,执行代码的流程如下:
3.2 hasnext()及其同伴方法hasnext()方法的返回值是一个布尔类型,如果输入中包含数据的输入,则返回true。否则返回false。通常用来做输入内容的验证。
它的同伴方法是以hasnext单词开头的方法,诸如hasnextline()、hasnextint()等方法。例如,上面的代码中,我们可以对应加入hasnext同伴方法结合条件判断语句,来提升代码的稳定性:
4. 实例int age;if (scanner.hasnextint()) {age = scanner.nextint();} else {system.out.println("不是int类型");}float height;if (scanner.hasnextfloat()) {height = scanner.nextfloat();} else {system.out.println("不是float类型");}代码块12345678910111213
前面我们已经对scanner类的基本用法有了一定的了解,下面我们来实现一个示例程序,这个程序用于估算一个人的体脂率,这里事先给出体脂率估算公式:
参数a = 腰围(cm)×0.74参数b = 体重(kg)× 0.082 44.74脂肪重量(kg)= a - b体脂率 =(脂肪重量 ÷ 体重)× 100%。代码块1234
从公式中我们可以看出,想要得到最终的体脂率,参数a(腰围)和参数 b(体重)是需要用户手动输入的,公式部分只需要使用算数运算符实现即可。下面是程序代码:
import java.util.scanner;public class getbodyfat {public static void main(string[] args) {// 初始化腰围float waistline = 0f;// 初始化体重float weight = 0f;// 声明浮点型参数a,b,bodyfatweight(脂肪重量)float a, b, bodyfatweight;scanner scanner = new scanner(system.in);system.out.println("请输入您的腰围(cm):");if (scanner.hasnextfloat()) {waistline = scanner.nextfloat();}system.out.println("请输入您的体重(kg):");if (scanner.hasnextfloat()) {weight = scanner.nextfloat();}// 计算参数a 公式:参数a = 腰围(cm)× 0.74a = waistline * 0.74f;// 计算参数b 公式:参数b = 体重(kg)× 0.082 44.74b = weight * 0.082f 44.74f;// 计算脂肪重量bodyfatweight = a - b;// 计算体脂率 =(脂肪重量 ÷ 体重)×100%。float result = bodyfatweight / weight * 100;system.out.println("您的体脂率为" result "%");}}代码块123456789101112131415161718192021222324252627282930
编译运行代码,按照提示输入,将估算出你的体脂含量:
请输入您的腰围(cm):70请输入您的体重(kg):50您的体脂率为5.919998%代码块12345
执行代码的流程如下:
java 异常处理java 的异常处理是 java 语言的一大重要特性,也是提高代码健壮性的最强大方法之一。当我们编写了错误的代码时,编译器在编译期间可能会抛出异常,有时候即使编译正常,在运行代码的时候也可能会抛出异常。本小节我们将介绍什么是异常、java 中异常类的架构、如何进行异常处理、如何自定义异常、什么是异常链、如何使用异常链等内容。
1. 什么是异常异常就是程序上的错误,我们在编写程序的时候经常会产生错误,这些错误划分为编译期间的错误和运行期间的错误。
下面我们来看几个常见的异常案例。
如果语句漏写分号,程序在编译期间就会抛出异常,实例如下:
public class hello {public static void main(string[] args) {system.out.println("hello world!")}}代码块12345
运行结果:
$$ javac hello.javahello.java:3: 错误: 需要';'system.out.println("hello world!")^1 个错误代码块12345
运行过程:
由于代码的第 3 行语句漏写了分号,java 编译器给出了明确的提示。
static 关键字写成了 statci,实例如下:
hello.java:2: 错误: 需要 标识符public statci void main(string[] args) {^1 个错误代码块1234
当数组下标越界,程序在编译阶段不会发生错误,但在运行时会抛出异常。实例如下:
public class arrayoutofindex {public static void main(string[] args) {int[] arr = {1, 2, 3};system.out.println(arr[3]);}}代码块123456
运行结果:
exception in thread "main" java.lang.arrayindexoutofboundsexception: index 3 out of bounds for length 3at arrayoutofindex.main(arrayoutofindex.java:4)代码块12
运行过程:
2. java 异常类架构在 java 中,通过 throwable 及其子类来描述各种不同类型的异常。如下是 java 异常类的架构图(不是全部,只展示部分类):
2.1 throwable 类throwable 位于 java.lang 包下,它是 java 语言中所有错误(error)和异常(exception)的父类。
throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printstacktrace() 等接口用于获取堆栈跟踪数据等信息。
主要方法:
fillinstacktrace: 用当前的调用栈层次填充 throwable 对象栈层次,添加到栈层次任何先前信息中;
getmessage:返回关于发生的异常的详细信息。这个消息在 throwable 类的构造函数中初始化了;
getcause:返回一个 throwable 对象代表异常原因;
getstacktrace:返回一个包含堆栈层次的数组。下标为 0 的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底;
printstacktrace:打印 tostring() 结果和栈层次到 system.err,即错误输出流。
2.2 error 类error 是 throwable 的一个直接子类,它可以指示合理的应用程序不应该尝试捕获的严重问题。这些错误在应用程序的控制和处理能力之外,编译器不会检查 error,对于设计合理的应用程序来说,即使发生了错误,本质上也无法通过异常处理来解决其所引起的异常状况。
常见 error:
assertionerror:断言错误;
virtualmachineerror:虚拟机错误;
unsupportedclassversionerror:java 类版本错误;
outofmemoryerror :内存溢出错误。
2.3 exception 类exception 是 throwable 的一个直接子类。它指示合理的应用程序可能希望捕获的条件。
exception 又包括 unchecked exception(非检查异常)和 checked exception(检查异常)两大类别。
2.3.1 unchecked exception (非检查异常)unchecked exception 是编译器不要求强制处理的异常,包含 runtimeexception 以及它的相关子类。我们编写代码时即使不去处理此类异常,程序还是会编译通过。
常见非检查异常:
nullpointerexception:空指针异常;
arithmeticexception:算数异常;
arrayindexoutofboundsexception:数组下标越界异常;
classcastexception:类型转换异常。
2.3.2 checked exception(检查异常)checked exception 是编译器要求必须处理的异常,除了 runtimeexception 以及它的子类,都是 checked exception 异常。我们在程序编写时就必须处理此类异常,否则程序无法编译通过。
常见检查异常:
ioexception:io 异常
sqlexception:sql 异常
3. 如何进行异常处理在 java 语言中,异常处理机制可以分为两部分:
抛出异常:当一个方法发生错误时,会创建一个异常对象,并交给运行时系统处理;
捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器。
java 通过 5 个关键字来实现异常处理,分别是:throw、throws、try、catch、finally。
异常总是先抛出,后捕获的。下面我们将围绕着 5 个关键字来详细讲解如何抛出异常以及如何捕获异常。
4. 抛出异常4.1 实例我们先来看一个除零异常的实例代码:
public class exceptiondemo1 {// 打印 a / b 的结果public static void divide(int a, int b) {system.out.println(a / b);}public static void main(string[] args) {// 调用 divide() 方法divide(2, 0);}}代码块1234567891011
运行结果:
exception in thread "main" java.lang.arithmeticexception: / by zeroat exceptiondemo1.divide(exceptiondemo1.java:4)at exceptiondemo1.main(exceptiondemo1.java:9)代码块123
运行过程:
我们知道 0 是不能用作除数的,由于 divide() 方法中除数 b 为 0,所以代码将停止执行并显示了相关的异常信息,此信息为堆栈跟踪,上面的运行结果告诉我们:main 线程发生了类型为 arithmeticexception 的异常,显示消息为 by zero,并且提示了可能发生异常的方法和行号。
4.2 throw上面的实例中,程序在运行时引发了错误,那么如何来显示抛出(创建)异常呢?
我们可以使用 throw 关键字来抛出异常,throw 关键字后面跟异常对象,改写上面的实例代码:
public class exceptiondemo2 {// 打印 a / b 的结果public static void divide(int a, int b) {if (b == 0) {// 抛出异常throw new arithmeticexception("除数不能为零");}system.out.println(a / b);}public static void main(string[] args) {// 调用 divide() 方法divide(2, 0);}}代码块123456789101112131415
运行结果:
exception in thread "main" java.lang.arithmeticexception: 除数不能为零at exceptiondemo2.divide(exceptiondemo2.java:5)at exceptiondemo2.main(exceptiondemo2.java:12)代码块123
运行过程:
代码在运行时同样引发了错误,但显示消息为 “除数不能为零”。我们看到 divide() 方法中加入了条件判断,如果调用者将参数 b 设置为 0 时,会使用 throw 关键字来抛出异常,throw 后面跟了一个使用 new 关键字实例化的算数异常对象,并且将消息字符串作为参数传递给了算数异常的构造函数。
我们可以使用 throw 关键字抛出任何类型的 throwable 对象,它会中断方法,throw 语句之后的所有内容都不会执行。除非已经处理抛出的异常。异常对象不是从方法中返回的,而是从方法中抛出的。
4.3 throws可以通过 throws 关键字声明方法要抛出何种类型的异常。如果一个方法可能会出现异常,但是没有能力处理这种异常,可以在方法声明处使用 throws 关键字来声明要抛出的异常。例如,汽车在运行时可能会出现故障,汽车本身没办法处理这个故障,那就让开车的人来处理。
throws 用在方法定义时声明该方法要抛出的异常类型,如下是伪代码:
public void demomethod() throws exception1, exception2, ... exceptionn {// 可能产生异常的代码}代码块123
throws 后面跟的异常类型列表可以有一个也可以有多个,多个则以 , 分割。当方法产生异常列表中的异常时,将把异常抛向方法的调用方,由调用方处理。
throws 有如下使用规则:
如果方法中全部是非检查异常(即 error、runtimeexception 以及的子类),那么可以不使用 throws 关键字来声明要抛出的异常,编译器能够通过编译,但在运行时会被系统抛出;
如果方法中可能出现检查异常,就必须使用 throws 声明将其抛出或使用 try catch 捕获异常,否则将导致编译错误;
当一个方法抛出了异常,那么该方法的调用者必须处理或者重新抛出该异常;
当子类重写父类抛出异常的方法时,声明的异常必须是父类所声明异常的同类或子类。
5. 捕获异常使用 try 和 catch 关键字可以捕获异常。try catch 代码块放在异常可能发生的地方。它的语法如下:
try {// 可能会发生异常的代码块} catch (exception e1) {// 捕获并处理try抛出的异常类型exception} catch (exception2 e2) {// 捕获并处理try抛出的异常类型exception2} finally {// 无论是否发生异常,都将执行的代码块}代码块123456789
我们来看一下上面语法中的 3 种语句块:
try 语句块:用于监听异常,当发生异常时,异常就会被抛出;
catch 语句块:catch 语句包含要捕获的异常类型的声明,当 try 语句块发生异常时,catch 语句块就会被检查。当 catch 块尝试捕获异常时,是按照 catch 块的声明顺序从上往下寻找的,一旦匹配,就不会再向下执行。因此,如果同一个 try 块下的多个 catch 异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面;
finally 语句块:无论是否发生异常,都会执行 finally 语句块。finally 常用于这样的场景:由于 finally 语句块总是会被执行,所以那些在 try 代码块中打开的,并且必须回收的物理资源(如数据库连接、网络连接和文件),一般会放在 finally 语句块中释放资源。
try 语句块后可以接零个或多个 catch 语句块,如果没有 catch 块,则必须跟一个 finally 语句块。简单来说,try 不允许单独使用,必须和 catch 或 finally 组合使用,catch 和 finally 也不能单独使用。
实例如下:
public class exceptiondemo3 {// 打印 a / b 的结果public static void divide(int a, int b) {system.out.println(a / b);}public static void main(string[] args) {try {// try 语句块// 调用 divide() 方法divide(2, 0);} catch (arithmeticexception e) {// catch 语句块system.out.println("catch: 发生了算数异常:" e);} finally {// finally 语句块system.out.println("finally: 无论是否发生异常,都会执行");}}}代码块1234567891011121314151617181920
运行结果:
catch: 发生了算数异常:java.lang.arithmeticexception: / by zerofinally: 无论是否发生异常,都会执行代码块12
运行过程:
divide() 方法中除数 b 为 0,会发生除零异常,我们在方法调用处使用了 try 语句块对异常进行捕获;如果捕获到了异常, catch 语句块会对 arithmeticexception 类型的异常进行处理,此处打印了一行自定义的提示语句;最后的 finally 语句块,无论发生异常与否,总会执行。
java 7 以后,catch 多种异常时,也可以像下面这样简化代码:
6. 自定义异常try {// 可能会发生异常的代码块} catch (exception | exception2 e) {// 捕获并处理try抛出的异常类型} finally {// 无论是否发生异常,都将执行的代码块}代码块1234567
自定义异常,就是定义一个类,去继承 throwable 类或者它的子类。
java 内置了丰富的异常类,通常使用这些内置异常类,就可以描述我们在编码时出现的大部分异常情况。一旦内置异常无法满足我们的业务要求,就可以通过自定义异常描述特定业务产生的异常类型。
实例:
public class exceptiondemo4 {static class mycustomexception extends runtimeexception {/*** 无参构造方法*/public mycustomexception() {super("我的自定义异常");}}public static void main(string[] args) {// 直接抛出异常throw new mycustomexception();}}代码块12345678910111213141516
运行结果:
exception in thread "main" exceptiondemo4$$mycustomexception: 我的自定义异常at exceptiondemo4.main(exceptiondemo4.java:13)代码块12
运行过程:
在代码中写了一个自定义异常 mycustomexception,继承自 runtimeexception,它是一个静态内部类,这样在主方法中就可以直接抛出这个异常类了。当然,也可以使用 catch 来捕获此类型异常。
7. 异常链异常链是以一个异常对象为参数构造新的异常对象,新的异常对象将包含先前异常的信息。简单来说,就是将异常信息从底层传递给上层,逐层抛出,我们来看一个实例:
public class exceptiondemo5 {/*** 第一个自定义的静态内部异常类*/static class firstcustomexception extends exception {// 无参构造方法public firstcustomexception() {super("第一个异常");}}/*** 第二个自定义的静态内部异常类*/static class secondcustomexception extends exception {public secondcustomexception() {super("第二个异常");}}/*** 第三个自定义的静态内部异常类*/static class thirdcustomexception extends exception {public thirdcustomexception() {super("第三个异常");}}/*** 测试异常链静态方法1,直接抛出第一个自定义的静态内部异常类* @throws firstcustomexception*/public static void f1() throws firstcustomexception {throw new firstcustomexception();}/*** 测试异常链静态方法2,调用f1()方法,并抛出第二个自定义的静态内部异常类* @throws secondcustomexception*/public static void f2() throws secondcustomexception {try {f1();} catch (firstcustomexception e) {throw new secondcustomexception();}}/*** 测试异常链静态方法3,调用f2()方法, 并抛出第三个自定义的静态内部异常类* @throws thirdcustomexception*/public static void f3() throws thirdcustomexception {try {f2();} catch (secondcustomexception e) {throw new thirdcustomexception();}}public static void main(string[] args) throws thirdcustomexception {// 调用静态方法f3()f3();}}代码块12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
运行结果:
exception in thread "main" exceptiondemo5$$thirdcustomexception: 第三个异常at exceptiondemo5.f3(exceptiondemo5.java:46)at exceptiondemo5.main(exceptiondemo5.java:51)代码块123
运行过程:
通过运行结果,我们只获取到了静态方法 f3() 所抛出的异常堆栈信息,前面代码所抛出的异常并没有被显示。
我们改写上面的代码,让异常信息以链条的方式 “连接” 起来。可以通过改写自定义异常的构造方法,来获取到之前异常的信息。实例如下:
/*** @author colorful@talelin*/public class exceptiondemo6 {/*** 第一个自定义的静态内部异常类*/static class firstcustomexception extends exception {// 无参构造方法public firstcustomexception() {super("第一个异常");}}/*** 第二个自定义的静态内部异常类*/static class secondcustomexception extends exception {/*** 通过构造方法获取之前异常的信息* @param cause 捕获到的异常对象*/public secondcustomexception(throwable cause) {super("第二个异常", cause);}}/*** 第三个自定义的静态内部异常类*/static class thirdcustomexception extends exception {/*** 通过构造方法获取之前异常的信息* @param ca(户籍所在地怎么填写?户籍所在地是指我国居民户口簿登记所在地,一般是指出生时其父母户口登记地方。按照户口登记管理条例,公民填写户籍所在地,应该填写到户籍管理机关所在地,即城市户口的应该填**省**市(县)**区;农村户口的应该填**省**县**乡。一般在填写户籍所在地时,只填写到县就可以了。)use 捕获到的异常对象*/public thirdcustomexception(throwable cause) {super("第三个异常", cause);}}/*** 测试异常链静态方法1,直接抛出第一个自定义的静态内部异常类* @throws firstcustomexception*/public static void f1() throws firstcustomexception {throw new firstcustomexception();}/*** 测试异常链静态方法2,调用f1()方法,并抛出第二个自定义的静态内部异常类* @throws secondcustomexception*/public static void f2() throws secondcustomexception {try {f1();} catch (firstcustomexception e) {throw new secondcustomexception(e);}}/*** 测试异常链静态方法3,调用f2()方法, 并抛出第三个自定义的静态内部异常类* @throws thirdcustomexception*/public static void f3() throws thirdcustomexception {try {f2();} catch (secondcustomexception e) {throw new thirdcustomexception(e);}}public static void main(string[] args) throws thirdcustomexception {// 调用静态方法f3()f3();}}代码块12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
运行结果:
exception in thread "main" exceptiondemo6$$thirdcustomexception: 第三个异常at exceptiondemo6.f3(exceptiondemo6.java:74)at exceptiondemo6.main(exceptiondemo6.java:80)caused by: exceptiondemo6$$secondcustomexception: 第二个异常at exceptiondemo6.f2(exceptiondemo6.java:62)at exceptiondemo6.f3(exceptiondemo6.java:72)... 1 morecaused by: exceptiondemo6$$firstcustomexception: 第一个异常at exceptiondemo6.f1(exceptiondemo6.java:51)at exceptiondemo6.f2(exceptiondemo6.java:60)... 2 more代码块1234567891011
运行过程:
通过运行结果,我们看到,异常发生的整个过程都打印到了屏幕上,这就是一个异常链。
1、通过本小节的学习,我们知道了异常就是程序上的错误,良好的异常处理可以提高代码的健壮性。java 语言中所有错误(error)和异常(exception)的父类都是 throwable。error 和 exception 是 throwable 的直接子类,我们通常说的异常处理实际上就是处理 exception 及其子类,异常又分为检查型异常和非检查型异常。通过抛出异常和捕获异常来实现异常处理。我们亦可以通过继承 throwable 类或者它的子类来自定义异常类。通过构造方法获取之前异常的信息可以实现异常链。
2、本小节我们学习了 java 的 scanner类,它是位于java.util包下的一个工具类,我们知道了它是一个简单的文本扫描器,可以解析基础数据类型和字符串。我们也学会了如何使用scanner类来获取用户的输入,next()方法和nextline()方法都可以扫描用户输入的字符串,要注意这两个方法的区别。我们也在最后给出了一个计算体脂率的示例代码,学习了scanner类,你就可以实现比较有意思的一些小程序了。如果你想了解更多有关scanner类的接口,也可翻阅官方文档。
3、本小节我们介绍了 java 的 stringbuilder 类,它具有可变性,对于频繁操作字符串的场景,使用它来代替 string 类可以提高程序的执行效率;也知道了 stringbuffer 是 stringbuilder 的线程安全版本,官方更推荐使用 stringbuilder;最后我们介绍了 stringbuilder 的常用构造方法和成员方法,如果你想了解更多关于 stringbuilder 的接口,可以翻阅官方文档进行学习。
4、本小节我们介绍了 java string类的常用方法:
使用length()方法可以获取字符串长度;
使用charat()、indexof()以及lastindexof()方法可以对字符串进行查找;
substring()方法可以对字符串的进行截取,split()、getbytes()方法可以将字符串切割为数组;
tolowercase()和touppercase()方法分别用于大小写转换,使用equals()方法对字符串进行比较,这里要注意,对字符串内容进行比较时,永远都不要使用==运算符。
这些方法大多有重载方法,实际工作中,要根据合适的场景选用对应的重载方法。
当然,本小节还有很多未介绍到的方法,使用到可以翻阅w66.com官网文档来进行学习。
本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的w66.com的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。