2021-11-07 反射和注解

反射机制

1. 反射机制的作用

通过java语言中的反射机制可以操作字节码文件。优点类似于黑客。(可以读和修改字节码文件。)通过反射机制可以操作代码片段。(class文件。)

反射机制相关的重要的类

  • java.lang.Class: 代表整个字节码,代表一个类型,代表整个类。
  • java.lang.reflect.Method: 代表字节码中的方法字节码。代表类中的方法。
  • java.lang.reflect.Constructor: 代表字节码中的构造方法字节码。代表类中的构造方法。
  • java.lang.reflect.Field: 代表字节码中的属性字节码。代表类中的成员变量(静态变量)。

2 Class

获取class的三种方式

Class.forName(“完整类名带包名”)方法
  • 静态方法
  • 方法的参数是一个字符串。
  • 字符串需要的是一个完整类名。
  • 完整类名必须带有包名。java.Lang包也不能省略。
package com.reflect;

public class ReflectTest01 {
    public static void main(String[] args) {
        try {
            Class c1 = Class.forName("java.lang.String");
            Class c2 = Class.forName("java.lang.Integer");
            Class c3 = Class.forName("java.util.Date");
            Class c4 = Class.forName("java.lang.System");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}
对象.getclass()方法

java中任何一个对象都有一个方法:getclass()

//java中任何一个对象都有一个方法:getclass()
String s = "abc";
Class x = s.getClass();
System.out.println(x == c1);    //true
类型.class属性
package com.reflect;

import java.util.Date;

public class ReflectTest01 {
    public static void main(String[] args) {
        Class c1 = null;
        try {
            c1 = Class.forName("java.lang.String");
            Class c2 = Class.forName("java.lang.Integer");
            Class c3 = Class.forName("java.util.Date");
            Class c4 = Class.forName("java.lang.System");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //java中任何一个对象都有一个方法:getclass()
        String s = "abc";
        Class x = s.getClass();
        System.out.println(x == c1);    //true

        //第三种方式,java语言中任何一种类型,包括基本数据类型,它都有.class属性。
        Class z = String.class;//z代表string类型
        Class k = Date.class;//k代表Date类型
        Class f = int.class;//f代表int类型
        Class e = double.class;//e代表double类型
        System.out.println(x==z);

    }
}

通过反射实例化对象

package com.reflect;

public class ReflectTest02 {
    public static void main(String[] args) {
        //通过反射机制,获取CLass,通过CLass来实例化对象
        try {
            Class c = Class.forName("com.reflect.bean.User");

            //newInstance()这个方法会调用User这个类的无参数构造方法,完成对象的创建。
            //重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的!
            Object obj = c.newInstance();
            System.out.println(obj);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }
}

验证反射机制的灵活性

java代码写一遍,再不改变java源代码的基础之上,可以做到不同对象的实例化。非常之灵活。(符合OCP开闭原则:对扩展开放,对修改关闭。)

后期你们要学习的是高级框架,而工作过程中,也都是使用高级框架,包括:ssh ssm
Spring SpringMVC MyBatis
Spring Struts Hibernate

这些高级框架底层实现原理:都采用了反射机制。所以反射机制还是重要的。学会了反射机制有利于你理解剖析框架底层的源代码。

package com.reflect;

import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

/*
验证反射机制的灵活性
 */
public class ReflectTest03 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {

        //以下代码是灵活的,代码不需要改动,可以修改配置文件,配置文件修改之后,可以创建出不同的实例对象。
        //通过IO流读取classinfo.properties文件
        FileReader reader = new FileReader("classinfo.properties");
        //创建属性类对象Map
        Properties pro = new Properties();
        //加载
        pro.load(reader);
        //关闭流
        reader.close();
        //通过key获取value
        String className = pro.getProperty("className");
        System.out.println(className);
        //通过反射机制实例化对象
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        System.out.println(obj);
    }

}

CLass.forName()发生了什么?

重点
如果只是希望一个类的静态代码块执行,其它代码一律不执行,可以使用:

CLass.forName("完整类名");

这个方法的执行会导致类加载,类加载时,静态代码块执行。

提示:

JDBC技术可能需要。

获取类路径下的文件绝对路径

package com.reflect;

import java.io.FileNotFoundException;
import java.io.FileReader;

public class AboutPath {
    public static void main(String[] args) throws FileNotFoundException {
        //这种方式的路径缺点是:移植性差,在IDEA中默认的当前路径是project的根。
        //这个代码假设离开了IDEA,换到了其它位置,可能当前路径就不是project的根了,这时这个路径就无效了。
        //FileReader reader = new FileReader("classinfo2.properties");

        //通用方式
        //只适用文件在类路径下,在src下的都是在类路径下
        //src是类的根路径

        /*解释:
        Thread.currentThread()当前线程对象
        getContextClassLoader()是线程对象的方法,可以获取到当前线程的类加载器对象。
        getResource()【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。*/

        ///D:/OneDrive%20-%20guyuer/Java/JavaSE/out/production/JavaSE/classinfo.properties
        String path = Thread.currentThread().getContextClassLoader().getResource("classinfo.properties").getPath();
        System.out.println(path);

        String path1 = Thread.currentThread().getContextClassLoader()
                .getResource("com/reflect/bean/db.properties").getPath();
        System.out.println(path1);
        ///D:/OneDrive%20-%20guyuer/Java/JavaSE/out/production/JavaSE/com/reflect/bean/db.properties
    }
}

以流的方式返回

package com.reflect;

import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class IoPropertiesTest {
    public static void main(String[] args) throws IOException {
        //获取一个文件的绝对路径了!!!!!
        /*String path=Thread.currentThread().getContextclassLoader()
                .getResource("classinfo2.properties").getPath();

        FileReader reader = new FileReader(path);*/
        //直接以流的形式返回。
        InputStream reader = Thread. currentThread(). getContextClassLoader()
                .getResourceAsStream("classinfo.properties");
        Properties pro = new Properties();
        pro.load(reader);
        reader.close();
        //通过key获取value
        String className = pro.getProperty("className");
        System.out.println(className);
    }
}

资源绑定器

package com.reflect;

import java.util.ResourceBundle;

/*
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。
使用以下这种方式的时候,属性配置文件xxx.properties必须放到类路径下。
 */
public class ResourceBundleTest {
    public static void main(String[] args) {
        //资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。文件扩展名也必须是properties
        //并且在写路径的时候,路径后面的扩展名不能写。
        ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
        String className = bundle.getString("className");
        System.out.println(className);
    }
}

类加载器

关于JDK中自带的类加载器:

  1. 什么是类加载器?

    专门负责加载类的命令/工具。

    ClassLoader

  2. JDK中自带了3个类加载器

    • 启动类加载器
    • 扩展类加载器
    • 应用类加载器
  3. 假设有这样一段代码:

    string s = " abc " ;
    

    代码在开始执行之前,会将所需要类全部加载到JVM当中。

    通过类加载器加载,看到以上代码类加载器会找string.class文件,找到就加载,那么是怎么进行加载的呢?

    • 首先通过、启动类加载器"加载。

    注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar

    rt.jar中都是JDK最核心的类库。

    • 如果通过“启动类加载器"加载不到的时候,会通过“扩展类加载器“加载。

    注意:扩展类加载器专门加载:c:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\*.jar

    • 如果,扩展类加载器"没有加载到,那么会通过应用类加载器”加载。

    注意:应用类加载器专门加载:classpath中的类。

双亲委派机制

java中为了保证类加载的安全,使用了双亲委派机制。优先从启动类加载器中加载,这个称为父",“父"无法加载到,再从扩展类加载器中加载,这个称为"母”。

双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。

3 Method

可变长度参数

可变长度参数

int... args
//语法是:类型...(注意:一定是3个点。)
package com.reflect;
/*
可变长度参数
int...args这就是可变长度参数
语法是:类型...(注意:一定是3个点。)
 */
public class ArgsTest {
    public static void main(String[] args) {
        m();
        m(10);
        m(10,20);
        m(10,20,30,40); //只能int

        String[] strs = {"a","b","c"};
        //也可以传1个数组
        m2(strs);
    }
    public static void m(int... args){
        System.out.println("m方法执行了");
    }
    public static void m2(String... args){
        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i]);
        }

    }
}

用法(了解)

类比Field

通过反射机制调用对象方法(重点)

重点:必须掌握,通过反射机制怎么调用一个对象的方法?

反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,但是java代码不需要做任何改动。这就是反射机制的魅力。|

package com.reflect;

import com.reflect.bean.UserService;

import java.lang.reflect.Method;

public class ReflectTest10 {
    public static void main(String[] args) throws Exception {
        //不使用反射机制,怎么调用方法
        //创建对象
        UserService userService = new UserService();
        //调用方法
        /*要素分析:
        要素1:对象UserService
        要素2:Login方法名
        要素3:实参列表
        要素4:返回值*/
        boolean loginSuccess = userService.login("admin","123");
        //System.out.println(LoginSuccess);
        System.out.println(loginSuccess?"登录成功":"登录失败");

        //使用反射机制来调用一个对象的方法该怎么做?
        Class userServiceClass = Class.forName("com.reflect.bean.UserService");
        //创建对象
        Object obj = userServiceClass . newInstance();
        //获取Method
        Method loginMethod = userServiceClass . getDeclaredMethod ("login",String.class,String.class);
        
        /*
        四要素:
        LoginMethod方法
        obj对象
        "admin","123"实参
        retValue 返回值
        */
        //调用方法
        Object retValue = loginMethod.invoke(obj, "admin", "123");
        System.out.println(retValue);

    }
}

4 Constructor

Constructor的使用

反编译类的Constructor

package com.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

/*
反编译类的Constructor
 */
public class ReflectTest11 {
    public static void main(String[] args) throws ClassNotFoundException {
        StringBuilder s = new StringBuilder();
        Class vipClass = Class.forName("com.reflect.bean.Vip");
        s.append(Modifier.toString(vipClass.getModifiers()));
        s.append(" class ");
        s.append(vipClass.getSimpleName());
        s.append("{\n");

        //拼接构造方法
        Constructor[] constructors = vipClass.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            s.append("\t");
            s.append(Modifier.toString(constructor.getModifiers()));
            s.append(" ");
            s.append(vipClass.getSimpleName());
            s.append("(");
            Class[] parameterTypes = constructor.getParameterTypes();
            for (Class parameterType : parameterTypes) {
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            if (parameterTypes.length > 0) {
                s.deleteCharAt(s.length() - 1);
            }
            s.append("){}\n");
        }
        s.append("}");
        System.out.println(s);
    }
}

/*
package com.reflect.bean;

public class Vip {
    int no;
    String name;
    String birth;
    boolean sex;

    public Vip(){

    }

    public Vip(int no) {
        this.no = no;
    }

    public Vip(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public Vip(int no, String name, String birth) {
        this.no = no;
        this.name = name;
        this.birth = birth;
    }

    public Vip(int no, String name, String birth, boolean sex) {
        this.no = no;
        this.name = name;
        this.birth = birth;
        this.sex = sex;
    }
}
 */

通过反射机制调用构造方法

package com.reflect;

import com.reflect.bean.Vip;

import java.lang.reflect.Constructor;
//通过反射机制调用构造方法实例化java对象。
public class ReflectTest12 {
    public static void main(String[] args) throws Exception {
        //不使用反射机制怎么创建对象
        Vip v1 = new Vip();
        Vip v2 = new Vip(1111, "yy");
        System.out.println(v2);
        //使用反射机制怎么创建对象呢?
        Class c = Class.forName("com.reflect.bean.Vip");
        //调用无参数构造方法
        c.newInstance();
        //调用有参数的构造方法怎么办?
        //第一步:先获取到这个有参数的构造方法
        Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);
        Object v3 = con.newInstance(110, "gg", "1990-1-1", true);
        System.out.println(v3);
        //获取无参数构造方法
        Constructor con2 = c.getDeclaredConstructor();
        Object v4 = con2.newInstance();
        System.out.println(v4);
    }
}

获取父类和父接口

package com.reflect;
/*
重点:给你一个类,怎么获取这个类的父类,已经实现了哪些接口?
 */
public class ReflectTest13 {
    //String举例
    public static void main(String[] args) throws Exception {
        Class stringClass = Class.forName("java.lang.String");
        //获取string的父类
        Class superClass  = stringClass.getSuperclass();
        System.out.println(superClass.getName());
        //获取string类实现的所有接口(一个类可以实现多个接口。)
        Class[] interfaces = stringClass.getInterfaces();
        for (Class in : interfaces){
            System.out.println(in.getName());
        }

    }
}

5 Field

field:属性

package com.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class ReflectTest05 {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取整个类
        Class studentClass = Class.forName("com.reflect.bean.Student");
        //获取类名
        String className = studentClass.getName();
        System.out.println("完整类名"+className);
        String simpleName = studentClass.getSimpleName();
        System.out.println("简类名"+simpleName);

        //获取类中所有的Field,只能获得public修饰的属性
        Field[] fields = studentClass.getFields();
        System.out.println(fields.length);      //1
        //取出这个Field
        Field field = fields[0];
        //获取field的名字
        String fieldName = field.getName();
        System.out.println(fieldName);  //no


        //获取所有的属性
        Field[] declaredFields = studentClass.getDeclaredFields();
        System.out.println(declaredFields.length);      //4
        System.out.println("=================================");
        for(Field fd : declaredFields){
            //获取属性修饰符列表
            int modifiers = fd.getModifiers();  //返回修饰符的代号(数字)
            String s = Modifier.toString(modifiers);
            System.out.print(s+"  ");

            //获取属性类型
            Class fdType = fd.getType();
            String fdTypeName = fdType.getSimpleName();
            System.out.print(fdTypeName+"   ");
            //获取属性名字
            System.out.println(fd.getName());
        }

    }
}
/*
package com.reflect.bean;
//反射属性:字段
public class Student {
    public int no;
    private String name;
    protected int age;
    boolean sex;
    public static final double MATH_PI = 3.1415926;
}

 */

反射属性

反编译

package com.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/*
反编译
反射机制的强大功能
 */
public class ReflectTest06 {
    public static void main(String[] args) throws ClassNotFoundException {
        //准备拼接字符串
        StringBuilder s = new StringBuilder();

        //Class studentClass  = Class.forName("com.reflect.bean.Student");
        //Class studentClass = Class.forName("java.lang.String");
        Class studentClass = Class.forName("java.lang.Integer");


        s.append(Modifier.toString(studentClass.getModifiers())+" class "+studentClass.getSimpleName()+" {\n");
        Field[] fields = studentClass.getDeclaredFields();
        for (Field fd : fields){
            s.append("\t");
            s.append(Modifier.toString(fd.getModifiers()));
            s.append(" ");
            s.append(fd.getType().getSimpleName());
            s.append(" ");
            s.append(fd.getName());
            s.append(";\n");
        }

        s.append("}");
        System.out.println(s);
    }
}

反射获取和赋值

package com.reflect;

import java.lang.reflect.Field;

/*
必须掌握:
    怎么通过反射机制访问一个java对象的属性?
    给属性赋值set
    获取属性的值get
 */
public class ReflectTest07 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Class studentClass  = Class.forName("com.reflect.bean.Student");
        Object obj = studentClass.newInstance();
//        Field[] fields = studentClass.getDeclaredFields();

        //获取no属性
        Field no = studentClass.getField("no");

        //属性赋值
        /*虽然使用了反射机制,但是三要素还是缺一不可:
        要素1:obj对象
        要素2:no属性
        要素3:2222值
        注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
         */
        no.set(obj,2222);

        //获取属性值
        System.out.println(no.get(obj));

        //私有属性,打破封装才能get,(反射机制的缺点:打破封装,可能会给不法分子留下机会!!!)
        Field name = studentClass.getDeclaredField("name");
        name.setAccessible(true);
        name.set(obj,"gu");
        System.out.println(name.get(obj));
    }
}

注解

1 注解的定义和使用

定义

注解,或者叫做注释类型,英文单词是:Annotation

注解Annotation是一种引用数据类型。编译之后也是生成xxx.class文件。

怎么自定义注解呢?语法格式?

[修饰符列表]@interface注解类型名{

}

package com.annotation;

public @interface MyAnnotation {

}

使用

注解怎么使用,用在什么地方?

第一:注解使用时的语法格式是:

@注解类型名

第二:注解可以出现在类上、属性上、方法上、变量上等…

注解还可以出现在注解类型上。

package com.annotation;
@MyAnnotation
public class AnnotationTest01 {
    @MyAnnotation
    private int no;

    @MyAnnotation
    public static void m1(){
        @MyAnnotation
        int i = 100;
    }

    @MyAnnotation
    public void m2(){

    }
}
@MyAnnotation
interface MyInterface{

}
@MyAnnotation
enum Season{
    SPRING,SUMMER,AUTUMN,WINTER
}

2 JDK内置的注解

java.lang包下的注释类型:

掌握:
Deprecated用@Deprecated注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。

掌握:
override表示一个方法声明打算重写超类中的另一个方法声明。

不用掌握:
suppresswarnings 指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。

override注解-重写

关于JDK Lang包下的override注解
源代码:
public @interface Override {
}
标识性注解,给编译器做参考的。
编译器看到万法上有这个注解的时候,编译器会自动检查该万法是否重写了父类的万法。
如果没有重写,报错。
这个注解只是在编译阶段起作用,和运行期无关!

Deprecated注解-已过时

package com.annotation;

//表示这个类已过时。
public class AnnotationTest03 {
    public static void main(String[] args) {
        AnnotationTest03 at = new AnnotationTest03();
        at.doSome();
    }

    @Deprecated
    public void doSome() {
        System.out.println("do something!");
    }

    //Deprecated这个注解标注的元素已过时。
    //这个注解主要是向其它程序员传达一个信息,告知已过时,有更好的解决方案存在。
    @Deprecated
    public static void doOther() {
        System.out.println("do other...");
    }
}
class T {
    public static void main(String[] args) {
        AnnotationTest03 at = new AnnotationTest03();
        at.doSome();
    }
}

元注解

标注注解类型的注解

常见的元注解有哪些?
  • Target

  • Retention

关于Target注解:

这是一个元注解,用来标注"注解类型"的"注解"

这个Target注解用来标注被标注的注解”可以出现在哪些位置上。

@Target(ElementType.METHOD):表示被标注的注解"只能出现在方法上。

关于Retention注解:

这是一个元注解,用来标注"注解类型"的"注解"

这个Retention注解用来标注被标注的注解”最终保存在哪里。

@Retention(RetentionPolicy.SOURCE):注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;

@Retention(RetentionPolicy.CLASS):注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;

@Retention(RetentionPolicy.RUNTIME):注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

3 自定义注解

使用属性

package com.annotation2;

public class MyAnnotationTest01 {
    //报错的原因:如果一个注解当中有属性,那么必须给属性赋值。(除非该属性使用default指定了默认值)
    /*
    @MyAnnotation
    public void doSome(){

    }
    */
    //指定name属性的值就好了。
    @MyAnnotation(name = "zhangsan",color = "red")
    public void doSome(){

    }
}
/*
package com.annotation2;

public @interface MyAnnotation {
    /**
     * 我们通常在注解当中可以定义属性,以下这个是MyAnnotation的name属性。
     * 看着像1个方法,但实际上我们称之为属性name。
     * @return
     */
/*
    String name();
    String color();
    int age() default 20;   //指定默认值,可以在用用注解的地方写,也可以不写默认
        }

 */

value属性

只有一个属性,且属性名是value时,使用时可以省略属性名

@MyAnnotation(value="hehe")
public void doSome(){

}

@MyAnnotation("haha")
public void doother(){
    
}

注解属性的类型

属性的类型可以是:

byte short int Long float double boolean char String Class 枚举类型,以及以上每一种的数组形式。

如果数组中只有1个元素:大括号可以省略。

判断类上面是否有注解

//判断类上面是否有@MyAnnotation
System.out.println(c.isAnnotationPresent(MyAnnotation.class));
if(c.isAnnotationPresent(MyAnnotation.class)){
	//获取该注解对象
	MyAnnotation myAnnotation=(MyAnnotation)c.getAnnotation(MyAnnotation.class);
	System.out.println("类上面的注解对象"+myAnnotation);
}

4 实际开发注解作用

需求:
假设有这样一个注解,叫做:@Id

这个注解只能出现在类上面,当这个类上有这个注解的时候,

要求这个类中必须有一个int类型的id属性。如果没有这个属性

就报异常。如果有这个属性则正常执行!

package com.annotation7;

import java.lang.reflect.Field;

public class Test {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("com.annotation7.User");
        //判断类上是否存在Id注解
        boolean isRight = false;
        if(c.isAnnotationPresent(Id.class)){
            Field[] declaredFields = c.getDeclaredFields();
            for (Field file : declaredFields){
                if("id".equals(file.getName()) && "int".equals(file.getType().getSimpleName())){
                    //表示这个类是合法的类。有@Id注解,则这个类中必须有int类型的id
                    isRight = true;
                    break;
                }
            }
            //判断
            if (!isRight){
                throw new HasNotIdPropertyException("被Id注解标注的类中必须有一个int类型的id属性");
            }
        }
    }
}
/*
package com.annotation7;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//只能出现在类上
@Target(ElementType.TYPE)
//可以被反射机制读到
@Retention(RetentionPolicy.RUNTIME)
public @interface Id {
}

package com.annotation7;
@Id
public class User {
    //int id;
    String name;
    String password;

}

package com.annotation7;

public class HasNotIdPropertyException extends RuntimeException{
    public HasNotIdPropertyException() {
    }

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

 */

!!JavaSE部分完结!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值