JAVA异常处理机制

目录

一.异常概述和体系

异常概述

异常体系

 异常分类:

检查异常(checked exception)

Exception异常划分

初识异常与常见异常

初识异常

常见异常

三、异常处理的处理机制

四、手动抛出异常:throw

五、异常的链化

六、自定义异常

自定义异常规则

自定义异常构建

使用自定义异常

七、异常的注意事项

八、finally块和return


一.异常概述和体系

异常概述

Java异常是指在程序运行时可能发生的错误或异常情况,比如错误输入、文件不存在、网络连接中断等等。

Java提供了一套强大的异常处理机制,使得程序能够在发生异常时进行适当的处理,避免程序崩溃或者数据丢失。

 Java程序在执行过程中所发生的异常事件可分为两类:
        Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError(栈溢出)和OOM(内存溢出)。一般不编写针对性的代码进行处理。
        Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
            空指针访问
            试图读取不存在的文件
            网络连接中断
            数组角标越界

Error代码示例:

public class Test {
    public static void main(String[] args) {
        /*
         * 1、栈溢出:java.lang.StackOverflowError
         *   原因 : 函数调用栈太深了,注意代码中是否有了循环调用方法而无法退出的情况
         *   StackOverflowError 是一个java中常出现的错误:在jvm运行时的数据区域中有一个java虚拟机栈,当执行java方法时会进行压栈弹栈的操作。在栈中会保存局部变量,操作数栈,方法出口等等。jvm规定了栈的最大深度,当执行时栈的深度大于了规定的深度,就会抛出StackOverflowError错误。
         * */
        main(args);
 
        /*
         * 2、堆溢出:java.lang.OutOfMemoryError
         *   原因:Java中所有的对象都存储在堆中,通常如果JVM无法再分配新的内存,内存耗尽,垃圾回收无法及时回收内存,就会抛出OutOfMemoryError。
         * */
        Integer[] arr = new Integer[1024 * 1024 * 1024];
 
    }
}

Exception代码示例:

import java.io.FileInputStream;
 
public class Test {
    public static void main(String[] args) {
        /*
         * 1、运行时异常:java.lang.ArithmeticException
         *  原因:ArithmeticException
         * */
        int a = 10;
        int b = 0;
        System.out.println(a / b);
 
        /*
         * 2、编译期异常:java.io.FileNotFoundException
         *   原因:文件找不到异常通常是两种情况:1、系统找不到指定的路径 2、拒绝访问(指定的是目录时,就会报拒绝访问异常)
         * */
        FileInputStream fis = new FileInputStream("a.txt");
 
    }
}

 异常发生时,立刻退出终止,或是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?

        Java提供了更加优秀的解决办法:异常处理机制。异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。

        Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。
 
        Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,我们也可以自定义异常。

异常体系

Java标准库内建了一些通用的异常,以Throwable为顶层父类。

        Throwable又派生出Error类和Exception类。

        错误:指Error类以及它的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。

        异常:Exception以及它的子类,代表程序运行时发生的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。

Error和Exception的区别:

Error用于表示系统级别的错误,而Exception则表示可以被程序本身处理的异常情况。

Error通常是由JVM自身引起的,通常会导致程序崩溃或无法恢复的情况。这些错误无法通过程序员的代码来预测和处理,因为它们通常是由底层操作系统、硬件故障或者JVM自身的内部问题引起的。例如,OutOfMemoryError是一种Error,表示JVM无法分配足够的内存空间导致程序崩溃,这是由于系统资源耗尽造成的,程序无法通过代码手段避免或解决。

    Exception,也就是我们经常见到的一些异常情况,表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

异常体系结构图:

 异常分类:

广义上的异常是包括Exception和Error;

        Java的异常(包括Exception和Error)从广义上分为检查异常(checked exceptions)和非检查的异常(unchecked exceptions)。

        其中根据Exception异常进行划分,可分为运行时异常和非运行时异常。

       注意:检查和非检查是对于javac来说的,这样就很好理解和区分了。

检查异常(checked exception)

检查异常(Checked Exception)是指在编译时就能被检查出来的异常,必须在代码中进行处理,否则程序将无法通过编译。

Checked异常通常由Java代码所引起,表示可能会发生的异常情况,如IO异常、SQL异常、ClassNotFoundException等等。这些异常都继承自Exception类,因此也叫作“受检查的异常”。

检查异常的设计考虑到了程序的正确性和可靠性,强迫程序员在代码中显式地进行异常处理,以保证程序的稳定和正确运行。Java的Io和数据库操作等常常会出现检查异常,提醒开发者在进行这些操作时,一定需要考虑依赖库的抛出的异常情况。

总之,Java中的检查异常通常是由Java代码所引起的,表示可能会发生的异常情况,必须在代码中进行处理或者声明抛出异常。这种异常的出现是为了保证程序的可靠性和正确性。

怎样处理检查异常

  1、继续抛出,消极的方法,一直可以抛到java虚拟机来处理,就是通过throws Exception抛出。

  2、用try...catch捕获

        注意,对于检查的异常必须处理,或者必须捕获或者必须抛出

非检查异常(unchecked exception )

 编译器不要求强制处置的异常,虽然你有可能出现错误,但是编译器不会在编译的时候检查,没必要,也不可能。

        javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try...catch...finally)这样的异常,也可以不处理。

        对于这些异常,我们应该修正代码,而不是去通过异常处理器处理。这样的异常发生的原因多半是代码写的有问题:如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。

怎样处理非检查异常

      1、用try...catch捕获
       2、继续抛出
       3、不处理
       4、通过代码处理

    一般我们是通过代码处理的,因为你很难判断会出什么问题,而且有些异常你也无法运行时处理,比如空指针,需要人手动的去查找。

        况且,捕捉异常并处理的代价远远大于直接抛出。

Exception异常划分

   运行时异常:
                是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是非检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

                运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

   编译期异常:
                是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不要自定义检查异常。

初识异常与常见异常

初识异常

 下面的代码会演示2个异常类型:ArithmeticException 和 InputMismatchException。

  前者由于整数除0引发,后者是输入的数据不能被转换为int类型引发。

import java.util.Scanner;
 
public class AllDemo {
    public static void main(String[] args) {
        System.out.println("欢迎使用命令行除法计算器");
        CMDCalculate();
    }
 
    public static void CMDCalculate() {
        Scanner scan = new Scanner(System.in);
        int num1 = scan.nextInt();
        int num2 = scan.nextInt();
        int result = devide(num1, num2);
        System.out.println("result:" + result);
        scan.close();
    }
 
    public static int devide(int num1, int num2) {
        return num1 / num2;
    }
}
 
/*****************************************
----欢迎使用命令行除法计算器----
2
0
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at AllDemo.devide(AllDemo.java:19)
	at AllDemo.CMDCalculate(AllDemo.java:13)
	at AllDemo.main(AllDemo.java:6)
----欢迎使用命令行除法计算器----
----欢迎使用命令行除法计算器----
1
r
Exception in thread "main" java.util.InputMismatchException
	at java.util.Scanner.throwFor(Scanner.java:864)
	at java.util.Scanner.next(Scanner.java:1485)
	at java.util.Scanner.nextInt(Scanner.java:2117)
	at java.util.Scanner.nextInt(Scanner.java:2076)
	at AllDemo.CMDCalculate(AllDemo.java:12)
	at AllDemo.main(AllDemo.java:6)
*****************************************/

    异常是在执行某个函数时引发的,而函数又是层级调用,形成调用栈的,因为,只要一个函数发生了异常,那么他的所有的caller都会被异常影响。当这些被影响的函数以异常信息输出时,就形成的了异常追踪栈。

        异常最先发生的地方,叫做异常抛出点。

上面的代码不使用异常处理机制,也可以顺利编译,因为2个异常都是非检查异常。但是下面的例子就必须使用异常处理机制,因为异常是检查异常。

        代码中我选择使用throws声明异常,让函数的调用者去处理可能发生的异常。为什么只throws了IOException?因为FileNotFoundException是IOException的子类

import java.io.FileInputStream;
import java.io.IOException;
 
public class ExceptionTest {
    public void testException() throws IOException {
        //FileInputStream的构造函数会抛出FileNotFoundException
        FileInputStream fileIn = new FileInputStream("E:\\a.txt");
 
        int word;
        //read方法会抛出IOException
        while ((word = fileIn.read()) != -1) {
            System.out.print((char) word);
        }
        //close方法会抛出IOException
        fileIn.close();
    }
}

常见异常

 java.lang.RuntimeException: 运行时异常
ClassCastException: 类类型转换异常,当试图将对象强制转换为不是实例的子类时,抛出该异常;

ArrayIndexOutOfBoundsException: 数组下标越界异常,当你使用不合法的索引访问数组时会抛出该异常;
NullPointerException: 空指针异常,通过null进行方法和属性调用会抛出该异常;
ArithmeticException: 算术运算异常,除数为0,抛出该异常;
NumberFormatException: 数字转换异常,当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常;
InputMismatchException: 输入不匹配异常,输入的值数据类型与设置的值数据类型不能匹配。

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Scanner;
 
public class ExceptionTest {
    //ClassCastException
    public void test1() {
        Object obj = new Date();
        String str = (String) obj;
    }
 
    //IndexOutOfBoundsException
    public void test2() {
        //ArrayIndexOutOfBoundsException
        int[] arr = new int[10];
        System.out.println(arr[10]);
 
        //StringIndexOutOfBoundsException
        String str = "abc";
        System.out.println(str.charAt(3));
    }
 
    //NullPointerException
    public void test3() {
        int[] arr = null;
        System.out.println(arr[3]);
 
        String str = "abc";
        str = null;
        System.out.println(str.charAt(0));
    }
 
    //ArithmeticException
    public void test4() {
        int a = 10;
        int b = 0;
        System.out.println(a / b);
    }
 
    //NumberFormatException
    public void test5() {
        String str = "123";
        str = "abc";
        int num = Integer.parseInt(str);
    }
 
    //InputMismatchException
    public void test6() {
        Scanner scanner = new Scanner(System.in);
        int score = scanner.nextInt();
        System.out.println(score);
        scanner.close();
    }
}

   java.io.IOExeption: 输入输出异常
 FileNotFoundException: 文件找不到异常,通常是两种情况:

1、系统找不到指定的路径

2、拒绝访问(指定的是目录时,就会报拒绝访问异常)


EOFException: 文件已结束异常,抛出EOFException一定是因为连接断了还在继续read;

java.lang.ClassNotFoundException: 类找不到异常,当我们通过配置文件去查找一个类的时候,如果配置路径写错,就会抛出该异常,比如:web.xml文件中根本就不存在该类的配置或者配置的路径写错;(比较常见)
java.sql.SQLException: SQL异常,数据库的各种信息的异常;

import java.io.File;
import java.io.FileInputStream;
 
public class ExceptionTest {
    
    public void test7() {
        File file = new File("hello.txt");
        FileInputStream fis = new FileInputStream(file);
 
        int data = fis.read();
        while (data != -1) {
            System.out.print((char) data);
            data = fis.read();
        }
 
        fis.close();
    }
 
}

三、异常处理的处理机制

  1. 抛出异常(throw):当程序执行的过程中,遇到了异常情况,可以使用throw关键字来抛出异常。

  2. 捕获异常(catch):使用try-catch语句来捕获和处理异常。在try块中,程序会尝试执行一段可能会抛出异常的代码;如果发生异常,Java会从try块中跳出,并在catch块中处理这个异常。

  3. 处理异常(finally):finally语句块可以在try-catch语句执行之后,无论是否抛出异常,都会执行其中的代码。通常用来做一些资源清理的操作。如果不处理异常,程序就会在运行中断,finally语句块也不会被执行。

    try {
        // 可能会出现异常的代码段
    } catch (ExceptionType e) {
        // 处理异常的代码段
    } finally {
        // 执行任何情况下都需要执行的代码段
    }

    在try块中,我们写入可能会抛出异常的代码。如果异常发生,则程序不再执行try块中的代码,并将异常传递给与之匹配的catch块。catch块中的代码会根据异常类型进行处理,比如打印日志、再次抛出异常等。最后,无论是否抛出异常,finally块中的代码都会被执行,通常用来实现资源的释放或清理。

    在处理Java异常时,程序员应该尽可能地了解异常类型、异常发生的原因,并且根据具体情况编写错误处理代码,以保证程序的正确性和稳定性。

    总之,Java中异常的处理机制主要由throw、catch和finally三部分组成。在程序发生异常时,程序员应该根据异常类型进行处理,同时也需要注意资源的释放和清理。

四、手动抛出异常:throw

Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。

首先,要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运行环境)。
throw exceptionObject

程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面可以抛出的异常必须是Throwable或其子类的实例。

throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。

public class StudentTest {
    public static void main(String[] args) {
        try {
            Student s = new Student();
            s.regist(-1001);
            System.out.println(s);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
 
class Student {
    private int id;
 
    public void regist(int id) throws Exception {
        if (id > 0) {
            this.id = id;
        } else {
            // System.out.println("您输入的数据非法!");
 
            //手动抛出异常对象
            // throw new RuntimeException("您输入的数据非法!");
            throw new Exception("您输入的数据非法!");
 
            //错误的
            // throw new String("不能输入负数");
        }
 
    }
 
    @Override
    public String toString() {
        return "Student [id=" + id + "]";
    }
}

throws和throw的区别:

在Java中,throws和throw是异常处理中两种非常重要的关键字,它们虽然看起来相似,但含义不同,作用也不同,具体区别如下:

  1. throws用于声明方法可能抛出的异常类型,表示该方法可能会抛出指定的异常类或其子类。这个异常类型在方法声明的后面使用throws关键字指定,表示如果该方法抛出了这种类型的异常,那么这个异常就会被传递到调用该方法的代码处,由调用者来处理这个异常。

  2. throw用于手动抛出异常,表示当前方法已经发生了异常,需要将异常抛出给调用者进行处理。throw语句需要抛出一个Throwable类型的实例对象,可以是Java标准库中提供的异常类对象,也可以是程序员自己定义的异常类对象。

简而言之,throws用于声明方法可能抛出的异常类型,而throw用于手动抛出异常对象。

下面是一个简单的示例代码:

public void method1() throws ExceptionType1, ExceptionType2 {
    // 方法可能会抛出ExceptionType1和ExceptionType2类型的异常
}

public void method2() {
    try {
        // 可能会出现异常的代码段
        throw new ExceptionType1();    // 手动抛出一个类型为ExceptionType1的异常
    } catch (ExceptionType1 e1) {
        // ExceptionType1类型异常的处理代码段
    } catch (ExceptionType2 e2) {
        // ExceptionType2类型异常的处理代码段
    } finally {
        // 必须执行的清理代码段
    }
}

在method1方法中,使用throws关键字声明该方法可能抛出ExceptionType1和ExceptionType2类型的异常,因此在调用该方法时,调用方必须处理这些异常情况。

在method2方法中,通过throw关键字手动抛出一个类型为ExceptionType1的异常,在catch块中针对不同类型的异常进行处理,并在finally块中执行必须执行的清理操作。

总之,throws用于声明方法可能抛出的异常类型,表示该方法可能会抛出指定的异常类或其子类,而throw用于手动抛出异常对象,表示当前方法已经发生了异常,需要将异常抛出给调用者进行处理。

五、异常的链化

异常链化(Chained Exception)是一种将多个异常信息链接在一起的技术,以便诊断和分析问题。当一个异常被抛出时,如果我们希望将它与其他的异常关联在一起,就可以利用异常链技术在异常对象上设置一个cause(原因)对象,从而产生一个异常链,这样可以将一个异常的起因追溯到它的根源,方便我们查找和解决问题。

在Java中,Throwable类提供了一个getCause()方法,可以获取Throwable对象的原因(cause)异常。如果不将一个异常与其原因联系在一起,则该方法将返回null值。

try {
    // 可能会发生异常的代码段
} catch (ExceptionType1 e1) {
    // 处理ExceptionType1类型异常的代码段
    throw new ExceptionType2("普通异常信息", e1);  // 将异常e1作为ExceptionType2的原因
}

在这个代码中,当出现ExceptionType1类型的异常时,我们将这个异常对象作为构造函数参数传递给ExceptionType2类,并将它作为ExceptionType2的原因异常,从而形成了一个异常链。如果我们将此异常对象打印出来,可以看到异常对象的cause属性被设置为前一个异常对象e1。

异常链技术可以提高程序的可维护性和易读性,并且有助于我们快速诊断和解决问题。但是,在使用异常链技术时需要注意遵循规范,比如异常的传递方向应当是从低层到高层,捕获异常应当是从高层到低层等等。

总之,Java中异常链化是一种针对多个异常信息进行链接的技术,它可以帮助我们追溯和分析问题。在使用异常链技术时需要遵守一些规范,以便保证代码的可维护性和可读性。

六、自定义异常

自定义异常规则

    如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展自RuntimeException。

        按照国际惯例,自定义的异常应该总是包含如下的构造函数:
                一个无参构造函数
                一个带有String参数的构造函数,并传递给父类的构造函数。
                一个带有String参数和Throwable参数,并都传递给父类构造函数。
                一个带有Throwable 参数的构造函数,并传递给父类的构造函数。

自定义异常构建

首先写一个自定义异常,继承Exception,代码如下:

public class MyException extends Exception {
 
    public MyException() {
        super();
    }
 
    public MyException(String message) {
        super(message);
    }
 
    public MyException(String message, Throwable cause) {
        super(message, cause);
    }
 
 
    public MyException(Throwable cause) {
        super(cause);
    }
}

使用自定义异常

        如果自定义异常是为了提示,在使用的时候,一定要用try..catch,不要直接用throw往外抛。

public class Test {
    public static void main(String[] args) {
        A a = new A();
        try {
            a.show(-2);
        } catch (MyException e) {
            System.out.println(e.getMessage());
        }
    }
}
 
class A {
    public void show(int num) throws MyException {
        if (num < 0) {
            MyException me = new MyException("异常:" + num + "不是正数");
            throw me;//抛出异常,结束方法show()的执行
        }
        System.out.println(num);
    }
}

七、异常的注意事项

  1. 异常捕获的顺序很重要:当一个方法抛出异常时,Java会在当前方法中查找一个能够处理异常的catch块,如果找不到,则这个异常将被传递到上层调用者中,直到找到一个能够处理该异常的catch块或者程序中止。因此,在写try-catch块时,需要将最具体的异常类型放在前面,最一般的异常类型放在最后。

  2. 避免空指针异常:空指针异常是程序开发中最常见的异常之一。在编写程序时,需要进行空指针检查,避免程序出现异常情况。如果必须使用null值,请谨慎处理,避免出现异常情况。

  3. 不要吞噬异常:在编写程序时,应该避免使用空的catch块,以避免吞噬异常。吞噬异常是指在程序中捕获异常,但没有进行处理或者处理不当的现象,导致程序中的异常没有得到有效处理,继而导致了更严重的问题。

  4. 资源管理和异常处理:在释放程序所持有资源时,尽量使用finally语句块来确保资源的释放。在异常处理过程中,要注意程序中的资源是否已经被释放。否则,就会导致资源泄漏和程序崩溃的问题。

  5. 自定义异常:在Java中,可以根据自己的需要定义异常类,使得程序更加方便维护和理解。自定义异常类需要继承Exception类或者RuntimeException类,并重写父类中的一些方法。

  6. 异常的链化:如果异常发生了多次,我们可以使用异常链的技术将多个异常信息链接在一起,可以方便追溯和分析问题。

  7. 异常的抛出:在使用throw抛出异常时,需要根据异常的类型、原因和上下文等来描述异常,使得异常信息更加明确、易于定位和解决问题。

总之,在Java中,异常处理是程序设计中必不可少的一部分。在编写程序时,需要注意异常的处理,避免出现异常情况或者应对异常情况,使得程序更加可靠、易于维护和扩展。

八、finally块和return

finally块是一个非常重要的代码块,通常用于执行一些必须在try或catch块中处理的清理代码,比如文件或网络连接的关闭等操作。finally块在try或catch块后执行,并无论是try块执行成功与否,都会执行其中的代码。

需要注意的是,在finally块中执行的语句也可以是return语句,这会导致try或catch块中的return语句失效,直接返回finally块中的return语句所指定的值。这种情况下,finally块中的return语句会覆盖try块和catch块中的return语句。

以下是一个简单的示例代码,演示finally块和return语句的关系:

public static int calculateSum(int[] numbers) {
    int sum = 0;
    try {
        for (int index = 0; index <= numbers.length; index++) {
            sum += numbers[index];
        }
        return sum;
    } catch (Exception e) {
        System.out.println("Exception encountered: " + e.getMessage());
        return sum;
    } finally {
        sum += 100;
        System.out.println("The sum in finally block is: " + sum);
        // 下面这行代码会直接返回 1000 而不是 try 或 catch 中的 sum
        return sum;
    }
}

在这个示例代码中,我们操作了一个整数数组,并使用一个try-catch语句块来计算数组中数字的总和。在finally块中,我们进行了一些必须执行的清理工作,并添加了一个return语句来返回新的总和1000,这将覆盖try块和catch块中的return语句。

在运行上述代码时,将输出以下内容:

Exception encountered: null
The sum in finally block is: 100

因为try块中存在数组越界异常,程序在执行try块代码时会抛出异常,然后转到catch块中执行异常处理代码,并返回一个值为0的sum。接着,因为finally块中存在return语句,程序将跳过try块和catch块中的return语句,直接返回finally块中的新的总和1000,这个值将成为方法的最终返回值。

因此,需要注意在使用try-catch-finally语句块时,finally块中的return语句可能会覆盖try或catch块中的return语句,程序返回finally块中return语句所指定的值。在实际应用中,需要按需使用finally块和return语句,以确保程序的正确性和稳定性。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值