Java基础--------反射


                                                

                               反射 


Java 反射是Java语言的一个很重要的特征,它使得Java具体了“动态性”。                             

反射机制的概述:   

      Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;

      对于任意一个对象,都能够调用它的任意一个方法和属性;

      这种动态获取信息以及动态调用对象的方法的功能称为 Java语言的反射机制。

  总之: 反射也称为对类的解剖。把类的各个组成部分映射成一个个相应的Java类。


    要想解剖一个类,必须先要获取到该类的字节码文件对象。

而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.


 什么叫字节码?

        当源程序中用到类时,首先要从硬盘把这个类的那些二进制代码,一个类编译成class放在硬盘上以后,就是一些二进制代码,要把这些二进制代码加载到内存中里面来,再用这些字节码去复制出一个一个对象来。

     所以说,每一个字节码就是一个类的实例对象。


下面就来讲一下反射的基石——Class类


谈到Class,大家估计会把把它和class联系到一块儿,下面就先来说说它们的区别吧:

   Classclass的区别:

        1classJava中的类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则由此类的实例对象确定,不同的实例对象有不同的属性值。

        2Class指的是Java程序中的各个Java类是属于同一类事物,都是Java程序的类,这些类称为Class

    例如:人对应的是Person类,Java类对应的就是Class

              ClassJava程序中各个Java类的总称;它是反射的基石,通过Class类来使用反射


反射是在运行状态下,通过class文件对象(Class的对象),去使用构造方法,成员变量,成员方法。

Class的概述:

所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就叫Class(描述字节码文件的对象)。


Class类中就包含属性有field(字段)method(方法)construction(构造函数)。


  而field中有修饰符、类型、变量名等复杂的描述内容,因此也可以将字段封装成为一个对象。

  用来获取类中field的内容,这个对象的描述叫Field

  同理,方法和构造函数也被封装成 对象MethodConstructor

要想对一个类进行内容的获取,必须要先获取该字节码文件的对象。该对象是Class类型。


那么,我们是如何获取到class文件对象的呢?  (字节码文件对象)
  A:Object类的getClass()方法
  B:数据类型的静态的class属性
  C:通过Class类的静态方法forName(String className)


三种方式练习如下:

public class ReflectDemo {
	public static void main(String[] args) throws ClassNotFoundException {
		// 方式1
		Person p = new Person(); //Person是一个类名 
		Class c = p.getClass();
                  
		Person p2 = new Person();
		Class c2 = p2.getClass();

		System.out.println(p == p2);// false
		System.out.println(c == c2);// true

		// 方式2
		Class c3 = Person.class;
		System.out.println(c == c3);// true

		// 方式3
		Class c4 = Class.forName("cn.itcast01.Person");// true
		System.out.println(c == c4);
	}
}


这三种方法,到底使用哪一种好呢?

        第一种方法:麻烦之处在于每次都需要具体的类和该类的对象,以及调用getClass方法。

        第二种方法:任何数据类型都具备着一个静态的属性class,这个属性直接获取到该类型的对应Class对象。比第一个简单,不用创建对象,不用调用getclass()方法,但是还是要使用具体的类,和该类的中的一个静态属性class完成;

        第三种方法:只要知道类的名字即可,不需要使用该类,也不需要调用具体的属性和行为,就可以获取到Class对象了。

    开发中建议用第三种,自己写例子测试可以使用前两种。 因为第三种方式可以结合配置文件使用。


九个预定义的Class


                1)包括八种基本类型byteshortintlongfloatdoublecharboolean)的字节码对象和一种返回值为void类型void.class 参看Class.isPrimitive();方法的帮助。


2Integer.TYPEInteger类的一个常量,它代表此包装类型包装的基本类型的字节码,所以和int.class是相等的

                      基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示


           总之 ,只要是在源程序中出现的类型都有各自的Class实例对象int[].class

             数组类型的Class实例对象,可以用 Class.isArray()  方法判断是否为数组类型的。


Class类中的方法:


   static Class forName(String className)
        返回与给定字符串名的类或接口的相关联的Class对象。
      

  Class getClass()

        返回的是Object运行时的类,即返回Class对象即字节码对象
      

  Constructor getConstructor()
        返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。
      

  Field getField(String name)
        返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段。
       

 Field[] getFields()
        返回包含某些Field对象的数组,表示所代表类中的成员字段。
       

 Method getMethod(String name,Class… parameterTypes)
        返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。
     

   Method[] getMehtods()
        返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。
      

  String getName()
        以String形式返回此Class对象所表示的实体名称。
      

  String getSuperclass()
        返回此Class所表示的类的超类的名称
      

  boolean isArray()
        判定此Class对象是否表示一个数组
        

boolean isPrimitive()
        判断指定的Class对象是否是一个基本类型。
      

  T newInstance()
        创建此Class对象所表示的类的一个新实例。


反射的应用

   1、Constructor类

      概述:

           如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化。这时怎么办呢?

           这时就不能使用Class类中的newInstance方法了。既然要通过指定的构造函数进行对象的初始化。

           就必须先获取这个构造函数——Constructor 

          Constructor代表某个类的构造方法。


  1)通过反射获取构造方法并使用

            Constructor类代表某个类中的一个构造方法

        得到某个类所有的构造方法:

              例子:Constructor[]  constructors=Class.forName("java.lang.String").getConstructors();

       得到某一个构造方法:

              例子:Constructor constructors=Class.forName("java.lang.String").getConstructors(String.class);

          创建实例对象:
         通常方式: String  str=new String(new StringBuffer("abc"));
        反射方式:String str=(String) Constructor  newInstance(new StringBuffer("abc"));


需要注意的是:

        1、创建实例时newInstance方法中的参数列表必须与获取Constructor的方法getConstructor方法中的参数列表一致。

          2newInstance():构造出一个实例对象,每调用一次就构造一个对象。

          3利用Constructor类来创建类实例的好处是可以指定构造函数Class类只能利用无参构造函数创建类实例对象。

小练习:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/*
 * 反射获取构造方法并使用
 * 构造方法                 Constructor
 * 成员变量                 Field
 * 成员方法                  Method
 */
public class ReflectDemo {
	public static void main(String[] args) throws ClassNotFoundException, 
	NoSuchMethodException, SecurityException, InstantiationException,
	IllegalAccessException, IllegalArgumentException,
	InvocationTargetException {
		//获取字节码文件对象
		Class c=Class.forName("cn.itcast09.Person");

		//获取构造器对象
		Constructor  con=c.getConstructor();  //表示我使用的是无参数的构造方法
		//通过构造器对象创建对象
		Object obj=con.newInstance();
		System.out.println(obj);


		//使用带参构造方法
		Constructor con1=c.getConstructor(String.class,int.class);
		//通过构造器对象创建对象
		Object  obj1=con1.newInstance("孙俪",32);
		System.out.println(obj1);
	}
}
运行后结果:
                       


    2、Field

                Field类代表某个类中一个成员变量

         类中的方法有:

                 Field getField(String s);    //只能获取公有和父类中公有

                      Field getDeclaredField(String s);     //获取该类中任意成员变量,包括私有 setAccessible(ture);


       如果是私有字段,要先将该私有字段进行取消权限检查的能力。也称暴力访问。

             set(Object obj, Object value);            //将指定对象变量上此Field对象表示的字段设置为指定的新值。

             Object get(Object obj);             //返回指定对象上Field表示的字段的值。

用一个案例帮助理解:
             将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"hello"改成"黑马,我正在为你而努力!"。
import java.lang.reflect.Field;
/*步骤 
* 1.得到所有成员变量的数组 
* 2.遍历数组 
* 3.判断数组内的元素是否是String类型的,用==判断,可以用getType方法获得Field对象的类型的class 
*    然后使用==来判断getType返回的class是否与String.class相同,从而判断数组中元素属否是String类型的 
* 4.得到变量的旧的字符串"hello" 
* 5.将就字符串中的"hello"替换成“黑马,我正在为你而努力”,获取新的字符串 
* 6.将新的字符串赋给成员变量。 
*/
//创建一个演示的类:
public class StringDemo {
     //定义成员变量
	public String  str1="hello,hurry up!";
	public String  str2="hello";
	public String  str3="有志者,事竞成!";
	//输出方法:
	@Override
	public String toString() {
		return str1 +" : "+ str2 + " : "+str3;

	}
}
public class ReflectDemo1 {
	public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
		//创建StringDemo对象
		StringDemo  sd=new StringDemo();
		//输出没有修改之前的sd对象中的成员变量  
		System.out.println(sd);
		
		//调用修改成员变量值的方法 
		ChangeStringvalue(sd);
		 //输出修改之后的sd对象中的成员变量。  
		System.out.println(sd);
	}
	
	
	//修改成员变量值的方法
	private static void ChangeStringvalue(Object obj) throws IllegalArgumentException, IllegalAccessException {
		//先获取类中所有的成员变量
		Field[]  fields=obj.getClass().getFields();
		//遍历数组
		for(Field field:fields){
			//判断数组中的成员是不是String类型
			if(field.getType()==String.class){ //这里应该用“==” 因为是同一个字节码
				//如果是,就得到变量对应的字符串
				String oldValue=(String)field.get(obj);
				//将字符串中的旧串替换成新串,并返回一个新的字符串
				String newValue=oldValue.replace("hello", "黑马,我正在为你而努力!");
				//将新的字符串赋给obj对象中对应的成员变量
				field.set(obj, newValue);
			}
		}
	}
}
    运行结果:
                              

    3、Method


 概述:Method类代表某个类中的一个成员方法。调用某个对象身上的方法,要先得到方法,再针对某个对象调用。

       专家模式:谁调用这个数据,就是谁在调用它的专家。

如人关门:

        调用者:是门调用关的动作,对象是门,因为门知道如何执行关的动作,通过门轴之类的细节实现。

        指挥者:是人在指挥门做关的动作,只是给门发出了关的信号,让门执行。

        总结:变量使用方法,是方法本身知道如何实现执行的过程,也就是“方法对象”调用方法,才执行了方法的每个细节的。

Method类中的方法:

        Method[]  getMethods();    //只获取公共和父类中的方法。

        Method[]  getDeclaredMethods();    //获取本类中包含私有。

        Method    getMethod("方法名",参数.class(如果是空参可以写null);

        Object   invoke(Object obj ,参数);     //调用方法

        如果方法是静态,invoke方法中的对象参数可以为null

如:

      获取某个类中的某个方法:(如String str =abc”)

            1)通常方式:str.charAt(1)

            2)反射方式:

                                  Method charAtMethod =Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);

                                  charAtMethod.invoke(str,1);

     说明:如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法


    用反射方式执行某个main方法:

         首先要明确为何要用反射:在写源程序时,并不知道使用者传入的类名是什么,但是虽然传入的类名不知道,而知道的是这个类中的方法有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"});

        这两种方式编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。

小练习:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/*
 *  获取成员方法并使用。
 */
public class ReflectDemo2 {
	public static void main(String[] args)throws Exception {
       //获取字节码文件对象
		Class c=Class.forName("cn.itcast09.Person");
		//创建对象
		Constructor con=c.getConstructor();
		Object obj=con.newInstance();
		
		//第一种: 无参数无返回值
		Method m1=c.getMethod("show",null);
		m1.invoke(obj, null);
		System.out.println("--------------");
		
		//第二种:带参数无返回值
		Method m2=c.getMethod("function",String.class);
		m2.invoke(obj, "张曦之");
		System.out.println("----------------");
		//第三种:带多个参数有返回值
		Method m3=c.getMethod("reutrnValue",String.class,int.class);
		Object ooo=m3.invoke(obj,"张曦之",26);
		System.out.println(ooo);
		System.out.println("-----------------");
		//第四种:私有方法的调用
		Method  m4=c.getDeclaredMethod("hello",null);
		m4.setAccessible(true);
		m4.invoke(obj, null);
		
	}
}
        打印结果:
                                  

通过反射运行配置文件里的内容:
public static void main(String[] args) throws Exception {
		// Student s = new Student();
		// s.love();
		// Teacher t = new Teacher();
		// t.love();

		// 如果有需求上的改动,这里的代码一直在发生改动。
		// 通过反射如何来实现呢
		// 反射+配置文件
		//作为配置文件来说
		/*
		 * 键是固定的,是已经知道的。
		 * 值是变化的。
		 * 
		 * className,methodName
		 */
		Properties prop = new Properties();
		FileReader fr = new FileReader("test.properties");
		prop.load(fr);
		fr.close();
		
		//获取类名
		String className = prop.getProperty("className");
		//获取方法名
		String methodName = prop.getProperty("methodName");
		
		//获取字节码文件对象
		Class c = Class.forName(className);
	
		Constructor con = c.getConstructor();
		Object obj = con.newInstance();
		
		Method m = c.getMethod(methodName, null);
		m.invoke(obj, null);
	}
}
  将来如果想调用哪个类,只要改一下配置文件就行了,很方便。

下面是一道面试题:
 需求:给你一个ArrayList<Integer>的一个对象, 如果要求往这个集合中添加一个字符串数据,该怎么实现呢?
import java.lang.reflect.Method;
import java.util.ArrayList;

/*
 *需求:给你一个ArrayList<Integer>的一个对象, 如果要求往这个集合中添加一个字符串数据,该怎么实现呢?
 * 通过反射实现。
 * 反射可以越过泛型检查
 */
public class ReflectTest {
	public static void main(String[] args)throws Exception {
		ArrayList<Integer> array=new ArrayList<Integer>();
		//获取字节码文件对象
		Class c=array.getClass();
		Method m=c.getMethod("add", Object.class);
		//往集合中添加数据
		m.invoke(array, "hello");
		m.invoke(array, "world");
		m.invoke(array, "java");
           //打印输出集合数据
		System.out.println(array);
	}
}
打印结果为:
                             
   这道题如果不用反射做的话,根本不能实现;
    Method m=c.getMethod("add", Object.class);实现了可以往Integer类型的集合中可以添加任意类型数据。

   
       好啦~~  反射的知识点点先总结到这儿,在以后的学习中,遇到相关知识,会继续补充!  
            有些的不好的地方欢迎大家给我指正! 谢谢!!!



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值