5、java常用工具类(上)-利来w66国际

java string类

在前面的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对象str
    string 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

可查看在线运行效果

运行结果:

索引位置为7的字符为:j
代码块1
3.2 查找字符串位置

这里介绍查找字符串位置的两个方法:

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

可查看在线运行效果

运行结果:

-1
代码块1
4. 字符串截取

字符串的截取也称为获取子串,在实际开发中经常用到,可以使用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

将字节数组转换为字符串的方法很简单,直接实例化一个字符串对象,将字节数组作为构造方法的参数即可:

// 此处的ascii为上面通过字符串转换的字节数组
string s = new string(ascii);
代码块12
6. 字符串大小写转换

字符串的大小写转换有两个方法:

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 的字符串:

stringbuilder str = new stringbuilder("hello");
代码块1
3.2 成员方法

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());
  }
}
 可查看在线运行效果

运行结果:

str的初始容量为:16
连接操作后,str的容量为34
代码块12
3.2.3 字符串替换

可以使用 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 替换为 java
    str.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 索引位置。同样可以实现字符串的替换,例如:

stringbuilder str = new stringbuilder("hello world!");
str.delete(6, 11);
str.insert(6, "java");
代码块123
3.2.4 字符串截取

可以使用 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) 进行截取:

string substring = str.substring(3, 5);
代码块1
3.2.5 字符串反转

可以使用 stringbuildr reverse() 方法,对字符串进行反转操作,例如:

实例演示

public class stringreverse {
  public static void main(string[] args) {
    stringbuilder str = new stringbuilder("hello java");
    system.out.println("str经过反转操作后为:"   str.reverse());
  }
}
123456

可查看在线运行效果

运行结果:

str经过反转操作后为:avaj olleh
代码块1
java scanner 类

一直以来,我们都使用system.out.println()方法向屏幕打印内容,那么如何接收输入的内容呢?本小节所学习的scanner类就可以实现对输入内容的接收。在本小节,我们将学习scanner类的定义,如何使用scanner类以及其常用方法,在学完这些基础知识后,我们会在最后学习一个比较有趣的实例程序。

1. 定义

scanner是一个简单的文本扫描器,可以解析基础数据类型和字符串。

它位于java.util包下,因此如果要使用此类,必须使用import语句导入:

import java.util.scanner;
代码块1
2. scanner 对象创建

想要使用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同伴方法结合条件判断语句,来提升代码的稳定性:

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
4. 实例

前面我们已经对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.74
    a = waistline * 0.74f;
    // 计算参数b 公式:参数b = 体重(kg)× 0.082   44.74
    b = 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.java
hello.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 3
 at 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 zero
 at 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 zero
finally: 无论是否发生异常,都会执行
代码块12

运行过程:

divide() 方法中除数 b 为 0,会发生除零异常,我们在方法调用处使用了 try 语句块对异常进行捕获;如果捕获到了异常, catch 语句块会对 arithmeticexception 类型的异常进行处理,此处打印了一行自定义的提示语句;最后的 finally 语句块,无论发生异常与否,总会执行。

java 7 以后,catch 多种异常时,也可以像下面这样简化代码:

try {
  // 可能会发生异常的代码块
} catch (exception | exception2 e) {
  // 捕获并处理try抛出的异常类型
} finally {
  // 无论是否发生异常,都将执行的代码块
}
代码块1234567
6. 自定义异常

自定义异常,就是定义一个类,去继承 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 more
caused 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的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
网站地图