反射的基础知识

1.1.1. java反射

1.1.1.1. 反射的概念

    主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

    反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接。但是反射使用不当会成本很高!

看概念很晕的,继续往下看。

反射性能低的主要原因:反射作用的代码jvm无法优化(正常是代码是编译、运行,而反射直接把所有拖到运行时解决)

1.1.1.1.  反射机制的作用

降低代码的耦合度

              1,反编译:.class-->.java

              2,通过反射机制访问java对象的属性,方法,构造方法、泛型、注解等;

             这样好像更容易理解一些,下边我们具体看怎么实现这些功能。

1.1.1.2. sun提供反射机制中的类

java.lang.Class-------代表字节码的类 --- 代表类的类;

java.lang.reflect.Field-------代表属性的类;   

java.lang.reflect.Method-----代表方法的类;              

java.lang.reflect.Constructor-----代表构造方法的类;

java.lang.annotation --- 代表注解的类   

java.lang.reflect.Type---代表泛型操作的类;

java.lang.package --- 代表包的类

java.lang.reflect.Modifier;

很多反射中的方法,属性等操作我们可以从这四个类中查询。还是哪句话要学着不断的查询API,那才是我们最好的老师。

1.1.1.3. Clazz对象的创建方式

Class对象的生成方式如下:

 1.类名.class           说明: JVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),不做类的初始化工作.返回Class的对象

2.Class.forName("类名字符串")  (注:类名字符串是包名+类名)  说明:装入类,并做类的静态初始化,返回Class的对象

3.实例对象.getClass()  说明:对类进行静态初始化、非静态初始化;返回引用运行时真正所指的对象(因为:子对象的引用可能会赋给父对象的引用变量中)所属的类的Class的对象

通过下面的程序,来观察一下Class对象的生成的原理。

package ClassTest;

public class TestClass {

public static void main(String[] args) {

try {

// 测试.class

@SuppressWarnings("rawtypes")

Class testTypeClass = TestClassType.class;

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

// 测试Class.forName()

@SuppressWarnings("rawtypes")

Class testTypeForName = Class.forName("ClassTest.TestClassType");

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

// 测试Object.getClass()

TestClassType testTypeGetClass = new TestClassType();

System.out.println("testTypeGetClass---"

+ testTypeGetClass.getClass());

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

package ClassTest;

public class  TestClassType {

// 构造函数

public TestClassType() {

System.out.println("----构造函数---");

}

// 静态的参数初始化

static {

System.out.println("---静态的参数初始化---");

}

// 非静态的参数初始化

{

System.out.println("----非静态的参数初始化---");

}

}

 运行结果如下

testTypeClass---class ClassTest.TestClassType

---静态的参数初始化---

testTypeForName---class ClassTest.TestClassType

----非静态的参数初始化---

----构造函数---

testTypeGetClass---class ClassTest.TestClassType

根据结果可以发现,三种生成的Class对象一样的。并且程序只打印一次“静态的参数初始化”。

我们知道,静态的方法属性初始化,是在加载类的时候初始化。而非静态方法属性初始化,是new类实例对象的时候加载。

因此,这段程序说明,三种方式生成Class对象,其实只有一个Class对象。在生成Class对象的时候,首先判断内存中是否已经加载。

所以,生成Class对象的过程其实是如此的:

当我们编写一个新的Java类时,JVM就会帮我们编译成class对象,存放在同名的.class文件中。在运行时,当需要生成这个类的对象,JVM就会检查此类是否已经装载内存中。若是没有装载,则把.class文件装入到内存中。若是装载,则根据class文件生成实例对象。

1.1.1.4. ClassLoader

public void testClassLoader(){

ClassLoader classLoader = ClassLoader.getSystemClassLoader();

System.err.println(classLoader);

classLoader = classLoader.getParent();

System.out.println(classLoader);

classLoader = classLoader.getParent();

System.out.println(classLoader);

try {

classLoader = Class.forName("reflect.pracrice.Demo").getClassLoader();

System.out.println(classLoader);

classLoader = Class.forName("java.lang.Object").getClassLoader();

System.out.println(classLoader);

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

//通过类加载器获取流

InputStream is = this.getClass().getClassLoader().getResourceAsStream("wsq.txt");

System.out.println(is);

}

1.1.1.5. 反射具体功能

1) Class常用的方法

Class类也使用了泛型,即是Class

  • getConstructor(Class[] params) 获取公共的(public)的构造方法,并且限定其中的参数个数和类型可以获得不同的公共构造方法
  • Constructor[] getConstructors() 返回所有的公共(public)的构造方法
  • getDeclaredConstructor(Class[] params) 获取所有指定的构造方法,但是需要注意的是当获取私有的构造方法的时候需要使用setAccessible设置访问权限为true才能进行构造,否则出现异常
  • Constructor[] getDeclaredConstructors() 返所有的构造方法包括public和private,protected修饰的
  • T newInstance() 返回的是一个调用默认的构造方法(public class_name())实例化的一个Object对象,如果使用泛型那么就返回T类型的,反之返回的是Object需要强制转换才能使用这个对象调用成员函数和成员变量
  • Class forName(String class_name) 返回class对象,每一个对都有一个方象法返回Class对象(test.class)
  • Package getPackage() 返回此类所在的包名(package demo) 当然也可以使Package.getName()获得包的名字(demo)比如constructor.getPackage().getName()(类的全路径名)
  • String getSimpleName()获取类的名字
  • int getModifiers() 返回的是类的修饰符的整数 类型(修饰符的类型有public private protected)其中得到整数可以使用Modifier中toString(int num)得到public,private,protected的类型,比如Modifier.toString(class1.getModifiers())
  • Method getMethod(String name, Class<?>... parameterTypes) 返回指定参数的方法Method对象,注意这里仅仅是返回的时公共的方法(public) 比如:Method method=class1.getMethod("display",new Class[]{int.class})这里的display是方法的名字,有一个参数,类型为int
  • Method[] getMethods() 获取所有的公共的方法(public)返回的是一个数组(Method)
  • Method getDeclaredMethod(String name,Class<?>... parameterTypes)返回所有的指定的参数的方法(public,private,protected,但是不包括继承的),其中参数可以为null(无参数)
  • Method[] getDeclaredMethods() 获取所有的方法
  • Field getField(String name) 指定名字的公共成员变量(public)
  • Field[] getFields() 获取所有的公共的成员变量
  • Field getDeclaredField(String name) 获取所有的指定名称的成员变量(public,protected,private),同样在调用私有成员变量的时候需要先设置访问的权限,field.setAccessible(true)
  • Field[] getDeclaredFields() 获取所有的成员变量(public,protected,private)
  • getSuperclass() 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。
  • URL getResource(String name) 查找指定名称的资源(图片,文件...)注意这个资源一定要和指定类在一个包中,否则返回null,比如查找Test类下的airplane.png图片:Test.class.getResource("airplane.png")这里返回的将是绝对路径

2) Method

主要提供的是对类中的方法的操作

常用的方法

  • Object invoke(Object obj,object args) 使用得到的Method对象调用方法,obj是类的已经构造好的对象,如果是静态方法直接写null,因为静态方法的调用不需要对象,返回值是Object类型的,如果接受返回值,需要使用强制转换成相应的类型,args是传入的参数,如果有多个参数,那么可以直接在后面用逗号添加或者直接创建数组new Object[]{22,"chenjiabing"}比如:method.invoke(test,22,"chenjiabing") method.invoke(test,new Object[]{22,"chenjiabing"})注意:如果调用的private类型的方法,那么需要在前面设置访问的权限,method.setAccessible(true)
  • String getName() 返回此方法的名字(display)
  • Modifier getModifiers() 返回此方法的修饰符的类型表示的整数(public,private...),可以使用Modifier.toString()转换成字符串形式
  • Class getReturnType() 返回这个方法的返回类型
  • String toString() 返回这个方法表示的字符串的形式

3) Field

主要提供对类的成员变量的操作

常用方法

  • String getName() 返回变量名字
  • Object get(Object obj) 返回此变量在指定对象中的值,因为在构造对象的时候每一个传入的变量的值都不一样,因此需要使用对象obj。obj表示传入的对象,返回的Object类型,因此需要强制转换
  • void set(Object obj,Object value) 改变obj对象上的变量的值为value
  • Modifier getModifiers() 返回整数表示修饰的类型
  • String getType() 获取变量的类型(int,String,double float.....)

4) Constructor

主要是用来对类的构造方法进行操作的,可以看出这个也使用了泛型,和上面的Class是一样的,注意这里如果没有使用泛型,那么原本放回T类型的现在都是返回Object

常用的方法

  • T newInstance(Object parms) 使用带有参数的构造方法实例化对象,如果使用了泛型,那么返回的就是T类型的,反之返回的是Object类型的,需要强制转换
  • getName() 以字符串的形式返回构造方法的名称,具体的路径包含包名(demo.Test)
  • int getModifiers() 和Class类中的方法一样

5) annotation

实现一些轻量级的配置。

@Override --- 标志一个重写的方法

@Deprecated --- 标志过时

@SuppressWarnings --- 压制警告

注解中的属性允许:基本类型,String,Class,其他注解,枚举以及上述类型的一维数组形式

如果注解中只有一个属性必须赋值,而且这个属性的名字为value,那么在赋值的时候value = 可以不写。

如果赋值的时候一维数组中只有一个值,那么{}可以省略不写

元注解---可以作用在注解上的注解

@Target---限制注解的使用范围

@Retention --- 限制注解的使用周期的

@Documented --- 让注解出现在文档中

@Inherited --- 表示此注解会随着继承作用到子类上

6) Modifier

Modifier 类提供了 static 方法和常量,对类和成员访问修饰符进行解码。修饰符集被表示为整数,用不同的位位置 (bit position) 表示不同的修饰符。

常用的方法

  • static String toString(int mode) 将代表修饰符的整数形式转换为字符串形式的修饰符,比如将1转换成public
  • static isInterface(int mode) 如果整数参数包括 interface 修饰符,则返回 true,否则返回 false
  • static isStatic(int mode)
  • static isPrivate(int mode)
  • static isPublic(int mode)
  • static isAbstract(int mode)

7) type

如果一个方法中的参数带了泛型,怎么获取该参数以及泛型参数的类型?如参数为(List,int)
见实例代码test5()。其中,getGenericParameterTypes只能获取方法的参数列表中的类型,返回的是一个类型数组[list类型,int类型];ParameterizedType是Type的子接口,需要向下转型为ParameterizedType,然后再使用方法getActualTypeArguments获取泛型的参数类型,返回的是个类型数组[People类型,String类型]

 public void test5() throws Exception {

        Class clazz1 = TestClass.class;

        TestClass tc1 = (TestClass)clazz1.newInstance();

        Method m1 = clazz1.getMethod("showHello");

        String str = (String) m1.invoke(tc1);

        System.out.println(str);

        

        //参数类型showParameterized(List list,int num)

        Method m2 = clazz1.getMethod("showParameterized", List.class,int.class);

        //获得你这个method对象(某个方法)的参数列表

        Type[] types = m2.getGenericParameterTypes();

        for (Type type : types) {

            System.out.println(type.toString());

            //java.util.List

            //int

        }

        

        //获得泛型的类型,ParameterizedType是Type的子接口,代表参数化类型

        Type tp = m2.getGenericParameterTypes()[0];//参数列表中的第一个参数

        ParameterizedType ptp = (ParameterizedType)tp;

        //getActualTypeArguments获得泛型参数列表里的泛型的类型

        Type T_type = ptp.getActualTypeArguments()[0];//泛型里的第一个参数列表

        System.out.println(T_type.toString());//class java.lang.String

    }

8) method和field的工具类

package utils;

import java.lang.reflect.Field;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import org.junit.Test;

public class ReflectUtils {

public static Object invoke(String clazzPath, String methodName, Object... args) {

try {

Object obj = Class.forName(clazzPath).newInstance();

Object result = invoke(obj, methodName, args);

return result;

} catch (InstantiationException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

return null;

}

public static Object invoke(Object obj, String methodName, Object... args) {

Class clazz = obj.getClass();

Class[] parameterTypes = getClassParameterTypes(args);

Method method = getMethod(clazz, methodName, parameterTypes);

setMethodAccesssible(method);

try {

Object result = method.invoke(obj, args);

return result;

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (InvocationTargetException e) {

e.printStackTrace();

}

return null;

}

public static Method getMethod(Class clazz, String methodName, Class... parameterTypes) {

for (Class clazz2 = clazz; clazz2 != Object.class; clazz2 = clazz2.getSuperclass()) {

try {

Method method = clazz2.getDeclaredMethod(methodName, parameterTypes);

return method;

} catch (NoSuchMethodException e) {

} catch (SecurityException e) {

e.printStackTrace();

}

}

return null;

}

public static Class getClass(String clazzPath) {

Class clazz = null;

try {

clazz = Class.forName(clazzPath);

} catch (ClassNotFoundException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}

return clazz;

}

public static void setMethodAccesssible(Method method) {

method.setAccessible(true);

}

public static Class[] getClassParameterTypes(Object... args) {

Class[] parameterTypes = new Class[args.length];

for (int i = 0; i < args.length; i++) {

parameterTypes[i] = args[i].getClass();

}

return parameterTypes;

}

public static Field getField( Class clazz,String filedName) {

Field field = null;

for (Class clazz2 = clazz; clazz2 != Object.class; clazz2 = clazz2.getSuperclass()) {

try {

field = clazz2.getDeclaredField(filedName);

} catch (NoSuchFieldException e) {

} catch (SecurityException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

return field;

}

public static Object getFieldValue(Object obj, Field field) throws IllegalAccessException {

field.setAccessible(true);

return field.get(obj);

}

public static void setFieldValue(Object obj, Field field,Object fieldValue) throws IllegalAccessException {

field.setAccessible(true);

field.set(obj, fieldValue);

}

}

9) Type对泛型类的操作

public class Dao <T> {

Dao(){

System.out.println("Dao的无参构造器");

// System.out.println(this);

// System.out.println(this.getClass());

// System.out.println(this.getClass().getSuperclass());

Type type = this.getClass().getGenericSuperclass();

System.out.println(type);

if(type instanceof ParameterizedType){

ParameterizedType parameterizedType = (ParameterizedType)type;

Type[] types = parameterizedType.getActualTypeArguments();

for (Type t : types) {

if(t instanceof Dao){

System.out.println("是Dao的子类");

}

}

}

}

1.1.1.6. 反射加配置文件,使我们的程序更加灵活

在设计模式学习当中,学习抽象工厂的时候就用到了反射来更加方便的读取数据库连接字符串等,当时不是太理解,就照着抄了。看一下.NET中的反射+配置文件的使用:

当时用的配置文件是app.config文件,内容是XML格式的,里边填写链接数据库的内容:

 <configuration>  

    lt;appSettings>  

    <add     key=""  value=""/>  

    lt;/appSettings>  

 </configuration>  

 反射的写法:   

    assembly.load("当前程序集的名称").CreateInstance("当前命名空间名称".要实例化的类名);  

这样的好处是很容易的方便我们变换数据库,例如我们将系统的数据库从SQL Server升级到Oracle,那么我们写两份D层,在配置文件的内容改一下,或者加条件选择一下即可,带来了很大的方便。

当然了,JAVA中其实也是一样,只不过这里的配置文件为.properties,称作属性文件。通过反射读取里边的内容。这样代码是固定的,但是配置文件的内容我们可以改,这样使我们的代码灵活了很多!

猜你喜欢

转载自www.cnblogs.com/ourbigdata/p/9296422.html