• 反射


    1. 反射

    1)问题1

    对象有编译类型和运行类型

               Object obj = new java.util.Date();

      编译类型:Object

      运行类型(其实就是obj对象真实的类型):java.util.Date

      需求:根据对象obj调用Date类中的一个方法,toLocaleString,如何来做?

        obj.toLocaleString()代码在编译阶段去编译类型Object中检查是否有该方法,若没有,编译失败.

     

    解决方案1:强制转换objDate类型,前提:必须知道对象的真实类型是什么?

          Date d = (Date)obj;

          d.toLocaleString();//YES

     

    如果我不知道obj的真实类型,那又如何去调用toLolcaeString方法. 如何去做?

    解决方案2: 使用反射

    2)问题2

    对象使用类描述的,但是Java中一些皆对象,其实所有的类,底层都有一个字节码对象,用什么来描述这一个个类底层的字节码对象

    解决方案 使用反射

     

    3.反射

    反射:(reflection):在运行时期,动态地去获取类中的信息(类的信息,方法信息,构造器信息,字段等信息进行操作);

    一个类中包含的信息有: 构造器,字段,方法

    如何使用反射描述这些相关的信息

    Class : 描述类

    Method : 描述方法

    Constructor :描述构造器

    Field :描述字段

    2. 获取类的Class实例三种方式

    在反射操作某一个类之前,应该先获取这个类的字节码实例

    获取字节码实例有三种方式

      类名.class 

      类的对象.getClass()

      Class.forName(“类的全限定名”)

    注意 :同一个类在JVM的字节码实例只有一份

    全限定名 = 包名 + 类名

     1 public class User {
     2 
     3 @Test
     4 
     5 public void testName() throws Exception {
     6 
     7 //1.使用类名.class 获取类的字节码实例
     8 
     9 Class<User>  clz1 = User.class;
    10 
    11 System.out.println(clz1.toString());
    12 
    13  
    14 
    15 //2.对象.getClass()
    16 
    17 User user = new User();
    18 
    19 Class<?> clz2 =  user.getClass();
    20 
    21 System.out.println(clz2);
    22 
    23  
    24 
    25 //3.Class.forName("全限定名"):用的最多
    26 
    27 Class<?> clz3 = Class.forName("cn.sxt.reflect.User");
    28 
    29 System.out.println(clz3);
    30 
    31 }
    32 
    33 }

     3. 获取九大内置类的字节码实例

    对于对象来说,可以直接使用对象.getClass()或者Class.forName(className); 类名.class都可以获取Class实例.

    但是我们的基本数据类型,就没有类的权限定名,也没有getClass方法.

    问题: 那么如何使用Class类来表示基本数据类型的Class实例?

    八大基本数据类型和 void关键字都是有 字节码实例的

    byte,short,int,long,char,float,double,boolean ,void关键字

    : 数据类型/void.class 即可

    每个基本数据类型都是包装类型 如 :int ----Integer包装类型

    注意: 基本数据类型和包装数据类型底层的字节码实例是不相同

     1 //获取8大基本数据类型和void的字节码实例
     2 
     3 //byte,short,int,long,char,float,double,boolean ,void关键字
     4 
     5 public class BaiscDataTypeClassTest {
     6 
     7 @Test
     8 
     9 public void testName() throws Exception {
    10 
    11 //1.获取byte的字节码实例
    12 
    13 Class<?> byteClz = byte.class;
    14 
    15 System.out.println(byteClz);
    16 
    17 //....
    18 
    19 //获取void关键字的字节码实例
    20 
    21 Class<?> voidClz =void.class;
    22 
    23 System.out.println(voidClz);
    24 
    25  
    26 
    27  
    28 
    29 //所有的基本数据类型都有包装类型
    30 
    31 //int---Integer
    32 
    33 //int 和Integer 的字节码是绝对不相等的
    34 
    35 Class<?> intClz1 = int.class;
    36 
    37 Class<?> intClz2 = Integer.class;
    38 
    39 System.out.println(intClz1);
    40 
    41 System.out.println(intClz2);
    42 
    43 System.out.println(intClz1 == intClz2);
    44 
    45 }
    46 
    47 }

    4. 获取数组类型的字节码实例

    表示数组的Class实例:

       String[] sArr1 = {"A","C"};

       Class clz = String[].class;//此时clz表示就是一个String类型的一位数组类型

    所有具有相同元素类型和维数的数组才共享同一份字节码(Class对象);

       注意:和数组中的元素没有一点关系.

     1 package cn.sxt.reflect._01.getclass;
     2 
     3  
     4 
     5 import static org.junit.Assert.*;
     6 
     7  
     8 
     9 import org.junit.Test;
    10 
    11  
    12 
    13 //获取数组类型的字节码实例
    14 
    15 public class ArrayClassTest {
    16 
    17 @Test
    18 
    19 public void testName() throws Exception {
    20 
    21 //定义数组
    22 
    23 int[] arr = {1,2,3};
    24 
    25 Class<int[]> clz1 = (Class<int[]>) arr.getClass();
    26 
    27 System.out.println(clz1);
    28 
    29 Class<int[]>  clz2= int[].class;
    30 
    31 System.out.println(clz2);
    32 
    33  
    34 
    35  
    36 
    37 int[] arr2 = {2,3,4};
    38 
    39 Class<int[]> clz3 = (Class<int[]>) arr2.getClass();
    40 
    41 System.out.println(clz3);
    42 
    43  
    44 
    45  
    46 
    47 System.out.println(clz1 == clz2);//true
    48 
    49 System.out.println(clz1 == clz3);//true
    50 
    51 }
    52 
    53 }

     5. 构造函数-Constructor

    1) 获取构造函数

    类的构函数有 有参数构造函数,无参构造函数,公共构造函数,非公共构造函数,根据不同的构造函数 Class提供了几种获取不同构造函数的方法

     Constructor<?>[]

    getConstructors() 
    获取所有的公共构造函数

     Constructor<?>[]

    getDeclaredConstructors() 
    获取所有的构造函数,和访问权限无关

     Constructor<T>

    getConstructor(Class<?>... parameterTypes) 
    获取指定的公共构造函数

    parameterTypes : 如果构造函数有参数,传递的是参数的字节码实例

     Constructor<T>

    getDeclaredConstructor(Class<?>... parameterTypes) 
    获取和访问权限无关的指定的构造函数

     1 //通过字节码实例获取构造器
     2 
     3 public class ConstructorTest {
     4 
     5 @Test
     6 
     7 public void testName() throws Exception {
     8 
     9  
    10 
    11 //1.获取Student的字节码实例
    12 
    13 Class<?>  stuClz = Student.class;
    14 
    15  
    16 
    17 //2.获取所有的公共构造函数
    18 
    19 Constructor<?>[] cts1 = stuClz.getConstructors();
    20 
    21 for (Constructor<?> ct : cts1) {
    22 
    23 System.out.println(ct);
    24 
    25 }
    26 
    27 System.out.println("----------------------");
    28 
    29 //3.获取所有的构造函数包括私有的
    30 
    31 Constructor<?>[] cts2 = stuClz.getDeclaredConstructors();
    32 
    33 for (Constructor<?> ct : cts2) {
    34 
    35 System.out.println(ct);
    36 
    37 }
    38 
    39 System.out.println("----------------------");
    40 
    41  
    42 
    43 //4.获取指定的构造函数(clz.getConstructor(...))只能获取公共的构造函数
    44 
    45 Constructor<?> ct1 = stuClz.getConstructor();
    46 
    47 System.out.println(ct1);
    48 
    49  
    50 
    51 Constructor<?> ct2 =stuClz.getConstructor(String.class);
    52 
    53 System.out.println(ct2);
    54 
    55 //4.获取指定的构造函数(clz.getDeclaredConstructor(...))获取的构造函数和权限没有关系
    56 
    57 Constructor<?> ct3=stuClz.getDeclaredConstructor(String.class,int.class);
    58 
    59 System.out.println(ct3);
    60 
    61 }
    62 
    63 }

    2) 调用构造函数创建对象

    调用构造器,创建对象

    Constructor<T>:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器

    常用方法:

    public T newInstance(Object... initargs):如调用带参数的构造器,只能使用该方式.

        参数:initargs:表示调用构造器的实际参数

        返回:返回创建的实例,T表示Class所表示类的类型

    如果:一个类中的构造器可以直接访问,同时没有参数.,那么可以直接使用Class类中的newInstance方法创建对象.

     public Object newInstance():相当于new 类名();

    调用私有的构造器:

    [1] Constructor 创建对象的方法

     T

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

    [2] Class类中创建对象的方法

    如果使用Class直接创建对象,必须保证类中有一个无参数公共构造函数

     T

    newInstance() 
    创建此 Class 对象所表示的类的一个新实例。

    [3]设置忽略访问权限

    默认执行私有构造函数报如下错误

    如果是私有构造方法,反射默认是无法直接执行的,找到父类中AccessibleObject的方法,设置为true,即可忽略访问权限

     

     void

    setAccessible(boolean flag) 
    如果执行私有方法,设置忽略访问权限 为 true即可

     1 //使用构造器创建对象
     2 
     3 public class NewInstanceTest {
     4 
     5 @Test
     6 
     7 public void testName() throws Exception {
     8 
     9  
    10 
    11 //1.获取Student的字节码实例
    12 
    13 Class<?> clz = Class.forName("cn.sxt.reflect.Student");
    14 
    15  
    16 
    17 //1.1如果类有无参数公共构造函数,直接可以使用类的字节码实例就创建对象
    18 
    19 Student stu0 = (Student) clz.newInstance();
    20 
    21  
    22 
    23  
    24 
    25 //2.获取一个参数的构造函数
    26 
    27 Constructor<Student> ct1 = (Constructor<Student>) clz.getConstructor(String.class);
    28 
    29 //2.1.创建对象
    30 
    31 Student stu1 = ct1.newInstance("东方不败");
    32 
    33  
    34 
    35 //3.获取私有构造函数并创建对象
    36 
    37 Constructor<Student> ct2 =  (Constructor<Student>) clz.getDeclaredConstructor(String.class,int.class);
    38 
    39 //3.1设置权限可以创建对象
    40 
    41 ct2.setAccessible(true);
    42 
    43 //3.2创建对象
    44 
    45 Student stu2 = ct2.newInstance("西门吹雪",50);
    46 
    47 }
    48 
    49 }

    6. 操作方法-Method

    一个类创建对象以后,一般就要执行对象的方法等等,使用反射操作对象

    首先要获取方法,再去执行

    1) 获取方法和方法的执行

    一个类中的方法有很多,无参,有参,静态,可变参数私有方法,等等,针对不同的方法处理,提供了不同的获取方案

    [1]使用Class 获取对应的方法

     Method[]

    getMethods() 
    获取所有的公共方法,包括父类的公共方法

     Method[]

    getDeclaredMethods() 
    获取所有本类的方法,包括本类的私有方法

     Method

    getDeclaredMethod(String name, Class<?>... parameterTypes) 
    获取指定方法名称的方法,和访问权限无关

    Name : 指定的方法名称

    parameterTypes : 方法参数的类型

    [2] Method执行方法

    方法获取以后就需要执行。Method对象中提供方法执行的功能

     Object

    invoke(Object obj, Object... args) 
    执行方法

    Obj :如果是对象方法,传指定的对象,如果是类方法,传 null

    Args: 方法的参数

    如果方法有返回结果,可以接收

    [3] 设置忽略访问权限

    如果是私有方法,反射默认是无法直接执行的,找到父类中AccessibleObject的方法,设置为true,即可忽略访问权限

     void

    setAccessible(boolean flag) 
    如果执行私有方法,设置忽略访问权限 为 true即可

    2)方法操作的代码

    [1]Student

     1 package cn.sxt.reflect._03method;
     2 
     3  
     4 
     5 import java.util.Arrays;
     6 
     7  
     8 
     9 public class Person {
    10 
    11  
    12 
    13 public void hell1() {
    14 
    15 System.out.println("我是无参数无返回值方法");
    16 
    17 }
    18 
    19  
    20 
    21 public String hello2(String name) {
    22 
    23  
    24 
    25 return "你好 :"+name;
    26 
    27 }
    28 
    29  
    30 
    31 private String hello3(String name,int age) {
    32 
    33 return "我是 :"+name+",今年 :"+age;
    34 
    35 }
    36 
    37  
    38 
    39  
    40 
    41  
    42 
    43 public static void  staticMethod(String name) {
    44 
    45 System.out.println("我是静态方法 :"+name);
    46 
    47 }
    48 
    49  
    50 
    51  
    52 
    53  
    54 
    55 public static void method1(int[] intArr) {
    56 
    57 System.out.println(Arrays.toString(intArr));
    58 
    59 }
    60 
    61  
    62 
    63 public static void method2(String[] strArr) {
    64 
    65 System.out.println(Arrays.toString(strArr));
    66 
    67 }
    68 
    69  
    70 
    71 }

    [2] 测试类

      1 package cn.sxt.reflect._03method;
      2 
      3  
      4 
      5 import static org.junit.Assert.*;
      6 
      7  
      8 
      9 import java.lang.reflect.Method;
     10 
     11  
     12 
     13 import org.junit.Test;
     14 
     15  
     16 
     17 //获取Person类的方法
     18 
     19 public class GetMethodTest {
     20 
     21  
     22 
     23 @Test
     24 
     25 public void testName() throws Exception {
     26 
     27 // 1.获取Person字节码实例
     28 
     29 Class<Person> clz = Person.class;
     30 
     31 // 2.创建对象
     32 
     33 Person p = clz.newInstance();
     34 
     35  
     36 
     37 // 3.获取方法(使用反射),获取所有公共方法,包含父类的公共方法
     38 
     39 Method[] methods1 = clz.getMethods();
     40 
     41 for (Method method : methods1) {
     42 
     43 System.out.println(method);
     44 
     45 }
     46 
     47 System.out.println("------------------------------");
     48 
     49 // 4.获取自己类中的所有方法(包括私有)
     50 
     51 Method[] methods2 = clz.getDeclaredMethods();
     52 
     53 for (Method method : methods2) {
     54 
     55 System.out.println(method);
     56 
     57 }
     58 
     59 System.out.println("------------------------------");
     60 
     61 // 4.获取单个指定名称的方法
     62 
     63 Method method = clz.getMethod("hello2", String.class);
     64 
     65 System.out.println(method);
     66 
     67  
     68 
     69 // 4.1执行方法
     70 
     71 Object res = method.invoke(p, "陆小凤");
     72 
     73 System.out.println(res);
     74 
     75  
     76 
     77 // 5.获取私有的方法
     78 
     79 Method hello3 = clz.getDeclaredMethod("hello3", String.class, int.class);
     80 
     81 System.out.println(hello3);
     82 
     83  
     84 
     85 // 5.1设置忽略访问权限
     86 
     87 hello3.setAccessible(true);
     88 
     89  
     90 
     91 Object res1 = hello3.invoke(p, "叶孤城", 30);
     92 
     93 System.out.println(res1);
     94 
     95  
     96 
     97 // 6.获取静态方法
     98 
     99 Method staticMethod = clz.getMethod("staticMethod", String.class);
    100 
    101  
    102 
    103 // 6.1执行静态方法
    104 
    105 staticMethod.invoke(null, "花满楼");
    106 
    107  
    108 
    109 // 7.获取有整数数组参数的方法
    110 
    111 Method method1 = clz.getMethod("method1", int[].class);
    112 
    113 method1.invoke(null, new Object[] {new int[] { 1, 2, 3, 4 }});
    114 
    115  
    116 
    117 // 8.获取有整数数组参数的方法
    118 
    119 /*
    120 
    121  * 如果反射传递的参数是引用类型,底层有一个拆箱的功能,会将数组的元素拆成一个个参数传递过来
    122 
    123  * 解决方案: 将数组外面在包装一层数组,如果拆箱一次,得到还是一个数组
    124 
    125  */
    126 
    127 Method method2 = clz.getMethod("method2", String[].class);
    128 
    129 method2.invoke(null,new Object[] {new String[] {"AA","BB","CC"}});
    130 
    131 }
    132 
    133 }

     

    [3]可变参数的方法执行

    如果方法中有可变参数(数组),如果反射传递的可变参数是引用类型底层有一个拆箱的功能,会将数组的元素拆成一个个参数传递过来

    解决方案: 将数组外面在包装一层数组,如果拆箱一次,得到还是一个数组

     1 package cn.sxt.reflect._03method;
     2 
     3  
     4 
     5 import java.util.Arrays;
     6 
     7  
     8 
     9 public class Person {
    10 
    11  
    12 
    13 public static void method1(int... intArr) {
    14 
    15 System.out.println(Arrays.toString(intArr));
    16 
    17 }
    18 
    19  
    20 
    21 public static void method2(String...strArr) {
    22 
    23 System.out.println(Arrays.toString(strArr));
    24 
    25 }
    26 
    27 }
     1     // 7.获取有整数数组参数的方法
     2 
     3 Method method1 = clz.getMethod("method1", int[].class);
     4 
     5 method1.invoke(null, new Object[] {new int[] { 1, 2, 3, 4 }});
     6 
     7  
     8 
     9 // 8.获取有整数数组参数的方法
    10 
    11 /*
    12 
    13  * 如果反射传递的参数是引用类型,底层有一个拆箱的功能,会将数组的元素拆成一个个参数传递过来
    14 
    15  * 解决方案: 将数组外面在包装一层数组,如果拆箱一次,得到还是一个数组
    16 
    17  */
    18 
    19 Method method2 = clz.getMethod("method2", String[].class);
    20 
    21 method2.invoke(null,new Object[] {new String[] {"AA","BB","CC"}});

    7. 操作字段(成员变量)-Field

    类中的字段有各种数据类型和各种访问权限,针对这些情况,反射操作有对应的方法来获取和处理

    1) Class中获取字段方法

     Field[]

    getFields()

    获取当前Class所表示类中所有的public的字段,包括继承的字段.

     Field[]

    getDeclaredFields()

    获取当前Class所表示类中所有的字段,不包括继承的字段.

     Field

    getField(String name)

    获取当前Class所表示类中

     Field

    getDeclaredField(String name)

    :获取当前Class所表示类中该fieldName名字的字段,不包括继承的字段.

    2) Field操作设置值/获取值

    给某个类中的字段设置值和获取值:

    1,找到被操作字段所在类的字节码

    2,获取到该被操作的字段对象

    3,设置值/获取值

    Field类常用方法:

     void setXX(Object obj, XX value) :为基本类型字段设置值,XX表示基本数据类型

     void set(Object obj, Object value) :表示为引用类型字段设置值

      参数:

          obj:    表示字段底层所属对象,若该字段是static,该值应该设为null

          value:  表示将要设置的值

    -------------------------------------------------------------------------------------

     XX getXX(Object obj) :获取基本类型字段的值,XX表示基本数据类型

     Object get(Object obj) :表示获取引用类型字段的值

       参数:

          obj:    表示字段底层所属对象,若该字段是static,该值应该设为null

       返回:返回该字段的值.

    void

    set(Object obj, Object value) 

    设置引用类型的值,非基本数据类型

    Obj: 要设置值得对象

    Value : 要设置的值

      1 package cn.sxt.reflect._04Field;
      2 
      3  
      4 
      5 import static org.junit.Assert.*;
      6 
      7  
      8 
      9 import java.lang.reflect.Field;
     10 
     11  
     12 
     13 import org.junit.Test;
     14 
     15  
     16 
     17 public class FieldTest {
     18 
     19  
     20 
     21 @Test
     22 
     23 public void testName() throws Exception {
     24 
     25 //1.获取People字节码
     26 
     27 Class<People> clz = People.class;
     28 
     29  
     30 
     31 People p = clz.newInstance();
     32 
     33  
     34 
     35 //2.获取所有公共字段
     36 
     37 Field[] fields1 = clz.getFields();
     38 
     39 for (Field field : fields1) {
     40 
     41 System.out.println(field);
     42 
     43 }
     44 
     45 System.out.println("---------------------");
     46 
     47 //3.获取所有字段,和访问权限无关
     48 
     49 Field[] fields2 = clz.getDeclaredFields();
     50 
     51 for (Field field : fields2) {
     52 
     53 System.out.println(field);
     54 
     55 }
     56 
     57 System.out.println("---------------------");
     58 
     59 //3.获取指定的公共字段
     60 
     61 Field emialField = clz.getField("emial");
     62 
     63 System.out.println(emialField);
     64 
     65  
     66 
     67 //为字段设置值
     68 
     69 emialField.set(p, "zhagnsan@qq.com");
     70 
     71 System.out.println(p);
     72 
     73 //4.获取指定所有的字段,和访问权限无关
     74 
     75 Field nameFiled = clz.getDeclaredField("name");
     76 
     77 System.out.println(nameFiled);
     78 
     79 //设置忽略访问权限
     80 
     81 nameFiled.setAccessible(true);
     82 
     83 nameFiled.set(p, "张三");
     84 
     85 System.out.println(p);
     86 
     87  
     88 
     89  
     90 
     91 //5 获取age字段
     92 
     93 Field ageFile = clz.getDeclaredField("age");
     94 
     95 ageFile.setAccessible(true);
     96 
     97 //设置忽略访问权限
     98 
     99 ageFile.setInt(p, 18);
    100 
    101 System.out.println(p);
    102 
    103  
    104 
    105 }
    106 
    107 }

    8. Class的其他API方法

     1 //Class字节码的其他api
     2 
     3 @Test
     4 
     5 public void testName() throws Exception {
     6 
     7  
     8 
     9 //1.获取UserDaoImpl的字节码实例
    10 
    11 Class<?> clz = Class.forName("cn.sxt.reflect._05otherapi.UserDaoImpl");
    12 
    13  
    14 
    15 //2.获取所有的接口
    16 
    17 Class<?>[] interfaces = clz.getInterfaces();
    18 
    19 for (Class<?> intface : interfaces) {
    20 
    21 System.out.println(intface);
    22 
    23 }
    24 
    25  
    26 
    27 //3.获取全限定名
    28 
    29 System.out.println(clz.getName());
    30 
    31  
    32 
    33 //4.获取简单类名
    34 
    35 System.out.println(clz.getSimpleName());
    36 
    37 //5.获取包
    38 
    39 System.out.println(clz.getPackage().getName());
    40 
    41 }

    9. JavaBean

    问题: 什么是javaBean?

    答: JavaBean就是一个个Java类,在java中符合JavaBean特点类才叫做JavaBean

    1) JavaBean的三个特点

    1. JavaBean类的修饰符必须是public,也就是一个JavaBean必须是一个对应一个类文件
    2. JavaBean必须有无参数公共构造方法(以便于反射直接通过直接通过字节码实例创建对象)
    3. JavaBean中的成员变量/字段必须有get/set方法提供对应的属性

    [1]JavaBean中的属性

    Java类有成员变量,成员变量绝对不是属性JavaBean中的属性是有get/set方法确定的

    Get方法确定属性

    public String getName() {
    
    return name;
    
    }
    
    //属性确定规则 : get方法去掉 get前缀 ,剩余部分 首字母小写
    //属性名称 : name

     Set方法确定属性

    public void setName(String name) {
    
    this.name = name;
    
    }
    
    //属性确定规则 : set方法去掉 set前缀 ,剩余部分 首字母小写
    
    //属性名称 : name

    如果一个成员变量get/set方法都有确定的属性就只有一个

    问题 get/set方法确定确定的属性一般不就和成员变量一样啊,为什么要有属性了

    答: 一般情况下,Eclipse工具有自动生成get/set方法的功能,确定的JavaBean的属性只是恰好和成员变量相同,但是成员变量不是属性

    如下特殊性情况,属性和成员变量名称不同

    //Filed
    
    private String firstName;
    
    //Filed
    
    private String lastName;
    
     
    
    //属性 : fullName
    
    public String getFullName() {
    
    return this.firstName + this.lastName;
    
    }

    10. 小结

      1.理解反射概念?反射能干啥?

        反射: jvm运行阶段,动态的获取类的信息(字节码实例,构造器,方法,字段),动态进行对象的创建,方法执行,字段操作。

      2.反射的常用类

        (1) Class :所有类的字节码实例的描述

        (2) Constructor :构造器

        (3) Method :方法

        (4) Field :字段

      3.JDBC+反射 代码封装

  • 相关阅读:
    Program C--二分
    Program A-归并排序
    Program E-- CodeForces 18C
    Program B--CodeForces 492B
    2015 HUAS Provincial Select Contest #1 C
    2015 HUAS Provincial Select Contest #1 B
    2015 HUAS Provincial Select Contest #1 A
    CSU 1111.三家人。第三次选拔赛D题:整理花园酬劳分配问题
    将10进制整数转换成16进制整数输出
    -UVa10935题:Trowing cards away1解答及简单分析
  • 原文地址:https://www.cnblogs.com/qq2267711589/p/10923203.html
Copyright © 2020-2023  润新知