反射机制

Java 中 的 反 射 首 先 是 能 够 获 取 到 Java 中 要 反 射 类 的 字 节 码 , 获 取 字 节 码 有 三 种 方 法 1.Class.forName(className) 2.类名.class 3.this.getClass()。然后将字节码中的方法,变量,构造函数等映射成相应的 Method、Filed、Constructor 等类,这些类提供了丰富的方法可以被我们所使用。

1、反射的概念

反射的引入:

Object obj = new Student();、
若程序运行时接收到外部传入的一个对象,该对象的编译类型是 Object,但程序又需要调用该对象运行类型的方法:
1.若编译和运行类型都知道,使用 instanceof 判断后,强转。
2.编译时根本无法预知该对象属于什么类,程序只能依靠运行时信息来发现对象的真实信息,这时就必须使用反射了。
3.要是想得到对象真正的类型,就得使用反射。

什么是反射机制?
简单的来说,反射机制指的是程序在运行时能够获取自身的信息(对象所属的类,类的所有成员变量和方法,运行时创建对象,运行时调用对象)。在 java 中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息(包名、全限包名、类的简称、父类、接口、所有公共类和接口、修饰符、构造方法、方法、字段)。

反射机制的优点与缺点:
为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念,
静态编译:在编译时确定类型,绑定对象,即通过。
动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了 java 的灵活性,体现了多态的应用,有以降低类之间的藕合性。
一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在 J2EE 的开发。
它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。


Class 类和 Class 类实例
Java 程序中的各个 Java 类属于同一类事物,描述这类事物的 Java 类就是 Class 类。
对比提问:众多的人用一个什么类表示?众多的 Java 类用一个什么类表示?
人              Person
Java 类     Class
对比提问: Person 类代表人,它的实例对象就是张三,李四这样一个个具体的人,Class类代表 Java 类,它的各个实例对象又分别对应什么呢?
Class类的各个实例对应各个类在内存中的字节码,例如,Person 类的字节码,ArrayList 类的字节码,等等;
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的;
用类来描述对象,类:描述数据的结构
用元数据来描述 Class,MetaData(元数据):描述数据结构的结构;
反射就是得到元数据的行为;
备注:一个类在虚拟机中只有一份字节码;
 

2、获得 Class 对象


如何得到各个字节码对应的实例对象?每个类被加载后,系统会为该类生成对应的 Class 对象,通过 Class 对象可以访问到 JVM 中的这个类,
3 种方式:

  • 1、调用某个类的 class 属性获取 Class 对象,如 Date.class 会返回 Date 类对应的 Class对象(其实就是得到一个类的一份字节码文件);
  • 2、使用 Class 类的 forName(String className)静态方法,className 表示全限定名;如String 的全限名:java.lang.String;
  • 3、调用某个对象的 getClass()方法。该方法属于 Object 类;Class<?> clz = new Date().getClass()
package JBTest;

public class Demo {
	public static void main(String[] args) throws Exception {
		// 获得Class对象的方法(三种)
		// 一:调用属性
		Class<String> c = String.class;
		System.out.println(c);// 打 印 结 果 : class
								// java.lang.StringString.class就表示JVM中一份表示String类的字节码
		Class<String> c2 = String.class;
		System.out.println(c == c2);// true都是String类的字节码一个类在虚拟机中只有一份字节码;
		// 二:使用forName()方法
		// Class cla = Class.forName("String");//ERROR,
		Class<String> cla = (Class<String>) Class.forName("java.lang.String");// 必
																				// 须用上全限定名,否则报错
		System.out.println(c == cla);// true
		// 三:利用对象调用Object的getClass方法;
		Class c3 = new String().getClass();
		System.out.println(c == c3);// ture
	}
}

3、九个预定义 Class 对象

基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void通过 class 属性也表示为 Class 对象;
Class 类中 boolean isPrimitive() :判定指定的 Class 对象是否表示一个基本类型。
包装类和 Void 类的静态 TYPE 字段;
Integer.TYPE == int.class ;包装类都有一个常量TYPE,用来表示其基本数据类型的字节码
Integer.class == int.class;
数组类型的 Class 实例对象:
Class<String[]> clz = String[].class;
数组的 Class 对象如何比较是否相等? 数组的维数和数组的类型;
Class 类中 boolean isArray() :判定此 Class 对象是否表示一个数组类型

package JBTest;

public class Demo {
	public static void main(String[] args) {
		Class<?> in = int.class;
		System.out.println(in);// int
		Class<?> in2 = Integer.class;
		// 包装类都有一个常量TYPE,用来表示其基本数据类型的字节码
		Class<?> in3 = Integer.TYPE;
		System.out.println(in2);// class java.lang.Integer
		System.out.println(in3);// int
		System.out.println(in3 == in);// true
										// 包装类都有一个常量TYPE,用来表示其基本数据类型的字节码,所以这里会相等!
		System.out.println(in3 == in2);// false
		Class<String[]> s = String[].class;
		Class<int[]> i = int[].class;
		// System.out.println(i ==s);//编译根本就通过不了,一个是int,一个是String
	}

	// 这两个自定义的方法是可以的,一个int,一个Integer//包装类与基本数据类型的字节码是不一样的
	public void show(int i) {
	}

	public void show(Integer i) {
	}
}

4、利用 Class 获取类的属性信息

package JBTest;

import java.lang.reflect.Modifier;

class A {
}

interface B {
}

interface C {
}

public class Demo extends A implements B, C {
	// 内部类
	public class C {
	}

	public interface D {
	}

	public static void main(String[] args) {
		// 类可以,接口也可以
		Class<Demo> c = Demo.class;
		System.out.println(c);// class junereflect624.BaseDemo3
		// 得到包名
		System.out.println(c.getPackage());// package junereflect624
		// 得到全限定名
		System.out.println(c.getName());// junereflect624.BaseDemo3
		// 得到类的简称
		System.out.println(c.getSimpleName());// BaseDemo3
		// 得到父类
		/**
		 * Class<? super T> getSuperclass() 此处super表示下限 返回表示此 Class
		 * 所表示的实体(类、接口、基本类型或 void)的超 类的 Class。
		 */
		System.out.println(c.getSuperclass().getSimpleName()); // A,先获取父类,再获取父类的简称
		// 得到接口System.out.println(c.getInterfaces());//[Ljava.lang.Class;@1b60280
		Class[] arr = c.getInterfaces();
		for (Class cla : arr) {
			System.out.println(cla);// interface junereflect624.B interface
									// junereflect624.C
		}
		// 获得public修饰的类
		/**
		 * Class<?>[] getClasses() 返回一个包含某些 Class 对象的数组,这些对象表示属于此 Class
		 * 对象所表示的类的成员的所有公共类和接口。 (如果内部类前面没有加上public 的话那么得不到!)
		 */
		Class[] cl = c.getClasses();
		System.out.println(cl.length);// 在内部类没有加上public修饰的时候长度为0,加上就是2(获取的是公共的)
		for (Class class1 : cl) {
			System.out.println(class1);
		}
		// 获得修饰符
		int i = c.getModifiers();
		System.out.println(i);// 常量值1表示public
		System.out.println(Modifier.toString(i));// 直接打印出public
	}
}

5、Class 中得到构造方法 Constructor、方法 Method、字段 Field

常用方法:
Constructor 类用于描述类中的构造方法:
Constructor<T> getConstructor(Class<?>... parameterTypes)返回该 Class 对象表示类的指定的 public 构造方法;
Constructor<?>[] getConstructors()返回该 Class 对象表示类的所有 public 构造方法;
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)返回该 Class 对象表示类的指定的构造方法,和访问权限无关;
Constructor<?>[] getDeclaredConstructors()返回该 Class 对象表示类的所有构造方法,和访问权限无关;
Method 类用于描述类中的方法:
Method getMethod(String name, Class<?> ... parameterTypes)返回该 Class 对象表示类和其父类的指定的 public 方法;
Method[] getMethods():返回该 Class 对象表示类和其父类的所有 public 方法;
Method getDeclaredMethod(String name, Class<?>... parameterTypes)返回该 Class 对象表示类的指定的方法。和访问权限无关,但不包括继承的方法;
Method[] getDeclaredMethods(): 获得类所有的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法;

6、利用反射创建对象

创建对象:
1、使用 Class 对象的 newInstance()方法创建该 Class 对象的实例,此时该 Class 对象必须要有无参数的构造方法。直接newInstance的话必须保证默认的构造方法正常存在,也就是没有被私有化!这是前提条件)

2、使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 的 newInstance()方法创建对象类的实例,此时可以选择使用某个构造方法。如果这个构造方法被私有化起来,那么必须先申请访问,将可以访问设置为 true;

AccessibleObject 对象的 setAccessible(boolean flag)方法,当 flag 为 true 的时候,就会忽略访问权限(可访问私有的成员)。若要访问对象 private 的成员?在调用之前使用 setAccessible(true),Xxx x = getDeclaredXxxx();//才能得到私有的类字段.
总结步骤:
1. 获取该类的 Class 对象。
2. 利用 Class 对象的 getConstructor()方法来获取指定的构造方法。
3. 申请访问(设置为可访问)
4. 调用 Constructor(构造方法)的 newInstance()方法创建对象。

7、使用反射调用方法

每个 Method 的对象对应一个具体的底层方法。获得 Method 对象后,程序可以使用 Method里面的 invoke 方法来执行该底层方法。
Object invoke(Object obj,Object ... args):obj 表示调用底层方法的对象,后面的 args 表示传递的实际参数。
如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null,想想为什么?
如果底层方法所需的形参个数为 0,则所提供的 args 数组长度可以为 0 或 null。不写,null,或 new Object[]{}
若底层方法返回的是数组类型,invoke 方法返回的不是底层方法的值,而是底层方法的返回类型;

//想要通过反射来调用Dept中的方法
Class<Dept> c = Dept.class;
Method m = c.getMethod("show", String.class);
Object o = m.invoke(c.newInstance(), "刘昭");System.out.println(o);
//私有化的方法
m = c.getDeclaredMethod("privateshow");//无参方法
m.setAccessible(true);
o = m.invoke(c.newInstance());
//静态方法的调用
m = c.getMethod("staticshow");
m.invoke(null);//staticshow为静态方法,不需创建对象,所以这里会是null

8、使用反射调用可变参数方法

使用反射操作对象-调用可变参数方法要把可变参数都当做是其对应的数组类型参数;   如 show(XX... is)作为 show(XX[] is)调用;

若可变参数元素类型是引用类型:JDK 内部接收到参数之后,会自动拆包取出参数再分配给该底层个数组实参先包装成一个 Object 对象或把实际参数作为一个 Object

若可变参数元素类型是基本类型:JDK 内部接收到参数之后,不会拆包,所以可以不必再封装.不过封议,不管基本类型还是引用类型都使用 Object[]封装一层,保证无误.

Class<VaryMethod> c = VaryMethod.class;
Method m = c.getMethod("show",int[].class);
m.invoke(null,new int[]{1,2,3});
m = c.getMethod("show",String[].class);
//m.invoke(null,new String[]{"A","B","C"});//ERROR
m.invoke(null,(Object)new String[]{"A","B","C"});//YES, 强转为Object类型
m.invoke(null,new Object[]{new String[]{"A","B","C"}});//推荐写法

9、使用反射操作字段

Field 提供两组方法操作字段:
xxx getXxx(Object obj):获取 obj 对象该 Field 的字段值,此处的 xxx 表示 8 个基本数据类型。

若该字段的类型是引用数据类型则使用,Object get(Object obj);
void setXxx(Object obj,xxx val):将 obj 对象的该 Field 字段设置成 val 值,此处的 xxx 表示 8个基本数据类型。若该字段的类型是引用数据类型则使用,void set(Object obj, Object value);

步骤:
1.获取类
2.获取字段
3.赋值(set(c.newInstance(),””));{如果为私有的话设置可接受}

//核心开始
/**
* void set(Object obj, Object value)
将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
*/
Cat c = clz.newInstance();
fi.setAccessible(true);
fi.set(c, "刘昭");//赋值成功
Object o = fi.get(c);
System.out.println(o);//取出成功

fi = clz.getDeclaredField("age");
fi.setAccessible(true);
fi.set(c, 21);
int i = fi.getInt(c);//左边的接受类型已经写成了int,右边的返回类型
就也必须是int
System.out.println(i);//获取成功

10、反射和泛型-反射来获取泛型信息

通过指定对应的 Class 对象,程序可以获得该类里面所有的 Field,不管该 Field 使用 private方法 public。获得 Field 对象后都可以使用 getType()来获取其类型。
Class<?> type = f.getType();//获得字段的类型
但此方法只对普通 Field 有效,若该 Field 有泛型修饰,则不能准确得到该 Field 的泛型参数,如Map<String,Integer>;为了获得指定 Field 的泛型类型,我们采用:
Type gType = f.getGenericType();得到泛型类型
然后将 Type 对象强转为 ParameterizedType,其表示增加泛型后的类型
Type getRawType()//返回被泛型限制的类型;
Type[] getActualTypeArguments()//返回泛型参数类型;

利用反射来获取泛型的类型(泛型信息)
步骤:
1.获取当前类
2.获取目标字段
3.获取包含泛型类型的类型 getGenericType()
4.强转至子类 ParameterizedType 因为 Type 没有任何对应的方法
5.获得泛型真正的类型 getActualTypeArguments()
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值