java-反射

反射

概念

所谓反射就是java提供一个可以在运行期动态的获取和操作java中所有类的所有信息。

java程序的运行流程

编写源代码 -->编译源代码为字节码–>加载运行字节码.

什么是加载字节码文件?

所谓加载字节码文件就是将字节码文件读取到内存中。加载字节码的过程就称之为类加载。

JVM是动态按需加载的,一个类在没有被使用的情况下是不会被加载到内存中的。

什么是使用一个类?如何让JVM完成类加载?

我们之前的程序中对类的正常的使用都会让JVM加载这个类。

案例:

Person p = new Person();//创建类对象
Tool.convent();//调用静态方法
List<Person> list;//类作为泛型
public void method(Person p){}  method(p);//类加载
.......

字节码文件到内存中之后会怎么样?

字节码被加载到JVM内存的方法区中:

在这里插入图片描述

方法区(线程共享)

被所有方法线程共享的一块内存区域。

用于存储已经被虚拟机加载的类信息,常量,静态变量等。

这个区域的内存回收目标主要针对常量池的回收和堆类型的卸载

JDK中有一个类 : java.lang.Class. 是一个引用类型,必然可以创建这个类型的对象。

我们的字节码文件被加载内存中之后,就会创建一个对应的Class类型的对象存储这个字节码文件中描述的所有信息。

如下程序:

Person  p = new Person();

在这里插入图片描述

每一个字节码文件被加载带内存后都会生成一个对应的Class类型的对象。这个对象时唯一的。这里的唯一是,一个字节码文件只会产生一个Class对象。

JDK提供了几个方法可以获取一个类的Class对象,我们一旦获取到了一个类的Class对象,我们一旦获取到了类的Class对象,我们就可以通过Class对象直接操作类信息。

我们可以通过Class对象越过之前的操作方法: 创建对象 new Person() 调用方法 p.eat() …

Class对象。

Class类

[1]获取一个类的Class对象的方法:

方法1: 通过Class类中的静态方法forname(全限定类名)获取Class对象

		try {
			Class clazz = Class.forName("com.st.demo1.Person");
			System.out.println(clazz);//class com.st.demo1.Person
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

这种方式也往往被作为类加载的方法。

方式2:通过类的一个成员变量获取

		//通过Person类的成员变量获取
		Class clazz1 = Person.class;
		System.out.println(clazz == clazz1);//true

每一个类都有class这个静态成员。

方式3:通过类对象.getClass()方法获取。这个方法是Object类的方法。

		//通过对象的getClass方法获取
		Person p = new Person();
		Person p1 = new Person();
		Class clazz2 = p.getClass();
		Class clazz3 = p1.getClass();
		System.out.println(clazz2 == clazz3);//true

[2]利用Class对象创建类对象

几个API

getName();获取这个类的全限定类名

		//获取Class对象
		Class clazz = Car.class;
		//获取类的全限定类名
		String name = clazz.getName();
		System.out.println(name);//com.st.demo2.Car

newInstance();通过无参数的构造方法创建这个类的一个对象。

	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
		//获取Class对象
		Class clazz = Car.class;
		//通过newInstance创建类对象  在没有指定泛型的情况下,这里创建的对象类型是Object
		Object car = clazz.newInstance();
		if(car instanceof Car) {
			Car c = (Car)car;
			c.run();//汽车在疯狂的奔跑
		};
	}

当然这个要求,这个类必须有无参数的构造方法,如果没有就报错。

在这里插入图片描述

使用cast方法进行类型的强制转换。

			Class<Car> clz = (Class<Car>) Class.forName("com.st.demo2.Car");
			//利用Class对象进行强制类型转换
			Car c = clz.cast(car);

[3]通过class获取字节码中申明的所有成员变量

几个API:

Field getDeclaredField(String name)
返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
Field[] getDeclaredFields()
返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。

Field getField(String name)
返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
Field[] getFields()
返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。

Field类:表示一个类中的成员变量。

案例:准备一个类:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 上午10:34:05
 */
public class Dog {
	private String name;
	private int age;
	public String color;
	Date birth;
	
	
	public String toString() {
		return "Dog [name=" + name + ", age=" + age + ", color=" + color + ", birth=" + birth + "]";
	}
	
}

测试类:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 上午10:35:20
 */
public class Test {
	public static void main(String[] args) throws NoSuchFieldException, SecurityException {
		//获取Class对象
		Class<Dog> clz = Dog.class;
		//获取指定名称的公共的字段
		Field field = clz.getField("addr");
		System.out.println(field);//public java.lang.String com.st.demo3.Dog.color
		//获取所有的公共字段
		Field [] fields = clz.getFields();
		System.out.println(Arrays.toString(fields));
		
		//通过field名称获取私有的字段
		Field field1 = clz.getDeclaredField("name");
		System.out.println(field1);//private java.lang.String com.st.demo3.Dog.name
		//获取所有申明的成员
		Field [] fs = clz.getDeclaredFields();
		for (int i = 0; i < fs.length; i++) {
			System.out.println(fs[i]);
		}
	}
}

[4]通过Field操作字段

Field类

在这里插入图片描述

获取的一些API:

Class<?> getType()
返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。

String getName()
返回此 Field 对象表示的字段的名称。

int getModifiers()
以整数形式返回由此 Field 对象表示的字段的 Java 语言修饰符。

案例:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 上午10:46:12
 */
public class Test1 {
	public static void main(String[] args) throws NoSuchFieldException, SecurityException {
		Class clz = Dog.class;
		Field field = clz.getField("addr");
		//获取类别的class
		Class<?> typeClaz = field.getType();
		System.out.println(typeClaz);//class java.lang.String
		//获取名称
		System.out.println(field.getName());//addr
		//获取修饰符
		System.out.println(field.getModifiers());//9
	}
}

tips:关于修饰符,java中的所有的修饰符都是由一个数字常量表示的,一个方法或者成员变量的修饰符组合就是所拥有的修饰符的常量数字的和。 上面的例子就是 public (1) + static(8) = 9

使用File类修改和获取一个对象的成员变量

API:

get(Object obj)获取属性值

getXxx(Object obj) 获取指定类型的属性值,指定的类型都是基本类型的包装类

set(Object obj, Object value) 设置属性值

setXxx(Object obj, Xxx value) 设置指定类型的值到属性。

案例:

/**
 * @author 戴着假发的程序员
 * @TODO  通过Field设置和获取属性值
 * @organization 飞虎队
 * 2020年9月1日 上午11:06:11
 */
public class Test2 {
	public static void main(String[] args) throws Exception, IllegalAccessException {
		Class clz = Dog.class;
		//创建类对象
		Object obj = clz.newInstance();
		System.out.println(obj);//color=null
		//获取color属性   public的属性
		Field field = clz.getField("color");
		//设置属性值(要设置属性的对象,要设置的值)
		field.set(obj, "yellow");
		System.out.println(obj);// color=yello
		//获取属性值
		Object color = field.get(obj);
//		field.getType().cast(color);//类型转换
		System.out.println(color);//yellow
		
		//私有属性的设置和获取
		//获取私有属性
		Field f = clz.getDeclaredField("name");
		f.setAccessible(true);//强制设置私有属性可以访问
		f.set(obj, "大黄");
		System.out.println(obj);//name=大黄
		Object name = f.get(obj);
		System.out.println(name);//大黄
	}
}

[5]通过Class获取一个类中的成员方法

Method getMethod(String name, Class<?>… parameterTypes)
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method[] getMethods()
返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。

Method getDeclaredMethod(String name, Class<?>… parameterTypes)
返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
Method[] getDeclaredMethods()
返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

案例:

准备一个类

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 上午11:19:06
 */
public class Person {
	public String showName() {
		return "旗木卡卡西";
	}
	public void setInfo(String name,int age,Date birth) {
		System.out.println(name+";"+age+";"+birth);
	}
	public static void think() {
		System.out.println("卡卡西思考人生!");
	}
	private void eat(String food,int count) {
		System.out.println("卡卡西吃了"+count+"个"+food);
	}
}

Method类: 表示一个方法。

测试类:

/**
 * @author 戴着假发的程序员
 * @TODO 获取一个类中的方法 
 * @organization 飞虎队
 * 2020年9月1日 上午11:22:14
 */
public class Test {
	public static void main(String[] args) throws Exception, SecurityException {
		Class clz = Person.class;
		//获取类中一个public的方法
		Method m = clz.getMethod("showName");//由于showName方法没有参数,所以第二个可变参数不传
		System.out.println(m);
		//获取一个有参数的方法
		Method m1 = clz.getMethod("setInfo", String.class,int.class,Date.class);
		System.out.println(m1);
		//获取所有的public的方法  会获取到所有的自己申明的方法以及从父类继承的所有的public的方法。
		Method [] ms = clz.getMethods();
		for (int i = 0; i < ms.length; i++) {
			System.out.println(ms[i]);
		}
		System.out.println("----------------------");
		//获取私有方法
		Method m2 = clz.getDeclaredMethod("eat", String.class,int.class);
		System.out.println(m2);
		//获取所有方法   这里只会获取在当前类中申明的方法,无论修饰符是什么,继承的方法是获取不到的。重写的方法也可以获取到
		Method [] ms2 = clz.getDeclaredMethods();
		for (int i = 0; i < ms2.length; i++) {
			System.out.println(ms2[i]);
		}
	}
}

[6]通过method类执行某一个方法

Method类.

在这里插入图片描述

获取方法信息的API:

String getName()
以 String 形式返回此 Method 对象表示的方法名称。

Class<?>[] getParameterTypes()
按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。

Class<?> getReturnType()
返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型。

int getModifiers()
以整数形式返回此 Method 对象所表示方法的 Java 语言修饰符。

案例:

/**
 * @author 戴着假发的程序员
 * @TODO
 * @organization 飞虎队 2020年9月1日 上午11:36:17
 */
public class Test1 {
	public static void main(String[] args) throws Exception {
		Class clz = Person.class;
		// 获取一个有参数的方法
		Method m1 = clz.getMethod("setInfo", String.class, int.class, Date.class);
		//获取方法名
		System.out.println(m1.getName());
		//获取方法的参数列表类型
		Class<?>[] parameterTypes = m1.getParameterTypes();
		System.out.println("----setInfo方法的参数类型----");
		for (int i = 0; i < parameterTypes.length; i++) {
			System.out.println(parameterTypes[i]);
		}
		System.out.println("----setInfo方法的参数类型----");
		//获取返回值类型
		System.out.println(m1.getReturnType());
		//获取访问修饰符的表示常量
		System.out.println(m1.getModifiers());
		
		
		Method m2 = clz.getMethod("showName");
		System.out.println(m2.getReturnType());
	}
}

方法修饰的组合情况:

public     1
private    2
protected  4
default    0
static     8
abstract   1024
final      16

问题:那些数字是静态方法?

8,9,25,24,26,28

Modifier类中的方法可以根据Method类的getModifiers()的返回值判断这个方法的修饰符:

static boolean isPublic(int mod)
如果整数参数包括 public 修饰符,则返回 true,否则返回 false。
static boolean isStatic(int mod)
如果整数参数包括 static 修饰符,则返回 true,否则返回 false。

[7]通过反射调用方法

Method类中的API:

Object invoke(Object obj, Object… args)
对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。

调用当前方法.

参数1:表示要执行当前方法的对象。如果当前方法为静态方法,则这个参数可以为null。

参数2:可变参数,就是执行这个方法时要传入的参数。

案例:

/**
 * @author 戴着假发的程序员
 * @TODO  使用method调用方法
 * @organization 飞虎队
 * 2020年9月1日 下午2:02:55
 */
public class Test2 {
	public static void main(String[] args) throws Exception, SecurityException {
		Class clz = Person.class;
		//创建实例对象
		Object obj = clz.newInstance();
		Method m = clz.getMethod("think");
		//静态方法不需要对象
		//判断是否是静态方法
		if(Modifier.isStatic(m.getModifiers())) {
			m.invoke(null);//静态方法可以不设置对象
		}
		Method m1 = clz.getMethod("setInfo", String.class,int.class,Date.class);
		//执行非静态方法,非静态方法必须传入一个当前类对象
		m1.invoke(obj,"卡卡西",28,new Date());  //obj.setInfo();
		
		//执行有返回值的方法
		Method m2 = clz.getMethod("showName");
		//返回值是Object类型,需要强制转换时可以使用Class的cast方法
		Object returnVal = m2.invoke(obj);
		System.out.println(returnVal);
		
		//执行私有方法
		Method m3 = clz.getDeclaredMethod("eat", String.class,int.class);
		//强制设置方法eat可以被访问
		m3.setAccessible(true);
		//执行方法
		m3.invoke(obj, "香蕉",3);
	}
}

[8]通过Class获取一个类的构造方法

newInstance()执行的是类的无参数的public的构造方法,如果一个类没有无参数的或者public的构造方法,则需要使用其他的构造方法来创建类对象。

几个API:

Constructor getConstructor(Class<?>… parameterTypes)
返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
Constructor<?>[] getConstructors()
返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。

Constructor getDeclaredConstructor(Class<?>… parameterTypes)
返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。
Constructor<?>[] getDeclaredConstructors()
返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。

案例:

准备一个类

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午2:16:42
 */
public class Book {
	private String isbn;
	private String title;
	private int pages;
	private float price;
	private float cost;
	public Book() {
	}
	public Book(String isbn, String title, int pages, float price, float cost) {
		super();
		this.isbn = isbn;
		this.title = title;
		this.pages = pages;
		this.price = price;
		this.cost = cost;
	}
	private Book(String isbn, String title, int pages) {
		super();
		this.isbn = isbn;
		this.title = title;
		this.pages = pages;
	}
	private Book(String isbn, String title) {
		super();
		this.isbn = isbn;
		this.title = title;
	}
	public Book(String isbn) {
		super();
		this.isbn = isbn;
	}
	public String toString() {
		return "Book [isbn=" + isbn + ", title=" + title + ", pages=" + pages + ", price=" + price + ", cost=" + cost
				+ "]";
	}
}

测试:

Constructor类:表示一个类的构造方法。

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午2:20:10
 */
public class Test {
	public static void main(String[] args) throws Exception, SecurityException {
		Class clz = Book.class;
		//获取一个public 的构造方法
		Constructor c1 =  clz.getConstructor();//获取无参数的构造方法
		System.out.println(c1);
		//获取有一个参数的构造方法
		Constructor c2 = clz.getConstructor(String.class);
		System.out.println(c2);
		System.out.println("--------");
		//获取所有的public的构造方法
		Constructor[] cs = clz.getConstructors();
		for (int i = 0; i < cs.length; i++) {
			System.out.println(cs[i]);
		}
		System.out.println("--------");
		//获取私有的构造方法
		Constructor c3 = clz.getDeclaredConstructor(String.class,String.class,int.class);
		System.out.println(c3);
		//获取所有的构造方法
		Constructor[] cs1 = clz.getDeclaredConstructors();
		for (int i = 0; i < cs1.length; i++) {
			System.out.println(cs1[i]);
		}
	}
}

[9]通过构造方法创建一个类的类对象

在这里插入图片描述

我们可以通过Constructor提供的API获取构造方法的访问权限和参数列表信息。

这个和Method没有太大的区别,就不赘述。

int getModifiers()
以整数形式返回此 Constructor 对象所表示构造方法的 Java 语言修饰符。
String getName()
以字符串形式返回此构造方法的名称。

Class<?>[] getParameterTypes()
按照声明顺序返回一组 Class 对象,这些对象表示此 Constructor 对象所表示构造方法的形参类型。

执行构造方法

T newInstance(Object… initargs)
使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。

案例:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午2:27:55
 */
public class Test2 {
	public static void main(String[] args) throws Exception, SecurityException {
		Class clz = Book.class;
		//获取一个public 的构造方法
		Constructor<Book> c1 =  clz.getConstructor();//获取无参数的构造方法
		//利用无参数的构造方法创建对象
		Book obj = c1.newInstance();
		System.out.println(obj);
		//获取有一个参数的构造方法
		Constructor c2 = clz.getConstructor(String.class);
		Object obj1 = c2.newInstance("9876546");
		System.out.println(obj1);
		//获取私有的构造方法
		Constructor c3 = clz.getDeclaredConstructor(String.class,String.class,int.class);
		//私有的构造方法,需要”暴力破解“
		c3.setAccessible(true);
		Object obj3 = c3.newInstance("897654321","反射机制的真谛",10000);
		System.out.println(obj3);
	}
}

[10]获取一个类的父类类型

使用Class的

Class<? super T> getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。

案例:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午2:33:19
 */
public class Test {
	public static void main(String[] args) {
		Class  clz = B.class;
		Class clz1 = A.class;
		System.out.println(clz.getSuperclass());//com.st.demo6.A
		System.out.println(clz1.getSuperclass());//java.lang.Object
	}
}
class A{}
class B extends A{}

[11]获取一个类实现的所有接口

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午2:35:17
 */
public class Test1 {
	public static void main(String[] args) {
		Class clz = Z.class;
		//获取所有接口
		Class [] interfaces = clz.getInterfaces();
		for (int i = 0; i < interfaces.length; i++) {
			System.out.println(interfaces[i]);
		}
	}
}
interface S{}
interface H{}
class Z implements S,H{}

案例

通过配置文件实现一个打印机的组装

程序架构:

在这里插入图片描述

按照结构将程序实现:


/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午3:07:03
 */
public interface Ink {
	public void show();
}
/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午3:07:20
 */
public interface Paper {
	public void shwoInfo();
}
/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午3:07:35
 */
public class Printer {
	private Ink ink;
	private Paper paper;
	
	public void print(String msg) {
		ink.show();
		paper.shwoInfo();
		System.out.println("打印内容:"+msg);
	}
    //setter和getter省略
}
/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午3:09:23
 */
public class A4Paper implements Paper {
	public void shwoInfo() {
		System.out.println("A4纸张");
	}
}
/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午3:10:18
 */
public class BlackInk implements Ink {
	public void show() {
		System.out.println("黑白墨盒");
	}
}
/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午3:10:18
 */
public class ColorInk implements Ink {
	public void show() {
		System.out.println("彩色墨盒");
	}
}

测试类:

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午3:11:20
 */
public class Test {
	public static void main(String[] args) {
		//准备两种墨盒
		Ink ink1 = new BlackInk();
		Ink ink2 = new ColorInk();
		//准备两种纸张
		Paper a4 = new A4Paper();
		Paper b5 = new B5Paper();
		//打印机
		Printer printer = new Printer();
		printer.setInk(ink1);
		printer.setPaper(a4);
		printer.print("这个是一个消息");
		//跟换
		printer.setInk(ink2);
		printer.print("消息2");
		printer.setPaper(b5);
		printer.print("消息3");
	}
}

修改程序:

添加一个配置文件:config.txt

printer:com.st.ex1.Printer
ink:com.st.ex1.ColorInk
paper:com.st.ex1.A4Paper

第一行是打印机的全限定类名

第二行是这个打印机的配置的墨盒的全限定类名

第三行是这个打印机配置的纸张的全限定类名

测试程序:实现根据配置文件组装打印机

/**
 * @author 戴着假发的程序员
 * @TODO 
 * @organization 飞虎队
 * 2020年9月1日 下午3:17:42
 */
public class Test1 {
	public static void main(String[] args) {
		Printer printer = getPrinter();
		printer.print("消息");
	}
	public static Printer getPrinter() {
		Printer printer = null;
		//通过读取配置文件完成打印机的组装
		Reader reader = null;
		BufferedReader bufReader = null;
		try {
			reader = new FileReader("config.txt");
			bufReader = new BufferedReader(reader);
			//读取第一行
			String line = bufReader.readLine();
			String className = line.split(":")[1];
			//创建Printer
			Class clz = Class.forName(className);
			printer = (Printer) clz.newInstance();
			//读取第二行
			className = bufReader.readLine().split(":")[1];
			clz = Class.forName(className);
			//创建墨盒
			Ink ink = (Ink) clz.newInstance();
			printer.setInk(ink);
			
			//读取第三行
			className = bufReader.readLine().split(":")[1];
			clz = Class.forName(className);
			//创建纸张
			Paper paper = (Paper) clz.newInstance();
			printer.setPaper(paper);
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			try {
				if(reader!=null)
					reader.close();
				if(bufReader!=null)
					bufReader.close();
			} catch (Exception e2) {
			}
		}
		return printer;
	} 
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

戴着假发的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值