J2SE 8的反射

1.获得Class的四种方式

//(1) 利用对象调用getClass()方法获取该对象的Class实例
Class<? extends ReflectTest> class1 = new ReflectTest().getClass();

//(2) 使用Class类的静态方法forName(),用类的名字获取一个Class实例
Class<?> class2 = Class.forName("reflect.ReflectTest");

//(3) 运用.class的方式获取Class实例
Class<ReflectTest> class3 = ReflectTest.class;

//对于基本数据类型的封装类,还可以采用TYPE来获取对应的基本数据类型的Class实例
Class<Byte> byteType = Byte.TYPE;
Class<Short> shortType = Short.TYPE;
Class<Integer> integerType = Integer.TYPE;
Class<Long> longType = Long.TYPE;
Class<Float>   floatType =   Float.TYPE;
Class<Double>  doubleType =  Double.TYPE;
Class<Boolean> booleanType = Boolean.TYPE;
Class<Character> charType = Character.TYPE;

// (4) 通过类的加载器
Class<?> class4 = Thread.currentThread().getContextClassLoader().loadClass("reflect.ReflectTest");
class4 = new ReflectTest().getClass().getClassLoader().loadClass("reflect.ReflectTest");
//this.getClass().getClassLoader().loadClass("reflect.ReflectTest");  ==>  用在方法中
class4 = ClassLoader.getSystemClassLoader().loadClass("reflect.ReflectTest");

方法三和方法四中是通过类名得到,这两点要非常注意,这里的ClassName一定要从包名具体到类名,唯一定位到一个类才行,不然就会报ClassNotFound错误


(1) Class.forName(String className)

源码

@CallerSensitive
public static Class<?> forName(String className)
			throws ClassNotFoundException {
	Class<?> caller = Reflection.getCallerClass();
	return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
							   ClassLoader loader)
	throws ClassNotFoundException
{
	Class<?> caller = null;
	SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
		// Reflective call to get caller class is only needed if a security manager
		// is present.  Avoid the overhead of making this call otherwise.
		caller = Reflection.getCallerClass();
		if (sun.misc.VM.isSystemDomainLoader(loader)) {
			ClassLoader ccl = ClassLoader.getClassLoader(caller);
			if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
				sm.checkPermission(
					SecurityConstants.GET_CLASSLOADER_PERMISSION);
			}
		}
	}
	return forName0(name, initialize, loader, caller);
}
@param initialize whether the class must be initialized
参数initialize 表示该类是否必须被初始化, 如果设为true,表示在加载以后,还会进入链接阶段
源文件在编译后,在运行时,分为三个阶段,加载,链接和初始化。这里的initializeBoolean就是定义是否进行链接和初始化。而Class.forName默认是设置的为true,所以利用Class.forName()得到的类类型,除了加载进来以外,还进行了链接和初始化操作。

举例

class Config {
    private String name;

    static boolean flag;
    static {
        flag = false;
        System.out.println("flag 的值为:" + flag);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
ClassLoader system = ClassLoader.getSystemClassLoader();
Class<Config> cls = null;
System.out.println("----------方法1----------");
cls = (Class<Config>) Class.forName("reflect.Config");

System.out.println("----------方法2----------");
cls = (Class<Config>) Class.forName("reflect.Config", false, system);

System.out.println("----------方法3----------");
cls = (Class<Config>) Class.forName("reflect.Config", true, system);

结果

----------方法1----------
flag 的值为:false
----------方法2----------
----------方法3----------

我们发现方法3没有输出flag的值,这是为什么呢?

原因是类加载过程中的缓存机制,由于方法1已经加载了该类,因此方法3不会再次加载该类,所以没有输出flag值,为了测试缓存的问题,我们将方法1与方法3的位置互换,程序的执行结果如下,可以看到方法3加载了该类,并且输出去了flag值,而方法1没有输出flag值。我们每次修改完代码都需要重启JVM来执行新的代码也是由类加载的缓存机制造成的。

ClassLoader system = ClassLoader.getSystemClassLoader();
Class<Config> cls = null;
System.out.println("----------方法3----------");
cls = (Class<Config>) Class.forName("reflect.Config", true, system);

System.out.println("----------方法2----------");
cls = (Class<Config>) Class.forName("reflect.Config", false, system);

System.out.println("----------方法1----------");
cls = (Class<Config>) Class.forName("reflect.Config");
结果

----------方法3----------
flag 的值为:false
----------方法2----------

----------方法1----------


Class.forName() 方法中,initialize参数控制类在加载的过程中是否进行初始化.


(2) ClassLoader.loadClass(String ClassName)

public Class<?> loadClass(String name) throws ClassNotFoundException {
	return loadClass(name, false);
}

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
{
	synchronized (getClassLoadingLock(name)) {
		// First, check if the class has already been loaded
		Class<?> c = findLoadedClass(name);
		if (c == null) {
			long t0 = System.nanoTime();
			try {
				if (parent != null) {
					c = parent.loadClass(name, false);
				} else {
					c = findBootstrapClassOrNull(name);
				}
			} catch (ClassNotFoundException e) {
				// ClassNotFoundException thrown if class not found
				// from the non-null parent class loader
			}

			if (c == null) {
				// If still not found, then invoke findClass in order
				// to find the class.
				long t1 = System.nanoTime();
				c = findClass(name);

				// this is the defining class loader; record the stats
				sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
				sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
				sun.misc.PerfCounter.getFindClasses().increment();
			}
		}
		if (resolve) {
			resolveClass(c);
		}
		return c;
	}
}
调用方法2的过程中, resolve参数的值为false。javadoc中关于该方法的 resolve参数的说明如下:
@param  resolve If <tt>true</tt> then resolve the class
参数resolve 如果值为true则resolve这个类
if (resolve) {
    resolveClass(c);
}
    /**
     * Links the specified class.  This (misleadingly named) method may be
     * used by a class loader to link a class.  If the class <tt>c</tt> has
     * already been linked, then this method simply returns. Otherwise, the
     * class is linked as described in the "Execution" chapter of
     * <cite>The Java™ Language Specification</cite>.
     *
     * @param  c
     *         The class to link
     *
     * @throws  NullPointerException
     *          If <tt>c</tt> is <tt>null</tt>.
     *
     * @see  #defineClass(String, byte[], int, int)
     */
    protected final void resolveClass(Class<?> c) {
        resolveClass0(c);
    }
从javadoc中我们可以看出, resolveClass方法主要是用来链接指定的类,通过 resolve参数我们可以发现,该参数控制了类加载过程的第二步(链接),该参数值为 false时不进行类的链接,为 true时进行类的链接,由于loadClass(String name, boolean resolve)为 protected方法,因此我们无法通过ClassLoader直接调用。

System.out.println("-----方法4-----");
cls = (Class<Config>)ClassLoader.getSystemClassLoader().loadClass("reflect.Config");

System.out.println("-----方法5-----");
Class<?> loadClass = Thread.currentThread().getContextClassLoader().loadClass("reflect.Config");

结果

-----方法4-----
-----方法5-----


注意,当加上 getDeclaredField()时

System.out.println("-----方法4-----");
cls = (Class<Config>)ClassLoader.getSystemClassLoader().loadClass("reflect.Config");
cls.getDeclaredField("flag").get("flag");
-----方法4-----

flag 的值为:false

System.out.println("-----方法5-----");
Class<?> loadClass = Thread.currentThread().getContextClassLoader().loadClass("reflect.Config");
loadClass.getDeclaredField("flag").get("flag");
-----方法5-----

flag 的值为:false


ClassLoader.getSystemClassLoader().loadClass()方法中,resolve参数控制类在加载的过程中是否进行链接。


(3) 关于mysql jdbc

进行数据库操作的时候,通常采用如下的方式加载数据库驱动。

Class.forName("com.mysql.jdbc.Driver"); 

为什么不是ClassLoader.getSystemClassLoader().loadClass()呢?这是因为Driver类中的静态代码块需要进行一些初始化配置。代码如下.

Copyright  2002-2004 MySQL AB, 2008 Sun Microsystems
package com.mysql.jdbc;

import java.sql.SQLException;

/**
 * The Java SQL framework allows for multiple database drivers. Each driver
 * should supply a class that implements the Driver interface
 * 
 * <p>
 * The DriverManager will try to load as many drivers as it can find and then
 * for any given connection request, it will ask each driver in turn to try to
 * connect to the target URL.
 * 
 * <p>
 * It is strongly recommended that each Driver class should be small and
 * standalone so that the Driver class can be loaded and queried without
 * bringing in vast quantities of supporting code.
 * 
 * <p>
 * When a Driver class is loaded, it should create an instance of itself and
 * register it with the DriverManager. This means that a user can load and
 * register a driver by doing Class.forName("foo.bah.Driver")
 * 
 * @see org.gjt.mm.mysql.Connection
 * @see java.sql.Driver
 * @author Mark Matthews
 * @version $Id$
 */
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    // ~ Static fields/initializers
    // ---------------------------------------------

    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    // ~ Constructors
    // -----------------------------------------------------------

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}


2. 基本类类型周边信息获取

Class对象的所有方法:

<U> Class<? extends U>asSubclass(Class<U> clazz)强制转换该 Class 对象,以表示指定的 class 对象所表示的类的一个子类。
Tcast(Object obj)将一个对象强制转换成此 Class 对象所表示的类或接口。
booleandesiredAssertionStatus()如果要在调用此方法时将要初始化该类,则返回将分配给该类的断言状态。
static Class<?>forName(String className)返回与带有给定字符串名的类或接口相关联的 Class 对象。
static Class<?>forName(String name, boolean initialize, ClassLoader loader)使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象。
AnnotatedType[]getAnnotatedInterfaces()返回注释的AnnotatedType
AnnotatedTypegetAnnotatedSuperclass()返回父类的注解的AnnotatedType
<A extends Annotation> AgetAnnotation(Class<A> annotationClass)如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。
Annotation[]getAnnotations()返回此元素上存在的所有注释。
<A extends Annotation> A[]getAnnotationsByType(Class<A> annotationClass)返回一个该类型的注解数组
StringgetCanonicalName()返回 Java Language Specification 中所定义的底层类的规范化名称。
Class<?>[]getClasses()返回类定义的公共的内部类,以及从父类、父接口那里继承来的内部类
ClassLoadergetClassLoader()返回该类的类加载器。
Class<?>getComponentType()返回表示数组组件类型的 Class。如果此类表示数组类,返回表示此类组件类型的 Class。否则返回null。 
Constructor<T>getConstructor(Class<?>... parameterTypes)返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
Constructor<?>[]getConstructors()返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
<A extends Annotation> AgetDeclaredAnnotation(Class<A> annotationClass)返回直接存在于此元素上的该类型注释。
Annotation[]getDeclaredAnnotations()返回直接存在于此元素上的所有注释。
<A extends Annotation> A[]getDeclaredAnnotationsByType(Class<A> annotationClass)返回直接存在于此元素上的该类型的注解数组。
Class<?>[]getDeclaredClasses()返回类定义的公共的内部类,不包括继承的。
Constructor<T>getDeclaredConstructor(Class<?>... parameterTypes)根据指定参数类型顺序返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。可获取非公共构造方法。
Constructor<?>[]getDeclaredConstructors()返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。不仅是公共的。
FieldgetDeclaredField(String name)返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
Field[]getDeclaredFields()返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。
MethodgetDeclaredMethod(String name, Class<?>... parameterTypes)返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
Method[]getDeclaredMethods()返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
Class<?>getDeclaringClass()如果此 Class 对象所表示的类或接口是另一个类的成员,则返回的 Class 对象表示该对象的声明类。即返回所在类的class对象,对成员class对象有效。
Class<?>getEnclosingClass()返回底层类的立即封闭类。
Constructor<?>getEnclosingConstructor()如果该 Class 对象表示构造方法中的一个本地或匿名类,则返回 Constructor 对象,它表示底层类的立即封闭构造方法。
MethodgetEnclosingMethod()如果此 Class 对象表示某一方法中的一个本地或匿名类,则返回 Method 对象,它表示底层类的立即封闭方法。
T[]getEnumConstants()如果此 Class 对象不表示枚举类型,则返回枚举类的元素或 null。
FieldgetField(String name)返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
Field[]getFields()返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
Type[]getGenericInterfaces()返回表示某些接口的 Type,这些接口由此对象所表示的类或接口直接实现。
TypegetGenericSuperclass()返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。
Class<?>[]getInterfaces()确定此对象所表示的类或接口实现的接口。
MethodgetMethod(String name, Class<?>... parameterTypes)返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method[]getMethods()返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
intgetModifiers()返回此类或接口以整数编码的 Java 语言修饰符。
StringgetName()以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
PackagegetPackage()获取此类的包。
ProtectionDomaingetProtectionDomain()返回该类的 ProtectionDomain。
URLgetResource(String name)查找带有给定名称的资源。
InputStreamgetResourceAsStream(String name)查找具有给定名称的资源。
Object[]getSigners()获取此类的标记。
StringgetSimpleName()返回源代码中给出的底层类的简称。
Class<? super T>getSuperclass()返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。
StringgetTypeName()返回该类型的名称
TypeVariable<Class<T>>[]getTypeParameters()按声明顺序返回 TypeVariable 对象的一个数组,这些对象表示用此 GenericDeclaration 对象所表示的常规声明来声明的类型变量。
booleanisAnnotation()如果此 Class 对象表示一个注释类型则返回 true。
booleanisAnnotationPresent(Class<? extends Annotation> annotationClass)如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。
booleanisAnonymousClass()当且仅当底层类是匿名类时返回 true。
booleanisArray()判定此 Class 对象是否表示一个数组类。
booleanisAssignableFrom(Class<?> cls)判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。
booleanisEnum()当且仅当该类声明为源代码中的枚举时返回 true。
booleanisInstance(Object obj)判定指定的 Object 是否与此 Class 所表示的对象赋值兼容。
booleanisInterface()判定指定的 Class 对象是否表示一个接口类型。
booleanisLocalClass()当且仅当底层类是本地类时返回 true。
booleanisMemberClass()当且仅当底层类是成员类时返回 true。
booleanisPrimitive()判定指定的 Class 对象是否表示一个基本类型。
booleanisSynthetic()如果此类是复合类,则返回 true,否则 false。有匿名类部类的类可以称作为复合类。
TnewInstance()调用默认构造方法创建此 Class 对象所表示的类的一个新实例。
StringtoGenericString()返回该对象的描述,包含标识符等。

(1) 类名,包名获取

//获取完整的类名(包含包名)  
public String getName();  
//仅获取类名  
public String getSimpleName();  
//获取类类型所对应的package对象  
public Package getPackage() 

举例

Class<?> clazz = ReflectTest.class;  
Package package1 = clazz.getPackage();  
  
System.out.println("包名:"+package1.getName());
System.out.println("完整的类名:"+clazz.getName());
System.out.println("仅获取类名:"+clazz.getSimpleName());

结果

包名:reflect
完整的类名:reflect.ReflectTest

仅获取类名:ReflectTest


(2)获取超类Class对象

//获取普通函数的父类Class对象  
public Class<?> getSuperclass();  
//针对泛型父类而设计  
public Type getGenericSuperclass();  

举例

Class<? super ArrayList> superclass = ArrayList.class.getSuperclass();
System.out.println(superclass.getName());

Type genericSuperclass = ArrayList.class.getGenericSuperclass();
System.out.println(genericSuperclass.getTypeName());

结果

java.util.AbstractList
java.util.AbstractList<E>


(3) 获取类所直接继承的接口的Class对象

//获取普通接口的方法  
public Class<?>[] getInterfaces();  
//获取泛型接口的方法  
public Type[] getGenericInterfaces(); 
//获取类继承的、带注解的接口
public AnnotatedType[] getAnnotatedInterfaces(); 

getInterfaces() & getGenericInterfaces()将获取指定类直接继承的接口列表!注意一点:直接继承!!!如果不是直接继承,那将是获取不到的。

举例

Class<?>[] interfaces = ArrayList.class.getInterfaces();
System.out.println(Arrays.toString(interfaces));

Type[] genericInterfaces = ArrayList.class.getGenericInterfaces();
System.out.println(Arrays.toString(genericInterfaces));

AnnotatedType[] annotatedInterfaces = ArrayList.class.getAnnotatedInterfaces();
System.out.println(Arrays.toString(annotatedInterfaces));

结果

[interface java.util.List, interface java.util.RandomAccess, interface java.lang.Cloneable, interface java.io.Serializable]
[java.util.List<E>, interface java.util.RandomAccess, interface java.lang.Cloneable, interface java.io.Serializable]

[sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedParameterizedTypeImpl@4e25154f, sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl@70dea4e, sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl@5c647e05, sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl@33909752]


(4) 获取某个类类型的所有接口

/** 
 * 获取所传类类型的所有继承的接口列表 
 * @param clazz 
 * @return 
 */  
public static Class<?>[] getAllInterface(Class<?> clazz){  
    //获取自身的所有接口  
    Class<?>[] interSelf = clazz.getInterfaces();
    
    //递规调用getAllInterface获取超类的所有接口  
    Class<?> superClazz = clazz.getSuperclass();  
    Class<?>[] interParent = null;  
    if (null != superClazz) {  
        interParent = getAllInterface(superClazz);  
    }  
  
    //返回值  
    if (interParent == null && interSelf != null){  
        return interSelf;  
    }else if (interParent == null && interSelf == null){  
        return null;  
    }else if (interParent != null && interSelf == null){  
        return interParent;  
    }else {  
        final int length = interParent.length + interSelf.length;  
        Class<?>[] result = new Class[length];  
        System.arraycopy(interSelf,0,result,0,interSelf.length);  
        System.arraycopy(interParent,0,result,interSelf.length,interParent.length);  
        
        return result;  
    }  
} 

举例

Class<?>[] allInterface = getAllInterface(ArrayList.class);
System.out.println(Arrays.toString(allInterface));

结果

[interface java.util.List, interface java.util.RandomAccess, interface java.lang.Cloneable, interface java.io.Serializable, interface java.util.List, interface java.util.Collection]

(5)泛型相关


1)Type

Class类中泛型相关

public Type getGenericSuperclass();
public Type[] getGenericInterfaces();
Type的五种类型
  • Class
  • ParameterizedType:代表的是一个泛型类型,比如Point;
  • TypeVariable:这个代表的就是泛型变量,例如Point,这里面的T就是泛型变量,而如果我们利用一种方法获得的对象是T,那它对应的类型就是TypeVariable;
  • WildcardType:通配符比如:? extends Integer,那它对应的类型就是WildcardType;
  • GenericArrayType:如果我们得到的是类似String[]这种数组形式的表达式,那它对应的类型就是GenericArrayType,非常值得注意的是如果type对应的是表达式是ArrayList这种的,这个type类型应该是ParameterizedType,而不是GenericArrayType,只有类似Integer[]这种的才是GenericArrayType类型。

2)getGenericSuperclass()

class Point<T> {  
    private T x,y;  
  
    public T getX() {  
        return x;  
    }  
  
    public void setX(T x) {  
        this.x = x;  
    }  
  
    public T getY() {  
        return y;  
    }  
  
    public void setY(T y) {  
        this.y = y;  
    }  
  
}  
//PointImpl类的实现  
class PointImpl extends Point<Integer> {  
}  
Class<?> clazz = PointImpl.class;  
Type genericSuperclassType = clazz.getGenericSuperclass(); 

if(genericSuperclassType instanceof ParameterizedType) {
	ParameterizedType parameterizedType = (ParameterizedType) genericSuperclassType;  
	
	//返回表示此类型实际类型参数的 Type 对象的数组  
    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
    for (Type parameterArgType : actualTypeArguments) {  
        Class parameterArgClass = (Class) parameterArgType;  
       	System.out.println("填充类型为:" + parameterArgClass.getName());
    }  
  
    //返回 Type 对象,表示声明此类型的类或接口。  
    Type type1 = parameterizedType.getRawType();  
    Class class22 = (Class) type1;  
    System.out.println("PointImpl的父类类型为:"+class22.getName());
}
填充类型为:java.lang.Integer

PointImpl的父类类型为:t.Point

3)getGenericInterfaces()

class Point<T> {  
    private T x,y;  
  
    public T getX() {  
        return x;  
    }  
  
    public void setX(T x) {  
        this.x = x;  
    }  
  
    public T getY() {  
        return y;  
    }  
  
    public void setY(T y) {  
        this.y = y;  
    }  
  
} 

interface PointInterface<T,U> {  
} 

class PointImpl extends Point<Integer> implements PointInterface<String,Double> {  
}  
Class<?> clazz = PointImpl.class;  
Type[] genericInterfaces = clazz.getGenericInterfaces(); 

for (Type genericSuperclassType : genericInterfaces) {
	if(genericSuperclassType instanceof ParameterizedType) {
		ParameterizedType parameterizedType = (ParameterizedType) genericSuperclassType;  
		
		//返回表示此类型实际类型参数的 Type 对象的数组  
		Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
		for (Type parameterArgType : actualTypeArguments) {  
			Class parameterArgClass = (Class) parameterArgType;  
			System.out.println("填充类型为:" + parameterArgClass.getName());
		}  
		
		//返回 Type 对象,表示声明此类型的类或接口。  
		Type type1 = parameterizedType.getRawType();  
		Class class22 = (Class) type1;  
		System.out.println("PointImpl的父类类型为:"+class22.getName());
	}
}
填充类型为:java.lang.String
填充类型为:java.lang.Double
PointImpl的父类类型为:t.PointInterface

4)ParameterizedType

  • getActualTypeArguments():用来返回当前泛型表达式中,用来填充泛型变量的真正值的列表。像我们这里得到的Point,用来填充泛型变量T的是Integer类型,所以这里返回的Integer类型所对应的Class对象。(有关这一段,下面会补充,这里先看getRawType)
  • getRawType():我们从我们上面的代码中,也可以看到,它返回的值是Point,所以它的意义就是声明当前泛型表达式的类或者接口的Class对象。比如,我们这里的type对应的是Point,而声明Point这个泛型的当然是Point类型。所以返回的是Point.Class

5)TypeVariable

type代表的类型是一个泛型变量时,它的类型就是TypeVariable。TypeVariable有两个函数

  • getName:就是得到当前泛型变量的名称;
  • getBounds:返回表示此类型变量上边界的 Type 对象的数组。如果没有上边界,则默认返回Object;
interface TypeVariablePointInterface<T, U> {
}

class TypeVariableointGenericityImpl<T extends Number & Serializable> implements TypeVariablePointInterface<T, Integer> {
}
Class<?> clazz = TypeVariableointGenericityImpl.class;
Type[] types = clazz.getGenericInterfaces();

for (Type type : types) {
	if (type instanceof ParameterizedType) {
		ParameterizedType parameterizedType = (ParameterizedType) type;
		// 返回表示此类型实际类型参数的 Type 对象的数组
		Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
		
		for (Type parameterArgType : actualTypeArguments) {

			if (parameterArgType instanceof TypeVariable) {
				TypeVariable typeVariable = (TypeVariable) parameterArgType;
				System.out.println("此接口的填充类型为:" + typeVariable.getName());

				// 返回表示此类型变量上边界的 Type 对象的数组。
				Type[] typebounds = typeVariable.getBounds();
				for (Type bound : typebounds) {
					Class<?> boundClass = (Class) bound;
					// 如果不写,则默认输出Object,如果写了,则输出对应的
					System.out.println("bound为:" + boundClass.getName());
				}
			}

			if (parameterArgType instanceof Class) {
				Class parameterArgClass = (Class) parameterArgType;
				System.out.println("此接口的填充类型为:" + parameterArgClass.getName());
			}
		}
	}
}
此接口的填充类型为:T
bound为:java.lang.Number
bound为:java.io.Serializable
此接口的填充类型为:java.lang.Integer

6)WildcardType

当type所代表的表达式是类型通配符相关的表达式时,比如<? extends Integer>,<? super String>,或者<?>等,这个type的类型就是WildcardType!
我们先来看看WildcardType的函数:
  • getUpperBounds:获取上边界对象列表,上边界就是使用extends关键定所做的的限定,如果没有默认是Object;
  • getLowerBounds:获取下边界对象列表,下边界是指使用super关键字所做的限定,如果没有,则为Null
举个例子:
<? extends Integer>:这个通配符的上边界就是Integer.Class,下边界就是null

<? super String>:这个通配符的下边界是String,上边界就是Object;

通配符只能用来填充泛型类来生成对象

interface PointSingleInterface<T> {
}

class PointWildcardImpl implements PointSingleInterface<Comparable<? extends Number>> {
}

Class<?> clazz = PointWildcardImpl.class;
// 此时的type对应PointSingleInterface<Comparable<? extends Number>>
Type[] types = clazz.getGenericInterfaces();

for (Type type : types) {
	if (type instanceof ParameterizedType) {
		ParameterizedType parameterizedType = (ParameterizedType) type;
		// 得到填充PointSingleInterface的具体参数,即:Comparable<? extends Number>,仍然是一个ParameterizedType
		Type[] actualTypes = parameterizedType.getActualTypeArguments();
		
		for (Type actualType : actualTypes) {
			if (actualType instanceof ParameterizedType) {
				ParameterizedType ComparableType = (ParameterizedType) actualType;
				// 对Comparable<? extends Number>再取填充参数,得到的type对应<? extends Number>,这个就是WildcardType了
				Type[] compareArgs = ComparableType.getActualTypeArguments();
				
				for (Type Arg : compareArgs) {
					if (Arg instanceof WildcardType) {
						// 将得到的对应WildcardType的type强转为WildcardType的变量
						WildcardType wt = (WildcardType) Arg;

						// 利用getLowerBounds得到下界,即派生自Super的限定,如果没有派生自super则为null
						Type[] lowerBounds = wt.getLowerBounds();
						for (Type bound : lowerBounds) {
							Class<?> boundClass = (Class) bound;
							System.out.println("lowerBound为:" + boundClass.getName());
						}

						// 通过getUpperBounds得到上界,即派生自extends的限定,如果没有,默认是Object
						Type[] upperBounds = wt.getUpperBounds();
						for (Type bound : upperBounds) {
							Class<?> boundClass = (Class) bound;
							// 如果不写,则默认输出Object,如果写了,则输出对应的
							System.out.println("upperBound为:" + boundClass.getName());
						}

					}
				}
			}
		}

	}
}
upperBound为:java.lang.Number

7)GenericArrayType

当type对应的类型是类似于String[]、Integer[]等的数组时,那type的类型就是GenericArrayType;这里要特别说明的如果type对应的是类似于ArrayList、List这样的类型,那type的类型应该是ParameterizedType,而不是GenericArrayType,因为ArrayList是一个泛型表达式。所以当且仅当type对应的类型是类似于String[]、Integer[]这样的数组时,type的类型才是GenericArrayType!


  • getGenericComponentType()

这是GenericArrayType仅有一个函数,由于getGenericComponentType所代表的表达是String[]这种的数组,所以getGenericComponentType获取的就是这里的数组类型所对应的Type,比如这里的String[]通过getGenericComponentType获取到的Type对应的就是String.

interface GenericArrayInterface<T> {
}

class GenericArrayImpl<U> implements GenericArrayInterface<U[]> {
}
Class<?> clazz = GenericArrayImpl.class;

Type[] interfaces = clazz.getGenericInterfaces();

for (Type type : interfaces) {
	if (type instanceof ParameterizedType) {
		ParameterizedType pt = (ParameterizedType) type;
		Type[] actualArgs = pt.getActualTypeArguments();
		
		for (Type arg : actualArgs) {
			if (arg instanceof GenericArrayType) {
				GenericArrayType arrayType = (GenericArrayType) arg;
				Type comType = arrayType.getGenericComponentType();
				
				System.out.println("数组类型为:" + comType.getTypeName());
			}
		}
	}
}
数组类型为:U

8)通用的类型转换函数

private static void parseClass(Class<?> c) {
	parseTypeParameters(c.getGenericInterfaces());
	System.out.println();
}

private static void parseTypeParameter(Type type) {
	if (type instanceof Class) {
		Class<?> c = (Class<?>) type;
		
		System.out.println(c.getSimpleName());
	} else if (type instanceof TypeVariable) {
		TypeVariable<?> tv = (TypeVariable<?>) type;
		
		System.out.println(tv.getName());
		parseTypeParameters(tv.getBounds());
	} else if (type instanceof WildcardType) {
		WildcardType wt = (WildcardType) type;
		System.out.println("?");
		
		parseTypeParameters(wt.getUpperBounds());
		parseTypeParameters(wt.getLowerBounds());
	} else if (type instanceof ParameterizedType) {
		ParameterizedType pt = (ParameterizedType) type;
		Type t = pt.getOwnerType();
		if (t != null) {
			parseTypeParameter(t);
		}
		parseTypeParameter(pt.getRawType());
		parseTypeParameters(pt.getActualTypeArguments());
	} else if (type instanceof GenericArrayType) {
		GenericArrayType arrayType = (GenericArrayType) type;
		Type t = arrayType.getGenericComponentType();
		parseTypeParameter(t);
	}
}

private static void parseTypeParameters(Type[] types) {
	for (Type type : types) {
		parseTypeParameter(type);
	}
}
parseClass(PointImpl.class);
parseClass(TypeVariableointGenericityImpl.class);
parseClass(PointWildcardImpl.class);
parseClass(GenericArrayImpl.class);

PointInterface
String
Double

TypeVariablePointInterface
T
Number
Serializable
Integer

PointSingleInterface
Comparable
?
Number

GenericArrayInterface
U
Object




3. 创建类的实例

//(1) 通过class获取新对象
ReflectTest newReflectTest1 = class1.newInstance();

//(2) 利用构造器获取新对象
ReflectTest newReflectTest2 = class1.getConstructor(new Class[] {}).newInstance(new Object[] {});

4.设置可见性 setAccessible

Field field = class1.getDeclaredField("name");
// 判断可见性
boolean accessible = field.isAccessible();

// 方法一,单个对象设置可见性
field.setAccessible(true);

// 方法二,批量设置可见性
AccessibleObject.setAccessible(class1.getDeclaredFields(), true);
field.setAccessible(class1.getDeclaredFields(), true);

5. 获得 构造器,方法,字段, 修饰符

// (1).获取构造器, getDeclaredConstructors,可获取private的 newInstance
Constructor[] constructors = class1.getConstructors();
for (Constructor constructor : constructors) {
	// 获取构造器的参数类型
	Class[] parameterTypes = constructor.getParameterTypes();

	// constructor.newInstance(initargs)
}

class1.getDeclaredConstructors();

// (2).获取方法, getDeclaredMethods,可获取private的 invoke
Method[] declaredMethods = class1.getDeclaredMethods();
for (Method method : declaredMethods) {
	// 获取方法的参数类型,比如可以存储到map中,后面再使用
	Class<?>[] parameterTypes = method.getParameterTypes();

	// 调用静态方法 method.invoke(null, args)
	// 调用类方法 method.invoke(实例化类, args)
	// method.invoke(obj, args)

	// 得到返回类型
	Class<?> returnType = method.getReturnType();
}

// (3).获取field,getDeclaredFields,可获取private的 set/get
Field[] declaredFields = class1.getDeclaredFields();
for (Field tempField : declaredFields) {

}
Field declaredField = class1.getDeclaredField("name");
declaredField.setAccessible(true);

// (4).得到修饰字符
String string = Modifier.toString(declaredField.getModifiers());

Modifier.isPublic(declaredField.getModifiers());
Modifier.isPrivate(declaredField.getModifiers());
Modifier.isStatic(declaredField.getModifiers());

// (5) 例子
MethodTest methodTest = new MethodTest();
printConstructorMessage(methodTest);
printMethodMessage(methodTest);
printFieldMessage(methodTest);
/**
 * 获取对象的构造函数的信息
 *
 * @param object
 */
public static void printConstructorMessage(Object object) {
	System.out.println("printConstructorMessage");
	
	// 要获取类的信息,首先要获取类的类类型
	Class clazz = object.getClass();// 传递的是哪个子类的对象, clazz就是该子类的类类型
	// 获取类的名称
	System.out.println("类的名称是:" + clazz.getName());

	/**
	 * 构造函数也是对象 java.lang.Constructor中封装了构造函数的信息 getConstructors获取所有的public的构造函数
	 * getDeclaredConstructors得到所有的构造函数
	 */
	Constructor[] constructors = clazz.getConstructors();

	for (Constructor constructor : constructors) {
		System.out.print(constructor.getName() + "(");
		// 获取构造函数的参数列表--->得到的是参数列表的类类型
		Class[] paramTypes = constructor.getParameterTypes();

		for (Class clazz1 : paramTypes) {
			System.out.print(clazz1.getName() + ",");
		}
		System.out.println(")");
	}
	
	System.out.println();
	System.out.println();
}

/**
 * 获取成员函数 object 该对象所属类的信息
 *
 * @param object
 */
public static void printMethodMessage(Object object) {
	System.out.println("printMethodMessage");
	
	// 要获取类的信息,首先要获取类的类类型
	Class clazz = object.getClass();// 传递的是哪个子类的对象, clazz就是该子类的类类型
	// 获取类的名称
	System.out.println("类的名称是:" + clazz.getName());
	/**
	 * Method类,方法对象 一个成员方法就是一个Method对象 getMethods()方法获取的是所有的public的函数,包括父类继承而来的
	 * getDeclaredMethods()获取的是所有该类自己声明的方法,不问访问权限
	 */
	Method[] methods = clazz.getDeclaredMethods();

	for (int i = 0; i < methods.length; i++) {
		// 1. 获取注解
		Annotation[] annotations = methods[i].getAnnotations();
		for (Annotation a : annotations) {
			System.out.println(a);
		}

		// 2. 获取权限修饰符
		System.out.println("isPublic:" + Modifier.isPublic(methods[i].getModifiers()));
		System.out.println("isPrivate:" + Modifier.isPrivate(methods[i].getModifiers()));
		System.out.println("isStatic:" + Modifier.isStatic(methods[i].getModifiers()));

		String str = Modifier.toString(methods[i].getModifiers());
		System.out.print(str + " ");

		// 3. 得到方法的返回值类型的类类型
		Class returnType = methods[i].getReturnType();
		System.out.print(returnType.getName() + " ");

		// 4. 得到方法的名称
		System.out.print(methods[i].getName() + "(");

		// 5.获取参数类型-->得到的是参数列表的类型的类类型
		Class[] paramTypes = methods[i].getParameterTypes();
		// 解析数组
		for (int j = 0; j < paramTypes.length; j++) {
			if (j == 1 || j == paramTypes.length - 1) {
				System.out.print(paramTypes[j].getName() + " args" + j);
			} else {
				System.out.print(paramTypes[j].getName() + " args" + j + ",");
			}
		}
		System.out.print(")");

		// 6.获取异常类型
		Class[] exps = methods[i].getExceptionTypes();
		if (exps.length != 0) {
			System.out.print(" throws ");
		}

		for (int k = 0; k < exps.length; k++) {
			System.out.print(exps[k].getName() + " ");
		}
		
		System.out.println();
		System.out.println();
	}
	
	System.out.println();
	System.out.println();
}

/**
 * 获取成员变量
 */
public static void printFieldMessage(Object object) {
	System.out.println("printFieldMessage");
	
	// 要获取类的信息,首先要获取类的类类型
	Class clazz = object.getClass();// 传递的是哪个子类的对象, clazz就是该子类的类类型
	// 获取类的名称
	System.out.println("类的名称是:" + clazz.getName());
	/**
	 * 成员变量也是对象 java.lang.reflect.Field Field类封装了关于成员变量的操作
	 * getFields()方法获取的是所有的public的成员变量的信息 getDeclaredFields获取的是该类自己声明的成员变量的信息
	 */
	Field[] fields = clazz.getDeclaredFields();

	for (Field field : fields) {
		// 获取每个属性的权限修饰符
		String str = Modifier.toString(field.getModifiers());
		// 得到成员变量的类型的类类型
		Class fieldType = field.getType();
		String typeName = fieldType.getName();

		// 得到成员变量的名称
		String fieldName = field.getName();
		System.out.println(str + " " + typeName + " " + fieldName);
	}
	
	System.out.println();
	System.out.println();
}
class MethodTest {
    private String name;
    // 注解类型在java之注解开发章节讲解过的
    @Resource(name = "Annotation name")
    public int age;
    static String desc = "这是一个人";

    public MethodTest() {
    }

    public MethodTest(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Deprecated
    public void print(int a, int b) throws Exception {
        System.out.println(a + b);
    }

    public void print(String a, String b) {
        System.out.println(a.toUpperCase() + "," + b.toLowerCase());
    }

    @Override
    public String toString() {
        return "MethodTest{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
    
    public static void foo() {
        System.out.println("MethodTest static method foo()");
    }
}

运行结果:

printConstructorMessage
类的名称是:reflect.MethodTest
reflect.MethodTest()


printMethodMessage
类的名称是:reflect.MethodTest
isPublic:true
isPrivate:false
isStatic:false
public java.lang.String toString()

isPublic:false
isPrivate:true
isStatic:false
private void print(java.lang.String args0,java.lang.String args1)

@java.lang.Deprecated()
isPublic:true
isPrivate:false
isStatic:false
public void print(int args0,int args1) throws java.lang.Exception

isPublic:true
isPrivate:false
isStatic:true
public static java.lang.String fooWithParameters(java.lang.String args0,int args1)

isPublic:true
isPrivate:false
isStatic:true
public static java.lang.String foo()



printFieldMessage
类的名称是:reflect.MethodTest
private java.lang.String name
public int age

static java.lang.String desc


6. 构造器反射

//(1) 获取private构造器  getDeclaredConstructor()
Constructor<MethodTest> privateDeclaredConstructor = MethodTest.class.getDeclaredConstructor(String.class,int.class);
privateDeclaredConstructor.setAccessible(true);
MethodTest privateInstance = privateDeclaredConstructor.newInstance(new Object[] {"test string private Constructor", 8});
System.out.println(privateInstance.toString());
System.out.println();

//(2) 获取public构造器  getConstructor()
Constructor<MethodTest> publicDeclaredConstructor = MethodTest.class.getConstructor(new Class[]{});
MethodTest publicInstance = publicDeclaredConstructor.newInstance(new Object[] {});
System.out.println(publicInstance.toString());
System.out.println();

运行结果:

MethodTest{name='test string private Constructor', age=8, desc=这是一个人}

MethodTest{name='null', age=0, desc=这是一个人}



7. 方法的反射  method.invoke()

//(1) 调用静态方法 method.invoke(null, args)
Method fooMethod = MethodTest.class.getMethod("foo", new Class[] {});
Object result = fooMethod.invoke(null, new Object[] {});
System.out.println(result);

Method fooWithParametersMethod = MethodTest.class.getMethod("fooWithParameters", new Class[] {String.class,int.class});
result = fooWithParametersMethod.invoke(null, "test string", 8);
System.out.println(result);

result = fooWithParametersMethod.invoke(null, new Object[] {"test string", 8});
System.out.println(result);
System.out.println();


//(2) 调用类方法 method.invoke(实例化类, args)
//获取private方法需要用getDeclaredMethod()或getDeclaredMethods()
Method privatePrintMethod = MethodTest.class.getDeclaredMethod("print", new Class[] {String.class, String.class});
privatePrintMethod.setAccessible(true);

privatePrintMethod.invoke(methodTest, new Object[] {"print string1", "print string2"});
System.out.println();
运行结果:

MethodTest static method foo()
MethodTest static method fooWithParameters()test string8
MethodTest static method fooWithParameters()test string8

PRINT STRING1,print string2

8. 字段的反射 field.set()/field.get()

//(1) 获取private字段	getDeclaredField()
Field privateDeclaredField = MethodTest.class.getDeclaredField("name");
privateDeclaredField.setAccessible(true);

privateDeclaredField.set(methodTest, "new private name");
System.out.println(methodTest.toString());
System.out.println();

//(2) 获取static字段	getDeclaredField()
Field staticField = MethodTest.class.getDeclaredField("desc");
staticField.set(null, "new static field");		//不需要指明类对象
System.out.println(methodTest.toString());
System.out.println();

//(3) 获取public字段	getField()
Field publicField = MethodTest.class.getField("age");
publicField.set(methodTest, 88888);
System.out.println(methodTest.toString());
System.out.println();

运行结果:

MethodTest{name='new private name', age=0, desc=这是一个人}

MethodTest{name='new private name', age=0, desc=new static field}

MethodTest{name='new private name', age=88888, desc=new static field}


9. 反射的应用 1    绕过泛型指定的类

Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行期就无效了。

//编译之后集合的泛型是去泛型化的,java中集合类型的泛型,是防止错误输入的,只在编译阶段有效,绕过编译就无效了,所以我们通过方法的反射来操作,可以绕过编译
ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<String>();
list1.add("hello");
// list1.add(20);编译错误
Class c1 = list.getClass();
Class c2 = list1.getClass();
System.out.println(c1 == c2);

// 反射的操作都是编译之后的操作
Method m = c2.getMethod("add", Object.class);
m.invoke(list1, 20);// 绕过编译操作就绕过了泛型
System.out.println(list1.size());
System.out.println(list1);

运行结果:

true
2

[hello, 20]


10. 反射的应用 2    动态代理

动态代理是指客户通过代理类来调用其他对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象

静态代理,其代理类和目标对象的类在编译期间就确定下来,不利于程序的扩展。即,每一个代理类只能为一个接口服务,也就是说程序开发中会产生很多代理类


编译时无法确定需要实现哪个接口时,才使用代理

只有调用到 Class<?>[] interfaces 方法时,才会调用Handler里面的invoke()方法

// 动态代理的使用
interface Subject {
	void action();
}

// 被代理类
class RealSubject implements Subject {
	@Override
	public void action() {
		System.out.println("我是被代理类,记得要执行我奥,么么~~~");
	}
}

class MyInvocationHandler implements InvocationHandler {
	Object object;// 实现了接口的被代理类的对象的声明
	// ①给被代理的对象实例化 ②返回一个代理类的对象

	public Object blind(Object object) {
		this.object = object;
		return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
	}

	// 当通过代理类的对象发起对被重写的方法的调用时,都会转化为对如下的invoke方法的调用
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// method方法的返回值是returnVal
		Object returnVal = method.invoke(object, args);
		return returnVal;
	}
}
运行结果:

我是被代理类,记得要执行我奥,么么~~~


11. 反射的应用 3    动态代理+切面编程

在调用指定方法之前和之后调用preAction()和postAction()方法

interface Human {
	void info();
	void fly();
}

class SuperMan implements Human {
	@Override
	public void info() {
		System.out.println("我是超人!");
	}

	@Override
	public void fly() {
		System.out.println("I believe I can fly!");
	}
}

class HumanUtil {
	public void preAction() {
		System.out.println("=============preAction()============");
	}

	public void postAction() {
		System.out.println("=============postAction()============");
	}
}

class ManInvocationHandler implements InvocationHandler {
	Object object;

	public void setObject(Object object) {
		this.object = object;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		HumanUtil h = new HumanUtil();

		h.preAction();
		Object returnVal = method.invoke(object, args);
		h.postAction();

		return returnVal;
	}
}

//动态的创建一个代理类的对象
class ManProxy {
	public static Object getProxyInstance(Object object) {
		ManInvocationHandler handler = new ManInvocationHandler();
		handler.setObject(object);
		
		return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), handler);
	}
}
// 在调用指定方法之前和之后调用preAction()和postAction()方法
SuperMan man = new SuperMan();//创建一个被代理类的对象

Human human = (Human) ManProxy.getProxyInstance(man);//返回一个代理类的对象
human.info();//通过代理类的对象嗲用重写的抽象的方法

System.out.println();
human.fly();
运行结果:

=============preAction()============
我是超人!
=============postAction()============

=============preAction()============
I believe I can fly!
=============postAction()============

12. 反射的优点与缺点


为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念

  • 静态编译:在编译时确定类型,绑定对象,即通过。
  • 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

  • 优点
    可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。

  • 缺点
    对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。




http://blog.csdn.net/harvic880925/article/details/50072739

http://blog.csdn.net/harvic880925/article/details/50085595

http://blog.csdn.net/harvic880925/article/details/50107951

http://blog.csdn.net/claram/article/details/52371940

转载于:https://www.cnblogs.com/xiang--liu/p/9710371.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值