Java高新技术之反射


一、反射的基石--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
需要取出每个元素对象,然后再对各个对象进行判断【arr[i].getClass().getName();】,因为其中每个具体元素的类型都可以不同,例如Object[] x = new Object[]{“abc”,Integer.Max}。


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值