-------------------------Java培训、Android培训,期待与您交流!-----------------------------
一、反射的概念
1) Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
2)一句话来讲,反射就是将Java类中的各个成分映射成相应的Java类。
3)即在Java中,描述事物的各种类同样也是一种事物,也可以用面向对象的方法来描述,即也有一个类来描述众多的Java类。
4)反射也称为对类的解剖。例如一个类有成员变量、构造方法、成员方法,包等信息,通过反射,就可以拿到这个类的各种信息。
二:Class类
1)所有的类文件(.class文件)都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就叫Class(描述字节码文件的对象)。
2)Class类描述的信息:类的名字,类的访问属性,类所属于的包名,字段名称列表,方法名称列表等。每一个字节码就是class的实例对象。如:Class clazz = String.class.
3)Class类是Java中反射的基石。
1、获取Class对象的三种方式:
1)通过所有数据类型都有的静态属性.class来获取
类名.class 例如:String.class
2)通过对象的getClass()方法获取
对象.getClass() 例如:new String("123").getClass();
3)通过Class类的静态方法forName()和要获得Class对象的类名来获取
Class.forName(包名.类名); 例如:Class.forName("java.lang.String");
代码示例:
1 public class ClassTest{ 2 public static void main(String...args)throws Exception{ 3 String str = "黑马程序员"; 4 Class cls1 =String.class; 5 Class cls2 = str.getClass(); 6 Class cls3 = Class.forName("java.lang.String"); 7 System.out.println(cls1 == cls2);//true 8 System.out.println(cls2 == cls3);//true 9 } 10 }
注:同一个类的字节码文件是唯一的,所以无论怎样获取,都是同一份字节码文件,即同一个Class实例对象
2、Java中预定义的九个Class对象:
1) 包括八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class。
2) Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,所以和int.class是相等的。基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示。
注意:具有相同数据类型和维数的数组在Java中被映射为同一个Class对象。
3、Class类中的常用方法:
1) static Class forName(String className) 返回与给定字符串名的类或接口的相关联的Class对象。
2) Class getClass() 返回的是Object运行时的类,即返回Class对象即字节码对象
3) Constructor getConstructor() 返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。
4) Field getField(String name) 返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段。
5) Field[] getFields() 返回包含某些Field对象的数组,表示所代表类中的成员字段。
6) Method getMethod(String name,Class… parameterTypes) 返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。
7) Method[] getMehtods() 返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。
8) String getName() 以String形式返回此Class对象所表示的实体名称。
9) String getSuperclass() 返回此Class所表示的类的超类的名称
10) boolean isArray() 判定此Class对象是否表示一个数组
11) boolean isPrimitive() 判断指定的Class对象是否是一个基本类型。
12) T newInstance() 创建此Class对象所表示的类的一个新实例。注意:此方法会使用该的空参数构造函数进行初始化实例对象。
三:Constructor类
1、概述:Constructor类的实例对象代表类的一个构造方法。
2、获取构造方法:
1)得到这个类的所有构造方法:如得到String类的所有构造方法
Constructor[] cons = Class.forName(“java.lang.String”).getConstructors();
2)获取某一个构造方法:
Constructor con=String.class.getConstructor(StringBuffer.class);
3、创建实例对象:
1)通常方式:String str = new String("123");
2)反射方式:String str = (String)con.newInstance("123");
注:
1、创建实例时newInstance方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致。
2、newInstance():构造出一个实例对象,每调用一次就构造一个对象。
3、利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。
代码示例:
1 package com.itheima.day01; 2 import java.lang.reflect.Constructor; 3 public class ReflectTest { 4 public static void main(String[] args) throws Exception { 5 //获取String类的Class对象 6 Class clszz = Class.forName("java.lang.String"); 7 //获取String类Class对象的构造方法 8 Constructor constructor = clszz.getConstructor(StringBuffer.class); 9 //通过此构造方法获取String类的实例对象 10 String str = (String) constructor.newInstance(new StringBuffer("黑马程序员")); 11 } 12 }
四、Field类
1、Field类代表反射某个类中的成员变量。
2、方法
1) Field getField(String s);//只能获取公有和父类中公有
2) Field getDeclaredField(String s);//获取该类中任意成员变量,包括私有
3) setAccessible(ture);//如果是私有字段,要先将该私有字段进行取消权限检查的能力。也称暴力访问。
4) set(Object obj, Object value);//将指定对象变量上此Field对象表示的字段设置为指定的新值。
5) Object get(Object obj);//返回指定对象上Field表示的字段的值。
示例:
1 package cn.itheima; 2 public class Person { 3 private String name; 4 public int age; 5 6 Person(){} 7 8 Person(String name,int age){ 9 this.name = name; 10 this.age = age; 11 } 12 13 public String toString(){ 14 return name+"::"+age; 15 } 16 } 17 18 //获取Person对象的成员变量 19 public static void getPersonField() throws Exception{ 20 //如果想要给该变量赋值,必须先要有对象。 21 Class clazz=Class.forName("cn.itheima.Person"); 22 Person p=(Person)clazz.newInstance(); 23 24 //获取所有的成员变量 25 Field[] fs=clazz.getFields(); 26 for(Field f:fs){ 27 System.out.println(f); 28 } 29 30 //获取指定的成员变量 31 Field fage=clazz.getField("age"); 32 Field fname=clazz.getDeclaredField("name"); 33 34 //显示改变后的值 35 fage.set(p, 20); 36 System.out.println(fage.get(p)); 37 38 //暴力访问私有变量 39 fname.setAccessible(true); 40 fname.set(p, "zhangsan"); 41 System.out.println(fname.get(p)); 42 }
注意:暴力反射的方式,也就是使用setAccessible(true)使private类型的成员变量也可以被获取值。
五:Method类
1、概念:
Method类代表某个类中的一个成员方法。
2、专家模式:谁调用这个数据,就是谁在调用它的专家。
如人画圆:
调用者:是圆调用画的动作,对象是圆,因为圆知道如何执行画的动作,通过圆心和半径等之类的细节实现。
指挥者:是人在指挥圆做画的动作,只是给圆发出了画自己的信号,让圆去执行。
总结:变量使用方法,是方法本身知道如何实现执行的过程,也就是“方法对象”调用方法,才执行了方法的每个细节的。
一种程序设计思想,即设计类时,应把此类有关改变自身成员变量的动作设计为自己的成员方法,而不是外置到另一个类中。
3、方法
Method[] getMethods();//只获取公共和父类中的方法。
Method[] getDeclaredMethods();//获取本类中包含私有。
Method getMethod("方法名",参数.class(如果是空参可以写null));
Object invoke(Object obj ,参数);//调用方法 注:如果方法是静态,invoke方法中的对象参数可以为null。
例如:
获取某个类中的某个方法:String str = "abcd";
Method methodCharAt =Class.forName("java.lang.String").getMethod("charAt",int.class);
调用这个方法:
1)通常方式:
str.charAt(2);
2)反射方式
methodCharAt.invoke(str,2);
注:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法
4、JDK1.4和JDK1.5的invoke方法的区别:
JDK1.4:public Object invoke(Object obj,Object[] args)
JDK1.5:public Object invoke(Object obj,Object... args)
即按JDK1.4的语法,需要将一个数组作为参数传递给invoke方法时,这时它会把一个数组作为一个元素。
这时如果我们要取出其中的元素,需要将数组中的元素通过数组一个个的取出。
所以,调用charAt方法的代码也可以用JDK 1.4改写为 charAt.invoke("str", new Object[]{1})形式。
代码示例:
1 package com.itheima.day1; 2 import java.lang.reflect.Method; 3 public class ReflectTests { 4 public static void main(String[] args) throws Exception { 5 String str = "黑马程序员"; 6 Method method = str.getClass().getMethod("charAt", int.class); 7 // 1.4写法。 8 System.out.println(method.invoke(str, new Object[] { 1 })); 9 // 1.5写法。 10 System.out.println(method.invoke(str, 0)); 11 } 12 }
5、对接受数组参数的成员方法进行反射
需求:用反射的方法运行某个类的mian函数
代码:
1 package com.itheima.day1; 2 import java.lang.reflect.Method; 3 public class MassTests { 4 Class clszz = Class.forName("com.itheima.day1.TestArguments"); 5 6 Method main = clszz.getMethod("main", String[].class); 7 8 main.invoke(null, new String[]{"aaa", "bbb", "ccc"}); 9 } 10 } 11 class TestArguments{ 12 public static void main(String[] args){ 13 for(String s: args){ 14 System.out.println(s); 15 } 16 } 17 }
这段代码会发生异常:非法参数异常:Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments
分析原因:
1)启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?
2)按JDK1.5的语法,整个数组是一个参数,而按JDK1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,Javac会到底按照哪种语法进行处理呢?
3)JDK1.5肯定要兼容JDK1.4的语法,会按JDK1.4的语法进行处理,即把数组打散成若干个单独的参数。
4)所以,在给main方法传递参数时,不能使用代码main.invoke(null,"aaa","bbb","ccc"),Javac只把它当作JDK1.4的语法进行理解,而不把它当作JDK1.5的语法解释,因此会出现参数类型不对的问题。
解决方法:
1)main.invoke(null,new Object[]{new String[]{"aaa","bbb","ccc"}});
2)main.invoke(null,(Object)new String[]{"aaa","bbb","ccc"});编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。
六、数组的反射
1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。数组字节码的名字:有[和数组对应类型的缩写,如int[]数组的名称为:[I
2、Object[]与String[]没有父子关系,Object与String有父子关系,所以new Object[]{“aaa”,”bb”}不能强制转换成new String[]{“aaa”,”bb”}; Object x =“abc”能强制转换成String x =“abc”。
3、如何得到某个数组中的某个元素的类型,
例:
int a = new int[3];Object[] obj=new Object[]{”ABC”,1};
无法得到某个数组的具体类型,只能得到其中某个元素的类型,
如:
Obj[0].getClass().getName()得到的是java.lang.String。
4、Array工具类用于完成对数组的反射操作。
Array.getLength(Object obj);//获取数组的长度
Array.get(Object obj,int x);//获取数组中的元素
5、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
示例:
1 package cn.itheima.Demo; 2 3 import java.lang.reflect.Array; 4 import java.util.Arrays; 5 6 public class ArrayReflect { 7 public static void main(String[] args) { 8 int [] a1 = new int[]{1,2,3}; 9 int [] a2 = new int[4]; 10 int[][] a3 = new int[2][3]; 11 String [] a4 = new String[]{"a","b","c"}; 12 System.out.println(a1.getClass().equals(a2.getClass()));//true 13 System.out.println(a1.getClass().equals(a3.getClass()));//false 14 System.out.println(a1.getClass().equals(a4.getClass()));//false 15 System.out.println(a1.getClass().getName());//[I 16 System.out.println(a4.getClass().getName());//[Ljava.lang.String; 17 System.out.println(a1.getClass().getSuperclass());//class java.lang.Object 18 System.out.println(a4.getClass().getSuperclass());//class java.lang.Object 19 20 Object obj1=a1; 21 Object obj2=a3; 22 Object obj3=a4; 23 24 // Object[] obj11=a1;//编译失败,因为a1中的元素是int类型,基本数据类型不是Object 25 Object[] obj13=a3; 26 Object[] obj14=a4;//编译成功,因为String数组中的元素属于Object 27 28 System.out.println(a1);//[[email protected] 29 System.out.println(a4);//[Ljava.lang.String;@6c10a234 30 System.out.println(Arrays.asList(a1));//[[email protected] 31 System.out.println(Arrays.asList(a4));//[a, b, c]32 } 33
6、Arrays.asList()方法处理int[]和String[]时的差异。
代码示例:
1 public classReflectTest { 2 public static void main(String[] args) throwsException { 3 int[] arr =new int[] { 1, 2, 3 }; 4 String[] str =newString[] {"aaa", "bbb", "ccc" }; 5 6 // 直接使用System.out.println无法打印出数组的内容 7 System.out.println(arr); // 结果:[[email protected] 8 System.out.println(str); // 结果:[Ljava.lang.String;@22adc446 9 10 // 通过Arrays.asList方法打印出集合的内容 11 System.out.println(Arrays.asList(arr)); // 结果:[[[email protected]] 12 System.out.println(Arrays.asLsit(str)); // 结果:[a,b,c] 13 14 /* 15 * 原因是因为JDK1.4中为Arrays.asList(Object[] a),JDK1.5中为Arrays.asList(T... a)。 16 * arr是int[]类型,JDK1.4中的asList方法处理不了,JDK1.5可以处理。但是JDK1.5将 int数组整体作为一个参数进行处理。 17 * 因此最终结果就是将 int[]进行了封装,结果类型也就成了[[I。 18 */ 19 } 20 }
练习:利用反射原理写一个打印任意数值的方法。如果是数组,则遍历打印。
代码:
1 package com.itheima.day1; 2 import java.lang.reflect.Array; 3 public class ReflectTest4 { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 int[] a1 = new int[]{1,2,3,4,5}; 8 printObject(a1); 9 String str = "adc"; 10 printObject(str); 11 } 12 //打印任意数值 13 private static void printObject(Object obj) { 14 Class clazz=obj.getClass(); 15 //如果传入的是数组,则遍历 16 if(clazz.isArray()){ 17 int len =Array.getLength(obj);//Array工具类获取数组长度方法 18 for(int x=0;x<len;x++){ 19 System.out.println(Array.get(obj, x));//Array工具获取数组元素 20 } 21 } 22 else 23 System.out.println(obj); 24 } 25 }