JavaSE 反射


Java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。
通过反射机制,程序员可以更深入地控制程序的运行过程

Class类

所有类均继承Object类,在Object类中定义了一个getClass()方法,该方法返回一个类型为Class的对象。

Class text=demo.getClass();       //demo为某一类的对象
Class text=demo.class;

利用Class类的对象,可以访问返回某一对象的描述信息

组成部分访问方法返回值类型说明
包路径getPackage()Package对象获得该类的存放路径
类名称getName()String 对象获得该类的名称
继承类getSuperClass()Class对象获得该类的继承类
实现接口getInterfaces()Class型数组获得该类实现的所有接口
构造方法getConstructors()Constructor数组获得所有权限为public的构造方法
构造方法getConstructors(Class<?> …parameterTypes)Constructor对象获得权限为public的指定构造方法
构造方法getDeclaredConstructors()Constructor数组获得所有构造方法,按声明顺序返回
构造方法getDeclaredConstructors(Class<?> …parameterTypesConstructor对象获得指定构造方法
方法getMethods()Method型数组获得所有权限为public的方法
方法getMethod(String name,Class<?> …parameterTypesMethod对象获得权限为public的指定方法
方法getDeclaredMethods()Method型数组获得所有方法,按声明顺序返回
方法getDeclaredMethod(String name,Class<?> …parameterTypesMethod对象获得指定方法
成员变量getFields()Field型数组获得所有权限为public的成员变量
成员变量getFields(String name)Field对象获得权限为public的指定成员变量
成员变量getDeclaredFields()Field型数组获得所有成员变量,按声明顺序返回
成员变量getDeclaredFields()Field对象获得指定成员变量
内部类getClasses()Class型数组获得所有权限为public的内部类
内部类getDeclaredClasses()Class对象
内部类的声明类getDeclaringClass()Class对象如果该类为内部类,则返回它的成员类,否则返回null

getFields()和getMethods()方法依次获得权限为public的成员变量和方法时,将包含从超类中继承到的成员变量和方法。getDeclaredFields()和getDeclaredMethods()方法只是获得在本类中定义的所有成员变量和方法

Class类与反射

通过反射机制,可以在程序中访问已经装载到JVM中的对象的描述,实现访问,检测和修改描述对象本身信息。在java.lang.reflect包中提供了对该功能的支持。
通过Class的相应方法和java.lang,reflect下的类就可以反射出相应的信息

Modifier类 (访问修饰符)

java.lang,reflect.Modifier
可以解析出getModifiers()方法(Class类,Construcor类,Filed类,Method类的方法)的返回值所表示的修饰符信息,因为getModifiers()方法返回的是int类型,无法直接得出修饰符信息,所以就需要Modeifier类来解析int值。可以通过toString(getModifiers())获得修饰符

Modifier.toSting(getModifiers())

Modifier的常用方法

方法返回值说明
isStatic(int mod)static boolean如果整数参数包含 public修饰符,则返回 true , false
isProtected(int mod)static boolean如果整数参数包含 protected修饰符,则返回 true
isPrivate(int mod)static boolean如果整数参数包含 private修饰符,则返回 true
isFinal(int mod)static boolean如果整数参数包含 final修饰符,则返回 true
isStatic(int mod)static boolean如果整数参数包含 static修饰符,则返回 true
toString(int mod)static String返回描述指定修饰符中的访问修饰符标志的字符串。
int modifiter=constructer.getModifiers();
String d=Modifiter.toString(modifiter);     //静态方法,类名.方法名调用
boolean isPrivate=Modifier.isPrivate(modifiter)

Construcor类 (访问构造方法)

Construcor:构造方法类
java.lang,reflect.Construcor

getConstructors():返回Constructor数组 获得所有权限为public的构造方法
getConstructors(Class<?> …parameterTypes):返回Constructor对象 获得权限为public的指定构造方法
getDeclaredConstructors():返回Constructor数组 获得所有构造方法,按声明顺序返回
getDeclaredConstructors(Class<?> …parameterTypes):返回Constructor对象 获得指定构造方法

如果是访问指定的构造方法,需要根据该构造方法的入口参数类型来访问。有以下两种方法

objectclass.getDeclaredConstructors(String.class,int.class);
objectclass.getDeclaredConstructors(new Class[]{String.class,int.class});

Constructor类中的常用方法

方法返回值说明
newInstance(Object… initargs)T(泛型)通过给构造方法利用指定参数创建一个该类的对象,如果未设置参数则表示采用默认无参数的构造方法
getModifiers()int获得可以解析出该构造方法所采用修饰符的整数
getName()String返回此构造函数的名称。
getParameterTypes()Class<?>[]返回一个 Class对象的数组获得各个参数的类型
getExceptionTypes()Class<?>[]返回一个 Class对象的数组获得各个异常的类型
isVarArgs()boolean查看该构造方法是否允许带有可变数量的参数,如果允许则返回true,否则返回false
setAccessible(boolean flag)void如果该构造方法的权限为private,默认为不允许通过利用反射newInstance方法创建对象。如果先执行该方法,并将入口参数设为true,则允许创建
package com.test;

public class Example {
    String s;
    int id;
    double d;
    
    public Example() {
    }
    
    public Example(String s) {
        this.s = s;
    }
    
    private Example(String s, int id, double d) {
        this.s = s;
        this.id = id;
        this.d = d;
    }
    @Override
    public String toString() {
        return "Example { s="+s+",id="+id+",d="+d+"}";
    }
}
package com.test;

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

public class TestReflect {
    public static void main(String[] args) {
        try {
           Class c =Class.forName("com.test.Example");
            System.out.println("获取所有构造方法");
            Constructor cons[]=c.getDeclaredConstructors();   //获取所有构造方法
            for(Constructor con:cons){
                System.out.print(Modifier.toString(con.getModifiers())+" ");  //修饰符
                System.out.print(con.getName()+" (");   //构造方法名
                Class[] type=con.getParameterTypes();
                for(int i=0;i<type.length;i++){
                    System.out.print(type[i].getSimpleName()+" args ");
                    if(i<type.length-1){
                        System.out.print(",");
                    }
                }
                System.out.println(") { }");
            }
            System.out.println("获取指定构造方法");
            Constructor con=c.getDeclaredConstructor(String.class);
            System.out.print(Modifier.toString(con.getModifiers())+" ");  //修饰符
            System.out.print(con.getName()+" (");   //构造方法名
            Class[] type=con.getParameterTypes();
            for(int i=0;i<type.length;i++){
                System.out.print(type[i].getSimpleName()+" args ");
                if(i<type.length-1){
                    System.out.print(",");
                }
            }
            System.out.println(") { }");

            System.out.println("通过Constructor的newInstance方法创建不同的对象");
            //创建不同的对象
            Constructor cs1=c.getDeclaredConstructor();  //无参数的构造方法
            Object obj=cs1.newInstance();
            System.out.println(obj.toString());

            Constructor cs2=c.getDeclaredConstructor(String.class);   //有参数的构造方法
            obj=cs2.newInstance("反射");
            System.out.println(obj.toString());

            Constructor cs3=c.getDeclaredConstructor(String.class,int.class,double.class);   ///有参数的构造方法
            cs3.setAccessible(true);    //因为三个参数的构造方法是私有的  所以通过setAccessible获取操作权限
            obj=cs3.newInstance("李明",20,180.9);
            System.out.println(obj.toString());

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

输出结果:
获取所有构造方法
private com.test.Example (String args ,int args ,double args ) { }
public com.test.Example (String args ) { }
public com.test.Example () { }
获取指定构造方法
public com.test.Example (String args ) { }
通过Constructor的newInstance方法创建不同的对象
Example { s=null,id=0,d=0.0}
Example { s=反射,id=0,d=0.0}
Example { s=李明,id=20,d=180.9}

Field 访问成员变量

通过Class的方法可以返回Field类型的对象或者数组
每个Field对象代表一个成员变量,利用Field对象可以操纵相应的成员变量

Class的相应方法如下

方法返回值说明
getFields()Field型数组获得所有权限为public的成员变量
getFields(String name)Field对象获得权限为public的指定成员变量
getDeclaredFields()Field型数组获得所有成员变量,按声明顺序返回
getDeclaredFields()Field对象获得指定成员变量
Class c=Example.class
Field f=c.getField("name");     //参数传入成员变量的名字  比如Example里面有个名字为name的成员变量

Field类的常用方法

方法返回值说明
getModifiers()int获得可以解析出该成员变量所采用修饰符的整数
getName()String返回此成员变量的名称。
getTypesClass<?>获得表示该成员变量类型的Class对象
get(Object obj)Object获得指定对象obj中成员变量的值,返回值为Object型
getBoolean(Object obj) boolean获得指定对象obj中成员变量类型为boolean的值
getFloat(Object obj)float获得指定对象obj中成员变量类型为float的值
getDouble(Object obj)double获得指定对象obj中成员变量类型为double的值
getInt(Object obj)int获得指定对象obj中成员变量类型为int的值
set(Object obj, Object value)void将指定对象obj中成员变量的值设置为value
setBoolean(Object obj, boolean z)void将指定对象obj中成员变量为boolean类型的值设置为z
setFloat(Object obj, float f)void将指定对象obj中成员变量为float 类型的值设置为z
setDouble(Object obj, double d)void将指定对象obj中成员变量为double 类型的值设置为d
setInt(Object obj, int i)void将指定对象obj中成员变量为int 类型的值设置为i
setAccessible(boolean flag)void如果该成员变量的权限为private,默认为不允许通过利用反射newInstance方法访问成员变量。如果先执行该方法,并将入口参数设为true,则允许访问
package com.test;

package com.test;

public class Example {
    public String str="XXX";
    public int id=1;
    private double pi=3.14;
}

package com.test;

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

public class FieldReflect {
    public static void main(String[] args) {
            try {
                    Class c=Example.class;
                    Field[] fs= c.getDeclaredFields();
                    System.out.println("----输出所有成员变量---");
                    for (Field f:fs){
                      System.out.print(Modifier.toString(f.getModifiers())+" ");
                      System.out.print(f.getType().getSimpleName()+" ");
                      System.out.print(f.getName()+" ");
                      System.out.println();
                    }

                    System.out.println("----输出指定成员变量----");
                    Field fd=c.getDeclaredField("pi");
                    System.out.print(Modifier.toString(fd.getModifiers())+" ");
                    System.out.print(fd.getType().getSimpleName()+" ");
                    System.out.print(fd.getName()+" ");
                    System.out.println();

                    System.out.println("-----获取值和修改值----");
                    Constructor con=c.getDeclaredConstructor();
                    Example e= (Example) con.newInstance();   //创造对象
                    Field f=c.getDeclaredField("pi");
                    f.setAccessible(true);
                    System.out.println("获取值");
                    System.out.println(f.get(e));
                    f.set(e,3.14159126);    //修改属性值
                    System.out.println("修改后的值");
                    System.out.println(f.get(e));    //修改属性值

            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
    }
}
输出结果:
----输出所有成员变量---
public String str 
public int id 
private double pi 
----输出指定成员变量----
private double pi 
-----获取值和修改值----
获取值
3.14
修改后的值
3.14159126

Method类 (访问方法)

通过Class的方法可以返回Method类型的对象或者数组
每个Method对象代表一个成员变量,利用Method对象可以操纵相应的成员变量

Class相应方法如下

方法返回值说明
getMethods()Method型数组获得所有权限为public的方法
getMethod(String name,Class<?> …parameterTypesMethod对象获得权限为public的指定方法
getDeclaredMethods()Method型数组获得所有方法,按声明顺序返回
getDeclaredMethod(String name,Class<?> …parameterTypesMethod对象获得指定方法
Class c=Example.class
Method m1=c.getMethod("name1");    // 无参数的方法  比如Example里面有个名字为name1的无参数成员方法
Method m2=c.getMethod("name2",String.class,int.class);   //有参数的方法 比如Example里面有个名字为name2的有参数成员方法参数类型为String和int

Method类常用方法

方法名返回值说明
getModifiers()int获得可以解析出该方法所采用修饰符的整数
getReturnTypeClass<?>返回一个 Class对象的数组获得返回类型
getName()String返回此方法的名称。
getParameterTypes()Class<?>[]返回一个 Class对象的数组获得各个参数的类型
getExceptionTypes()Class<?>[]返回一个 Class对象的数组获得各个异常的类型
isVarArgs()boolean查看该方法是否允许带有可变数量的参数,如果允许则返回true,否则返回false
invoke(Object obj, Object… args)Object在具有指定参数的方法对象上调用此方法
setAccessible(boolean flag)void如果该方法的权限为private,默认为不允许通过利用反射newInstance方法创建对象。如果先执行该方法,并将入口参数设为true,则允许创建
package com.test;

public class Example {
    public String str="XXX";
    public int id=1;
    private double pi=3.14;
    public String getStr(){
        return  this.str;
    }
    public int getId() {
        return id;
    }
    public double getPi() {
        return pi;
    }
    public void setStr(String str) {
        this.str = str;
    }
    public void setId(int id) {
        this.id = id;
    }
    public void setPi(double pi) {
        this.pi = pi;
    }
}
package com.test;

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

public class MethodReflect {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Class c=Example.class;
        Method[] methods=c.getDeclaredMethods();
        System.out.println("----输出所有方法----");
        for (Method me:methods){
            System.out.print(Modifier.toString(me.getModifiers())+" ");
            System.out.print(me.getReturnType().getSimpleName()+" ");
            System.out.print(me.getName()+"(");
            Class[] types=me.getParameterTypes();
            for(int i=0;i<types.length;i++){
                System.out.print(types[i].getSimpleName()+" arg");
                if(i<types.length-1){
                    System.out.print(",");
                }
            }
            System.out.print(") ");
            Class[] exceptions=me.getExceptionTypes();
            if(exceptions.length>0) {
                System.out.print("throws ");
                for (int i = 0; i < exceptions.length; i++) {
                    System.out.print(exceptions[i].getSimpleName());
                    if (i < exceptions.length - 1) {
                        System.out.print(",");
                    }
                }
            }
            System.out.println("{ }");
        }

        System.out.println("---执行方法---");
        Constructor con=c.getConstructor();
        Example e= (Example) c.newInstance();
        Method m=c.getDeclaredMethod("getId");
        m.setAccessible(true);
        System.out.println(m.invoke(e));   //输出返回值
    }
}

输出结果:
----输出所有方法----
public int getId() { }
public void setStr(String arg) { }
public void setId(int arg) { }
public void setPi(double arg) { }
public String getStr() { }
public double getPi() { }
---执行方法---
1

访问注释信息

如果将Annotation的元注释 @Retention设置为RetentionPoilcy.RUNTIME(写入JVM),就可以获取相关的Annotation信息
Constructor,Field,Method均有反射Annotation信息的方法

方法说明
isAnnotationPresent(Class<? extends Annotation> annotationClass判断是否添加了指定的Annotation信息
getAnnotation(Class annotationClass)获得指定的Annotation类型
getAnnotations()返回所有注解,返回一个Annotation数组
public Student{
   String name;     //姓名
   int id;          //学号
   int age;          //年龄
   double height;     //身高
}

上面的代码中使用"//"符号来注释代码,但是在运行时,是无法看到这些注解的。如果要在运行时获得注解信息,就需要反射Annnotation

package com.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentAnnotation {
      String remark() default "";    //备注
      boolean enable() default true;  //属性是否启用
}
package com.test;

public class Student {
    @StudentAnnotation(remark = "学生姓名")
    public String name;     //姓名
    @StudentAnnotation(remark = "学号")
    public int id;          //学号
    @StudentAnnotation(remark = "年龄")
    public int age;          //年龄
    @Deprecated     //注释属性过时
    @StudentAnnotation(remark = "身高",enable = false)
    public double height;     //身高
}

package com.test;

import java.lang.reflect.Field;

public class AnnotationReflect {
    public static void main(String[] args) {
        Class c=Student.class;
        Field fs[]=c.getDeclaredFields();
        for (Field f:fs){
            if(f.isAnnotationPresent(StudentAnnotation.class)){  //Annotation被反射,Annotation的@Retention需要设置为RetentionPolicy.RUNTIME
                System.out.print("属性"+f.getName()+"的注解: ");
                StudentAnnotation sa=f.getAnnotation(StudentAnnotation.class);
                System.out.print("备注="+sa.remark()+" ");
                System.out.println("属性是否有效="+sa.enable());
            }
        }
    }
}
输出结果:
属性name的注解: 备注=学生姓名 属性是否有效=true
属性id的注解: 备注=学号 属性是否有效=true
属性age的注解: 备注=年龄 属性是否有效=true
属性height的注解: 备注=身高 属性是否有效=false

返回方法参数的Annotation信息要使用getParameterAnnotations(),返回一个Annotation的二维数组

package com.test;

public class Student {
    @StudentAnnotation(remark = "学生姓名")
    public String name;     //姓名
    @StudentAnnotation(remark = "学号")
    public int id;          //学号
    @StudentAnnotation(remark = "年龄")
    public int age;          //年龄
    @StudentAnnotation(remark = "成绩",enable = true)
    public double score;     //成绩

    @StudentAnnotation(remark = "构造方法",enable = true)
    public Student( @StudentAnnotation(remark = "名字参数") String name, @StudentAnnotation(remark = "学号参数") int id,@StudentAnnotation(remark = "年龄参数")int age,@StudentAnnotation(remark = "成绩参数")double score){
        this.name=name;
        this.id=id;
        this.age=age;
        this.score=score;
    }
    @StudentAnnotation(remark = "获得学生信息")
    public String getStudentInfo(){
        return "学生姓名:"+name+"学号:"+id+"年龄:"+age+"成绩:"+score;
    }
}

package com.test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Reflect {
    public static void main(String[] args) {
        Class c=Student.class;
        try {
            Field fs[]=c.getDeclaredFields();
            System.out.println("-------成员属性Annotation-----");
            for (Field f:fs){
                if(f.isAnnotationPresent(StudentAnnotation.class)){  //Annotation被反射,Annotation的@Retention需要设置为RetentionPolicy.RUNTIME
                    System.out.print("属性"+f.getName()+"的注解: ");
                    StudentAnnotation sa=f.getAnnotation(StudentAnnotation.class);
                    System.out.print("备注="+sa.remark()+" ");
                    System.out.println("属性是否有效="+sa.enable());
                }
            }
            Constructor con=c.getConstructor(String.class, int.class, int.class, double.class);
            System.out.println("---------构造方法Annotation-----");
            if(con.isAnnotationPresent(StudentAnnotation.class)){
               StudentAnnotation sa= (StudentAnnotation) con.getAnnotation(StudentAnnotation.class);
                System.out.println(sa.remark()+" "+sa.enable());
            }
            System.out.println("---------构造方法参数Annotation-------");
            Annotation[][]  types=con.getParameterAnnotations();     //参数的Annotation,返回二维数组
            for(int i=0;i<types.length;i++){
                for(int j=0;j<types[i].length;j++){
                    StudentAnnotation sa= (StudentAnnotation) types[i][j];
                    System.out.print(" 参数备注="+sa.remark());
                   // System.out.println("参数s "+sa.enable());
                }
            }
            System.out.println();
            System.out.println("--------方法Annotation-------");
            Method m=c.getDeclaredMethod("getStudentInfo");
            if(m.isAnnotationPresent(StudentAnnotation.class)){
                StudentAnnotation sa=m.getAnnotation(StudentAnnotation.class);
                System.out.println("方法备注="+sa.remark()+" 方法是否有效="+sa.enable());
            }
            Annotation[][] methodtype=m.getParameterAnnotations();
            if(methodtype.length>0)
            for(int i=0;i<methodtype.length;i++){
                for(int j=0;j<methodtype[i].length;j++){
                    StudentAnnotation sa= (StudentAnnotation) types[i][j];
                    System.out.print(" 参数备注="+sa.remark());
                    // System.out.println("参数s "+sa.enable());
                }
            }
         } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}
输出结果:
-------成员属性Annotation-----
属性name的注解: 备注=学生姓名 属性是否有效=true
属性id的注解: 备注=学号 属性是否有效=true
属性age的注解: 备注=年龄 属性是否有效=true
属性score的注解: 备注=成绩 属性是否有效=true
---------构造方法Annotation-----
构造方法 true
---------构造方法参数Annotation-------
 参数备注=名字参数 参数备注=学号参数 参数备注=年龄参数 参数备注=成绩参数
--------方法Annotation-------
方法备注=获得学生信息 方法是否有效=true

访问泛型

Java采用泛型擦除的机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有的和泛型有关的类型全部擦除。
为了通过反射操作这些类型以迎合实际开发的需要,Java就新增ParameterizedType,GenericArrayType,TypeVariable 和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。

ParameterizedType: 表示一种参数化的类型,比如Collection
• GenericArrayType: 表示一种元素类型是参数化类型或者类型变量的数组类型
• TypeVariable: 是各种类型变量的公共父接口
• WildcardType: 代表一种通配符类型表达式
1.获取指定方法参数泛型信息

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class test {
    public void dosome(Map<Integer,String> map,List<String> list){

    }
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        Method method=Class.forName("test").getMethod("dosome",Map.class,List.class);
        Type[] types= method.getGenericParameterTypes();  //获得参数
        for(Type type:types){
           System.out.print(type+"参数的泛型:");
            if(type instanceof ParameterizedType){
                Type[] genericTypes=((ParameterizedType) type).getActualTypeArguments();
                for(Type genericType:genericTypes){
                    System.out.println("泛型类型"+genericType);
                }
            }
        }
    }
}

输出结果:
java.util.Map<java.lang.Integer, java.lang.String>参数的泛型:泛型类型class java.lang.Integer
泛型类型class java.lang.String
java.util.List<java.lang.String>参数的泛型:泛型类型class java.lang.String

2.获得指定方法返回值泛型

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class test {
    public Map<Integer,String> dosome(){
        System.out.println("do");
        return null;
    }
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        Method method=Class.forName("ReflectionLearn.test").getMethod("dosome",null);
        Type type= method.getGenericReturnType();  //获得返回值
        if(type instanceof  ParameterizedType){
            Type[] genericTypes=((ParameterizedType) type).getActualTypeArguments();
            for(Type genericType:genericTypes){
                System.out.println(genericType);
            }
        }

    }
}

输出结果:
class java.lang.Integer
class java.lang.String
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值