您的位置:首页 > 编程语言 > Java开发

java反射机制详解

2017-04-22 16:16 260 查看
反射:程序运行期间发现更多的类及其属性的机制。
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.5Hashcode



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();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  JAVA反射