反射:程序运行期间发现更多的类及其属性的机制。
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实例,
对于基本 数据类型的封装类,还可以采用.TYPE来获取相对应的基本数据类型的Class实例类名.class,例如,System.class Class cls2 = String.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();
java反射机制详解
最新推荐文章于 2024-09-11 16:58:56 发布