异常和String类

异常

1 初识异常

除以 0

System.out.println(10 / 0);
// 执行结果
Exception in thread "main" java.lang.ArithmeticException: / by zero

数组下标越界

int[] arr = {1, 2, 3};
System.out.println(arr[100]);
// 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100

所谓异常指的就是程序在 运行时 出现错误时通知调用者的一种机制.

关键字 "运行时"有些错误是这样的, 例如将 System.out.println 拼写错了, 写成了 system.out.println. 此时编译过程中就会出错, 这是 “编译期” 出错.而运行时指的是程序已经编译通过得到 class 文件了, 再由 JVM 执行过程中出现的错误

2 捕获异常

try{
	有可能出现异常的语句 ;
}[catch (异常类型 异常对象) {
} ... ]
[finally {
	异常的出口
}]

try 代码块中放的是可能出现异常的代码.
catch 代码块中放的是出现异常后的处理行为.
finally 代码块中的代码用于处理善后工作, 会在最后执行.
其中 catch 和 finally 都可以根据情况选择加或者不加
使用 try catch 后的程序执行过程

int[] arr = {1, 2, 3};
try {
	System.out.println("before");
	System.out.println(arr[100]);
	System.out.println("after");
} catch (ArrayIndexOutOfBoundsException e) {
// 打印出现异常的调用栈
	e.printStackTrace();
}
System.out.println("after try catch");
// 执行结果
before
java.lang.ArrayIndexOutOfBoundsException: 100
at demo02.Test.main(Test.java:10)
after try catch

关于 “调用栈”
方法之间是存在相互调用关系的, 这种调用关系我们可以用 “调用栈” 来描述. 在 JVM 中有一块内存空间称为 “虚
拟机栈” 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的
方式查看出现异常代码的调用栈

catch 可以有多个

int[] arr = {1, 2, 3};
try {
	System.out.println("before");
	arr = null;
	System.out.println(arr[100]);
	System.out.println("after");
} catch (ArrayIndexOutOfBoundsException e) {
	System.out.println("这是个数组下标越界异常");
	e.printStackTrace();
} catch (NullPointerException e) {
	System.out.println("这是个空指针异常");
	e.printStackTrace();
}
System.out.println("after try catch");
// 执行结果
before
这是个空指针异常
java.lang.NullPointerException
at demo02.Test.main(Test.java:12)
after try catch

也可以用一个 catch 捕获所有异常(不推荐)

int[] arr = {1, 2, 3};
try {
	System.out.println("before");
	arr = null;
	System.out.println(arr[100]);
	System.out.println("after");
} catch (Exception e) {
	e.printStackTrace();
}
System.out.println("after try catch");
// 执行结果
before
java.lang.NullPointerException
at demo02.Test.main(Test.java:12)
after try catch

由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常.
备注: catch 进行类型匹配的时候, 不光会匹配相同类型的异常对象, 也会捕捉目标异常类型的子类对象.
如刚才的代码, NullPointerException 和 ArrayIndexOutOfBoundsException 都是 Exception 的子类, 因此都能被捕获到.

finally 表示最后的善后工作, 例如释放资源

int[] arr = {1, 2, 3};
try {
	System.out.println("before");
	arr = null;
	System.out.println(arr[100]);
	System.out.println("after");
} catch (Exception e) {
	e.printStackTrace();
} finally {
	System.out.println("finally code");
}
// 执行结果
before
java.lang.NullPointerException
at demo02.Test.main(Test.java:12)
finally code

无论是否存在异常, finally 中的代码一定都会执行到. 保证最终一定会执行到 Scanner 的 close 方法
异常处理流程
程序先执行 try 中的代码
如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
如果找到匹配的异常类型, 就会执行 catch 中的代码
如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
如果上层调用者也没有处理的了异常, 就继续向上传递.
一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止

3抛出异常

除了 Java 内置的类会抛出一些异常之外, 程序猿也可以手动抛出某个异常. 使用 throw 关键字完成这个操作

public static void main(String[] args) {
	System.out.println(divide(10, 0));
}
public static int divide(int x, int y) {
	if (y == 0) {
	throw new ArithmeticException("抛出除 0 异常");
}
	return x / y;
}
// 执行结果
Exception in thread "main" java.lang.ArithmeticException: 抛出除 0 异常
at demo02.Test.divide(Test.java:14)
at demo02.Test.main(Test.java:9)

关于 finally 的注意事项

public static void main(String[] args) {
	System.out.println(func());
}
public static int func() {
	try {
		return 10;
	} finally {
		return 20;
	}
}
// 执行结果
20

finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally). 但是如果
finally 中也存在 return 语句, 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return.
一般我们不建议在 finally 中写 return (被编译器当做一个警告

String类

1. 创建字符串

常见的构造 String 的方式

// 方式一
String str = "Hello Bit";
// 方式二
String str2 = new String("Hello Bit");
// 方式三
char[] array = {'a', 'b', 'c'};
String str3 = new String(array);

“hello” 这样的字符串字面值常量, 类型也是 String.
String 也是引用类型. String str = “Hello”; 这样的代码内存布局如下
在这里插入图片描述
引用类似于 C 语言中的指针, 只是在栈上开辟了一小块内存空间保存一个地址. 但是引用和指针又不太相同, 指
针能进行各种数字运算(指针+1)之类的, 但是引用不能, 这是一种 “没那么灵活” 的指针.
另外, 也可以把引用想象成一个标签, “贴” 到一个对象上. 一个对象可以贴一个标签, 也可以贴多个. 如果一个对
象上面一个标签都没有, 那么这个对象就会被 JVM 当做垃圾对象回收掉

2. 字符串比较相等

String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);
// 执行结果
true
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2);
// 执行结果
false

两种创建 String 方式的差异
代码1内存布局
在这里插入图片描述
我们发现, str1 和 str2 是指向同一个对象的. 此时如 “Hello” 这样的字符串常量是在 字符串常量池中

关于字符串常量池
如 “Hello” 这样的字符串字面值常量, 也是需要一定的内存空间来存储的. 这样的常量具有一个特点, 就是不需要
修改(常量嘛). 所以如果代码中有多个地方引用都需要使用 “Hello” 的话, 就直接引用到常量池的这个位置就行
了, 而没必要把 “Hello” 在内存中存储两次
代码2内存布局
在这里插入图片描述
通过 String str1 = new String(“Hello”); 这样的方式创建的 String 对象相当于再堆上另外开辟了空间来存储
“Hello” 的内容, 也就是内存中存在两份 “Hello”
String 使用 == 比较并不是在比较字符串内容, 而是比较两个引用是否是指向同一个对象(即地址)
Java 中要想比较字符串的内容, 必须采用String类提供的equals方法

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
// System.out.println(str2.equals(str1)); // 或者这样写也行
// 执行结果
true

我们推荐使用 “方式二”. 一旦 str 是 null, 方式一的代码会抛出异常, 而方式二不会

String str = null;
// 方式一
System.out.println(str.equals("Hello")); // 执行结果 抛出 java.lang.NullPointerException 异// 方式二
System.out.println("Hello".equals(str)); // 执行结果 false

3. 字符串常量池

a) 直接赋值

String str1 = "hello" ;
String str2 = "hello" ;
String str3 = "hello" ;
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // true
System.out.println(str2 == str3); // true

在这里插入图片描述
String类的设计使用了共享设计模式
在JVM底层实际上会自动维护一个对象池(字符串常量池)
如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存
到这个对象池之中.
如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用
如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用

b) 采用构造方法
类对象使用构造方法实例化是标准做法。

String str = new String("hello") ;

在这里插入图片描述
这样的做法有两个缺点:

  1. 如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 “hello” 也
    是一个匿名对象, 用了一次之后就不再使用了, 就成为垃圾空间, 会被 JVM 自动回收掉).
  2. 字符串共享问题. 同一个字符串可能会被存储多次, 比较浪费空间

我们可以使用 String 的 intern 方法来手动把 String 对象加入到字符串常量池中

// 该字符串常量并没有保存在对象池之中
String str1 = new String("hello") ;
String str2 = "hello" ;
System.out.println(str1 == str2);
// 执行结果
false
String str1 = new String("hello").intern() ;
String str2 = "hello" ;
System.out.println(str1 == str2);
// 执行结果
true

4. 理解字符串不可变

String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str);
// 执行结果
hello world!!!

形如 += 这样的操作, 表面上好像是修改了字符串, 其实不是. 内存变化如下
在这里插入图片描述
+= 之后 str 打印的结果却是变了, 但是不是 String 对象本身发生改变, 而是 str 引用到了其他的对象

5. 字符, 字节与字符串

5.1 字符与字符串
字符串内部包含一个字符数组,String 可以和 char[] 相互转换
在这里插入图片描述

5.2 字节与字符串
字节常用于数据传输以及编码转换的处理之中,String 也能方便的和 byte[] 相互转换
在这里插入图片描述

6. 字符串常见操作

6.1 字符串比较
上面使用过String类提供的equals()方法,该方法本身是可以进行区分大小写的相等判断。除了这个方法之外,String
类还提供有如下的比较操作:
在这里插入图片描述
6.2 字符串查找
从一个完整的字符串之中可以判断指定内容是否存在,对于查找方法有如下定义
在这里插入图片描述

6.3 字符串替换
使用一个指定的新的字符串替换掉已有的字符串数据,可用的方法如下
在这里插入图片描述
6.4 字符串拆分
可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串。
在这里插入图片描述
6.5 字符串截取
在这里插入图片描述
6.6 其他操作方法
在这里插入图片描述

7. StringBuffer 和 StringBuilder

任何的字符串常量都是String对象,而且String的常量一旦声明不可改变,如果改变对象内容,改变的是其引用的指向而已。
通常来讲String的操作比较简单,但是由于String的不可更改特性,为了方便字符串的修改,提供StringBuffer和
StringBuilder类。
在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer类中需要更改为append()方法

public synchronized StringBuffer append(各种数据类型 b)

String和StringBuffer最大的区别在于:String的内容无法修改,而StringBuffer的内容可以修改。频繁修改字符串的情况考虑使用StingBuffer。
String类
public final class String implements
java.io.Serializable, Comparable,
CharSequence
StringBuffer类
public final class StringBuffer extends
AbstractStringBuilder implements java.io.Serializable,
CharSequence

可以发现两个类都是"CharSequence"接口的子类。这个接口描述的是一系列的字符集。所以字符串是字符集的子
类,如果以后看见CharSequence,最简单的联想就是字符串。
注意:String和StringBuffer类不能直接转换。如果要想互相转换,可以采用如下原则:
String变为StringBuffer:利用StringBuffer的构造方法或append()方法
StringBuffer变为String:调用toString()方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值