Java从入门到精通!第七天(异常处理和String类)

发布于:2025-07-19 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、异常

1. 在使用计算机语言开发项目的过程中,即使程序员把代码写的尽善尽美,在系统运行的时候可能出现问题,因为有些问题不能靠代码来解决,比如客户端输入错误数据,读取的文件不存在,网络突然断开等等。

2. 异常:

在 Java 语言中,将程序执行过程中发生的不正常情况称为异常。(区别于编译错误,编译错误不解决程序都无法运行)

3. Java 程序将其在执行过程中发生的异常分为两类:

(1) Error(错误):

Java 虚拟机无法解决的严重问题,比如 JVM 系统内部错误,资源耗尽,比如 StackOverFlowError(栈溢出),对于这种错误,一般不针对性处理。

(2) Exception(异常):

因编程错误或外在因素导致的问题,可以使用针对性的代码进行处理,比如: 空指针异常

视图读取不存在的文件

网络连接中断

数组下标越界等等

对于这种异常,一般有两种解决方案,一种是遇到之后立即终止程序运行,还有一种实在编写代码的时候,就考虑可能发生的异常,然后编写异常检测程序进行提示。

捕获异常最好的时机是编译期间,但是有些异常是在程序运行时才发生,那么我们需要对这些运行时已异常进行捕获处理。

4. 异常(Exception)分类

(1) 运行时异常:

是指编译器不要求处理的异常,一般是指编程时的逻辑错误,是程序员应该积极避免出现的异常,是 java.lang.RuntimeException 及其子类都是运行时异常。

(2) 编译时异常:

是指编译器要求必须处理的异常,编译器要求 Java 程序必须捕获或声明所有的编译时异常,编译才能通过。

5. 常见的异常 Exception

(1) 运行时异常:不用在代码中显式的捕获

java.lang.RuntimeException  运行时异常

ClassCastException  类型转换异常

ArrayIndexOutofBoundsException  数组越界异常

NullPointException  空指针异常

ArithmeticException  数学运算异常

NumberFormatException  数字格式异常

InputMismatchException  输入错误异常

...

(2) 编译时异常:必须在代码中捕获,否则编译都不能通过

java.io.IOException  输入输出异常

FileNotFoundException  文件找不到异常

EOFException  读到文件末尾异常

ClassNotFoundException  找不到对应的类的异常

InterruptedException  中断异常

SQLException  SQL语句的异常

...

6. 异常的继承层次图

示例1:运行时异常

示例2:运行时异常

示例3:编译时异常,必须捕获,否则连编译都不能通过

7. Java 中的异常处理

(1) Java 中的异常处理机制,是将异常处理程序代码集中在一起,与正常的代码分开,使程序简洁、优雅,并易于维护。

(2) Java 中处理异常有两种方法

1) 通过 try...catch...finally 语句捕获异常

2) 通过“throws + 异常类型” 来抛出异常

(3) Java 异常处理机制

1) Java 提供的异常处理机制是抓抛模型(抓是指 try...catch...finally,抛是指 throws)

2) Java 程序运行时如果发生异常,JVM 会生成一个异常对象,由 Java 虚拟机抛出异常。--- 自动抛出

3) 当然程序员也可以手动抛出异常,程序员自己创建异常对象并将其抛出。

4) 异常抛出的机制:向上抛出

(4) 处理异常的方式一:

通过 try...catch...finally 处理异常,语法:

  try{
      //可能发生异常的代码
  }catch(异常类型1 e){
      //处理异常1
  }catch(异常类型2 e){
      //处理异常2
  }
  ...
  finally{
      //无论是否发生异常,都会执行finally语句块,一般用于释放已经打开的资源
  }

注意:如果明确知道catch语句后的异常类,那么可以使用其父类作为catch参数,例如,所有的异常类都继承于Exception,那么将Exception作为catch的参数就能够捕获到所有的异常。但是这样的话会搞不清楚具体的异常类型是什么类型,所以不建议这样来使用,而是需要在catch中指定具体的异常类型来捕获。

try...catch...finally的执行过程图:

有异常: 无异常:

示例:处理异常

示例2:捕获多个异常

(5) 处理异常的方式二:throws 声明可能发生异常

1) 语法:throws 异常类型1,异常类型2,...

如果一个方法可能发生异常,但是该方法本身不能去处理这种异常,那么可以在方法末尾声明抛出异常,表明该方法只是声明抛出了异常,并不会处理异常,异常的处理交给方法的调用者来负责,在方法声明中用 throws 语句可以声明抛出异常的列表,throws 后面可以跟上抛出异常的类型,也可以是异常类型的父类,抛出的机制符合下图:

当然,如果其中一个方法能够处理异常,则异常不再往上抛

示例:抛出异常

(6) 手动抛出异常

1) Java 的异常对象除了可以由虚拟机发现程序异常之后自动抛出异常对象之外,还可以由程序员手动抛出。

2) 手动抛出异常的语法:

    异常类 e = new 异常类构造函数();
    throw e;//抛出异常对象

注意:throw 和 throws 的区别,throw 是真实的抛出了一场,代码应该有明确的捕获机制,throws 用在方法上,表明该方法可能发生异常,但没有处理异常的能力,通过 throws 声明该方法可能发生的异常,由调用者来处理异常。同时 throw 只能抛出异常对象,不能抛出非异常对象,比如 throw new String(“异常”); 是错误的语法。

(7) 用户自定义异常

一般情况下,用户可以去继承 Exception 或 RuntimeException 来创建自定义异常。

示例:自定义异常

package com.edu.ex;

public class UserExceptionDemo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Seller seller = new Seller();
        for (int i = 0; i < 100; i++) {
            try {
                seller.sell();
            } catch (NoStockException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}
//自定义异常
class NoStockException extends Exception {
    //无参构造器
    public NoStockException() {
        // TODO Auto-generated constructor stub
        super();
    }
    //带参构造器

    public NoStockException(String message) {
        //将异常信息 message 传递给父类构造器,发生异常时,可以显示对应异常信息
        super(message);
        // TODO Auto-generated constructor stub
    }
}
//售货员
class Seller{
    private int count = 10000;//初始化10000个商品
    //表明 sell() 可能会发生 NoStockException 异常
    public void sell() throws NoStockException {
        count -= 100;
        if(count <= 0) {
            //如果商品不够了,则手动抛出异常
            throw new NoStockException("商品已售罄,下次再来");
        }
    }
}

(8) 总结异常处理

二、字符串 String

1. 字符串:String 类,Java 中所有的字符串字面值都是作为该类的实例

2. String 类是 final 类,表示不可变的字符串序列,该类不能够被继承

查看源码:

3. String 类内部包含了一个字符类型的数组,我们定义的字符串就是存放在这个字符数组中的。

4. 字符串是一个常量,使用双引号括起来,它们的值在创建之后不能被修改。

5. 字符串的内存图:

示例:字符串的初始化

首先查看 String 类源码中的构造函数:

 ......
    public String() {
        this.value = "".value;//空的字符串对应的字符数组赋予当前String的内部的字符数组value,得到一个空的字符数组
    }

    public String(String original) {
        //参数 original 对应的字符数组赋予当前 String 的字符串
        this.value = original.value;
        this.hash = original.hash;//同时赋予哈希值
    }

    public String(char value[]) {
        //将参数字符串数组 value 拷贝到当前 String 内部的字符数组
        this.value = Arrays.copyOf(value, value.length);
    }

    //把参数 value 字符数组的内容,从偏移量 offset 开始,总共拷贝 count 长度到当前 String 的字符数组 value 中
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }
    ......

示例代码:

不同方式初始化 String 的内存图:

注意:字符串常量池在 JDK1.7 之前是放在方法区的,而之后是放在堆上面的,即目前我们使用的 JDK1.8 实际上字符串常量池是放在堆上面。

示例2:

6. String 的常用方法(重要最好能全部记住,自己用一遍很好记!!!)

int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将String 中的所有字符转换为大写
String trim():返回字符串的副本,去掉左右的空格
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引toffset开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的char子序列s时,返回true
int indexOf(String str):返回指定子字符串str在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串str在此字符串中第一次出现处的索引,从指定的索引fromIndex开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用newChar 替换此字符串中出现的所有oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列,替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement) : 使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement) : 使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
boolean matches(String regex):测试此字符串是否匹配给定的正则表达式。
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
public char[] toCharArray():转换为字符数组

示例1:获取指定字符串中大写字母、小写字母、数字的个数

 
示例2:将一个字符串的首字母大写,其他字母小写

 
示例3:查询在大的字符串中,出现指定小字符串的次数,例如”
 
示例4:String 中使用正则表达式
案例1:


案例2:


案例3:

 
虽然 String 提供了很多的 API 供调用,但是它有一个缺点就是一旦字符串定义了之后就不能修改,但是 StringBuffer 能做到。


练习:定义一个字符串,统计某个字符总共出现了多少次。


7. StringBuffer 类
(1) StringBuffer 类不同于 String,其对象的创建必须通过构造器生成,其构造器包含如下:
1) StringBuffer():创建初始容量为 16 个字符的字符缓冲区(字符数组)
源码:

2) StringBuffer(int capacity):创建指定容量为 capacity 的字符缓冲区
源码:

3) StringBuffer(String str):创建初始内容为 str 的字符串缓冲区,此时该缓冲区 value 的容量为 str 的长度 + 16
源码:

(2) StringBuffer 的扩容机制
StringBuffer 有初始化容量,当它包含的字符串没有超过容量时,不会扩容,但是一般它包含的字符串超出了容量之后,就会进行扩容,我们查看StringBuffer 附加字符串(相当于字符串拼接)的源码:
 
它直接调用了父类的append()方法来附加字符串,查看父类的append()方法:

该方法 ensureCapacityInternal(count + len) 会决定是否扩容:

具体的扩容方法:newCapacity(minimumCapacity):

扩容方法newCapacity()默认情况下每次扩展的容量是原来字符数组的长度乘以2再加上2(int newCapacity = (value.length << 1) + 2;)
(3) 对比String和StringBuffer
String的操作都会生成新的String对象,不仅效率低下,而且浪费大量有限的内存空间;StringBuffer是可变类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量来容纳更多的字符串,但是不会去产生新的StringBuffer,所以StringBuffer效率比String高。
例如:

(4) StringBuilder 类
是JDK5.0开始提供的字符串操作类,用法和 StringBuffer 一模一样。StringBuilder 和 StringBuffer 的区别是,StringBuilder 中方法没有 synchronized 关键字修饰,不是线程安全,但是由于没有加锁,所以 StringBuilder 的效率比 StringBuffer 的效率高。这样我们从效率上排序:StringBuilder 效率 > StringBuffer 效率 > String 效率

(5) StringBuffer 常用的方法

String 包含的方法几乎都能在 StringBuffer 中使用,而且 StringBuffer 相对于 String 最大的区别是 StringBuffer 可以修改字符串的内容,但是 String 不能修改,常用的方法:除了 String 包含的方法之外,还有:

还有观察源码,发现 StringBuffer 中每一个方法都是 synchronized 的(对方法进行了加锁),所以 StringBuffer是线程安全的类。

示例:


网站公告

今日签到

点亮在社区的每一天
去签到