零基础 “入坑” Java--- 十六、字符串String & 异常

发布于:2025-08-04 ⋅ 阅读:(12) ⋅ 点赞:(0)


在上一章节中,我们已经对String类型的 常用的方法进行了学习,String的知识不止于此。本章节,我们再来对String进行探究,并学习有关异常的知识。

一、String

1.字符串的不可变性

在之前对字符串的学习中,我们了解到 字符串是一种不可变对象,即字符串不能被修改。当我们对字符串进行操作时,都会生成一个新的字符串。

是什么原因导致字符串的不可变性的呢?我们来阅读一下String类型的源码:
在这里插入图片描述
字符串不能修改的真正原因是因为:value被private修饰,在类外拿不到value的引用,并非是因为final。

2.字符串的修改

    public static void main(String[] args) {
        String s = "hello";
        //避免直接对String类型的对象进行修改,因为String的不可变性,所有的修改都会创建新对象,效率低
        s += " world";
        System.out.println(s);
    }

使用上面这种方法,虽然可以完成对字符串的修改,但是效率很低。

我们可以使用StringBuilder和StringBuffer对字符串进行修改。

3.StringBuilder和StringBuffer

StringBuilder和StringBuffer的大部分功能都是相同的,我们就以StringBuilder举例:
在这里插入图片描述
在这里插入图片描述

    public static void main(String[] args) {
        //StringBuilder和StringBuffer功能类似
        StringBuilder stringBuilder = new StringBuilder();

        StringBuilder stringBuilder1 = new StringBuilder("666");
        //在原数据上进行修改,不会生成新的对象

        //String不可变,而StringBuilder和StringBuffer可变
        stringBuilder1.append(888);
        stringBuilder1.append("222");
        stringBuilder1.append(888).append(99.99);
        System.out.println(stringBuilder1);
        stringBuilder1.setCharAt(1, 'M');
        stringBuilder1.insert(1, "==");
        System.out.println(stringBuilder1);

        stringBuilder1.reverse();

        System.out.println(stringBuilder1);
        /**
         * String 和 StringBuilder并不能直接转换,要想转换:
         * 1.String 变 StringBuilder:利用StringBuilder构造方法 或 append方法
         *      构造方法:StringBuilder stringBuilder1 = new StringBuilder("666");
         * 2.StringBuilder 变 String:toString方法
         */
        String str = stringBuilder1.toString();
        System.out.println(str);
    }

当我们需要频繁修改字符串的内容时,建议使用StringBuilder和StringBuffer。

在使用StringBuilder和StringBuffer时我们也需要注意:String和StringBuilder并不能直接转换。

有关字符串的知识我们已经了解的差不多了,接下来我们通过几道习题尝试运用一下学过的知识。

4.【字符串练习】

4.1 字符串中的第一个唯一字符

字符串中的第一个唯一字符

在这里插入图片描述
解题思路:
在这里插入图片描述
在这道题目中,有两个点值得我们注意:

1.因为字符’a’对应的ASCII码值为97,在申请数组时,为了节省空间,我们只申请26个,后续遍历数组时,用字符对应的ASCII码值 - ‘a’ 即可。
2.在第二次遍历数组时,需要对字符串进行遍历,寻找第一个只出现一次的字符,而不应对计数数组遍历。

class Solution {
    public int firstUniqChar(String s) {
        //定义一个计数数组,下标分别对应 26个英文字母 - 'a'
        int[] array = new int[26];

        //遍历字符串,将出现的字符在计数数组中对应的位置+1
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            array[ch - 'a']++;
        }

        //再次遍历字符串,寻找字符在计数数组中是否为1
        //注意:遍历字符串,不是遍历计数数组!!!
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (array[ch - 'a'] == 1) {
                return i;
            }
        }
        return -1;
    }
}

4.2 字符串最后一个单词的长度

字符串最后一个单词的长度

在这里插入图片描述

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNext()) { // 注意 while 处理多个 case
            String s = in.nextLine();
            String[] str = s.split(" ");
            String ss = str[str.length - 1];
            System.out.println(ss.length());
        }
    }
}

在这道题目中,我们使用了split方法对字符串进行分割,获取最后一个单词,计算最后一个单词的长度即可。

4.3 验证回文串

验证回文串

在这里插入图片描述
解题思路:
在这里插入图片描述

class Solution {
    //判断是否为合法字符
    public boolean isLegal(char ch) {
        if (ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z') {
            return true;
        }
        return false;
    }

    public boolean isPalindrome(String s) {
        //将所有大写字符转换为小写字符
        s = s.toLowerCase();
        int left = 0;
        int right = s.length() - 1;
        //left < right:相遇结束循环
        while (left < right) {
            //left < right:防止越界
            while (left < right && !isLegal(s.charAt(left))) {
                left++;
            }
            while (left < right && !isLegal(s.charAt(right))) {
                right--;
            }
            //至此,left 和 right均为合法字符
            if (s.charAt(left) != s.charAt(right)) {
                return false;
            } else {
                left++;
                right--;
            }
        }
        //循环条件不满足,结束循环,是回文串
        return true;
    }
}

在这道题目中,我们需要注意:最外层的while循环的"left < right"判断的是结束条件,而内层的while循环的"left < right"是判断是否越界(如:字符串中的字符 均为 非字母数字字符的情况)。

二、异常

1.初识异常

在Java中,将程序执行过程中发生的不正常的行为称为异常。 在之前的学习中,我们也已经遇到过很多可能会出现异常的情况:

  • 算术异常:
    在这里插入图片描述
  • 数组越界异常
    在这里插入图片描述
  • 空指针异常
    在这里插入图片描述

2.异常的分类

异常可能在编译时发生,也可能在程序运行时发生,根据发生的时机不同,可以将异常分为:编译时异常和运行时异常。

  • 编译时异常:在程序编译期间发生的异常,称为编译时异常,也称为受查异常。
  • 运行时异常:在程序执行期间发生的异常,称为运行时异常,也称为非受查异常。RuntimeException及其子类对应的异常,均称为运行时异常。

3.异常的处理

在Java中,对异常进行处理依赖于5个关键字:throw,try,catch,finally,throws。

  • try-catch捕获异常

使用 try-catch 进行防御性编程:

    public static void main(String[] args) {
        try {
            //System.out.println(10 / 0);
            int[] array1 = new int[]{1,2,3};
            //System.out.println(array1[6]);
            int[] array = null;
            System.out.println(array.length);
        } catch (ArithmeticException e) {
            System.out.println("捕获算术异常");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界异常");
        } catch (NullPointerException e) {
            System.out.println("空指针异常");
        }
    }

将可能会出现异常的代码放在try的代码块中,使用catch匹配异常,当try中的代码出现异常时,就会寻找catch的匹配条件,如果满足,就会进入到对应的catch语句中。

try-catch 还可以与 finally 一起使用:

    public static void main(String[] args) {
        try {
            System.out.println(10 / 0);
        } catch (ArithmeticException e) { //catch需捕获到对应的异常,否则程序依旧异常终止
            e.printStackTrace(); //打印异常信息
            System.out.println("捕获异常");
            return;
        } finally {
            //finally中的代码一定会被执行,哪怕之前的代码中存在return
            System.out.println("finally一定被执行");
            //不建议在finally中使用return语句
        }
        System.out.println("666");
    }

在使用 try - catch - finally 对异常进行捕获时,我们需要注意:

1.try代码块中,对于抛出异常位置之后的代码均不会执行。
2.如果抛出的异常的类型与catch的异常类型不匹配,则不会捕捉到异常,程序就会报错。
3.使用try-catch语句成功捕捉到异常之后,try-catch后的代码可以正常执行。
4.finally中的代码一定会被执行,哪怕之前的代码中存在return;不建议在finally中使用return语句,如果finally中和 try或catch 中均存在return语句,则执行finally中的return语句。
5.可以使用"printStackTrace"打印异常信息。

当多个异常的处理方式相同时,可以合并写为:

    public static void main(String[] args) {
        try {
            //System.out.println(10 / 0);
            int[] array1 = new int[]{1,2,3};
            System.out.println(array1[6]);
            int[] array = null;
            System.out.println(array.length);
        } catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
            System.out.println("捕获异常");
        } catch (NullPointerException e) {
            System.out.println("空指针异常");
        }
    }

Exception是所有异常的父类,当异常的处理具有父子关系时,一定是子类异常在前,父类异常在后,否则会报错:

    public static void main(String[] args) {
        try {
            //System.out.println(10 / 0);
            int[] array1 = new int[]{1,2,3};
            System.out.println(array1[6]);
            int[] array = null;
            System.out.println(array.length);
        } catch (NullPointerException e) {
            System.out.println("空指针异常");
        } catch (Exception e) {
            System.out.println("异常");
        }
    }

也可以通过Exception捕获所有异常,但并不推荐,捕获到的异常太模糊。


  • throw抛出异常
    在Java中,可以借助throw关键字,抛出一个指定的异常对象:
    //通过throws抛出异常,交给调用者处理(JVM)
    public static void main1(String[] args) throws CloneNotSupportedException {
        int a = 10;
        if (a == 10) {
            //throw 常用于抛出自定义异常
            throw new CloneNotSupportedException("a == 10"); //编译时异常
        }
    }

注意事项:

throw:
1.throw必须写在方法体内部。
2.通过throw抛出的对象必须是Exception或Exception的子类。
3.如果抛出的对象是RuntimeException或RuntimeException的子类,则可以不用自己处理,交给JVM处理。
4.如果抛出的是编译时异常,则必须用户自己处理,否则编译不通过。
5.异常一旦抛出,其后的代码均不会执行。


throws:
1.throws用于处理编译时异常,当用户不想处理方法中的编译时异常时,就可以通过throws将异常抛给方法的调用者处理。即当前方法不处理异常,提醒方法的调用者处理异常。
2.throws必须跟在方法的参数列表之后。
3.声明的异常必须是Exception或Exception的子类。
4.如果方法内部抛出了多个异常,throws之后必须跟多个异常类型,多个异常类型之间用","隔开;如果抛出的多个异常类型之间具有父子关系,也可以直接声明父类。
5.当调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出。


throw 和 throws:
当通过throw抛出一个运行时异常时,无需进行处理;但如果抛出的是一个编译时异常,就需要对其进行处理,最简单的方式就是通过throws处理。

4.异常处理流程总结

在这里插入图片描述

5.自定义异常

在我们进行自定义异常之前,我们先来看看异常的源码是怎样的(以算术异常为例):
在这里插入图片描述
自定义异常实现方式:

1.自定义一个类,并让其继承Exception或者RuntimeException,成为异常类。
2.实现一个带有String参数类型的构造方法,参数为出现异常的原因。

我们仿照源码编写自定义异常:

//运行时异常(非受查异常):extends RuntimeException
public class myException extends RuntimeException{
    public myException() {
    }

    public myException(String message) {
        super(message);
    }
}

此时我们就编写好了一个自定义的运行时异常。

使用也很简单:

    //运行时异常不用throws
    public static void main(String[] args) {
        int a = 10;
        if (a == 10) {
            throw new myException("我的异常");
        }
    }

我们再来编写一个自定义的编译时异常:

//编译时异常(受查异常):extends Exception
public class my_Exception extends Exception{
    public my_Exception() {
    }

    public my_Exception(String message) {
        super(message);
    }
}

使用编译时异常:

    //编译时异常必须throws
    public static void main1(String[] args) throws my_Exception {
        int a = 10;
        if (a == 10) {
            throw new my_Exception("我的异常");
        }
    }

由上面两个自定义异常的例子我们可以得出:

1.自定义异常通常继承Exception或者RuntimeException。
2.继承Exception的异常类默认是受查异常。
3.继承RuntimeException的异常类默认是非受查异常。


Ending。


网站公告

今日签到

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