java反射机制详解

反射:程序运行期间发现更多的类及其属性的机制。

Java反射机制主要提供了以下功能:

1.在运行时判断任意一个对象所属的类;

2.在运行时构造任意一个类的对象;

3.在运行时判断任意一个类所具有的成员变量和方法;

4.在运行时调用任意一个对象的方法;生成动态代理。

 

5.反编译:.class-->.java

6. 通过反射机制访问java对象的属性,方法,构造方法等;
 

 

1.透彻分析反射的基础_Class类

对比提问:Person类代表人,它的实例对象就是张三,李四这样一个个具体的人,

Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。

对比提问:众多的人用一个什么类表示?

众多的Java类用一个什么类表示?

 

 人-----Person

Java类--------Class

 Class类代表Java类,它的各个实例对象又分别对应什么呢?

 对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。

 一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class类

Class的定义:

Class类:

在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。

Class类代表Java类,它的各个实例对象代表各类的字节码,请注意一个Class对象实际上表示的是一个类型。而这个类型未必一定是一种类,例如int不是类,但int.class是一个Class类型的对象。Class对象实际上是个泛型类,例如 Employee.class的类型Class<Employee>.

 java.lang.Class

• static Class forName(String className)
返回描述类名为 className 的 Class 对象。

• Object newlnstance()

返回这个类的一个新实例。

 

1.1获取Class实例方式

class类方法1

利用对象调用getClass()方法获取该对象的Class实例;

对象.getClass(),例如,new Date().getClass()

Class类方法2

使用Class类的静态方法forName(),用类的名字获取一个Class实例

Class.forName("类名"),例如,Class.forName("java.util.Date");

Class类方法3

运用.class的方式来获取Class实例,

类名.class,例如,System.class
Class cls2 = String.class;
对于基本 数据类型的封装类,还可以采用.TYPE来获取相对应的基本数据类型的Class实例

在newInstance()调用类中缺省的构造方法 ObjectnewInstance()(可在不知该类的名字的时候,创建这个类的实例) 

Creates a new instance of the class represented by this Classobject.

在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。

九个预定义Class实例对象:

参看Class.isPrimitive方法的帮助

Int.class== Integer.TYPE

数组类型的Class实例对象

Class.isArray()

总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…

String str1 = "abc";
Class cls1 = str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
System.out.println(cls1 == cls2); //true
System.out.println(cls1 == cls3);  //true
		
System.out.println(cls1.isPrimitive()); //false
System.out.println(int.class.isPrimitive()); //true
System.out.println(int.class == Integer.class); //false
System.out.println(int.class == Integer.TYPE); //true
System.out.println(int[].class.isPrimitive()); //false
System.out.println(int[].class.isArray());	//true
 

2.JAVA反射

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

  反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。

  一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。

 java.lang.reflect 包中有三个类 Field、 Method 和 Constructor 分别用于描述类的域、 方
法和构造器.

• Field[] getFields() 1.1

• Filed[] getDeclaredFie1ds() 1.1

getFields 方法将返回一个包含 Field 对象的数组, 这些对象记录了这个类或其超类的
公有域。getDeclaredField 方法也将返回包含 Field 对象的数组, 这些对象记录了这个
类的全部域。 如果类中没有域, 或者 Class 对象描述的是基本类型或数组类型, 这些
方法将返回一个长度为 0 的数组。

• Method[] getMethods() 1.1
• Method[] getDeclareMethods() 1.1
返回包含 Method 对象的数组: getMethods 将返回所有的公有方法, 包括从超类继承
来的公有方法;getDeclaredMethods 返回这个类或接口的全部方法, 但不包括由超类
继承了的方法。

• Constructor[] getConstructors() 1.1

• Constructor[] getDeclaredConstructors() 1.1

返回包含 Constructor 对象的数组, 其中包含了 Class 对象所描述的类的所有公有构造
器(getConstructors ) 或所有构造器(getDeclaredConstructors。)

 

2.1 Constructor类

• Class  getDeclaringClass( )
返冋一个用于描述类中定义的构造器、 方法或域的 Class 对象。

• Cl ass[ ]  getExceptionTypes ( ) ( 在 Constructor 和 Method 类 中)
返回一个用于描述方法抛出的异常类型的 Class 对象数组。

• int getModifiers( )
返回一个用于描述构造器、 方法或域的修饰符的整型数值。使用 Modifier 类中的这个方法可以分析这个返回值。

• String getName( )
返冋一个用于描述构造器、 方法或域名的字符串。

• Class[ ] getParameterTypes ( ) ( 在 Constructor 和 Method 类 中)
返回一个用于描述参数类型的 Class 对象数组。

• Class getReturnType( ) ( 在 Method 类 中)
返回一个用于描述返回类型的 Class 对象。

 

Constructor类代表某个类中的一个构造方法

得到某个类所有的构造方法:

 

例子:         

 Constructor constructor =Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);  //获得方法时要用到类型

 创建实例对象:

 通常方式:Stringstr = new String(new StringBuffer("abc"));

 反射方式: String str = (String)constructor.newInstance(newStringBuffer("abc")); //调用获得的方法时要用到上面相同类型的实例对象

Class.newInstance()方法:
例子:Stringobj = (String)Class.forName("java.lang.String").newInstance();
 

该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。

该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。

通过反射实例化对象,一般是通过Constructor的newInstance方法。为了简便,Class类也提供了newInstance方法,但只能调用无参的构造方法。

 

2.2Field类

Field类代表某个类中一个成员变量

方法

     

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

问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。

示例代码:

ReflectPoint point = newReflectPoint(1,7);
Field y =Class.forName("bbbbbbbbbbb").getField("y");
System.out.println(y.get(point));
Field  x=Class.forName("cccccccccccc").getDeclaredField("x");
x.setAccessible(true);
System.out.println(x.get(point));

Class的getField("变量名")方法得到的是public的field。

要得到private的field需用getDeclaredField("变量名")方法。

要用私有变量还需设置可获取:

Field fieldX =p1.getClass().getDeclaredField("x");
fieldX.setAccessible(true);
System.out.println(fieldX.get(p1));

将任意一个对象中所有String类型的成员变量所对应的字符串中的b改为a。

private static void changStringValue(Object obj) { 
Field[] fields =obj.getClass().getFields(); 
for(Fieldfield : fields){ 
   if(field.getType() == String.class){
    String oldValue = (String)field.get(obj); 
    String newValue = oldValue.replace("b", "a"); 
    field.set(obj,newValue);
  }
}
 

2.3 Method类

Method对象之后,就可以在运行时动态地调用方法了。Method类里面最主要的方法有以下几种
  1.获取方法所在的类:                           getDeclaringClass()
  2.获取方法签名中所有声明的抛出异常:getExceptionTypes() 
  3.获取方法签名中所有参数的类型:       getParameterTypes()
  4.获取方法签名中返回值的类型:           getReturnType()
  5.调用方法:                                         Object invoke(Object obj, Object... args)

 

Method类的核心就是invoke方法,该方法用于Method对象唤 起对象中对应的方法,特别要注意的是第二个参数:通常这是一个Object数组,这意味着如果参数是原子类型数据,必须先把他转换成对应的封装类对 象,invoke方法会在后台自动将其解压成原子类型。

Method类代表某个类中的一个成员方法

 得到类中的某一个方法:

 例子Method charAt =Class.forName("java.lang.String").getMethod("charAt",int.class);
 调用方法:
 通常方式:System.out.println(str.charAt(1));
 反射方式: System.out.println(charAt.invoke(str, 1));

•   如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法!

 jdk1.4和jdk1.5的invoke方法的区别:

Jdk1.5:public Object invoke(Object obj,Object...args)

Jdk1.4:public Object invoke(Object obj,Object[]args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。

 

用反射方式执行某个类中的main方法

 目标:

不用反射,调用main方法的代码:

TestArguments.main(new String[]{"aaa","bbb","ccc"});

 写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?

package test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class mainMethod {

	public static void main(String[] args) throws NoSuchMethodException, SecurityException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Method m=Class.forName("test.TargetMain").
				getMethod("main", String[].class);
		m.invoke(null, (Object)new String[]{"1","2","3"});
	}

}
 
package test;

public class TargetMain {

	public static void main(String[] args) {
		for (int i = 0; i < args.length; i=i+1) {
			System.out.println(args[i]);
		}

	}

}

问题:

 启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[]args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。

解决办法:

 mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});

 mainMethod.invoke(null,(Object)new String[]{"xxx"});,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了

 

2.4数组

数组的反射

具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

int [] a1 = new int[]{1,2,3};

Object aObj3 = a1; //对

Object[] aObj3 = a1; //错

Arrays.asList()方法处理int[]和String[]时的差异。

Arrays.asList(String[]),放回一个List,List中的元素就是原来String[]中的元素。

而Arrays.asList(int[],放回一个List,只有一个元素,就是int[]本身。

 Array工具类用于完成对数组的反射操作。

如果传入的对象是数组,则一个个打印数组元素,如果是非数组则直接打印:

private static void printObject(Object obj) {
		Class clazz = obj.getClass();
		if(clazz.isArray()){
			int len = Array.getLength(obj);
			for(int i=0;i<len;i++){
				System.out.println(Array.get(obj, i));
			}
		}else{
			System.out.println(obj);
		}
	}

int[] a1 = new int[3];

int[] a2 = new int[4];

System.out.println(a1.getClass() == a2.getClass());  //true
 

Arrays.asList方法,如果参数是String[],则会提取元素,组成List。如果是int[]这样的基本类型,因为基本类型不是Object,所以组成的List中只有一个元素:int[]。

补充,理解数组:

int[][] a3 = new int[3][4];

String[] a4 = new String[3];

a3是一个数组,数组的每个元素的类型是int[].

a4是一个数组,数组的每个元素的类型是String

 

2.5 Hashcode

Hashcode只有在实现了哈希算法的数据结构(如HashSet)中才有用。

HashSet 是如何保证元素唯一性的呢?

是通过元素的两个方法,hashCode和equals来完成。

如果元素的hashcode值不同,不会调用equals。

如果元素的HashCode值相同,才会判断equals是否为true。当HashCode值和equals都为true时,两个对象认为是同一个对象。

内存泄漏 (没有的对象还在占用内存) 

Collection col = new HashSet();
ReflectPoint p1 = new ReflectPoint(3,3); // ReflectPoint类中基于x,y编写hashcode和equals方法
ReflectPoint p2 = new ReflectPoint(5,5);
col.add(p1);
col.add(p2);
System.out.println(col.size()); //2
p1.y = 6;  //p1的值改变,导致hashcode改变,在原来的HashSet中将无法找到相等的元素,所以无法remove,造成内存泄漏.
col.remove(p1);
System.out.println(col.size());//仍旧是2
 

2.6用类加载器加载资源文件

InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("config.properties "); // config.properties位于classPath目录下
InputStream ips 
= ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");
// config.properties位于n.itcast.day1包下
InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties"); //在ReflectTest2的同一包下
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/resources/resource.properties"); //在ReflectTest2不同的包下


Properties props = new Properties();
props.load(ips);
ips.close();
String className = props.getProperty("className");

//Collection col = (Collection)Class.forName(className).newInstance();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值