Java——反射

Java反射机制详解
本文深入解析Java反射机制,涵盖Class类实例化、反射与工厂模式、类操作、反射调用构造方法、普通方法及属性等内容,同时介绍ClassLoader类加载器与动态代理的应用。

目录

1.初识反射

1.1 Class类实例化对象

1.2 反射与工厂模式

2.反射与类操作

2.1 取得父类、父接口信息

2.2 反射调用构造方法

2.3 反射调用普通方法

2.4 反射调用类中属性

3.反射与普通类

3.1 反射与单级VO操作

4.ClassLoader类加载器

4.1 认识ClassLoader

4.2 类加载器双亲委派模型

5. 动态代理


1.初识反射

反射指的是对象的反向处理操作,根据实例化的对象,倒推出对象身后的组成(类、构造、普通、成员等)

  • 反射核心处理在于Object类的方法——取得类的Class对象:

该方法返回的是一个Class类对象,这个Class类描述的就是各个类的组成(构造方法、普通方法、普通属性)。

public final native Class<?> getClass();
import java.util.Date;

/**
 * 初识反射
 * Author: qqy
 */
public class Test {
    public static void main(String[] args) {
        //1.普通的一个对象,通过java.util.Date这个类实例化的
        Date date = new Date();
        //2.反向操作,反射
        Class classz = date.getClass();
        //调用toString()
        System.out.println(classz);
    }
}
  • 1.1 Class类实例化对象

任何一个类的Class对象由JVM加载类后产生,用户只能调用指定方法来取得该对象。

Class类对象的产生模式一共有三种:

  1. 任何类的实例化对象可以通过Object类中的getClass()方法取得Class类对象。

  2. "类名称.class":直接根据某个具体的类来取得Class类的实例化对象。

  3. 使用Class类提供的静态方法,传入类的全名称来取得其Class对象:

    public static Class<?> forName(String className) throws ClassNotFoundException 
import java.util.Date;

/**
 * Class对象获取
 * 1. object.getClass
 * 2. ClassName.class
 * 3. Class.forName("className");
 * Author: qqy
 */
public class Test1 {
    public static void main(String[] args) {

        Date date = new Date();

        //1.
        Class classz = date.getClass();

        //一般在开发反射的代码中我们经常使用类的全限定名(包名+类名)
        //2.
        Class<java.util.Date> dateClass = java.util.Date.class;
        System.out.println(dateClass);        //class java.util.Date
        try {
            Date date2 = dateClass.newInstance();
            System.out.println(date2);        //Sat Jan 19 08:32:58 CST 2019
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        //3.
        Class dateClass1 = null;
        try {
            //注意使用全限定名
            dateClass1 = Class.forName("java.util.Date");
            System.out.println(dateClass1);    //class java.util.Date
        //类找不到异常
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //当前这三个Class对象都是由Class<java.util.Date>获取,都来描述
        //java.util.Date这个类
        System.out.println("比较三个Class对象");
        System.out.println(classz == dateClass);   //true
        System.out.println(classz == dateClass1);  //true

        //true原因:
        //Class类是描述我们在Java中定义的类
        //Class类的对象是Class文件加载到JVM中的标识对象(类加载器)
        //Class类的对象在JVM中有且只有一个
    }
}
  • 除了第一种方法会产生Date类的实例化对象之外,其他的两种都不会产生 Date类的实例化对象,只是取得一个类的Class对象。可以通过反射实例化对象:
//Class类提供 —— 只能调用无参数构造方法,且无参构造必须是public权限(必须有无参构造方法)
public T newInstance()
        throws InstantiationException, IllegalAccessException

//Constructor类提供 —— 类中可以没有无参数构造方法,可以调用类中其他有参构造,直接传入参数值
public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
  • 1.2 反射与工厂模式

传统工厂类在实际开发之中,每增加一个接口的子类就需要修改工厂类。

如果要想解决关键字new带来的问题,最好的做法就是通过反射来完成处理,因为Class类可以使用newInstance() 实例化对象,同时Class.forName()能够接收类名称。

  • 引入反射后,每当新增接口子类,无需更改程序,只需修改参数就好。(简单工厂模式)
/**
 * 水果接口
 * Author: qqy
 */
public interface IFruit {
    void eat();
}

public class Apple implements IFruit{
    @Override
    public void eat() {
        System.out.println("[Apple] 吃苹果 ");
    }
}

public class Orange implements IFruit {
    @Override
    public void eat() {
        System.out.println("[Orange] 吃橘子 ");
    }
}

/**
 * 传统工厂模式
 * Author: qqy
 */
public class IFruitFactory {
    private IFruitFactory() {

    }

    public static IFruit getIFruit(String fruitName) {
        if ("apple".equalsIgnoreCase(fruitName)) {
            return new Apple();
        }
        if ("orange".equalsIgnoreCase(fruitName)) {
            return new Orange();
        }
        throw new IllegalArgumentException(fruitName + " not found");
    }
}

/**
 * 简单工厂模式(反射)
 * Author: qqy
 */
public class NewIFruitFactory {

    private NewIFruitFactory() {

    }

       //较传统工厂模式,将参数变为类名
    public static IFruit getIFruit(String className) {
        try {
            //取得任一子类反射对象
            Class c = Class.forName(className);
            //通过反射实例化对象
            //c.newInstance()是Object类
            return (IFruit) c.newInstance();
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException(className + " not found.");
        } catch (IllegalAccessException | InstantiationException e) {
            throw new RuntimeException("Instance " + className + " failed");
        }

    }
}

/**
 * 测试
 * Author: qqy
 */
public class TestFruitFactory {
    public static void main(String[] args) {
        if (args.length == 1) {
            String fruitName = args[0];
            //传统工厂模式
            //IFruit fruit = IFruitFactory.getIFruit(fruitName);
            
            //简单工厂模式
            IFruit fruit = NewIFruitFactory.getIFruit(fruitName);
            fruit.eat();
        } else {
            System.out.println("程序没有输入参数");
        }
    }
}

2.反射与类操作

利用反射可以做出一个对象具备的所有操作行为,所有操作都可以基于Object进行。

  • 2.1 取得父类、父接口信息

  1. 获取Class对象所描述的类的包名称 —— public Package getPackage();
  2. 获取Class对象所描述的类的继承的类 —— public native Class<? super T> getSuperclass(); 
  3. 获取Class对象所描述的类的实现的接口 —— public Class<?>[ ] getInterfaces();

接口返回的是数组,而类返回的只有一个 —— ∵单继承,多实现

/**
 * 取得父类信息
 * Author: qqy
 */
public class Test2 {
    public static void main(String[] args) {
        //1.获取Class对象所描述的类的包
        //2.获取Class对象所描述的类的继承的类
        //3.获取Class对象所描述的类的实现的接口

        try {
            Class testClass = Class.forName("com.qqy.reflect.Test4");

            //1.
            Package packageObj = testClass.getPackage();
            System.out.println("打印包名:");
            System.out.println(packageObj.getName());//包名  com.qqy.reflect

            //2.
            //getSuperclass()返回值只有一个 —— 单继承
            Class superclass = testClass.getSuperclass();
            System.out.println("打印父类toString()");
            System.out.println(superclass);        //class com.qqy.reflect.Test2
            //打印父类SimpleName(类名):Test2
            System.out.println("打印父类SimpleName:" + superclass.getSimpleName());  
            //打印父类Name(全限定名):com.qqy.reflect.Test2
            System.out.println("打印父类Name:" + superclass.getName()); 

            //3.
            Class[] classes = testClass.getInterfaces();
            System.out.println("打印实现的接口:");
            //interface com.qqy.reflect.IMessage
            //interface com.qqy.reflect.IFruit
            for (Class c : classes) {
                System.out.println(c);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

interface IMessage {

}

interface IFruit {

}

//隐式extends Object
class Test3 extends Test2 implements IMessage, IFruit {

}
  • 2.2 反射调用构造方法

Constructor —— 描述类中构造方法

  • 取得指定参数类型的构造:
//只能取得本类中public权限的指定参数类型构造方法
public Constructor<T> getConstructor(Class<?>... parameterTypes)        
        throws NoSuchMethodException, SecurityException

//可以取得本类中全部指定参数类型构造方法,包含私有构造
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException
  • 取得类中的所有构造
//只能取得本类中public权限的构造方法
public Constructor<?>[] getConstructors() throws SecurityException

//可以取得本类中全部构造方法,包含私有构造
public Constructor<?>[] getDeclaredConstructors() throws SecurityException 

import java.lang.reflect.Constructor;

/**
 * getConstructors与getDeclaredConstructors
 * Author: qqy
 */
public class Test4 {
    public static void main(String[] args) {
        try {
            Class classes = Class.forName("com.qqy.reflect.Person");

            Constructor[] constructors = classes.getConstructors();
            System.out.println("打印输出Person类的构造方法:");
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }

            System.out.println("-------------------");

            Constructor[] constructors1=classes.getDeclaredConstructors();
            for (Constructor constructor1 : constructors1) {
                System.out.println(constructor1);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class Person {

    public String name;
    private Integer age;

    //无参构造方法
    private Person() {
    }

    //一个参数构造方法
    protected Person(String name) {
        this.name = name;
    }

    //两个参数构造方法
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

 

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 反射调用构造方法
 * Author: qqy
 */
public class Test4 {
    public static void main(String[] args) {
        try {
            Class classes = Class.forName("com.qqy.reflect.Person");

            //1.构造方法
            //至少有一个构造方法
            Constructor[] constructors = classes.getConstructors();
            System.out.println("打印输出Person类的构造方法:");
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }

            System.out.println("打印输出Person类的一个参数的构造方法:");
            System.out.println(classes.getConstructor(java.lang.String.class));

            //2. class.newInstance => 直接通过类实例化对象 —— 类里面有无参数构造方法
            //Person();
            System.out.println((Person) classes.newInstance());

            //Person(String name)
            //3. Constructor => 获取构造方法对象,执行构造对象的newInstance(参数值...)
            Constructor constructor = classes.getConstructor(java.lang.String.class);
            //constructor.newInstance —— 类中可以没有无参数构造方法,直接传入参数值
            Person person = (Person) constructor.newInstance("张三");
            System.out.println(person);
        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

class Person {

    public String name;
    private Integer age;

    //无参构造方法
    public Person() {
    }

    //一个参数构造方法
    public Person(String name) {
        this.name = name;
    }

    //两个参数构造方法
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 2.3 反射调用普通方法

  • 后续可考虑SpEL调用
  • 取得全部普通方法:
//取得本类以及父类中所有public方法
public Method[] getMethods() throws SecurityException

//取得本类中所有普通方法,包含私有方法
public Method[] getDeclaredMethods() throws SecurityException
  • 取得指定名称普通方法:
//既传名称,也要传参数类型 —— 存在方法重载
public Method getMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException 

//
public Method getDeclaredMethod(String name, Class<? >... parameterTypes) 
        throws NoSuchMethodException, SecurityException
  • Method类中提供调用类中普通方法 —— invoke():
public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,InvocationTargetException

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 反射调用普通方法
 * Author: qqy
 */
public class Test5 {
    public static void main(String[] args) {
        try {
            Class classes = Class.forName("com.qqy.reflect.Person");

            //1.获取Person类中的所有普通方法
            Method[] methods = classes.getMethods();
            for (Method m : methods) {
                System.out.println(m);
            }

            //2.获取Person类中的setName方法
            Method setNameMethod = classes.getMethod("setName", java.lang.String.class);

            //3.调用方法
            Person person = (Person) classes.newInstance();
            //set()返回值是void
            Object voidObject = setNameMethod.invoke(person, "张三");
            System.out.println("setName=" + voidObject);  //setName=null
            System.out.println(person);                   //Person{name='张三', age=null}
            //获取Person类中的getName方法
            Method getNameMethod = classes.getMethod("getName");
            Object stringObject = getNameMethod.invoke(person);
            System.out.println("getName=" + stringObject);   //getName=张三
            System.out.println(person);
        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
  • 2.4 反射调用类中属性

在Class类中提供有两组取得属性的方法:

  1. 第一组-取得类中全部可访问公开属性(包括父类):
    public Field[] getFields() throws SecurityException 
  2. 第一组(父类中)-取得类中指定名称属性:
    public Field getField(String name) throws NoSuchFieldException, SecurityException 
  3. 第二组(本类中)-取得类中全部属性:
    public Field[] getDeclaredFields() throws SecurityException 
  4. 第二组(本类中)-取得类中指定名称属性 :
    public Field getDeclaredField(String name)
            throws NoSuchFieldException, SecurityException 
    
  • java.lang.reflect.Field 类中有两个重要方法:
  1. 设置属性内容 :
    public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException 
  2. 取得属性内容 :
    public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException
  • 2.4.1 Java反射继承结构

在AccessibleObject类中提供有一个方法——动态设置封装(不安全):

     Constructor、Method、Field类都是AccessibleObject子类

     在本次JVM进程中有效且只能通过反射调用

public void setAccessible(boolean flag) throws SecurityException

在Field类之中有一个特别有用的方法——取得属性类型:

public Class<?> getType()
import java.lang.reflect.Field;

/**
 * 反射调用类中属性
 * Author: qqy
 */
public class Test4 {
    public static void main(String[] args) {
        try {
            Class classes = Class.forName("com.qqy.reflect.Student1");
            //获取属性 public
            System.out.println("输出Student类的所有公开属性:");
            for (Field f : classes.getFields()) {
                System.out.println(f);
            }

            System.out.println("输出Student类的指定公开属性:");
            Field major1 = classes.getField("name");
            System.out.println(major1);

            System.out.println("输出Student类的本类声明的属性");
            for (Field f : classes.getDeclaredFields()) {
                System.out.println(f);
            }

            Object obj=classes.newInstance();
            System.out.println("获取指定属性");
            Field major = classes.getDeclaredField("major");
            System.out.println(major);

            //Student一个对象
            Student1 student = (Student1) classes.newInstance();
            System.out.println("实例化后的信息:" + student);

            //major是私有属性,本不能更改值,但是可以强制访问
            //在一次JVM进程中有效,当JVM关闭后,即使再次开启,仍无效
            major.setAccessible(true);
            major.set(student, "信息与计算科学");

            System.out.println("通过major Field赋值之后:" + student);
            Object value = major.get(student);
            System.out.println("通过major Field获取值:" + value);

            //在一次JVM进程中,setAccessible一直有效
            major.set(student, "网络工程");

            System.out.println("通过major Field赋值之后:" + student);
            Object value1 = major.get(student);
            System.out.println("通过major Field获取值:" + value1);


            //获取Field类型
            System.out.println(major.getType());

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

class Student1 extends Person {

    private String teacher;

    public String major;

    public String getTeacher() {
        return teacher;
    }

    public void setTeacher(String teacher) {
        this.teacher = teacher;
    }

    public String getMajor() {
        return major;
    }

    public void setMajor(String major) {
        this.major = major;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", teacher='" + teacher + '\'' +
                ", major='" + major + '\'' +
                "} " + super.toString();
    }
}

3.反射与普通类

  • 3.1 反射与单级VO操作

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 利用反射,调用一次set,给所有属性赋值
 * emp.name:张三|emp.age:18
 * Author:qqy
 */
public class Test8 {
    public static void main(String[] args) {
        String value = "emp.name:张三|emp.job:老师";
        EmpAction empAction = new EmpAction();
        empAction.setValue(value);
        System.out.println(empAction.getEmp());
    }
}

class Emp {
    private String name;
    private String job;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "name='" + name + '\'' +
                ", job='" + job + '\'' +
                '}';
    }
}

class EmpAction {
    private Emp emp = new Emp();

    public void setValue(String value) {
        BeanOperation.setBeanValue(this, value);
    }

    public Emp getEmp() {
        return emp;
    }
}

class BeanOperation {
    private BeanOperation() {

    }

    /**
     * 取得真实操作的类对象
     *
     * @param actionObject xxAction对象
     * @param value        emp.name:张三|emp.job:老师
     */
    public static void setBeanValue(Object actionObject, String value) {
        //1.字符串拆分
        String[] temp = value.split("\\|");
        for (int i = 0; i < temp.length; i++) {
            String[] result = temp[i].split(":");
            //取得真实设置的内容
            String realValue = result[1];
            //取得真实类名称
            String realClassName = result[0].split("\\.")[0];
            //取得要设置的属性名称
            String attrName = result[0].split("\\.")[1];

            //通过反射取得xxAction中的真实对象
            //取得了emp对象
            Object realObj = getRealObject(actionObject, realClassName);

            //取得真实类(Emp)class对象
            Class<?> classz=realObj.getClass();
            //获得setEmp方法名
            String setName="set"+initCap(attrName);
            try {
                Method setMethod=classz.getDeclaredMethod(setName,String.class);
                setMethod.invoke(realObj,realValue);
            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    private static Object getRealObject(Object actionObj, String realClassName) {
        //取得action对象的class对象
        Class<?> classz = actionObj.getClass();
        //获得getEmp方法名
        String methodName = "get" + initCap(realClassName);
        Object realObj = null;
        //取得Method对象
        try {
            //取得getEmp的Method对象
            Method method = classz.getDeclaredMethod(methodName);
            //相当于调用了empAction.getEmp();
            realObj = method.invoke(actionObj);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return realObj;
    }

    //首字母大写
    private static String initCap(String str) {
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}

4.ClassLoader类加载器

classpath:类加载路径

  • 4.1 认识ClassLoader

类加载器:通过一个类的全名称来获取此类的二进制字节流,实现这个操作的代码模块称为类加载器。

把*.class加载到JVM中

public class TestClassLoader {
    public static void main(String[] args) {
        Class classz = TestClassLoader.class; //Class类的对象
        System.out.println(classz.getClassLoader());//类加载器
        System.out.println(classz.getClassLoader().getParent());
        System.out.println(classz.getClassLoader().getParent().getParent());
    }
}
  • 4.1.1 JDK中内置的三大类加载器

1. BootstrapClassLoader —— 启动类加载器:

     a. 使用C++实现,是JVM的一部分,其他所有类加载器均使用Java实现。 

     b. 负责将存放于Java_HOME \ lib目录下的能被JVM识别的类库(rt.jar - 存放了Java所有基础类库,java.lang,java.util)加载到JVM中。

     c. 启动类加载器无法被Java程序直接引用。

2. ExtClassLoader —— 扩展类加载器:

     a. 使用Java实现,并且可以被Java程序直接引用。

     b. 加载Java_HOME \ lib \ ext目录下能被识别的类库。

3. AppClassLoader —— 应用程序类加载器

     a. 负责加载用户路径(classpath)上指定的类库

     b. 如果应用程序中没有自定义类加载器,则此类加载器就是Java程序中默认的类加载器。

  • 4.2 类加载器双亲委派模型

  • 4.2.1 类加载器关系

  • 4.2.2 双亲委派模型 —— JDK1.2引入

JDK内置的三种类加载器与用户自定义类加载器之间的层次关系称为类加载器的双亲委派模型。

  • 要求: 除了顶层的父类加载器外,其余的类加载器都应有自己的父类加载器
  • 执行流程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载此类,而是把类加载请求委托给父类加载器完成,每一个层次类加载器均是如此。
  • 只有当父类加载器无法完成加载请求时(在自己搜索范围内没有找到此类),子加载器才会尝试自己去加载。
  • 双亲委派模型保证Java程序稳定运行。Java中基础类库一定由顶层Bootstrap类加载器加载。因此,诸如Object等核心类在各种类加载器环境下都是同一个类。(当自己创建一个java.lang.Object时,Bootstrap类加载器加载的不是自己定义的类)
  • 一个类加载器已经加载了一个类,不会再次加载一个同名类 -> 一个类文件在同一个类加载器中只加载一次 -> 无法加载用户自定义的跟java rt / ext的同名类 
  • package java.lang;
    
    /**
     * 自定义Object类能否被加载
     * Author:qqy
     */
    public class Object {
        public static void main(String[] args) {
            System.out.println(Object.class==java.lang.Object.class);
        }
    }
    
    //错误: 在类 java.lang.Object 中找不到 main 方法, 请将 main 方法定义为:
    //   public static void main(String[] args)
    //否则 JavaFX 应用程序类必须扩展javafx.application.Application
  • 4.2.3 比较两个类相等的前提

在两个类是由同一个类加载器加载的前提下才有意义。

5. 动态代理

一个代理类代理所有类似接口。要进行动态代理的实现,代理类不再具体实现某一个接口,实现InvocationHandler接口。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 动态代理实现,代理类不需要用户自己去实现
 * Author:qqy
 */
public class Test1 {
    public static void main(String[] args) {
        ISubject1 subject1=(ISubject1)new ProxySubject1().bind(new RealSubject1());
        subject1.eat(2);
    }
}

interface ISubject1{
    void eat(int count);
}

class RealSubject1 implements ISubject1{

    @Override
    public void eat(int count) {
        System.out.println("吃"+count+"两饭");
    }
}

class ProxySubject1 implements InvocationHandler{
    private Object realObj;

    /**
     * 绑定真实主题类
     * @param realObj
     * @return 代理类
     */
    public Object bind(Object realObj){
        this.realObj=realObj;
        return Proxy.newProxyInstance(realObj.getClass().getClassLoader(),realObj.getClass().getInterfaces(),this);
    }

    public void before(){
        System.out.println("洗手");
    }

    public void after(){
        System.out.println("洗碗");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        this.before();
        Object result=method.invoke(realObj,args);
        this.after();
        return result;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值