Java基础总结篇---反射机制

在初学java基础时,由于学的不扎实,看着头大,就觉得反射应该用不上,这些重要的知识就那样一笔带过了,到后来的项目开发中认识到很多东西动不动就用反射,所以回过头来把这个给重新补一下,自己欠下的债,迟早是要还的。

类的加载机制


当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。

  • 加载
    就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
  • 连接
    • 验证 是否有正确的内部结构,并和其他类协调一致
    • 准备 负责为类的静态成员分配内存,并设置默认初始化值
    • 解析 将类的二进制数据中的符号引用替换为直接引用
  • 初始化
何时类被初始化?
  • 创建类的实例
  • 访问类的静态变量,或者为静态变量赋值
  • 调用类的静态方法
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类
  • 直接使用java.exe命令来运行某个主类
类加载器

负责将.class文件加载到内在中,并为之生成对应的Class对象。

类加载器的组成
  • Bootstrap ClassLoader 根类加载器
    也被称为引导类加载器,负责Java核心类的加载,比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
  • Extension ClassLoader 扩展类加载器
    负责JRE的扩展目录中jar包的加载。在JDK中JRE的lib目录下ext目录
  • Sysetm ClassLoader 系统类加载器
    负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径

进入正文 -----

反射机制


JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

简单来说:通过字节码文件对象,去使用成员变量,构造方法,成员方法。

Java基础总结篇---反射机制

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.先简单了解一下Class类,这可和我们的class关键字不一样啊!

类 Class


Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。

Class的方法返回对象常用的三个类:Filed成员变量 Method成员方法 Constructor 构造函数

获取Class的方法


开发常用方式三,因为方式三中可以用配置文件来管理类名,更加灵活。后面举个例子????

  • 方式1

通过类的实例获取该类的字节码文件对象,该类处于创建对象阶段

Person p = new Person();
Class c = p.getClass();
Person p2 = new Person();
Class c2 = p2.getClass();

System.out.println(p == p2);// false
System.out.println(c == c2);// true
  • 方式2

当类被加载成.class文件时,此时Person类变成了.class,在获取该字节码文件对象,也就是获取自己, 该类处于字节码阶段。

Class c3 = Person.class;
System.out.println(c == c3);
  • 方式3

通过Class类中的静态方法forName,直接获取到一个类的字节码文件对象,此时该类还是源文件阶段,并没有变为字节码文件。

Class c4 = Class.forName("cn.xianglei.Person");
		

获取构造方法


  • public Constructor[] getConstructors()
    • 所有公共构造方法
  • public Constructor[] getDeclaredConstructors()
    • 所有构造方法
  • // public Constructor getConstructor(Class<?>… parameterTypes)
    • 你要获取的构造方法的构造参数个数及数据类型的class字节码文件对象
  • public T newInstance(Object… initargs)
    • 创建此 Class 对象所表示的类的一个新实例。
	/*
	 * 通过反射获取构造方法并使用。
	 */
	public class ReflectDemo {
		public static void main(String[] args) throws Exception {
			// 获取字节码文件对象
			Class c = Class.forName("cn.itcast_01.Person");
	
			// 获取构造方法
			// public Constructor[] getConstructors():所有公共构造方法
			// public Constructor[] getDeclaredConstructors():所有构造方法
			// Constructor[] cons = c.getDeclaredConstructors();
			// for (Constructor con : cons) {
			// System.out.println(con);
			// }
	
			// 获取单个构造方法 可变参数
			// public Constructor<T> getConstructor(Class<?>... parameterTypes)
			// 参数表示的是:你要获取的构造方法的构造参数个数及数据类型的class字节码文件对象
			Constructor con = c.getConstructor();// 返回的是构造方法对象
			Constructor con = c.getConstructor(String.class, int.class,
				String.class);

				// 通过带参构造方法对象创建对象
				// public T newInstance(Object... initargs)
			Object obj = con.newInstance("林青霞", 27, "北京");
		
			// public T newInstance(Object... initargs)
			// 使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
			Object obj = con.newInstance();
			System.out.println(obj);


			
			// 访问私有方法只能使用下面这种方式。
			Constructor con = c.getDeclaredConstructor(String.class);
	
			// 用该私有构造方法创建对象
			// 如果报出 IllegalAccessException:非法的访问异常。
			// 暴力访问
			con.setAccessible(true);// 值为true则指示反射的对象在使用时应该取消Java语言访问检查。
			Object obj = con.newInstance("风清扬");
	
			System.out.println(obj);
			
		}
	}

获取成员变量


Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。

  • public Field[] getFields()
    • 返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
  • public Field[] getDeclaredFields()
    • 返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。
  • public Field getField(String name)
    • 返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。name 参数是一个 String,用于指定所需字段的简称。
  • public Field getDeclaredField(String name)
    • 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。name 参数是一个 String,它指定所需字段的简称。
	public class ReflectDemo {
		public static void main(String[] args) throws Exception {
			// 获取字节码文件对象
			Class c = Class.forName("cn.itcast_01.Person");
	
			// 获取所有的成员变量
			// Field[] fields = c.getFields();
			// Field[] fields = c.getDeclaredFields();
			// for (Field field : fields) {
			// System.out.println(field);
			// }
	
			/*
			 * Person p = new Person(); p.address = "北京"; System.out.println(p);
			 */
	
			// 通过无参构造方法创建对象
			Constructor con = c.getConstructor();
			Object obj = con.newInstance();
			System.out.println(obj);
	
			// 获取单个的成员变量
			// 获取address并对其赋值
			Field addressField = c.getField("address");
			// public void set(Object obj,Object value)
			// 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
			addressField.set(obj, "北京"); // 给obj对象的addressField字段设置值为"北京"
			System.out.println(obj);
	
			// 获取name并对其赋值
			// NoSuchFieldException
			Field nameField = c.getDeclaredField("name");
			// IllegalAccessException
			nameField.setAccessible(true);
			nameField.set(obj, "林青霞");
			System.out.println(obj);
	
			// 获取age并对其赋值
			Field ageField = c.getDeclaredField("age");
			ageField.setAccessible(true);
			ageField.set(obj, 27);
			System.out.println(obj);
		}
	}

获取成员方法


  • public Method[] getMethods()
    • 获取自己的包括父亲的公共方法
  • public Method[] getDeclaredMethods()
    • 获取自己的所有的方法
  • public Method getMethod(String name,Class<?>… parameterTypes)
    • 返回值是Object接收,第一个参数表示对象是谁,第二参数表示调用该方法的实际参数
	public class ReflectDemo {
		public static void main(String[] args) throws Exception {
			// 获取字节码文件对象
			Class c = Class.forName("cn.xianglei.Person");
	
			// 获取所有的方法
			// Method[] methods = c.getMethods(); // 获取自己的包括父亲的公共方法
			// Method[] methods = c.getDeclaredMethods(); // 获取自己的所有的方法
			// for (Method method : methods) {
			// System.out.println(method);
			// }
	
			Constructor con = c.getConstructor();
			Object obj = con.newInstance();
	
			/*
			 * Person p = new Person(); p.show();
			 */
	
			// 获取单个方法并使用
			// public void show()
			// public Method getMethod(String name,Class<?>... parameterTypes)
			// 第一个参数表示的方法名,第二个参数表示的是方法的参数的class类型
			Method m1 = c.getMethod("show");
			// obj.m1(); // 错误
			// public Object invoke(Object obj,Object... args)
			// 返回值是Object接收,第一个参数表示对象是谁,第二参数表示调用该方法的实际参数
			m1.invoke(obj); // 调用obj对象的m1方法
	
			System.out.println("----------");
			// public void method(String s)
			Method m2 = c.getMethod("method", String.class);
			m2.invoke(obj, "hello");
			System.out.println("----------");
	
			// public String getString(String s, int i)
			Method m3 = c.getMethod("getString", String.class, int.class);
			Object objString = m3.invoke(obj, "hello", 100);
			System.out.println(objString);
			// String s = (String)m3.invoke(obj, "hello",100);
			// System.out.println(s);
			System.out.println("----------");
	
			// private void function()
			Method m4 = c.getDeclaredMethod("function");
			m4.setAccessible(true);
			m4.invoke(obj);
		}
	}

反射案例


  • 通过反射运行配置文件,通常需要有配置文件配合使用。

  • class.txt内存入以下键值组合。

			className=cn.itcast.test.Worker
			methodName=love

		public class Test {
			public static void main(String[] args) throws Exception {
			
		
				// 加载键值对数据
				Properties prop = new Properties();
				FileReader fr = new FileReader("class.txt");
				prop.load(fr);
				fr.close();
		
				// 获取数据
				String className = prop.getProperty("className");
				String methodName = prop.getProperty("methodName");
		
				// 反射
				Class c = Class.forName(className);
		
				Constructor con = c.getConstructor();
				Object obj = con.newInstance();
		
				// 调用方法
				Method m = c.getMethod(methodName);
				m.invoke(obj);
			}
		}
  • 反射越过泛型检查
		/*
		 * 我给你ArrayList<Integer>的一个对象,我想在这个集合中添加一个字符串数据,如何实现呢?
		 */
		public class ArrayListDemo {
			public static void main(String[] args) throws NoSuchMethodException,
					SecurityException, IllegalAccessException,
					IllegalArgumentException, InvocationTargetException {
				// 创建集合对象
				ArrayList<Integer> array = new ArrayList<Integer>();
		
				// array.add("hello");
				// array.add(10);
		
				Class c = array.getClass(); // 集合ArrayList的class文件对象
				Method m = c.getMethod("add", Object.class);
			   // 小看源码可以知道add方法的参数是泛型也可以理解为Object类型
				m.invoke(array, "hello"); // 调用array的add方法,传入的值是hello
				m.invoke(array, "world");
				m.invoke(array, "java");
		
				System.out.println(array);
			}
		}
  • 通用的设置某个对象某个属性的值
	public class Tool {
		public void setProperty(Object obj, String propertyName, Object value)
				throws NoSuchFieldException, SecurityException,
				IllegalArgumentException, IllegalAccessException {
			// 根据对象获取字节码文件对象
			Class c = obj.getClass();
			// 获取该对象的propertyName成员变量
			Field field = c.getDeclaredField(propertyName);
			// 取消访问检查
			field.setAccessible(true);
			// 给对象的成员变量赋值为指定的值
			field.set(obj, value);
		}
	}

	public class ToolDemo {
		public static void main(String[] args) throws NoSuchFieldException,
				SecurityException, IllegalArgumentException, IllegalAccessException {	
			Dog d = new Dog();
			t.setProperty(d, "sex", '男');
			t.setProperty(d, "price", 12.34f);
	
			System.out.println(d);
		}
	}
	class Dog {
		char sex;
		float price;
	
		@Override
		public String toString() {
			return sex + "---" + price;
		}
	}
	
	class Person {
		private String name;
		public int age;
	
		@Override
		public String toString() {
			return name + "---" + age;
		}
	}

总结

反射基本上就这样讲完了,其实就是对其一些api进行讲解,不懂的就查看API,重要的思想,就要在实际中遇到了才能得到更好的理解。后面的再讲解的一些设计模式里面也多用到反射,以及后面还要研究的Spring框架的核心大量用到反射。所以啊!不要觉得当前学的看似没用,实际上是自己太菜~