黑马程序员——Java之反射
2015-12-18 19:36
691 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
内容提要:
反射的基石——Class类
Class类中的方法
通过Class对象获取类实例
Constructor类
Field类
Method类
Jdk1.5和Jdk1.4的区别
数组的反射
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。
反射技术可以对类进行解剖。
反射就是把Java类中的各种成分映射成相应的Java类。例如一个Java类用一个Class类的对象来表示,一个类中的组成部分:成员变量、构造方法、成员方法、包等信息也用一个个的Java类来表示。就像汽车是一个类,汽车中的发动机、变速箱等等也是一个个的类。表示Java类的Class类显然要提供一系列的方法,来获得其中的变量、方法、构造方法等信息,这些信息就是用相应类的实例对象来表示,他们是Field、Method、Constructor、Package等等,把他们表示成反射API类的一个个实例对象。
反射的基石——Class类
Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class(大写的C),要注意与小写的class关键字相区别。
Class类描述了哪些方面的信息?有类名字、类的访问属性、类所属于的包名、字段名称的列表、方法名称的列表等等。比如:人(实例对象)- - ->Person类;Java类
- - ->Class类,也就是说Class- - - >代表的是一类事物。所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就是Class,用来描述字节码文件的对象。
Person类代表的人这类事物,有比如张三、李四这样的实例对象;而Class类代表着Java中涉及到的类,他的实例对象是什么呢?1.对应的是内存中的字节码;2.一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以他们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示。
平常使用的类实例对象,需要JVM先将类的字节码加载到内存中去,再使用这个字节码去创建对象。每一个类在内存中仅仅有一份字节码;不同字节码是不同的类。加载XX.class文件进内存时就被封装成了对象,该对象就是字节码文件对象。
要想对一个类进行内容的获取,必须要先获取该字节码文件的对象,该对象属于Class类型。
对于Class类的实例对象,不能使用new Class()得到一个类的字节码。有三种方式得到一个Class实例对象:
代码分析:代码使用三种方式得到相应类的字节码文件:String.class,并演示了Class类的相关方法。
综上所述,可以总结获取类型字节码文件的三种方法:
方法一、通过对象的getClass()方法进行获取,但每次都需要具体的类和该类的对象,以及调用getClass方法。
方法二、任何数据类型都具备一个静态的属性class,这个属性直接获取到该类型对应的Class对象,但需要使用具体的类。
方法三、通过类名直接获取,但必须是全类名。其中Class.forName()的作用:返回字节码,有两种方式返回:其一是这份字节码曾经被加载过,已经存在于JVM中,直接返回即可;另一种是JVM不存在这份字节码,则用类加载器去加载并缓冲。
需要注意的是:八个基本数据类型,也对应了Class字节码对象。特别的,void.class也对应一个基本类型的Class字节码对象。
(比如int[])任何类型都可以用Class对象来表示,都对应着一份字节码文件。
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如:int[],void…
Class类中的方法
static Class forName(String className):返回与给定字符串名的类或接口的相关联的Class对象。
Field getField(String name):返回一个Field对象,他表示此Class对象所代表的类或接口的指定公共成员字段。
Field[] getFields():返回包含某些Field对象的数组,表示所代表类中的成员字段。
Method getMethod(String name, Class... parameterTypes):返回一个Method对象,他表示的是此Class对象所代表的类的指定公共成员方法。
Method[] getMethods():返回一个包含某些Method对象的数组,是所代表的类中的公共成员方法。
通过Class对象获取类实例
步骤:
1.查找并加载指定名字的字节码文件进内存,并封装成Class对象;
2.通过Class对象的newInstance方法创建该Class对应的类实例;
3.调用newInstance()方法会去使用该类的空参数构造函数进行初始化。
反射就是把Java类中的各种成分映射成相应的Java类。
一个Java类用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示。
表示Java类的Class类显然要提供一系列的方法来获取其中的变量、方法、构造方法、修饰符、包等等信息,这些信息就是用相应类的实例对象来表示。比如Field、Method、Constructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。
Constructor类
如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化,这是该怎么办?这是就不能使用Class类的newInstance方法了。既然要通过指定的构造函数进行对象的初始化,就必须先获取这个类的构造函数——Constructor。
Constructor类代表某个类的构造方法类,里面包含了该类的所有构造方法。
代码分析:获得的类字节码后,通过getConstructors()方法获得了该类的相关构造方法,并将结果存储在Constructor类的数组中。代码还演示了使用得到的构造器创建对象。
获取构造方法:
其一、得到这个类的所有构造方法;
其二、获取某一个构造方法。
创建实例对象的两种方法比较:
其一、通常方式;其二、反射方法。
需要注意的是:创建实例时newInstance()方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致;newInstance()构造出一个实例对象,每调用一次就构造一个对象;利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。
一个类有多个构造方法,用什么方式区分清楚得到的方法?根据参数个数和类型来区分。需要指出的是:获得方法时要指出参数的类型,调用获得的方法时要用到与之相同类型的实例对象。newInstance()的两种方式,其中Class类的静态方法有缓存的动作,导致程序性能下降。
反射会导致程序性能下降。
Filed类
Field类代表某个类的成员变量类。
Field getDeclaredField(String s):获取该类中任意成员变量,包括私有的;
setAccessible(true):如果是私有字段,要先将该私有字段进行取消权限检查的能力,也称暴力反射。
set(Object obj, Object value):将指定对象上此Field对象表示的字段设置为指定的值。
Object get(Object obj):返回指定对象上File表示的字段的值。
代码分析:由Demo类的class文件,通过getField()方法获得了该类的名为name的字段。但是突然想到了一个问题:和Constructor比较,构造器能够得到构造器的数组,字段为什么就不行?通过查看API,可见:使用getFields()方法即可获得全部可见的字段。
通过反射机制,可以替换一个对象的字段内容。
代码分析:通过反射,获取了指定字段,并将其替换为新内容。
Method类
Method类代表某个类的成员方法类。调用某个对象上的方法,要先得到方法,再针对某个对象调用。
Method[] getMethods():只能获取公共和父类中的方法;
Method[] getDeclaredMethods():获取本类中的方法,包括私有的;
Method getMethod("方法名",参数.class(如果是空参数,可以写null));
Object invoke(Object obj,参数):方法的调用。如果方法是静态的,invoke方法中的对象参数obj可以为null。
代码分析:这个程序能够根据用户提供的类名,去执行该类中的main()方法。
练习:
JDK1.4与JDK1.5的区别
Jdk1.5:public Object invoke(Object obj, Object … args); 可变参;Jdk1.4:public Object invoke(Object obj, Object[] args); 将一个数组作为参数传递给invoke方法,数组中的每个元素分别对应被调用方法中的一个参数。
对接受数组参数的成员方法进行反射:按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,会按jdk1.4的语法进行处理(兼容性考虑),即把数组打散称为若干个单独的参数。
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
每个数组的父类,都是Object类型。
基本数据类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本数据类型的一维数组,既可以被当做Object类型使用,又能被当做Object[]类型使用。
启动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"});
这两种方式编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。
代码分析:计算结果输出了各种类型class文件类型。
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
Object[]与String[]没有父子关系,Object与String有父子关系,所以new Object[]{"abc","bbb"}不能强制转化为new
String[]{"abc","bbb"}。
基本数据类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
数组的反射
数组反射的应用:
代码分析:只能得到数组元素的类型,不能得到声明数组的类型。
反射的作用就是实现框架的功能,在程序中无法直接new某个类的实例对象了,需要反射来实现。
内容提要:
反射的基石——Class类
Class类中的方法
通过Class对象获取类实例
Constructor类
Field类
Method类
Jdk1.5和Jdk1.4的区别
数组的反射
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。
反射技术可以对类进行解剖。
反射就是把Java类中的各种成分映射成相应的Java类。例如一个Java类用一个Class类的对象来表示,一个类中的组成部分:成员变量、构造方法、成员方法、包等信息也用一个个的Java类来表示。就像汽车是一个类,汽车中的发动机、变速箱等等也是一个个的类。表示Java类的Class类显然要提供一系列的方法,来获得其中的变量、方法、构造方法等信息,这些信息就是用相应类的实例对象来表示,他们是Field、Method、Constructor、Package等等,把他们表示成反射API类的一个个实例对象。
反射的基石——Class类
Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class(大写的C),要注意与小写的class关键字相区别。
Class类描述了哪些方面的信息?有类名字、类的访问属性、类所属于的包名、字段名称的列表、方法名称的列表等等。比如:人(实例对象)- - ->Person类;Java类
- - ->Class类,也就是说Class- - - >代表的是一类事物。所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就是Class,用来描述字节码文件的对象。
Person类代表的人这类事物,有比如张三、李四这样的实例对象;而Class类代表着Java中涉及到的类,他的实例对象是什么呢?1.对应的是内存中的字节码;2.一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以他们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示。
平常使用的类实例对象,需要JVM先将类的字节码加载到内存中去,再使用这个字节码去创建对象。每一个类在内存中仅仅有一份字节码;不同字节码是不同的类。加载XX.class文件进内存时就被封装成了对象,该对象就是字节码文件对象。
要想对一个类进行内容的获取,必须要先获取该字节码文件的对象,该对象属于Class类型。
对于Class类的实例对象,不能使用new Class()得到一个类的字节码。有三种方式得到一个Class实例对象:
public class ReflectionDemo { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub String s1 = "abc"; Class cls1 = s1.getClass(); // 使用对象得到类字节码 Class cls2 = String.class; // 类型名.class,即代表类字节码文件 // 在反射中最常用的一种方式 Class cls3 = Class.forName("java.lang.String"); // Class类的静态方法 System.out.println(cls2.getTypeName()); System.out.println(cls2.isPrimitive()); // String不是基本类型 System.out.println(cls1 == cls2); // 判断是否是同一份字节码文件 System.out.println(cls1 == cls3); System.out.println(cls2 == cls3); System.out.println(int.class.isPrimitive()); // int是基本类型 System.out.println(int.class == Integer.class); // 两者不是同一份字节码 System.out.println(Integer.TYPE); // Integer中的字段TYPE代表的是其基本类型字节码 System.out.println(int.class == Integer.TYPE); System.out.println(int[].class.isPrimitive()); // 数组不是基本类型 System.out.println(int[].class.isArray()); // 判断是否是数组类型 // 获取当前类所在的包 System.out.println(new ReflectionDemo().getClass().getPackage()); } }
代码分析:代码使用三种方式得到相应类的字节码文件:String.class,并演示了Class类的相关方法。
综上所述,可以总结获取类型字节码文件的三种方法:
方法一、通过对象的getClass()方法进行获取,但每次都需要具体的类和该类的对象,以及调用getClass方法。
方法二、任何数据类型都具备一个静态的属性class,这个属性直接获取到该类型对应的Class对象,但需要使用具体的类。
方法三、通过类名直接获取,但必须是全类名。其中Class.forName()的作用:返回字节码,有两种方式返回:其一是这份字节码曾经被加载过,已经存在于JVM中,直接返回即可;另一种是JVM不存在这份字节码,则用类加载器去加载并缓冲。
需要注意的是:八个基本数据类型,也对应了Class字节码对象。特别的,void.class也对应一个基本类型的Class字节码对象。
(比如int[])任何类型都可以用Class对象来表示,都对应着一份字节码文件。
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如:int[],void…
Class类中的方法
static Class forName(String className):返回与给定字符串名的类或接口的相关联的Class对象。
Field getField(String name):返回一个Field对象,他表示此Class对象所代表的类或接口的指定公共成员字段。
Field[] getFields():返回包含某些Field对象的数组,表示所代表类中的成员字段。
Method getMethod(String name, Class... parameterTypes):返回一个Method对象,他表示的是此Class对象所代表的类的指定公共成员方法。
Method[] getMethods():返回一个包含某些Method对象的数组,是所代表的类中的公共成员方法。
通过Class对象获取类实例
步骤:
1.查找并加载指定名字的字节码文件进内存,并封装成Class对象;
2.通过Class对象的newInstance方法创建该Class对应的类实例;
3.调用newInstance()方法会去使用该类的空参数构造函数进行初始化。
public class Test { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub // 获取Person类的Class对象 String className = "java1218.Person"; Class clazz = Class.forName(className); // 通过newInstance方法获取类的无参构造函数实例 Person p = (Person) clazz.newInstance(); } } class Person { private String name; public int age; Person() { System.out.println("Person is running"); } Person(String name, int age) { this.age = age; this.name = name; } public String toString() { return name + "::" + age; } }
反射就是把Java类中的各种成分映射成相应的Java类。
一个Java类用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示。
表示Java类的Class类显然要提供一系列的方法来获取其中的变量、方法、构造方法、修饰符、包等等信息,这些信息就是用相应类的实例对象来表示。比如Field、Method、Constructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。
Constructor类
如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化,这是该怎么办?这是就不能使用Class类的newInstance方法了。既然要通过指定的构造函数进行对象的初始化,就必须先获取这个类的构造函数——Constructor。
Constructor类代表某个类的构造方法类,里面包含了该类的所有构造方法。
import java.lang.reflect.Constructor; public class ReflectionDemo2 { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub String s = "abc"; Class cls = s.getClass(); // 使用对象得到类字节码 // 获得String类的所有构造方法 Constructor[] constructors = cls.getConstructors(); // 还有另外两种方式 // 获得String类的具有一个StringBuffer参数的构造方法 Constructor constructor = cls.getConstructor(StringBuffer.class); // 调用构造方法对象的newInstance方法创建对象 String str = (String) constructor.newInstance(new StringBuffer("abd"));// 返回的是Object对象 // 以上语句和new String(new StringBuffer("abc"));相同 System.out.println(str.charAt(1)); // 方法二:使用Class类的静态方法,先得到默认(不带参数)构造方法,再创建对象 String obj = (String) Class.forName("java.lang.String").newInstance(); } }
代码分析:获得的类字节码后,通过getConstructors()方法获得了该类的相关构造方法,并将结果存储在Constructor类的数组中。代码还演示了使用得到的构造器创建对象。
获取构造方法:
其一、得到这个类的所有构造方法;
其二、获取某一个构造方法。
public class Test { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub // 获取Person类的Class对象 String className = "java1218.Person"; Class clazz = Class.forName(className); // 通过newInstance方法获取类的无参构造函数实例 Person p1 = (Person) clazz.newInstance(); //只能是无参构造器 //获取指定构造函数的类实例 Constructor con = clazz.getConstructor(String.class, int.class); //反射方式:通过构造函数类实例的newInstance方法,创建对应类对象 Person p2 = (Person) con.newInstance("lisi", 30); System.out.println(p2.toString()); //普通方式,调用默认构造方法 Person p3 = new Person(); Person p4 = new Person("zhangsan",26); } }
创建实例对象的两种方法比较:
其一、通常方式;其二、反射方法。
需要注意的是:创建实例时newInstance()方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致;newInstance()构造出一个实例对象,每调用一次就构造一个对象;利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。
一个类有多个构造方法,用什么方式区分清楚得到的方法?根据参数个数和类型来区分。需要指出的是:获得方法时要指出参数的类型,调用获得的方法时要用到与之相同类型的实例对象。newInstance()的两种方式,其中Class类的静态方法有缓存的动作,导致程序性能下降。
反射会导致程序性能下降。
Filed类
Field类代表某个类的成员变量类。
Field getDeclaredField(String s):获取该类中任意成员变量,包括私有的;
setAccessible(true):如果是私有字段,要先将该私有字段进行取消权限检查的能力,也称暴力反射。
set(Object obj, Object value):将指定对象上此Field对象表示的字段设置为指定的值。
Object get(Object obj):返回指定对象上File表示的字段的值。
import java.lang.reflect.Field; public class ReflectionDemo3 { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub Demo d = new Demo("zhangfeng", 26); // 此处的field指的是一个变量,并不和某个对象绑定 Field field_public = d.getClass().getField("name"); // 取出d对象上的该字段的值 System.out.println(field_public.get(d)); // Field field_private = d.getClass().getField("age"); //只能得到可见的 Field field_private = d.getClass().getDeclaredField("age"); // 能得到不可见的 field_private.setAccessible(true); // 暴力反射 System.out.println(field_private.get(d)); // private修饰的字段,不可见 } } class Demo { public String name; private int age; public Demo(String name, int age) { super(); this.name = name; this.age = age; } }
代码分析:由Demo类的class文件,通过getField()方法获得了该类的名为name的字段。但是突然想到了一个问题:和Constructor比较,构造器能够得到构造器的数组,字段为什么就不行?通过查看API,可见:使用getFields()方法即可获得全部可见的字段。
通过反射机制,可以替换一个对象的字段内容。
/* * 需求:通过反射将对象中的字段内容进行替换 * */ import java.lang.reflect.Field; public class ReflectionDemo4 { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub DemoTest dt = new DemoTest(); changeField(dt); System.out.println(dt); } public static void changeField(Object obj) throws Exception { Field[] fields = obj.getClass().getFields(); for (Field field : fields) { //输出字段信息 System.out.println(field); /* 测试结果 * public java.lang.String java1218.DemoTest.str1 * public java.lang.String java1218.DemoTest.str2 * */ if (field.getType() == String.class) { String oldValue = (String) field.get(obj); //获取obj对象的该字段内容 String newValue = oldValue.replace('h', 'w'); field.set(obj, newValue); } /* 测试结果 * ww-->ww*/ } } } class DemoTest { //私有成员变量 private int x; private int y; //公有成员变量 public String str1 = "hh-->"; public String str2 = "ww"; public DemoTest() { } public DemoTest(int x, int y) { super(); this.x = x; this.y = y; } public String toString() // System.out会自动调用toString()方法 { return str1 + str2; } }
代码分析:通过反射,获取了指定字段,并将其替换为新内容。
Method类
Method类代表某个类的成员方法类。调用某个对象上的方法,要先得到方法,再针对某个对象调用。
Method[] getMethods():只能获取公共和父类中的方法;
Method[] getDeclaredMethods():获取本类中的方法,包括私有的;
Method getMethod("方法名",参数.class(如果是空参数,可以写null));
Object invoke(Object obj,参数):方法的调用。如果方法是静态的,invoke方法中的对象参数obj可以为null。
import java.lang.reflect.Method; public class ReflectionDemo5 { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub String str = "zhangfeng"; // 获取String类上的名为charAt方法,该方法有一个int类型的参数 Method methodcharAt = String.class.getMethod("charAt", int.class); // 通过方法对象invoke引发方法执行,作用在str对象上 System.out.println(methodcharAt.invoke(str, 2)); System.out.println(methodcharAt.invoke(str, new Object[] { 2 })); // 按照jdk1.4的写法传入数组 Method methodMain = TestArgument.class.getMethod("main", String[].class); // main方法需要的是String[]类型 // 如果invoke时,对象位置的参数为null时,表明该方法是类的静态方法 // methodMain.invoke(null,new String[]{"111","222","333"}); // //会拆开这个字符串数组,变成三个参数 methodMain.invoke(null, new Object[] { new String[] { "111","222","333" } }); // 将字符串数组打包,变成一个参数 } } class TestArgument { public static void main(String[] args) { for (String str : args) System.out.println(str); } }
代码分析:这个程序能够根据用户提供的类名,去执行该类中的main()方法。
练习:
import java.io.File; import java.io.FileInputStream; import java.util.Properties; public class ReflectTest { public static void main(String[] args) throws Exception{ /* *笔记本电脑使用usb设备 */ NoteBook computer=new NoteBook(); computer.run(); //关联配置文件 File file=new File("usb.properties"); FileInputStream fis=new FileInputStream(file); //将配置文件信息缓存到集合中 Properties ps=new Properties(); ps.load(fis); System.out.println(ps.size()); for(int x=1;x<=ps.size();x++){ String className=ps.getProperty("usb"+x);//获取配置文件中类名 Class clazz=Class.forName(className);//获取类的Class对象 USB usb=(USB)clazz.newInstance();//得到类实例 computer.useUSB(usb);//开始使用 } fis.close();//关流 } } //USB 接口 interface USB { void open(); void close(); } //笔记本电脑 class NoteBook { public void run(){ System.out.println("NoteBook is run"); } public void useUSB(USB usb){ if(usb!=null){ usb.open(); usb.close(); } } } //鼠标 class MouseUSB implements USB { @Override public void open() { System.out.println("Mouse is use"); } @Override public void close() { System.out.println("Mouse is close"); } } //键盘 class KeyUSB implements USB { @Override public void open() { System.out.println("Key is use"); } @Override public void close() { System.out.println("Key is close"); } }
JDK1.4与JDK1.5的区别
Jdk1.5:public Object invoke(Object obj, Object … args); 可变参;Jdk1.4:public Object invoke(Object obj, Object[] args); 将一个数组作为参数传递给invoke方法,数组中的每个元素分别对应被调用方法中的一个参数。
对接受数组参数的成员方法进行反射:按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,会按jdk1.4的语法进行处理(兼容性考虑),即把数组打散称为若干个单独的参数。
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
每个数组的父类,都是Object类型。
基本数据类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本数据类型的一维数组,既可以被当做Object类型使用,又能被当做Object[]类型使用。
启动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"});
这两种方式编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。
import java.util.Arrays; public class ReflectionDemo6 { public static void main(String[] args) { // TODO Auto-generated method stub int[] arr1 = new int[] { 1, 2, 3 }; int[] arr2 = new int[3]; int[][] arr3 = new int[4][4]; String[] arr4 = new String[] { "as", "dd" }; String[] arr5 = new String[3]; System.out.println(arr1.getClass() == arr2.getClass()); // 是相同的字节码文件 System.out.println(arr4.getClass() == arr5.getClass()); // 是相同的字节码文件 System.out.println(arr1.getClass().getName()); // [I:int类型的一维数组 System.out.println(arr3.getClass().getName()); // [[I:int类型的二维数组 System.out.println(arr4.getClass().getName()); // [java.lang.String // 输出的都是java.lang.Object System.out.println(arr1.getClass().getSuperclass().getName()); //java.lang.Object System.out.println(arr3.getClass().getSuperclass().getName()); //java.lang.Object System.out.println(arr4.getClass().getSuperclass().getName()); //java.lang.Object Object obj1 = arr1; Object obj2 = arr4; // java.lang.Object Object[] obj3 = arr3; // [java.lang.Object Object[] obj4 = arr4; // Object[] obj5 = arr2; //can`t convert from int[] to object[] // 测试 System.out.println(Arrays.asList(arr1)); // 输出的是hashCode System.out.println(Arrays.asList(arr4)); // 打印内容 } }
代码分析:计算结果输出了各种类型class文件类型。
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
Object[]与String[]没有父子关系,Object与String有父子关系,所以new Object[]{"abc","bbb"}不能强制转化为new
String[]{"abc","bbb"}。
基本数据类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
数组的反射
数组反射的应用:
/* * 数组反射: * Array类专门用于数组反射的工具类 * */ import java.lang.reflect.Array; public class ReflectionDemo7 { public static void main(String[] args) { // TODO Auto-generated method stub int[] arr1 = new int[] { 1, 2, 3 }; int[] arr2 = new int[3]; int[][] arr3 = new int[4][4]; String[] arr4 = new String[] { "as", "dd" }; String[] arr5 = new String[3]; Object obj1 = arr1; printObject(obj1); // 1 2 3 System.out.println("<--------------->"); Object obj2 = arr2; printObject(obj2); // 0 0 0 System.out.println("<--------------->"); Object obj3 = arr3; printObject(obj3); // [I@106d69c[I@52e922[I@25154f[I@10dea4e System.out.println("<--------------->"); Object obj4 = arr4; printObject(obj4); // as dd System.out.println("<--------------->"); printObject("xyz"); // 直接打印对象 xyz } // 数组的反射 private static void printObject(Object obj) { Class cl = obj.getClass(); if (cl.isArray()) { int len = Array.getLength(obj); // 获取数组长度 for (int index = 0; index < len; index++) System.out.print(Array.get(obj, index)); // 获取该数组指定位置的元素 } else System.out.println(obj); } }
代码分析:只能得到数组元素的类型,不能得到声明数组的类型。
反射的作用就是实现框架的功能,在程序中无法直接new某个类的实例对象了,需要反射来实现。
import java.io.InputStream; import java.util.Collection; import java.util.Properties; import java1205.FileInputStream; public class ReflectionTest { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub InputStream ips = new FileInputStream("config.properties"); // 类名在配置文件中,未出现在源文件中 Properties props = new Properties(); props.load(ips); ips.close(); String className = props.getProperty("className"); Collection collection = (Collection) Class.forName(className).newInstance(); /* 下面就可以通过config.properties中定义的className使用该类 */ } }代码分析:由于无法获知运行类的类名,需要通过反射来实现获取。
相关文章推荐
- 一道面试智力题
- 黑马程序员——Java---线程
- 黑马程序员--谈谈java中的面向对象
- 写在2-14,程序员才看得懂的情书
- 黑马程序员--面向对象
- [.Net码农]FastReport.Net使用
- 【.Net码农】List<T>转换为DataTable
- LeetCode1——Two Sum我的解法
- 面试时,问哪些问题能试出一个Android应用开发者真正的水平?
- 八种程序员境界,你将要做哪一种
- 程序员遇到BUG的解释
- 黑马程序员————Java基础之多线程
- iOS面试、笔试题目总结
- 陈皓:程序员技术练级攻略
- 月光博客:写给新手程序员的一封信
- 黑马程序员--浅谈快速排序
- Java狡猾面试题 Top10
- 黑马程序员--谈谈哲学家就餐的问题
- 初当技术面试官感悟:给面试者的忠告
- 大龄程序员怎样渡过中年危机