一、反射的基石--Class类
1.xxx.java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。Java程序中的各个Java类,它们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意与小写class关键字的区别。Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表、方法名称的列表,等等。学习反射,首先就要明白Class这个类。
人-->Person类
很多java类-->Class类
每个java类都是Class的一个实例对象,它们的内容不同,但是,它们的特征相同,譬如,都有方法,有字段,有父类,有包。
2.Class类代表Java类,它的各个实例对象又分别对应什么呢?
对应各个类在内存中的字节码(一个类在虚拟机中通常只有一份字节码),例如,Person类的字节码,ArrayList类的字节码,等等。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?(譬如,都有方法,有字段,有父类,有包。)
3.如何得到各个字节码对应的实例对象( Class类型)
类名.class,例如,System.class
对象.getClass(),例如,new Date().getClass()
Class.forName("类名"),例如,Class.forName("java.util.Date");//要全名, 包名.类名
4.九个预定义Class实例对象:
8个基本数据类型(byte int short double等)加void
Int.class == Integer.TYPE//得到的Integer类里包装的基本数据类型int字节码
System.out.println("void:"+void.class.isPrimitive());//true 是原始数据
System.out.println("int:"+int.class.isPrimitive());//true
System.out.println("int[]:"+int[].class.isPrimitive());//false
System.out.println("int[]:"+int[].class.isArray());//ture
System.out.println(int.class==Integer.class);//false
System.out.println(int.class==Integer.TYPE);//true
二、反射
反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
三、Constructor类
Constructor类代表某个类中的一个构造方法
得到某个类所有的构造方法:
例子:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
例子: Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);//根据构造方法的参数类型得到指定的构造方法
创建实例对象:
- 通常方式:String str = new String(new StringBuffer("abc"));
- 反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));//获得的构造方法使用的参数类型要与原来一致
Class.newInstance()方法:
例子:Date date=(Date)Class.forName("java.util.Date").newInstance();
System.out.println(date);
- 该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
- 该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。(由此可见反射是较耗资源的,会降低程序的性能)
四、Field类
Field类代表某个类中的一个成员变量
演示用eclipse自动生成Java类的构造方法
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。
示例代码:
ReflectPoint point = new ReflectPoint(1,7);
Field y = Class.forName("cn.itcast.corejava.ReflectPoint").getField("y");
System.out.println(y.get(point));
//Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getField("x");
Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getDeclaredField("x");
x.setAccessible(true);
System.out.println(x.get(point));
练习:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。
public class ReflectPoint
{
private int x;
public int y;
public String str1="blue";
private String str2="bubble";
public String str3="soso";
public ReflectPoint(int x, int y)
{
super();
this.x = x;
this.y = y;
}
public String toString()
{
return str1+" "+str2+" "+str3;
}
}
public class ReflectTest
{
public static void main(String[] args)throws Exception
{
//Class cls1=System.class;
//System.out.println(cls1);
//Class clsP1=Person.class;
//Class clsP2=new Person().getClass();
//System.out.println(clsP1==clsP2);
//Class cls2=Class.forName("java.lang.String");
//Class clsP3=Class.forName("day01.Person");
//System.out.println(clsP1==clsP3);
//
//System.out.println("void:"+void.class.isPrimitive());//true
//System.out.println("int:"+int.class.isPrimitive());//true
//System.out.println("int[]:"+int[].class.isPrimitive());//false
//System.out.println("int[]:"+int[].class.isArray());//ture
//System.out.println(int.class==Integer.class);//false
//System.out.println(int.class==Integer.TYPE);//true
Constructor[] constructors=String.class.getConstructors();//得到String类的所有构造方法
Constructor constructor=String.class.getConstructor(StringBuffer.class);//得到指定参数的构造方法
//用构造方法创建实例
String s=(String)constructor.newInstance(new StringBuffer("JAVA"));
System.out.println(s);
//Class类里面的无参构造方法
Date date=(Date)Class.forName("java.util.Date").newInstance();
System.out.println(date);
ReflectPoint rp=new ReflectPoint(6,9);
Field fieldY=rp.getClass().getField("y");//注意fieldY不是对象的变量,是所在类的变量
int rpY=(Integer) fieldY.get(rp);//根据对象取得变量的值
System.out.println(rpY);
Field fieldX=rp.getClass().getDeclaredField("x");//因为x被private修饰
fieldX.setAccessible(true);//把私有的变量设置成可以访问
System.out.println(fieldX.get(rp));
changeStringValue(rp);
System.out.println(rp);
}
public static void changeStringValue(Object obj)
{
//getFields()只能获取公有的成员
Field []fields=obj.getClass().getDeclaredFields();//获取该对象所属类的所有字段,包括私有的
for (Field field : fields)
{
if(field.getType()==java.lang.String.class)//判断字段的类型
{
field.setAccessible(true);//非public修饰的字段设置成可访问
try
{
String s = (String)field.get(obj);
field.set(obj, s.replace('b', 'a'));
} catch (IllegalArgumentException e)
{
e.printStackTrace();
} catch (IllegalAccessException e)
{
e.printStackTrace();
}
}
}
}
}
五、Method类
@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方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?用反射的话你要调用什么类的主方法,程序能根据你的输入进行调用。
@问题:启动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"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
Method mainMethod=Class.forName(args[0]).getMethod("main", String[].class);
//调用方法(静态的用null)
mainMethod.invoke(null, (Object)new String[]{"111","222","333"});//解决一
mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}});//解决二
六、数组的反射
- 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
int []a1=new int[3];
int []a2=new int[4];
int [][]a3=new int[3][4];
String []a4=new String[3];
System.out.println(a1.getClass()==a2.getClass());//true
System.out.println(a1.getClass()==a3.getClass());//false
System.out.println(a1.getClass()==a4.getClass());//false
- 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
System.out.println(a2.getClass().getSuperclass().getName());//java.lang.Object
System.out.println(a3.getClass().getSuperclass().getName());//java.lang.Object
System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
- 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
Object aObj1 = a1;
Object aObj2 = a4;
//Object[] aObj3 = a1; ERROR!!!
Object[] aObj4 = a3;
Object[] aObj5 = a4;
- Arrays.asList()方法处理int[]和String[]时的差异。(为了兼容jdk1.4造成的,jdk1.4中asList(Object []obj) 处理不了int[]只能给jdk1.5+处理,当成一个参数用Object接收了)
int []a1=new int[]{1,2,3};
String []a4=new String[]{"a","b","c"};
System.out.println(Arrays.asList(a1));//[[I@422ede]
System.out.println(Arrays.asList(a4));//[a, b, c]
- Array工具类用于完成对数组的反射操作。
public static void printObject(Object obj)
{
Class cls=obj.getClass();
if(cls.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);
}
}
- 思考题:怎么得到数组中的元素类型?NO