【Java高新技术】——反射


一、类的加载

1、类的加载相关概述

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

①加载

  • 就是指将class文件读入内存,并为之创建一个Class对象
  • 任何类被使用时系统都会建立一个Class对象。

②连接

  • 验证 是否有正确的内部结构,并和其他类协调一致
  • 准备 负责为类的静态成员分配内存,并设置默认初始化值
  • 解析 将类的二进制数据中的符号引用替换为直接引用
③初始化:跟以前的初始化动作一样。

2、类加载器

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

②类加载器的组成:

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

二、反射

1、反射的基石——Class类

①所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就叫Class(描述字节码文件的对象)。

  • Class类中就包含属性有field(字段)、method(方法)、construction(构造函数)。
  • 而field中有修饰符、类型、变量名等复杂的描述内容,因此也可以将字段封装称为一个对象。用来获取类中field的内容,这个对象的描述叫Field。同理方法和构造函数也被封装成对象Method、Constructor。要想对一个类进行内容的获取,必须要先获取该字节码文件的对象。该对象是Class类型。
  • Class类描述的信息:类的名字,类的访问属性,类所属于的包名,字段名称的列表,方法名称的列表等。每一个字节码就是class的实例对象。如:classcls=Data.class;

②Class和class的区别

  • class:Java中的类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则由此类的实例对象确定,不同的实例对象有不同的属性值。
  • Class:指的是Java程序中的各个Java类是属于同一类事物,都是Java程序的类,这些类称为Class。例如人对应的是Person类,Java类对应的就是Class。Class是Java程序中各个Java类的总称;它是反射的基石,通过Class类来使用反射。

2、反射机制

JAVA反射机制是在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法,对于任意一个对象,都能调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。简而言之,反射就是通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法。

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用就是Class类中方法

获取Class文件对象的方式:

①Object类的getClass()方法。获取字节码文件对象。

Personp = new Person();

Classc = p.getClass();

②数据类型的静态属性class:比第一种方式简单,不用创建对象,也不会调用getClass方法,但是还是要使用具体的类对象和该类的一个静态属性完成。

Classc2 = Person.class;

③Class类中的静态方法forName()方法。注意:要拿带包名的全路径名称:这种方式比较简单,不用创建对象,也不用调用方法,也不需要去调用具体的属性和行为,就能获取到Class对象了。

Classc3 = Class.forName("Person");

一般我们到底使用哪种方法呢?

自己玩:任选一种,第二种较为方便

开发:第三种。为什么呢?因为第三种是一个字符串,而不是一个具体的类名,这样我们就可以把这样的字符串配置到配置文件中。

三种获取Class文件对象的方式演示:

package cn.itcast_01;

/*
 *获取Class文件对象的三种方式
 */
public class ReflectDemo {
	public static void main(String[] args) throws ClassNotFoundException {
		//第一种
		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

		//第二种
		Class c3 = Person.class;
		// int.class;
		// String.class;
		System.out.println(c == c3);

		// 第三种
		// ClassNotFoundException
		Class c4 = Class.forName("cn.itcast_01.Person");
		System.out.println(c == c4);
	}
}

3、Class类中的常用方法

①获取构造方法

  • getConstructor(Class<?>... parameterTypes):返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
  • getDeclaredConstructor(Class<?>... parameterTypes):返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。
②创建对象
  • newInstance():创建此 Class 对象所表示的类的一个新实例。
③获取所有成员
  • getFields():返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
  • getDeclaredFields():返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。
④获取单个成员
  • getField(String name):返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
  • getDeclaredField(String name):返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
⑤获取所有方法
  • getMethods():返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
  • getDeclaredMethods(): 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
⑥获取单个方法
  • getMethod(String name,Class<?>... parameterTypes): 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
  • getDeclaredMethod(String name,Class<?>... parameterTypes): 返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
方法演示代码如下:
package cn.itcast_02;

import java.lang.reflect.Constructor;

import cn.itcast_01.Person;

/*
 * 通过反射获取构造方法并使用。
 */
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();// 返回的是构造方法对象

		Person p = new Person();
		System.out.println(p);
		// public T newInstance(Object... initargs)
		// 使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,
		//并用指定的初始化参数初始化该实例。
		Object obj = con.newInstance();
		System.out.println(obj);

		Person p1 = (Person) obj;
		p1.show();
	}
}

4、Constructor<T>类

①概述:当指定的类中没有空参数构造函数时,或者创建的类对象需要通过指定的构造函数进行初始化,这时候就不能使用Class类中的newInstance方法。这时候我们就需要通过其它的方法来实现了。
②Constructor代表的是某个类的一个构造方法。提供关于类的单个构造方法的信息以及对它的访问权限。
③获取构造方法
  • 获取所有的构造方法:Constructor[] cons = Class.forName(“cn.itheima.Person”).getConstructors();
  • 获取单个构造方法:Constructor con=Person.class.getConstructor(String.class,int.class);
④创建实例对象
  • 反射方式:Person p= (Person)con.newInstance(“lisi”,30);
  • 通常方式:Person p = new Person(“lisi”,30);
注意:创建实例时newInstance方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致。

5、Field类

①Field代表的是某个类的某个成员变量。
②方法:
  • Field getField(String s);只能获取公有和父类中公有
  • Field getDeclaredField(String s);获取该类中任意成员变量,包括私有
  • setAccessible(ture);如果是私有字段,要先将该私有字段进行取消权限检查的能力。也称暴力访问。
  • set(Object obj, Object value);将指定对象变量上此Field对象表示的字段设置为指定的新值。
  • Object get(Object obj);返回指定对象上Field表示的字段的值。
代码演示如下:
package cn.itcast_03;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

/*
 * 通过发生获取成员变量并使用
 */
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);
	}
}

6、Method类

①概述:Method类代表类中的一个成员方法,调用某个对象身上的方法,要先得到方法,再针对某个对象调用。
②举一个例子:获得String类的charAt方法。
Method methodCharAt=Class.forName("java.lang.String").getMethod("charAt",int.class);
System.out.println(methodCharAt.invoke(str,1));//调用str对象中charAt(1)
③调用方法
  • 通常方式:System.out.println(str.charAt(1));
  • 反射方式:System.out.println(charAt.invoke(str,1));//str是一个对象,这里str可以是null,说明invoke()方法是一个静态方法。
④实例应用:用反射方式执行某个类中的main方法
a、目的:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的Main方法。
b、问题:启动java程序的main方法的参数是一个字符串数组, 即public static void main(String[]args),通过反射方式来调用这个main方法时,按jdk1.5的语法,整个数组是一个参数,而jdk1.4的语法,数组的每个元素对应一个参数,当做把一个字符串数组作为参数传递给invoke方法如何处理(注意兼容)。所以,在给main方法传递参数时,不能使用代码mainMthod.invoke(null,newString[]{“xxxx”}),javac只把它当做JDK1.4的语法进行理解,。而不能把它当做JDK1.5的语法解释,因此,会出现参数类型不对的问题。
c、解决办法:
  • 方法一:mainMethod.invoke(null,newObject[]{new String[]{xxxx}});
  • 方法二:mainMethod.invoke)((Object)newString[]{"xxxx"});编译器会做特殊处理,编译时不把参数当做数组看待,也就不会数组达三成若干参数
实例一:Method方法演示
package cn.itcast_04;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class ReflectDemo {
	public static void main(String[] args) throws Exception {
		// 获取字节码文件对象
		Class<?> c = Class.forName("cn.itcast_01.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);
	}
}
实例二: 写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
package cn.itheima;
//定义一个测试类
class Test{
	public static void main(String[] args){
		for(String arg : args){
			System.out.println(arg);
		}
	}
}
//用反射方式根据用户提供的类名,去执行该类中的main方法。
import java.lang.reflect.Method;

public class PerformedMain{

	public static void main(String[] args) throws Exception {
		//普通方式
		Test.main(new String[]{"123","456","789"});
		System.out.println("-----------------------------");
				
		//反射方式
		String className=args[0];
		Class clazz=Class.forName(className);
				
		Method methodMain=clazz.getMethod("main",String[].class);
		//方式一:强制转换为超类Object,不用拆包
		methodMain.invoke(null, (Object)new String[]{"123","456","789"});
		//方式二:将数组打包,编译器拆包后就是一个String[]类型的整体 
		methodMain.invoke(null, new Object[]{new String[]{"123","456","789"}});
	}

三、数组的反射

①具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
②代表数组的Class实例对象的getSuperClass()方法,返回的父类为Object类对应的Class
③基本类型的一维数组可以被当做Object类型使用,不能作为Object[]类型使用,不能当做Object[]类型使用,非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类使用
④ 注意区别 Array.asList()方法处理int[]和String[]时的差异
⑤ Array工具类用于完成对数组的反射操作
  • Array.getLength(Object obj);获取数组的长度
  •  Array.get(Object obj,int x);获取数组中的元素
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值