异常

在理想状态下,用户输入数据的格式永远是正确的,选择打开的文件也一定存在,并且永远不会出现bug。

然而,在现实世界中却充满了不良的数据和带有问题的代码。

对于异常情况,例如:可能造成程序崩溃的错误输入,Java使用一种称为异常处理(exception handing)的错误捕获机制处理。

1、处理错误

假设在一个Java程序运行期间出现了一个错误。

这个错误可能是由于文件包含了错误信息,或者网络连接出现问题造成的,也有可能是使用无效的数组下标,或者视图使用一个没有被赋值的对象引用造成的。用户期望在出现错误时,程序能够采用一些理智的行为。如果由于出现错误而使得某些操作没有完成,程序应该:

  • 返回一种安全状态,并能够让用户执行一些其他命令
  • 允许用户保存所有操作的结果,并以妥善的方式终止程序。

要做到这些并不是一件很容易的事情。

为了能够在程序中处理异常情况,必须研究程序中可能会出现的错误和问题,以及哪类问题需要关注。

  1. 用户输入错误。
  2. 设备错误。
  3. 物理限制:磁盘满了,可用存储空间已被用完。
  4. 代码错误:程序方法有可能正确执行。例如:方法可能返回一个错误的答案,或者错误的调用了其他的方法。计算的数组索引不合法,试图在散列表中查找一各不存在的记录,或者试图让一个空栈弹出操作。

如果某个方法不能够采用正常的完成任务,就可以通过另外一个途径退出方法。

在这种情况下,方法并不返回任何值,而是抛出throw一个封装了错误信息的对象;调用这个方法的代码也将无法继续执行,取而代之的是,异常处理机制开始搜索能够处理这种异常状况的异常处理器(exception handler)

2、异常的分类

异常具有自己的语法和特定的继承结构

在Java中,异常对象都是派生于Throwable类的一个实例;

若Java中内置的异常类不能够满足需求,用户可以创建自己的异常类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JRLHMHQn-1580544173368)(images/04.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MKCkmlYn-1580544173370)(images/06.png)]

需要注意的是:所有异常都是由Throwable继承出来的,但是下一层立即分解为两个分支:ErrorException

  1. Error类层次结构描述了Java运行时系统的内部错误资源耗尽错误

    ​ 应用程序不应该抛出这种类型,如果出现了这样的内部错误,除了通知给用户,并尽力使程序安全的终止外,再也无能为力,这种清理很少出现

  2. 在Java中,需要关注Exception层次结构这种层次结构分解为两个分支:一个分支派生于RuntimeException;另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但由于像I/O错误这类问题派生的异常属于其他异常

    派生于RuntimeException的异常包含下面的几种情况:

    • 错误的类型转换
    • 数组越界访问
    • 访问null指针

    不是派生于RuntimeException的异常包括

    • 试图在文件尾部后面读取数据
    • 视图打开一个不存在的文件
    • 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在

    如果出现RuntimeException异常,那么就一定是你的问题,是一条相当有道理的规则

    应该通过检测数组下标是否越界来避免ArrayIndexOfBoundsException异常,应该通过在使用变量之前检测是否为null来杜绝NullPointerException异常的发生

  3. Java语言将派生于Error类或RuntimeException类的所有异常称为非受检查异常;所有其他异常为受查异常编译器将核查是否所有的受查异常提供了异常处理器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XKx08vQh-1580544173371)(images/07.png)]`

Throwable类

/**
 * The {@code Throwable} class is the superclass of all errors and
 * exceptions in the Java language. Only objects that are instances of this
 * class (or one of its subclasses) are thrown by the Java Virtual Machine or
 * can be thrown by the Java {@code throw} statement. Similarly, only
 * this class or one of its subclasses can be the argument type in a
 * {@code catch} clause.
 
 Throwable是Java语言中错误和异常的顶级类。
 只有这个类的实例或者它的子类可以被虚拟机抛出,或者在catch语句中处理。
 
 
 *
 * For the purposes of compile-time checking of exceptions, {@code
 * Throwable} and any subclass of {@code Throwable} that is not also a
 * subclass of either {@link RuntimeException} or {@link Error} are
 * regarded as checked exceptions.
 *
 * <p>Instances of two subclasses, {@link java.lang.Error} and
 * {@link java.lang.Exception}, are conventionally used to indicate
 * that exceptional situations have occurred. Typically, these instances
 * are freshly created in the context of the exceptional situation so
 * as to include relevant information (such as stack trace data).
 *
   Error类和Exception类的子类的实例,通常被看做异常情况的产生。
   通常情况下这些实例在异常情况下被创建来提供相关的错误信息。、
 
 
 * <p>A throwable contains a snapshot of the execution stack of its
 * thread at the time it was created. 
 一个throeable包含了异常产生时执行错误异常栈的一个快照。
 
 It can also contain a message
 * string that gives more information about the error.
 它同时他也包含了关于这个错误的更多信息。
 
 Over time, a
 * throwable can {@linkplain Throwable#addSuppressed suppress} other
 * throwables from being propagated.  Finally, the throwable can also
 * contain a <i>cause</i>: another throwable that caused this
 * throwable to be constructed.  The recording of this causal information
 * is referred to as the <i>chained exception</i> facility, as the
 * cause can, itself, have a cause, and so on, leading to a "chain" of
 * exceptions, each caused by another.
 *
 * <p>One reason that a throwable may have a cause is that the class that
 * throws it is built atop a lower layered abstraction, and an operation on
 * the upper layer fails due to a failure in the lower layer.  It would be bad
 * design to let the throwable thrown by the lower layer propagate outward, as
 * it is generally unrelated to the abstraction provided by the upper layer.
 * Further, doing so would tie the API of the upper layer to the details of
 * its implementation, assuming the lower layer's exception was a checked
 * exception.  Throwing a "wrapped exception" (i.e., an exception containing a
 * cause) allows the upper layer to communicate the details of the failure to
 * its caller without incurring either of these shortcomings.  It preserves
 * the flexibility to change the implementation of the upper layer without
 * changing its API (in particular, the set of exceptions thrown by its
 * methods).
 *
 * <p>A second reason that a throwable may have a cause is that the method
 * that throws it must conform to a general-purpose interface that does not
 * permit the method to throw the cause directly.  For example, suppose
 * a persistent collection conforms to the {@link java.util.Collection
 * Collection} interface, and that its persistence is implemented atop
 * {@code java.io}.  Suppose the internals of the {@code add} method
 * can throw an {@link java.io.IOException IOException}.  The implementation
 * can communicate the details of the {@code IOException} to its caller
 * while conforming to the {@code Collection} interface by wrapping the
 * {@code IOException} in an appropriate unchecked exception.  (The
 * specification for the persistent collection should indicate that it is
 * capable of throwing such exceptions.)
 *
 * <p>A cause can be associated with a throwable in two ways: via a
 * constructor that takes the cause as an argument, or via the
 * {@link #initCause(Throwable)} method.  New throwable classes that
 * wish to allow causes to be associated with them should provide constructors
 * that take a cause and delegate (perhaps indirectly) to one of the
 * {@code Throwable} constructors that takes a cause.
 *
 * Because the {@code initCause} method is public, it allows a cause to be
 * associated with any throwable, even a "legacy throwable" whose
 * implementation predates the addition of the exception chaining mechanism to
 * {@code Throwable}.
 *
 * <p>By convention, class {@code Throwable} and its subclasses have two
 * constructors, one that takes no arguments and one that takes a
 * {@code String} argument that can be used to produce a detail message.
 * Further, those subclasses that might likely have a cause associated with
 * them should have two more constructors, one that takes a
 * {@code Throwable} (the cause), and one that takes a
 * {@code String} (the detail message) and a {@code Throwable} (the
 * cause).
 *
 * @author  unascribed
 * @author  Josh Bloch (Added exception chaining and programmatic access to
 *          stack trace in 1.4.)
 * @jls 11.2 Compile-Time Checking of Exceptions
 * @since JDK1.0
 */
public class Throwable implements Serializable {
    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -3042686055658047285L;

    /**
     * Native code saves some indication of the stack backtrace in this slot.
     */
    private transient Object backtrace;

    /**
     * Specific details about the Throwable.  For example, for
     * {@code FileNotFoundException}, this contains the name of
     * the file that could not be found.
     *
     * @serial
     */
    private String detailMessage;


    /**
     * Holder class to defer initializing sentinel objects only used
     * for serialization.
     */
    private static class SentinelHolder {
        /**
         * {@linkplain #setStackTrace(StackTraceElement[]) Setting the
         * stack trace} to a one-element array containing this sentinel
         * value indicates future attempts to set the stack trace will be
         * ignored.  The sentinal is equal to the result of calling:<br>
         * {@code new StackTraceElement("", "", null, Integer.MIN_VALUE)}
         */
        public static final StackTraceElement STACK_TRACE_ELEMENT_SENTINEL =
            new StackTraceElement("", "", null, Integer.MIN_VALUE);

        /**
         * Sentinel value used in the serial form to indicate an immutable
         * stack trace.
         */
        public static final StackTraceElement[] STACK_TRACE_SENTINEL =
            new StackTraceElement[] {STACK_TRACE_ELEMENT_SENTINEL};
    }

    /**
     * A shared value for an empty stack.
     */
    private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];

    /*
     * To allow Throwable objects to be made immutable and safely
     * reused by the JVM, such as OutOfMemoryErrors, fields of
     * Throwable that are writable in response to user actions, cause,
     * stackTrace, and suppressedExceptions obey the following
     * protocol:
     *
     * 1) The fields are initialized to a non-null sentinel value
     * which indicates the value has logically not been set.
     *
     * 2) Writing a null to the field indicates further writes
     * are forbidden
     *
     * 3) The sentinel value may be replaced with another non-null
     * value.
     *
     * For example, implementations of the HotSpot JVM have
     * preallocated OutOfMemoryError objects to provide for better
     * diagnosability of that situation.  These objects are created
     * without calling the constructor for that class and the fields
     * in question are initialized to null.  To support this
     * capability, any new fields added to Throwable that require
     * being initialized to a non-null value require a coordinated JVM
     * change.
     */

    /**
     * The throwable that caused this throwable to get thrown, or null if this
     * throwable was not caused by another throwable, or if the causative
     * throwable is unknown.  If this field is equal to this throwable itself,
     * it indicates that the cause of this throwable has not yet been
     * initialized.
     *
     * @serial
     * @since 1.4
     */
    private Throwable cause = this;

    /**
     * The stack trace, as returned by {@link #getStackTrace()}.
     *
     * The field is initialized to a zero-length array.  A {@code
     * null} value of this field indicates subsequent calls to {@link
     * #setStackTrace(StackTraceElement[])} and {@link
     * #fillInStackTrace()} will be be no-ops.
     *
     * @serial
     * @since 1.4
     */
    private StackTraceElement[] stackTrace = UNASSIGNED_STACK;

    // Setting this static field introduces an acceptable
    // initialization dependency on a few java.util classes.
    private static final List<Throwable> SUPPRESSED_SENTINEL =
        Collections.unmodifiableList(new ArrayList<Throwable>(0));

    /**
     * The list of suppressed exceptions, as returned by {@link
     * #getSuppressed()}.  The list is initialized to a zero-element
     * unmodifiable sentinel list.  When a serialized Throwable is
     * read in, if the {@code suppressedExceptions} field points to a
     * zero-element list, the field is reset to the sentinel value.
     *
     * @serial
     * @since 1.7
     */
    private List<Throwable> suppressedExceptions = SUPPRESSED_SENTINEL;

    /** Message for trying to suppress a null exception. */
    private static final String NULL_CAUSE_MESSAGE = "Cannot suppress a null exception.";

    /** Message for trying to suppress oneself. */
    private static final String SELF_SUPPRESSION_MESSAGE = "Self-suppression not permitted";

    /** Caption  for labeling causative exception stack traces */
    private static final String CAUSE_CAPTION = "Caused by: ";

    /** Caption for labeling suppressed exception stack traces */
    private static final String SUPPRESSED_CAPTION = "Suppressed: ";

    /**
     * Constructs a new throwable with {@code null} as its detail message.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     *
     * <p>The {@link #fillInStackTrace()} method is called to initialize
     * the stack trace data in the newly created throwable.
     */
    public Throwable() {
        fillInStackTrace();
    }

    /**
     * Constructs a new throwable with the specified detail message.  The
     * cause is not initialized, and may subsequently be initialized by
     * a call to {@link #initCause}.
     *
     * <p>The {@link #fillInStackTrace()} method is called to initialize
     * the stack trace data in the newly created throwable.
     *
     * @param   message   the detail message. The detail message is saved for
     *          later retrieval by the {@link #getMessage()} method.
     */
    public Throwable(String message) {
        fillInStackTrace();
        detailMessage = message;
    }

    /**
     * Constructs a new throwable with the specified detail message and
     * cause.  <p>Note that the detail message associated with
     * {@code cause} is <i>not</i> automatically incorporated in
     * this throwable's detail message.
     *
     * <p>The {@link #fillInStackTrace()} method is called to initialize
     * the stack trace data in the newly created throwable.
     *
     * @param  message the detail message (which is saved for later retrieval
     *         by the {@link #getMessage()} method).
     * @param  cause the cause (which is saved for later retrieval by the
     *         {@link #getCause()} method).  (A {@code null} value is
     *         permitted, and indicates that the cause is nonexistent or
     *         unknown.)
     * @since  1.4
     */
    public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }

Error类

/**
 * An {@code Error} is a subclass of {@code Throwable}
 * that indicates serious problems that a reasonable application
 * should not try to catch. Most such errors are abnormal conditions.
 * The {@code ThreadDeath} error, though a "normal" condition,
 * is also a subclass of {@code Error} because most applications
 * should not try to catch it.
 * <p>
 * A method is not required to declare in its {@code throws}
 * clause any subclasses of {@code Error} that might be thrown
 * during the execution of the method but not caught, since these
 * errors are abnormal conditions that should never occur.
 *
 * That is, {@code Error} and its subclasses are regarded as unchecked
 * exceptions for the purposes of compile-time checking of exceptions.
 *
 * @author  Frank Yellin
 * @see     java.lang.ThreadDeath
 * @jls 11.2 Compile-Time Checking of Exceptions
 * @since   JDK1.0
 */
public class Error extends Throwable {
    static final long serialVersionUID = 4980196508277280342L;

Exception

/**
 * An {@code Error} is a subclass of {@code Throwable}
 * that indicates serious problems that a reasonable application
 * should not try to catch. Most such errors are abnormal conditions.
 * The {@code ThreadDeath} error, though a "normal" condition,
 * is also a subclass of {@code Error} because most applications
 * should not try to catch it.
 * <p>
 * A method is not required to declare in its {@code throws}
 * clause any subclasses of {@code Error} that might be thrown
 * during the execution of the method but not caught, since these
 * errors are abnormal conditions that should never occur.
 *
 * That is, {@code Error} and its subclasses are regarded as unchecked
 * exceptions for the purposes of compile-time checking of exceptions.
 *
 * @author  Frank Yellin
 * @see     java.lang.ThreadDeath
 * @jls 11.2 Compile-Time Checking of Exceptions
 * @since   JDK1.0
 */
public class Error extends Throwable {
    static final long serialVersionUID = 4980196508277280342L;

RuntimeException

/**
 * {@code RuntimeException} is the superclass of those
 * exceptions that can be thrown during the normal operation of the
 * Java Virtual Machine.
 *
 * <p>{@code RuntimeException} and its subclasses are <em>unchecked
 * exceptions</em>.  Unchecked exceptions do <em>not</em> need to be
 * declared in a method or constructor's {@code throws} clause if they
 * can be thrown by the execution of the method or constructor and
 * propagate outside the method or constructor boundary.
 *
 * @author  Frank Yellin
 * @jls 11.2 Compile-Time Checking of Exceptions
 * @since   JDK1.0
 */
public class RuntimeException extends Exception {
    static final long serialVersionUID = -7034897190745766939L;

3、异常的处理

3.1、声明受查的异常

如果遇到了无法处理的情况,那么Java的方法就可以抛出一个异常。

这个道理很简单:一个方法不仅需要告诉编译器需要返回什么值 ,还需要告诉编译器有可能发生什么错误,例如一段读取文件的不存在,或者内容为空;因此,视图处理文件信息的代码需要通知编译期可能会抛出IOException类的异常。

方法应该在其首部声明所有可能抛出的异常,这样就可以从首部反应出方法可能抛出哪类受查异常。

  public FileInputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null);
    }
/**
		这个声明表示这个构造器将根据给定的String参数产生一个FileInputStream对象,但也有可能抛出一个FileNotException异常。
		如果发生这种糟糕情况,构造器将不会初始化一个新的FileInputStream对象,而是抛出一个FileNotFoundException类对象。如果这个方法帧的抛出一个异常对象,运行时系统就会开始搜索异常处理器,以便知道如何处理FileNotFoundException.



**/

在自己编写方法时,不必将所有可能抛出的异常都进行声明,记住遇到下面4种情况时应该抛出异常:

  1. 调用一个抛出首查异常的方法,例如,FileInputStream构造器
  2. 程序运行过程中发现错误,并且利用throw语句抛出一个首查异常
  3. 程序出现错误,例如,a[-1]=0会抛出一个ArrayIndexOutOfBoundsException这样的非受查异常
  4. Java虚拟机和运行时库出现的内部错误

当出现前两种情况之一,则必须调用这个方法的程序员有可能抛出异常。为什么?因为任何一个抛出异常。为什么?因为任何一个抛出异常的方法都有可能是一个死亡陷阱。如果没有处理器捕获这个异常,当前执行的线程就会结束。

对于那些可能被他使用的Java方法,应该根据 异常规则(exception specification),方法的首部声明这个方法可能抛出的异常

class MyAnimakation{
    
    
    
    
    public Image loadImage(String s) throws IOException {
        .....
    }
}==

从Error继承的错误,不需要声明;同样,也不应该声明从RuntimeException继承的那些非受查异常

总之,一个方法必须声明所有可能抛出的受查异常,而非首查异常要么不可控制(Error),要么就应该避免发生(RuntimeeXCEPTION)。如果方法没有声明所有可能发生的受查异常,编译期就会发生一个错误信息

如果子类覆盖了超类的一个方法,子类方法中声明的受查异常不能比超类方法中声明的异常更通用(也就是说,子类方法中声明的受查异常不能比超类方法中声明的异常更通用)

如果类中的一个方法声明将会抛出一个异常,而这个异常是某个特定类的实例时,则这个方法就要可能抛出一个这个类的异常,或者这个类的任意一个子类的异常。

3.2、自定义异常

在程序中,可能会遇到任何标准异常类都没有能够充分的描述清楚的问题;在这种情况下,创建自己的异常类就是一件顺理成章的事情了

我们需要左的是定义一个派生于Exception的类,或者派生于Exception子类的类。

定义一个派生于IOException的类,习惯上,定义的类应该包括两个构造器:一个是默认的构造器;另一个是带有纤细描述信息的构造器(超类Throwable的toString方法会打印出这些详细信息,这在调试中非常有用)

class  FileFormatException extends IOException {
    
    public FileFormatException(){}
    
    public FileFormatException(String gripe){
        super(gripe);
    }
   
}

Throwable常用API

//构造一个新的Throwable对象,这个对象没有详细的描述信息
Throwable();

//构造一个新的Throwable对象,这个对象带有特定的详细描述信息。习惯上,所有派生的异常类支持一个默认的构造器和一个带有详细描述信息的构造器
Throwable(String message);

//获得Throwable对象的详细描述信息
String getMessage();

3.3、抛出异常

抛出异常: throw new EOFException()

对于一个已经存在的异常类,将其抛出非常容易:

  1. 找到一个合适的异常类;
  2. 创建这个类的一个对象;
  3. 将对象抛出;

一旦方法抛出异常,这个方法就不可能返回到调用者。也就是说,不必为返回默认值或错误代码担忧

String readData(Scanner in) throws EOFException{
    
    while(....){
        if(n<len){
            throw new EOFException();
        }
    }
    return s;
}

3.4、捕获异常

如果某个异常发生的时候没有在任何地方进行捕获,那么程序就会终止执行,并在控制台上打印出异常信息;其中包括异常的类型和堆栈的内容

try/catch语句块内容

try{
    code
    more code
    
    more  code
}catch (ExceptionType e){
    handler for this type
}

如果在try语句块中的任何代码中抛出了一个在catch子句中说明的异常类,那么

  1. 程序将跳过try语句块中的其余代码;
  2. 程序将执行catch子句中的处理器代码;
  3. 如果方法中的任何代码抛出一个在catch子句中没有声明的异常类型,那么这个方法就会立刻退出
public void read(String fileName){
    
    try{
        
        	InputStream in = new FileInputStream(fileName);
        	int b ;
        	while((b=in.read())!=-1){
                process input;
            }
    }catch(IOException exception){
        exception.printStackTrace();
    }
}

3.5、捕获多个异常

在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做不同的处理;可以按照下列方式为每个异常类型使用一个单独的catch子句:

try{
    code that might throw exceptions
}catch (FileNotFoundException e){
    
}catch (UnknownHostException e){
    emergency action for
}catch (IOException e){
    
}

在JavaSe中,同一个catch子句中可以捕获多个异常类型。例如,假设对应缺少文化部个未知主机的动作是一样的,就可以合并catch子句:

try{
    
}catch (FileNotFoundException|UnkownException e){
    
}catch (IOException e){
}

只有当捕获的异常类型彼此之间不存在子类关系时才需要这些特性

捕获多个异常时,异常变量隐含final变量;例如不能在以下子句中为e赋不同的值

catch (FileNotFoundException | UnkownHostException e){
    
}

3.6、再次抛出异常与异常链

在Catch子句中可以抛出一个异常,这样做的目的是改变异常的类型

如果开发了一个供其他程序员使用的子系统,那么用于表示子系统故障的异常类型可能会产生多种解释;ServletException就是这样一个异常类型的例子;执行Servlet的代码可能不想知道发生错误的细节原因,但希望明确的直到Servlet是否有问题:

try{
    access the database
}catch(SQLException e){
    throw new ServletException("database error:"+e.getMessage());
}

不过,可以有一种更好的处理方法,并且将原始异常设置为新异常的原因:

try{
    access the database
}catch(SQLException e){
    Throwable se = new ServletException("database error");
    se.initCause(e);
    throw se;
}
/**
	当捕获到异常时,就可以使用下面这条语句重新得到原始异常:
	Throwable e = se.getCause();
	
	强烈建议使用这种包装技术,这样可以让用户抛出子系统中的高级异常,而不丢失原始异常的细节。

**/

3.7、finally子句

当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行。

如果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题,Java中提供了很好的解决方案: finally子句

数据库,IO流

try{
    code that might throw exceptions
}catch (IOException e){
    show error message
}finally{
    in.close();
}

3.8、带资源的try语句

带资源的try语句(try -with-resources)的最简单形式为

try(Resource res=....){
    work with res
}

try块退出时,会自动调用res.close()。

3.9、使用异常机制的技巧

  1. 异常处理不能替代简单的测试: 异常的基本规则是:只在异常情况下使用异常机制

  2. 不要过分的细化异常

  3. 不要只抛出RuntimeException;应该寻找更加适当的子类或创建自己的异常类

  4. 不要只捕获Throwable异常,foe则会使得程序代码更难读,更难维护

  5. 考虑受查异常与非受查与非受查异常的区别,已检查异常本来就狠庞大,不要为逻辑错误抛出这些异常

  6. 将一种异常转换为另一种更加适合的异常时不要犹豫

  7. 早抛出,晚捕获

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值